From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/3rdparty/display-library/adl_defines.h | 2649 +++++ src/3rdparty/display-library/adl_sdk.h | 42 + src/3rdparty/display-library/adl_structures.h | 4282 +++++++ src/3rdparty/display-library/repo.json | 6 + src/3rdparty/widecharwidth/repo.json | 6 + src/3rdparty/widecharwidth/widechar_width_c.h | 1590 +++ src/3rdparty/yyjson/repo.json | 6 + src/3rdparty/yyjson/yyjson.c | 11065 +++++++++++++++++++ src/3rdparty/yyjson/yyjson.h | 8230 ++++++++++++++ src/common/FFPlatform.h | 37 + src/common/FFlist.h | 141 + src/common/FFstrbuf.h | 584 + src/common/apple/Info.plist.in | 16 + src/common/apple/cf_helpers.c | 225 + src/common/apple/cf_helpers.h | 43 + src/common/apple/osascript.h | 5 + src/common/apple/osascript.m | 15 + src/common/apple/smc_temps.c | 465 + src/common/apple/smc_temps.h | 32 + src/common/apple/version.h | 5 + src/common/apple/version.m | 43 + src/common/argType.h | 44 + src/common/arrutil.h | 16 + src/common/attributes.h | 19 + src/common/base64.h | 21 + src/common/binary.h | 18 + src/common/color.h | 55 + src/common/commandoption.h | 8 + src/common/dbus.h | 46 + src/common/debug.h | 37 + src/common/duration.h | 5 + src/common/edidHelper.h | 13 + src/common/ffdata.h | 22 + src/common/font.h | 25 + src/common/format.h | 14 + src/common/frequency.h | 5 + src/common/haiku/version.cpp | 26 + src/common/haiku/version.h | 3 + src/common/impl/FFPlatform.c | 94 + src/common/impl/FFPlatform_private.h | 8 + src/common/impl/FFPlatform_unix.c | 321 + src/common/impl/FFPlatform_windows.c | 313 + src/common/impl/FFlist.c | 34 + src/common/impl/FFstrbuf.c | 903 ++ src/common/impl/base64.c | 110 + src/common/impl/binary_apple.c | 303 + src/common/impl/binary_linux.c | 147 + src/common/impl/binary_windows.c | 71 + src/common/impl/commandoption.c | 261 + src/common/impl/dbus.c | 334 + src/common/impl/debug_windows.c | 55 + src/common/impl/duration.c | 87 + src/common/impl/edidHelper.c | 132 + src/common/impl/font.c | 513 + src/common/impl/format.c | 761 ++ src/common/impl/frequency.c | 26 + src/common/impl/init.c | 300 + src/common/impl/io_unix.c | 333 + src/common/impl/io_windows.c | 501 + src/common/impl/jsonconfig.c | 365 + src/common/impl/kmod_apple.c | 12 + src/common/impl/kmod_bsd.c | 7 + src/common/impl/kmod_linux.c | 25 + src/common/impl/kmod_nbsd.c | 40 + src/common/impl/kmod_nosupport.c | 5 + src/common/impl/kmod_sunos.c | 23 + src/common/impl/kmod_windows.c | 28 + src/common/impl/library.c | 180 + src/common/impl/lua.c | 233 + src/common/impl/memrchr.c | 21 + src/common/impl/netif.c | 22 + src/common/impl/netif_apple.c | 218 + src/common/impl/netif_bsd.c | 124 + src/common/impl/netif_gnu.c | 36 + src/common/impl/netif_haiku.c | 125 + src/common/impl/netif_linux.c | 443 + src/common/impl/netif_windows.c | 92 + src/common/impl/networking_common.c | 192 + src/common/impl/networking_linux.c | 502 + src/common/impl/networking_windows.c | 376 + src/common/impl/option.c | 185 + src/common/impl/parsing.c | 136 + src/common/impl/path.c | 277 + src/common/impl/percent.c | 293 + src/common/impl/printing.c | 156 + src/common/impl/processing_linux.c | 672 ++ src/common/impl/processing_windows.c | 281 + src/common/impl/properties.c | 172 + src/common/impl/settings.c | 609 + src/common/impl/size.c | 42 + src/common/impl/smbios.c | 805 ++ src/common/impl/strutil.c | 80 + src/common/impl/sysctl.c | 93 + src/common/impl/temps.c | 165 + src/common/impl/time.c | 97 + src/common/impl/wcwidth.c | 31 + src/common/init.h | 7 + src/common/io.h | 300 + src/common/jsonconfig.h | 27 + src/common/kmod.h | 5 + src/common/library.h | 109 + src/common/lua.h | 187 + src/common/mallocHelper.h | 38 + src/common/memrchr.h | 15 + src/common/netif.h | 30 + src/common/networking.h | 38 + src/common/option.h | 96 + src/common/parsing.h | 24 + src/common/path.h | 21 + src/common/percent.h | 43 + src/common/printing.h | 21 + src/common/processing.h | 55 + src/common/properties.h | 47 + src/common/settings.h | 47 + src/common/size.h | 5 + src/common/smbios.h | 103 + src/common/strutil.h | 115 + src/common/sysctl.h | 17 + src/common/temps.h | 9 + src/common/textModifier.h | 5 + src/common/thread.h | 115 + src/common/time.h | 80 + src/common/unused.h | 6 + src/common/wcwidth.h | 12 + src/common/windows/c-logo.sh | 10 + src/common/windows/com.c | 67 + src/common/windows/com.h | 23 + src/common/windows/getline.c | 58 + src/common/windows/getline.h | 6 + src/common/windows/logo.ico | Bin 0 -> 107906 bytes src/common/windows/logo.svg | 1 + src/common/windows/manifest.xml | 18 + src/common/windows/nt.h | 1325 +++ src/common/windows/perflib_.h | 134 + src/common/windows/registry.c | 388 + src/common/windows/registry.h | 66 + src/common/windows/unicode.c | 44 + src/common/windows/unicode.h | 46 + src/common/windows/unicode.hpp | 27 + src/common/windows/util.hpp | 21 + src/common/windows/variant.cpp | 20 + src/common/windows/variant.hpp | 164 + src/common/windows/version.c | 77 + src/common/windows/version.h | 14 + src/common/windows/version.rc | 37 + src/common/windows/wmi.h | 54 + src/detection/battery/battery.h | 32 + src/detection/battery/battery_android.c | 160 + src/detection/battery/battery_apple.c | 117 + src/detection/battery/battery_bsd.c | 90 + src/detection/battery/battery_haiku.c | 71 + src/detection/battery/battery_linux.c | 203 + src/detection/battery/battery_nbsd.c | 109 + src/detection/battery/battery_nosupport.c | 7 + src/detection/battery/battery_obsd.c | 60 + src/detection/battery/battery_windows.c | 384 + src/detection/bios/bios.h | 14 + src/detection/bios/bios_android.c | 16 + src/detection/bios/bios_apple.c | 58 + src/detection/bios/bios_bsd.c | 31 + src/detection/bios/bios_linux.c | 21 + src/detection/bios/bios_nbsd.c | 20 + src/detection/bios/bios_nosupport.c | 5 + src/detection/bios/bios_windows.c | 107 + src/detection/bluetooth/bluetooth.h | 14 + src/detection/bluetooth/bluetooth_apple.m | 112 + src/detection/bluetooth/bluetooth_bsd.c | 30 + src/detection/bluetooth/bluetooth_haiku.cpp | 30 + src/detection/bluetooth/bluetooth_linux.c | 256 + src/detection/bluetooth/bluetooth_nosupport.c | 5 + src/detection/bluetooth/bluetooth_windows.c | 223 + src/detection/bluetoothradio/bluetoothradio.c | 161 + src/detection/bluetoothradio/bluetoothradio.h | 18 + .../bluetoothradio/bluetoothradio_apple.m | 69 + .../bluetoothradio/bluetoothradio_linux.c | 153 + .../bluetoothradio/bluetoothradio_nosupport.c | 5 + .../bluetoothradio/bluetoothradio_windows.c | 99 + src/detection/board/board.h | 13 + src/detection/board/board_android.c | 9 + src/detection/board/board_apple.c | 32 + src/detection/board/board_bsd.c | 15 + src/detection/board/board_linux.c | 26 + src/detection/board/board_nbsd.c | 20 + src/detection/board/board_nosupport.c | 5 + src/detection/board/board_windows.c | 46 + src/detection/bootmgr/bootmgr.c | 44 + src/detection/bootmgr/bootmgr.h | 13 + src/detection/bootmgr/bootmgr_apple.c | 69 + src/detection/bootmgr/bootmgr_bsd.c | 62 + src/detection/bootmgr/bootmgr_haiku.cpp | 17 + src/detection/bootmgr/bootmgr_linux.c | 32 + src/detection/bootmgr/bootmgr_nosupport.c | 5 + src/detection/bootmgr/bootmgr_windows.c | 74 + src/detection/bootmgr/efi_helper.h | 22 + src/detection/brightness/brightness.h | 26 + src/detection/brightness/brightness_apple.c | 244 + src/detection/brightness/brightness_bsd.c | 147 + src/detection/brightness/brightness_haiku.cpp | 46 + src/detection/brightness/brightness_linux.c | 176 + src/detection/brightness/brightness_nbsd.c | 26 + src/detection/brightness/brightness_nosupport.c | 5 + src/detection/brightness/brightness_obsd.c | 44 + src/detection/brightness/brightness_windows.c | 223 + src/detection/btrfs/btrfs.h | 28 + src/detection/btrfs/btrfs_linux.c | 178 + src/detection/btrfs/btrfs_nosupport.c | 5 + src/detection/camera/camera.h | 15 + src/detection/camera/camera_android.c | 58 + src/detection/camera/camera_apple.m | 61 + src/detection/camera/camera_linux.c | 94 + src/detection/camera/camera_nosupport.c | 5 + src/detection/camera/camera_windows.cpp | 154 + src/detection/chassis/chassis.c | 81 + src/detection/chassis/chassis.h | 14 + src/detection/chassis/chassis_apple.c | 40 + src/detection/chassis/chassis_bsd.c | 16 + src/detection/chassis/chassis_linux.c | 39 + src/detection/chassis/chassis_nbsd.c | 20 + src/detection/chassis/chassis_nosupport.c | 5 + src/detection/chassis/chassis_windows.c | 54 + src/detection/codec/codec.c | 14 + src/detection/codec/codec.h | 43 + src/detection/codec/codec_android.c | 102 + src/detection/codec/codec_apple.c | 96 + src/detection/codec/codec_linux.c | 470 + src/detection/codec/codec_vulkan.c | 270 + src/detection/codec/codec_windows.cpp | 553 + src/detection/command/command.c | 58 + src/detection/command/command.h | 6 + src/detection/cpu/cpu.c | 519 + src/detection/cpu/cpu.h | 35 + src/detection/cpu/cpu_apple.c | 158 + src/detection/cpu/cpu_arm.h | 588 + src/detection/cpu/cpu_bsd.c | 109 + src/detection/cpu/cpu_haiku.c | 68 + src/detection/cpu/cpu_linux.c | 1098 ++ src/detection/cpu/cpu_nbsd.c | 109 + src/detection/cpu/cpu_nosupport.c | 5 + src/detection/cpu/cpu_obsd.c | 78 + src/detection/cpu/cpu_sunos.c | 154 + src/detection/cpu/cpu_windows.c | 320 + src/detection/cpucache/cpucache.h | 44 + src/detection/cpucache/cpucache_apple.c | 63 + src/detection/cpucache/cpucache_linux.c | 132 + src/detection/cpucache/cpucache_nosupport.c | 5 + src/detection/cpucache/cpucache_shared.c | 82 + src/detection/cpucache/cpucache_windows.c | 47 + src/detection/cpuusage/cpuusage.c | 74 + src/detection/cpuusage/cpuusage.h | 12 + src/detection/cpuusage/cpuusage_apple.c | 33 + src/detection/cpuusage/cpuusage_bsd.c | 58 + src/detection/cpuusage/cpuusage_haiku.c | 27 + src/detection/cpuusage/cpuusage_linux.c | 46 + src/detection/cpuusage/cpuusage_nosupport.c | 6 + src/detection/cpuusage/cpuusage_sunos.c | 38 + src/detection/cpuusage/cpuusage_windows.c | 169 + src/detection/cursor/cursor.h | 12 + src/detection/cursor/cursor_apple.m | 63 + src/detection/cursor/cursor_linux.c | 101 + src/detection/cursor/cursor_nosupport.c | 5 + src/detection/cursor/cursor_windows.c | 17 + src/detection/de/de.h | 6 + src/detection/de/de_linux.c | 284 + src/detection/de/de_nosupport.c | 5 + src/detection/disk/disk.c | 55 + src/detection/disk/disk.h | 40 + src/detection/disk/disk_bsd.c | 214 + src/detection/disk/disk_haiku.cpp | 74 + src/detection/disk/disk_linux.c | 337 + src/detection/disk/disk_nosupport.c | 5 + src/detection/disk/disk_sunos.c | 145 + src/detection/disk/disk_windows.c | 128 + src/detection/diskio/diskio.c | 88 + src/detection/diskio/diskio.h | 15 + src/detection/diskio/diskio_apple.c | 57 + src/detection/diskio/diskio_bsd.c | 120 + src/detection/diskio/diskio_linux.c | 99 + src/detection/diskio/diskio_nbsd.c | 50 + src/detection/diskio/diskio_nosupport.c | 5 + src/detection/diskio/diskio_obsd.c | 39 + src/detection/diskio/diskio_sunos.c | 42 + src/detection/diskio/diskio_windows.c | 101 + src/detection/displayserver/displayserver.c | 67 + src/detection/displayserver/displayserver.h | 123 + .../displayserver/displayserver_android.c | 209 + src/detection/displayserver/displayserver_apple.c | 197 + .../displayserver/displayserver_haiku.cpp | 71 + .../displayserver/displayserver_windows.c | 219 + src/detection/displayserver/linux/common.c | 16 + .../displayserver/linux/displayserver_linux.c | 91 + .../displayserver/linux/displayserver_linux.h | 13 + src/detection/displayserver/linux/drm.c | 437 + .../displayserver/linux/wayland/global-output.c | 173 + .../wayland/kde-output-device-v2-client-protocol.h | 1384 +++ .../linux/wayland/kde-output-device-v2-protocol.c | 128 + .../wayland/kde-output-order-v1-client-protocol.h | 134 + .../linux/wayland/kde-output-order-v1-protocol.c | 37 + .../displayserver/linux/wayland/kde-output.c | 300 + .../displayserver/linux/wayland/wayland.c | 340 + .../displayserver/linux/wayland/wayland.h | 89 + ...output-management-unstable-v1-client-protocol.h | 1292 +++ .../wlr-output-management-unstable-v1-protocol.c | 176 + .../xdg-output-unstable-v1-client-protocol.h | 414 + .../wayland/xdg-output-unstable-v1-protocol.c | 76 + .../displayserver/linux/wayland/zwlr-output.c | 212 + src/detection/displayserver/linux/wmde.c | 523 + src/detection/displayserver/linux/xcb.c | 441 + src/detection/displayserver/linux/xlib.c | 365 + src/detection/dns/dns.h | 6 + src/detection/dns/dns_apple.c | 106 + src/detection/dns/dns_linux.c | 143 + src/detection/dns/dns_windows.c | 63 + src/detection/editor/editor.c | 160 + src/detection/editor/editor.h | 14 + src/detection/font/font.c | 19 + src/detection/font/font.h | 20 + src/detection/font/font_apple.m | 37 + src/detection/font/font_haiku.cpp | 54 + src/detection/font/font_linux.c | 53 + src/detection/font/font_nosupport.c | 7 + src/detection/font/font_windows.c | 63 + src/detection/gamepad/gamepad.h | 11 + src/detection/gamepad/gamepad_apple.c | 48 + src/detection/gamepad/gamepad_bsd.c | 65 + src/detection/gamepad/gamepad_haiku.cpp | 18 + src/detection/gamepad/gamepad_linux.c | 97 + src/detection/gamepad/gamepad_nosupport.c | 5 + src/detection/gamepad/gamepad_windows.c | 201 + src/detection/gpu/adl.h | 66 + src/detection/gpu/asahi_drm.h | 1194 ++ src/detection/gpu/d3dkmthk.h | 362 + src/detection/gpu/gpu.c | 208 + src/detection/gpu/gpu.h | 89 + src/detection/gpu/gpu_amd.c | 460 + src/detection/gpu/gpu_android.c | 55 + src/detection/gpu/gpu_apple.c | 208 + src/detection/gpu/gpu_apple.m | 87 + src/detection/gpu/gpu_bsd.c | 230 + src/detection/gpu/gpu_driver_specific.h | 90 + src/detection/gpu/gpu_drm.c | 380 + src/detection/gpu/gpu_gnu.c | 146 + src/detection/gpu/gpu_haiku.c | 51 + src/detection/gpu/gpu_intel.c | 231 + src/detection/gpu/gpu_linux.c | 663 ++ src/detection/gpu/gpu_mthreads.c | 201 + src/detection/gpu/gpu_nbsd.c | 120 + src/detection/gpu/gpu_nosupport.c | 6 + src/detection/gpu/gpu_nvidia.c | 299 + src/detection/gpu/gpu_obsd.c | 101 + src/detection/gpu/gpu_pci.c | 251 + src/detection/gpu/gpu_sunos.c | 54 + src/detection/gpu/gpu_windows.c | 605 + src/detection/gpu/gpu_windows.cpp | 83 + src/detection/gpu/igcl.h | 216 + src/detection/gpu/intel_drm.h | 125 + src/detection/gpu/mtml.h | 110 + src/detection/gpu/nvapi.h | 50 + src/detection/gpu/nvml.h | 152 + src/detection/gtk_qt/gtk.c | 206 + src/detection/gtk_qt/gtk_qt.h | 25 + src/detection/gtk_qt/qt.c | 209 + src/detection/host/host.h | 20 + src/detection/host/host_android.c | 27 + src/detection/host/host_apple.c | 68 + src/detection/host/host_bsd.c | 26 + src/detection/host/host_linux.c | 117 + src/detection/host/host_mac.c | 444 + src/detection/host/host_nbsd.c | 25 + src/detection/host/host_nosupport.c | 5 + src/detection/host/host_obsd.c | 22 + src/detection/host/host_windows.c | 74 + src/detection/icons/icons.h | 11 + src/detection/icons/icons_linux.c | 30 + src/detection/icons/icons_nosupport.c | 5 + src/detection/icons/icons_windows.c | 47 + src/detection/initsystem/initsystem.h | 13 + src/detection/initsystem/initsystem_haiku.c | 35 + src/detection/initsystem/initsystem_linux.c | 116 + src/detection/initsystem/initsystem_nosupport.c | 5 + src/detection/keyboard/keyboard.h | 8 + src/detection/keyboard/keyboard_apple.c | 36 + src/detection/keyboard/keyboard_bsd.c | 92 + src/detection/keyboard/keyboard_haiku.cpp | 27 + src/detection/keyboard/keyboard_linux.c | 104 + src/detection/keyboard/keyboard_nosupport.c | 5 + src/detection/keyboard/keyboard_windows.c | 85 + src/detection/libc/libc.h | 10 + src/detection/libc/libc_android.c | 24 + src/detection/libc/libc_apple.c | 12 + src/detection/libc/libc_bsd.c | 20 + src/detection/libc/libc_linux.c | 25 + src/detection/libc/libc_nosupport.c | 7 + src/detection/libc/libc_windows.cpp | 68 + src/detection/lm/lm.h | 12 + src/detection/lm/lm_linux.c | 168 + src/detection/lm/lm_nosupport.c | 5 + src/detection/loadavg/loadavg.h | 5 + src/detection/loadavg/loadavg_bsd.c | 23 + src/detection/loadavg/loadavg_linux.c | 33 + src/detection/loadavg/loadavg_nosupport.c | 5 + src/detection/loadavg/loadavg_sunos.c | 7 + src/detection/locale/locale.h | 5 + src/detection/locale/locale_linux.c | 22 + src/detection/locale/locale_windows.c | 16 + src/detection/localip/localip.h | 47 + src/detection/localip/localip_linux.c | 1011 ++ src/detection/localip/localip_windows.c | 270 + src/detection/media/media.c | 45 + src/detection/media/media.h | 21 + src/detection/media/media_apple.m | 209 + src/detection/media/media_linux.c | 288 + src/detection/media/media_nosupport.c | 5 + src/detection/media/media_windows.cpp | 421 + src/detection/memory/memory.h | 11 + src/detection/memory/memory_apple.c | 35 + src/detection/memory/memory_bsd.c | 16 + src/detection/memory/memory_haiku.c | 16 + src/detection/memory/memory_linux.c | 62 + src/detection/memory/memory_nbsd.c | 18 + src/detection/memory/memory_nosupport.c | 5 + src/detection/memory/memory_obsd.c | 18 + src/detection/memory/memory_sunos.c | 9 + src/detection/memory/memory_windows.c | 19 + src/detection/mouse/mouse.h | 8 + src/detection/mouse/mouse_apple.c | 36 + src/detection/mouse/mouse_bsd.c | 56 + src/detection/mouse/mouse_haiku.cpp | 27 + src/detection/mouse/mouse_linux.c | 45 + src/detection/mouse/mouse_nosupport.c | 5 + src/detection/mouse/mouse_windows.c | 84 + src/detection/netio/netio.c | 81 + src/detection/netio/netio.h | 20 + src/detection/netio/netio_apple.c | 55 + src/detection/netio/netio_bsd.c | 62 + src/detection/netio/netio_haiku.cpp | 52 + src/detection/netio/netio_linux.c | 91 + src/detection/netio/netio_nosupport.c | 5 + src/detection/netio/netio_sunos.c | 68 + src/detection/netio/netio_windows.c | 71 + src/detection/opencl/opencl.c | 218 + src/detection/opencl/opencl.h | 14 + src/detection/opengl/opengl.h | 17 + src/detection/opengl/opengl_apple.c | 66 + src/detection/opengl/opengl_haiku.cpp | 38 + src/detection/opengl/opengl_linux.c | 200 + src/detection/opengl/opengl_shared.c | 214 + src/detection/opengl/opengl_windows.c | 120 + src/detection/os/os.c | 21 + src/detection/os/os.h | 19 + src/detection/os/os_android.c | 16 + src/detection/os/os_apple.m | 105 + src/detection/os/os_haiku.c | 66 + src/detection/os/os_linux.c | 397 + src/detection/os/os_nbsd.c | 6 + src/detection/os/os_obsd.c | 6 + src/detection/os/os_sunos.c | 17 + src/detection/os/os_windows.c | 92 + src/detection/packages/packages.c | 119 + src/detection/packages/packages.h | 72 + src/detection/packages/packages_apple.c | 56 + src/detection/packages/packages_bsd.c | 28 + src/detection/packages/packages_haiku.c | 12 + src/detection/packages/packages_linux.c | 651 ++ src/detection/packages/packages_nbsd.c | 9 + src/detection/packages/packages_nix.c | 149 + src/detection/packages/packages_nosupport.c | 4 + src/detection/packages/packages_obsd.c | 9 + src/detection/packages/packages_sunos.c | 17 + src/detection/packages/packages_windows.c | 203 + src/detection/physicaldisk/physicaldisk.h | 17 + src/detection/physicaldisk/physicaldisk_apple.c | 191 + src/detection/physicaldisk/physicaldisk_bsd.c | 127 + src/detection/physicaldisk/physicaldisk_haiku.c | 104 + src/detection/physicaldisk/physicaldisk_linux.c | 202 + src/detection/physicaldisk/physicaldisk_nbsd.c | 324 + .../physicaldisk/physicaldisk_nosupport.c | 5 + src/detection/physicaldisk/physicaldisk_obsd.c | 279 + src/detection/physicaldisk/physicaldisk_sunos.c | 107 + src/detection/physicaldisk/physicaldisk_windows.c | 349 + src/detection/physicalmemory/physicalmemory.c | 73 + src/detection/physicalmemory/physicalmemory.h | 22 + .../physicalmemory/physicalmemory_apple.m | 163 + .../physicalmemory/physicalmemory_linux.c | 235 + .../physicalmemory/physicalmemory_nosupport.c | 6 + src/detection/poweradapter/poweradapter.h | 15 + src/detection/poweradapter/poweradapter_apple.c | 39 + src/detection/poweradapter/poweradapter_linux.c | 91 + .../poweradapter/poweradapter_nosupport.c | 5 + src/detection/processes/processes.h | 5 + src/detection/processes/processes_bsd.c | 23 + src/detection/processes/processes_haiku.c | 14 + src/detection/processes/processes_linux.c | 27 + src/detection/processes/processes_nbsd.c | 15 + src/detection/processes/processes_nosupport.c | 5 + src/detection/processes/processes_obsd.c | 15 + src/detection/processes/processes_windows.c | 34 + src/detection/publicip/publicip.c | 95 + src/detection/publicip/publicip.h | 12 + src/detection/sound/audio_oss_sunos.h | 898 ++ src/detection/sound/sound.h | 16 + src/detection/sound/sound_apple.c | 112 + src/detection/sound/sound_bsd.c | 90 + src/detection/sound/sound_haiku.cpp | 88 + src/detection/sound/sound_linux.c | 164 + src/detection/sound/sound_nbsd.c | 58 + src/detection/sound/sound_nosupport.c | 5 + src/detection/sound/sound_obsd.c | 90 + src/detection/sound/sound_sunos.c | 109 + src/detection/sound/sound_windows.cpp | 122 + src/detection/swap/swap.h | 12 + src/detection/swap/swap_apple.c | 28 + src/detection/swap/swap_bsd.c | 78 + src/detection/swap/swap_haiku.c | 27 + src/detection/swap/swap_linux.c | 82 + src/detection/swap/swap_nosupport.c | 5 + src/detection/swap/swap_obsd.c | 35 + src/detection/swap/swap_sunos.c | 32 + src/detection/swap/swap_windows.c | 35 + src/detection/terminalfont/terminalfont.c | 407 + src/detection/terminalfont/terminalfont.h | 13 + src/detection/terminalfont/terminalfont_android.c | 71 + src/detection/terminalfont/terminalfont_apple.m | 106 + src/detection/terminalfont/terminalfont_linux.c | 545 + src/detection/terminalfont/terminalfont_windows.c | 287 + src/detection/terminalshell/terminalshell.c | 949 ++ src/detection/terminalshell/terminalshell.h | 35 + src/detection/terminalshell/terminalshell_linux.c | 449 + .../terminalshell/terminalshell_windows.c | 379 + src/detection/terminalsize/terminalsize.h | 13 + src/detection/terminalsize/terminalsize_linux.c | 38 + src/detection/terminalsize/terminalsize_windows.c | 45 + src/detection/terminaltheme/terminaltheme.c | 105 + src/detection/terminaltheme/terminaltheme.h | 18 + src/detection/theme/theme.h | 11 + src/detection/theme/theme_apple.c | 22 + src/detection/theme/theme_linux.c | 60 + src/detection/theme/theme_nosupport.c | 5 + src/detection/theme/theme_windows.c | 27 + src/detection/tpm/tpm.h | 11 + src/detection/tpm/tpm_apple.c | 30 + src/detection/tpm/tpm_bsd.c | 22 + src/detection/tpm/tpm_linux.c | 24 + src/detection/tpm/tpm_nosupport.c | 5 + src/detection/tpm/tpm_windows.c | 51 + src/detection/uptime/uptime.h | 11 + src/detection/uptime/uptime_bsd.c | 27 + src/detection/uptime/uptime_haiku.c | 8 + src/detection/uptime/uptime_linux.c | 38 + src/detection/uptime/uptime_sunos.c | 19 + src/detection/uptime/uptime_windows.c | 17 + src/detection/users/users.h | 14 + src/detection/users/users_linux.c | 204 + src/detection/users/users_nosupport.c | 6 + src/detection/users/users_obsd.c | 39 + src/detection/users/users_windows.c | 61 + src/detection/version/version.c | 110 + src/detection/version/version.h | 19 + src/detection/vulkan/vulkan.c | 332 + src/detection/vulkan/vulkan.h | 15 + src/detection/wallpaper/wallpaper.h | 5 + src/detection/wallpaper/wallpaper_apple.m | 72 + src/detection/wallpaper/wallpaper_haiku.cpp | 54 + src/detection/wallpaper/wallpaper_linux.c | 102 + src/detection/wallpaper/wallpaper_nosupport.c | 5 + src/detection/wallpaper/wallpaper_windows.c | 15 + src/detection/weather/weather.c | 62 + src/detection/weather/weather.h | 7 + src/detection/wifi/wifi.h | 62 + src/detection/wifi/wifi_android.c | 69 + src/detection/wifi/wifi_apple.m | 165 + src/detection/wifi/wifi_bsd.c | 153 + src/detection/wifi/wifi_linux.c | 976 ++ src/detection/wifi/wifi_nbsd.c | 168 + src/detection/wifi/wifi_nosupport.c | 5 + src/detection/wifi/wifi_obsd.c | 109 + src/detection/wifi/wifi_windows.c | 242 + src/detection/wm/wm.h | 7 + src/detection/wm/wm_apple.m | 94 + src/detection/wm/wm_linux.c | 335 + src/detection/wm/wm_nosupport.c | 9 + src/detection/wm/wm_windows.c | 204 + src/detection/wmtheme/wmtheme.h | 5 + src/detection/wmtheme/wmtheme_apple.m | 40 + src/detection/wmtheme/wmtheme_haiku.cpp | 30 + src/detection/wmtheme/wmtheme_linux.c | 340 + src/detection/wmtheme/wmtheme_nosupport.c | 7 + src/detection/wmtheme/wmtheme_windows.c | 182 + src/detection/zpool/libzfs_simplified.h | 48 + src/detection/zpool/zpool.c | 130 + src/detection/zpool/zpool.h | 17 + src/fastfetch.c | 864 ++ src/fastfetch.h | 46 + src/fastfetch_config.h.in | 21 + src/fastfetch_datatext.h.in | 4 + src/flashfetch.c | 129 + src/logo/ascii/_/unknown.txt | 18 + src/logo/ascii/a.inc | 881 ++ src/logo/ascii/a/adelie.txt | 16 + src/logo/ascii/a/aeon.txt | 14 + src/logo/ascii/a/aeros.txt | 21 + src/logo/ascii/a/aerynos.txt | 18 + src/logo/ascii/a/aerynos_old.txt | 16 + src/logo/ascii/a/afterglow.txt | 15 + src/logo/ascii/a/aix.txt | 20 + src/logo/ascii/a/almalinux.txt | 20 + src/logo/ascii/a/alpine.txt | 20 + src/logo/ascii/a/alpine2.txt | 16 + src/logo/ascii/a/alpine2_small.txt | 6 + src/logo/ascii/a/alpine3_small.txt | 6 + src/logo/ascii/a/alpine_small.txt | 6 + src/logo/ascii/a/alter.txt | 20 + src/logo/ascii/a/altlinux.txt | 22 + src/logo/ascii/a/amazon.txt | 19 + src/logo/ascii/a/amazon_linux.txt | 10 + src/logo/ascii/a/amiga.txt | 13 + src/logo/ascii/a/amogos.txt | 19 + src/logo/ascii/a/anarchy.txt | 28 + src/logo/ascii/a/android.txt | 18 + src/logo/ascii/a/android_small.txt | 6 + src/logo/ascii/a/anduinos.txt | 19 + src/logo/ascii/a/antergos.txt | 19 + src/logo/ascii/a/antix.txt | 12 + src/logo/ascii/a/anushos.txt | 20 + src/logo/ascii/a/aoscos.txt | 17 + src/logo/ascii/a/aoscos_old.txt | 20 + src/logo/ascii/a/aoscosretro.txt | 18 + src/logo/ascii/a/aoscosretro_small.txt | 9 + src/logo/ascii/a/aperture.txt | 20 + src/logo/ascii/a/apricity.txt | 18 + src/logo/ascii/a/arch.txt | 19 + src/logo/ascii/a/arch2.txt | 19 + src/logo/ascii/a/arch3.txt | 19 + src/logo/ascii/a/arch_old.txt | 16 + src/logo/ascii/a/arch_small.txt | 7 + src/logo/ascii/a/archbox.txt | 19 + src/logo/ascii/a/archcraft.txt | 20 + src/logo/ascii/a/archcraft2.txt | 20 + src/logo/ascii/a/archlabs.txt | 21 + src/logo/ascii/a/archstrike.txt | 17 + src/logo/ascii/a/arco.txt | 20 + src/logo/ascii/a/arco_small.txt | 11 + src/logo/ascii/a/arkane.txt | 23 + src/logo/ascii/a/armbian.txt | 22 + src/logo/ascii/a/armbian2.txt | 14 + src/logo/ascii/a/arselinux.txt | 15 + src/logo/ascii/a/artix.txt | 20 + src/logo/ascii/a/artix2.txt | 20 + src/logo/ascii/a/artix2_small.txt | 7 + src/logo/ascii/a/artix_small.txt | 13 + src/logo/ascii/a/arya.txt | 15 + src/logo/ascii/a/asahi.txt | 18 + src/logo/ascii/a/asahi2.txt | 18 + src/logo/ascii/a/aster.txt | 18 + src/logo/ascii/a/asteroidos.txt | 17 + src/logo/ascii/a/astos.txt | 23 + src/logo/ascii/a/astra_linux.txt | 22 + src/logo/ascii/a/athenaos.txt | 29 + src/logo/ascii/a/athenaos_old.txt | 24 + src/logo/ascii/a/aurora.txt | 28 + src/logo/ascii/a/axos.txt | 16 + src/logo/ascii/a/azos.txt | 17 + src/logo/ascii/b.inc | 218 + src/logo/ascii/b/bedrock.txt | 17 + src/logo/ascii/b/bedrock_small.txt | 6 + src/logo/ascii/b/berserkarch.txt | 21 + src/logo/ascii/b/biglinux.txt | 19 + src/logo/ascii/b/bitrig.txt | 17 + src/logo/ascii/b/blackarch.txt | 21 + src/logo/ascii/b/blackmesa.txt | 20 + src/logo/ascii/b/blackpanther.txt | 23 + src/logo/ascii/b/blag.txt | 17 + src/logo/ascii/b/blankon.txt | 17 + src/logo/ascii/b/bluelight.txt | 19 + src/logo/ascii/b/bodhi.txt | 18 + src/logo/ascii/b/bonsai.txt | 16 + src/logo/ascii/b/bredos.txt | 9 + src/logo/ascii/b/bsd.txt | 19 + src/logo/ascii/b/bunsenlabs.txt | 20 + src/logo/ascii/c.inc | 515 + src/logo/ascii/c/cachyos.txt | 22 + src/logo/ascii/c/cachyos_small.txt | 8 + src/logo/ascii/c/calculate.txt | 20 + src/logo/ascii/c/calinixos.txt | 25 + src/logo/ascii/c/calinixos_small.txt | 15 + src/logo/ascii/c/carbs.txt | 18 + src/logo/ascii/c/cbl_mariner.txt | 18 + src/logo/ascii/c/celos.txt | 15 + src/logo/ascii/c/center.txt | 13 + src/logo/ascii/c/centos.txt | 19 + src/logo/ascii/c/centos_small.txt | 7 + src/logo/ascii/c/cereus.txt | 27 + src/logo/ascii/c/chakra.txt | 18 + src/logo/ascii/c/chaletos.txt | 20 + src/logo/ascii/c/chapeau.txt | 18 + src/logo/ascii/c/chimera_linux.txt | 18 + src/logo/ascii/c/chonkysealos.txt | 21 + src/logo/ascii/c/chrom.txt | 18 + src/logo/ascii/c/cleanjaro.txt | 12 + src/logo/ascii/c/cleanjaro_small.txt | 7 + src/logo/ascii/c/clear_linux.txt | 20 + src/logo/ascii/c/clearos.txt | 20 + src/logo/ascii/c/clover.txt | 20 + src/logo/ascii/c/cobalt.txt | 16 + src/logo/ascii/c/codex.txt | 12 + src/logo/ascii/c/condres.txt | 17 + src/logo/ascii/c/cosmic.txt | 11 + src/logo/ascii/c/crux.txt | 18 + src/logo/ascii/c/crux_small.txt | 7 + src/logo/ascii/c/crystal.txt | 20 + src/logo/ascii/c/cucumber.txt | 20 + src/logo/ascii/c/cuerdos.txt | 13 + src/logo/ascii/c/cutefishos.txt | 9 + src/logo/ascii/c/cuteos.txt | 14 + src/logo/ascii/c/cyberos.txt | 15 + src/logo/ascii/c/cycledream.txt | 21 + src/logo/ascii/d.inc | 206 + src/logo/ascii/d/dahlia.txt | 17 + src/logo/ascii/d/darkos.txt | 20 + src/logo/ascii/d/debian.txt | 17 + src/logo/ascii/d/debian_small.txt | 6 + src/logo/ascii/d/deepin.txt | 18 + src/logo/ascii/d/desaos.txt | 16 + src/logo/ascii/d/devuan.txt | 15 + src/logo/ascii/d/devuan_small.txt | 7 + src/logo/ascii/d/dietpi.txt | 19 + src/logo/ascii/d/dracos.txt | 13 + src/logo/ascii/d/dragonfly.txt | 15 + src/logo/ascii/d/dragonfly_old.txt | 17 + src/logo/ascii/d/dragonfly_small.txt | 7 + src/logo/ascii/d/drauger.txt | 16 + src/logo/ascii/d/droidian.txt | 15 + src/logo/ascii/e.inc | 256 + src/logo/ascii/e/elbrus.txt | 14 + src/logo/ascii/e/elementary.txt | 17 + src/logo/ascii/e/elementary_small.txt | 6 + src/logo/ascii/e/elive.txt | 17 + src/logo/ascii/e/emmabuntus.txt | 15 + src/logo/ascii/e/emperoros.txt | 13 + src/logo/ascii/e/encryptos.txt | 13 + src/logo/ascii/e/endeavouros.txt | 15 + src/logo/ascii/e/endeavouros_small.txt | 7 + src/logo/ascii/e/endless.txt | 20 + src/logo/ascii/e/enos.txt | 6 + src/logo/ascii/e/enso.txt | 19 + src/logo/ascii/e/eshanizedos.txt | 27 + src/logo/ascii/e/eurolinux.txt | 18 + src/logo/ascii/e/evolutionos.txt | 21 + src/logo/ascii/e/evolutionos_old.txt | 18 + src/logo/ascii/e/evolutionos_small.txt | 9 + src/logo/ascii/e/eweos.txt | 15 + src/logo/ascii/e/exherbo.txt | 22 + src/logo/ascii/e/exodia_predator.txt | 24 + src/logo/ascii/f.inc | 288 + src/logo/ascii/f/fastfetch.txt | 11 + src/logo/ascii/f/fedora.txt | 19 + src/logo/ascii/f/fedora2_small.txt | 5 + src/logo/ascii/f/fedora_coreos.txt | 20 + src/logo/ascii/f/fedora_kinoite.txt | 18 + src/logo/ascii/f/fedora_old.txt | 17 + src/logo/ascii/f/fedora_sericea.txt | 17 + src/logo/ascii/f/fedora_silverblue.txt | 17 + src/logo/ascii/f/fedora_small.txt | 9 + src/logo/ascii/f/femboyos.txt | 20 + src/logo/ascii/f/feren.txt | 16 + src/logo/ascii/f/filotimo.txt | 20 + src/logo/ascii/f/finnix.txt | 17 + src/logo/ascii/f/floflis.txt | 19 + src/logo/ascii/f/freebsd.txt | 15 + src/logo/ascii/f/freebsd_small.txt | 6 + src/logo/ascii/f/freemint.txt | 15 + src/logo/ascii/f/frugalware.txt | 23 + src/logo/ascii/f/funtoo.txt | 10 + src/logo/ascii/f/furreto.txt | 22 + src/logo/ascii/g.inc | 244 + src/logo/ascii/g/galliumos.txt | 19 + src/logo/ascii/g/garuda.txt | 18 + src/logo/ascii/g/garuda_dragon.txt | 24 + src/logo/ascii/g/garuda_small.txt | 5 + src/logo/ascii/g/gentoo.txt | 18 + src/logo/ascii/g/gentoo_small.txt | 7 + src/logo/ascii/g/ghostbsd.txt | 13 + src/logo/ascii/g/ghostfreak.txt | 19 + src/logo/ascii/g/glaucus.txt | 12 + src/logo/ascii/g/gnewsense.txt | 12 + src/logo/ascii/g/gnome.txt | 21 + src/logo/ascii/g/gnu.txt | 18 + src/logo/ascii/g/gobolinux.txt | 6 + src/logo/ascii/g/goldendoglinux.txt | 19 + src/logo/ascii/g/grapheneos.txt | 23 + src/logo/ascii/g/grombyang.txt | 18 + src/logo/ascii/g/guix.txt | 10 + src/logo/ascii/g/guix_small.txt | 7 + src/logo/ascii/g/gxde.txt | 20 + src/logo/ascii/h.inc | 211 + src/logo/ascii/h/haiku.txt | 12 + src/logo/ascii/h/haiku2.txt | 17 + src/logo/ascii/h/haiku_small.txt | 8 + src/logo/ascii/h/hamonikr.txt | 23 + src/logo/ascii/h/hardclanz.txt | 19 + src/logo/ascii/h/harmonyos.txt | 19 + src/logo/ascii/h/hash.txt | 13 + src/logo/ascii/h/hce.txt | 24 + src/logo/ascii/h/heliumos.txt | 20 + src/logo/ascii/h/huayra.txt | 17 + src/logo/ascii/h/hybrid.txt | 16 + src/logo/ascii/h/hydroos.txt | 8 + src/logo/ascii/h/hyperbola.txt | 16 + src/logo/ascii/h/hyperbola_small.txt | 7 + src/logo/ascii/h/hypros.txt | 17 + src/logo/ascii/i.inc | 83 + src/logo/ascii/i/iglunix.txt | 12 + src/logo/ascii/i/instantos.txt | 20 + src/logo/ascii/i/interix.txt | 17 + src/logo/ascii/i/irix.txt | 19 + src/logo/ascii/i/ironclad.txt | 11 + src/logo/ascii/i/itc.txt | 10 + src/logo/ascii/j.inc | 21 + src/logo/ascii/j/januslinux.txt | 24 + src/logo/ascii/k.inc | 239 + src/logo/ascii/k/kaisen.txt | 17 + src/logo/ascii/k/kali.txt | 21 + src/logo/ascii/k/kali_small.txt | 16 + src/logo/ascii/k/kalpa_desktop.txt | 22 + src/logo/ascii/k/kaos.txt | 16 + src/logo/ascii/k/kdelinux.txt | 14 + src/logo/ascii/k/kdeneon.txt | 19 + src/logo/ascii/k/kernelos.txt | 19 + src/logo/ascii/k/kibaos.txt | 6 + src/logo/ascii/k/kibojoe.txt | 13 + src/logo/ascii/k/kiss.txt | 7 + src/logo/ascii/k/kiss2.txt | 11 + src/logo/ascii/k/kogaion.txt | 20 + src/logo/ascii/k/korora.txt | 16 + src/logo/ascii/k/krassos.txt | 19 + src/logo/ascii/k/kslinux.txt | 11 + src/logo/ascii/k/kubuntu.txt | 20 + src/logo/ascii/k/kylin.txt | 22 + src/logo/ascii/l.inc | 326 + src/logo/ascii/l/lainos.txt | 20 + src/logo/ascii/l/langitketujuh.txt | 11 + src/logo/ascii/l/laxeros.txt | 20 + src/logo/ascii/l/lede.txt | 9 + src/logo/ascii/l/lfs.txt | 18 + src/logo/ascii/l/libreelec.txt | 20 + src/logo/ascii/l/lilidog.txt | 16 + src/logo/ascii/l/limeos.txt | 23 + src/logo/ascii/l/lingmo.txt | 20 + src/logo/ascii/l/linspire.txt | 12 + src/logo/ascii/l/linux.txt | 12 + src/logo/ascii/l/linux_small.txt | 7 + src/logo/ascii/l/linuxlite.txt | 20 + src/logo/ascii/l/linuxlite_small.txt | 7 + src/logo/ascii/l/linuxmint.txt | 19 + src/logo/ascii/l/linuxmint2.txt | 19 + src/logo/ascii/l/linuxmint_old.txt | 16 + src/logo/ascii/l/linuxmint_small.txt | 7 + src/logo/ascii/l/live_raizo.txt | 20 + src/logo/ascii/l/lliurex.txt | 16 + src/logo/ascii/l/lmde.txt | 17 + src/logo/ascii/l/locos.txt | 18 + src/logo/ascii/l/lubuntu.txt | 20 + src/logo/ascii/l/lunar.txt | 13 + src/logo/ascii/m.inc | 436 + src/logo/ascii/m/macaronios.txt | 11 + src/logo/ascii/m/macos.txt | 17 + src/logo/ascii/m/macos2.txt | 17 + src/logo/ascii/m/macos2_small.txt | 7 + src/logo/ascii/m/macos3.txt | 16 + src/logo/ascii/m/macos_small.txt | 7 + src/logo/ascii/m/mageia.txt | 19 + src/logo/ascii/m/mageia_small.txt | 7 + src/logo/ascii/m/magix.txt | 19 + src/logo/ascii/m/magpieos.txt | 20 + src/logo/ascii/m/mainsailos.txt | 18 + src/logo/ascii/m/mainsailos_small.txt | 7 + src/logo/ascii/m/mandriva.txt | 15 + src/logo/ascii/m/manjaro.txt | 14 + src/logo/ascii/m/manjaro_small.txt | 7 + src/logo/ascii/m/massos.txt | 18 + src/logo/ascii/m/matuusos.txt | 20 + src/logo/ascii/m/maui.txt | 20 + src/logo/ascii/m/mauna.txt | 15 + src/logo/ascii/m/meowix.txt | 10 + src/logo/ascii/m/mer.txt | 27 + src/logo/ascii/m/midnightbsd.txt | 16 + src/logo/ascii/m/midos.txt | 20 + src/logo/ascii/m/midos_old.txt | 20 + src/logo/ascii/m/minimal.txt | 18 + src/logo/ascii/m/minix.txt | 17 + src/logo/ascii/m/miracle_linux.txt | 16 + src/logo/ascii/m/mos.txt | 18 + src/logo/ascii/m/msys2.txt | 25 + src/logo/ascii/m/mx.txt | 17 + src/logo/ascii/m/mx2.txt | 18 + src/logo/ascii/m/mx_small.txt | 7 + src/logo/ascii/n.inc | 266 + src/logo/ascii/n/namib.txt | 20 + src/logo/ascii/n/nebios.txt | 19 + src/logo/ascii/n/nekos.txt | 23 + src/logo/ascii/n/neptune.txt | 17 + src/logo/ascii/n/netbsd.txt | 17 + src/logo/ascii/n/netbsd2.txt | 20 + src/logo/ascii/n/netbsd_small.txt | 8 + src/logo/ascii/n/nethydra.txt | 11 + src/logo/ascii/n/netrunner.txt | 20 + src/logo/ascii/n/nexalinux.txt | 19 + src/logo/ascii/n/nitrux.txt | 18 + src/logo/ascii/n/nixos.txt | 20 + src/logo/ascii/n/nixos2.txt | 17 + src/logo/ascii/n/nixos_old.txt | 28 + src/logo/ascii/n/nixos_old_small.txt | 7 + src/logo/ascii/n/nixos_small.txt | 7 + src/logo/ascii/n/nobara.txt | 17 + src/logo/ascii/n/nomadbsd.txt | 13 + src/logo/ascii/n/nuros.txt | 8 + src/logo/ascii/n/nurunner.txt | 19 + src/logo/ascii/n/nutyx.txt | 23 + src/logo/ascii/o.inc | 488 + src/logo/ascii/o/obarun.txt | 19 + src/logo/ascii/o/obrevenge.txt | 18 + src/logo/ascii/o/obsidianos.txt | 16 + src/logo/ascii/o/omnios.txt | 8 + src/logo/ascii/o/openbsd.txt | 23 + src/logo/ascii/o/openbsd_small.txt | 7 + src/logo/ascii/o/openeuler.txt | 23 + src/logo/ascii/o/openindiana.txt | 16 + src/logo/ascii/o/openkylin.txt | 18 + src/logo/ascii/o/openmamba.txt | 21 + src/logo/ascii/o/openmandriva.txt | 22 + src/logo/ascii/o/openruyi.txt | 12 + src/logo/ascii/o/openstage.txt | 17 + src/logo/ascii/o/opensuse.txt | 18 + src/logo/ascii/o/opensuse_leap.txt | 18 + src/logo/ascii/o/opensuse_leap_old.txt | 16 + src/logo/ascii/o/opensuse_microos.txt | 10 + src/logo/ascii/o/opensuse_slowroll.txt | 20 + src/logo/ascii/o/opensuse_small.txt | 7 + src/logo/ascii/o/opensuse_tumbleweed.txt | 22 + src/logo/ascii/o/opensuse_tumbleweed2.txt | 19 + src/logo/ascii/o/opensuse_tumbleweed_old.txt | 13 + src/logo/ascii/o/opensuse_tumbleweed_small.txt | 9 + src/logo/ascii/o/openwrt.txt | 18 + src/logo/ascii/o/openwrt_old.txt | 9 + src/logo/ascii/o/openwrt_small.txt | 9 + src/logo/ascii/o/opnsense.txt | 20 + src/logo/ascii/o/oracle.txt | 11 + src/logo/ascii/o/orchid.txt | 20 + src/logo/ascii/o/orchid_small.txt | 13 + src/logo/ascii/o/oreon.txt | 22 + src/logo/ascii/o/origami.txt | 12 + src/logo/ascii/o/origami_small.txt | 8 + src/logo/ascii/o/os2warp.txt | 17 + src/logo/ascii/o/os_elbrus.txt | 14 + src/logo/ascii/o/osmc.txt | 20 + src/logo/ascii/p.inc | 369 + src/logo/ascii/p/pacbsd.txt | 24 + src/logo/ascii/p/panwah.txt | 21 + src/logo/ascii/p/parabola.txt | 16 + src/logo/ascii/p/parabola_small.txt | 6 + src/logo/ascii/p/parch.txt | 25 + src/logo/ascii/p/pardus.txt | 18 + src/logo/ascii/p/parrot.txt | 24 + src/logo/ascii/p/parsix.txt | 21 + src/logo/ascii/p/pcbsd.txt | 27 + src/logo/ascii/p/pclinuxos.txt | 19 + src/logo/ascii/p/pearos.txt | 21 + src/logo/ascii/p/pengwin.txt | 15 + src/logo/ascii/p/pentoo.txt | 20 + src/logo/ascii/p/peppermint.txt | 21 + src/logo/ascii/p/peropesis.txt | 5 + src/logo/ascii/p/phyos.txt | 17 + src/logo/ascii/p/pikaos.txt | 18 + src/logo/ascii/p/pisi.txt | 19 + src/logo/ascii/p/pnm_linux.txt | 19 + src/logo/ascii/p/pop.txt | 20 + src/logo/ascii/p/pop_small.txt | 8 + src/logo/ascii/p/porteus.txt | 23 + src/logo/ascii/p/postmarketos.txt | 18 + src/logo/ascii/p/postmarketos_small.txt | 9 + src/logo/ascii/p/prismlinux.txt | 21 + src/logo/ascii/p/prismlinux_small.txt | 13 + src/logo/ascii/p/proxmox.txt | 20 + src/logo/ascii/p/puffos.txt | 12 + src/logo/ascii/p/puppy.txt | 18 + src/logo/ascii/p/pureos.txt | 12 + src/logo/ascii/p/pureos_small.txt | 6 + src/logo/ascii/q.inc | 89 + src/logo/ascii/q/q4os.txt | 19 + src/logo/ascii/q/qts.txt | 14 + src/logo/ascii/q/quasar.txt | 25 + src/logo/ascii/q/qubes.txt | 21 + src/logo/ascii/q/qubyt.txt | 15 + src/logo/ascii/q/quibian.txt | 18 + src/logo/ascii/q/quirinux.txt | 17 + src/logo/ascii/r.inc | 297 + src/logo/ascii/r/radix.txt | 18 + src/logo/ascii/r/raspbian.txt | 23 + src/logo/ascii/r/raspbian_small.txt | 10 + src/logo/ascii/r/ravynos.txt | 20 + src/logo/ascii/r/rebornos.txt | 20 + src/logo/ascii/r/rebornos_small.txt | 7 + src/logo/ascii/r/redcore.txt | 16 + src/logo/ascii/r/redos.txt | 18 + src/logo/ascii/r/redos_small.txt | 8 + src/logo/ascii/r/redrose.txt | 17 + src/logo/ascii/r/redstar.txt | 18 + src/logo/ascii/r/refracta.txt | 18 + src/logo/ascii/r/regata.txt | 20 + src/logo/ascii/r/regolith.txt | 18 + src/logo/ascii/r/rengeos.txt | 16 + src/logo/ascii/r/rhaymos.txt | 14 + src/logo/ascii/r/rhel.txt | 18 + src/logo/ascii/r/rhel_old.txt | 16 + src/logo/ascii/r/rhel_small.txt | 9 + src/logo/ascii/r/rhino.txt | 12 + src/logo/ascii/r/rocky.txt | 19 + src/logo/ascii/r/rocky_small.txt | 12 + src/logo/ascii/r/rosa.txt | 18 + src/logo/ascii/s.inc | 530 + src/logo/ascii/s/sabayon.txt | 18 + src/logo/ascii/s/sabotage.txt | 11 + src/logo/ascii/s/sailfish.txt | 13 + src/logo/ascii/s/salentos.txt | 20 + src/logo/ascii/s/salientos.txt | 20 + src/logo/ascii/s/salix.txt | 18 + src/logo/ascii/s/sambabox.txt | 19 + src/logo/ascii/s/sasanqua.txt | 18 + src/logo/ascii/s/scientific.txt | 20 + src/logo/ascii/s/secureblue.txt | 20 + src/logo/ascii/s/semc.txt | 9 + src/logo/ascii/s/septor.txt | 20 + src/logo/ascii/s/serene.txt | 20 + src/logo/ascii/s/serpent_os.txt | 18 + src/logo/ascii/s/sharklinux.txt | 15 + src/logo/ascii/s/shastraos.txt | 19 + src/logo/ascii/s/shebang.txt | 20 + src/logo/ascii/s/siduction.txt | 20 + src/logo/ascii/s/skiffos.txt | 9 + src/logo/ascii/s/slackel.txt | 22 + src/logo/ascii/s/slackware.txt | 21 + src/logo/ascii/s/slackware_small.txt | 7 + src/logo/ascii/s/sleeperos.txt | 16 + src/logo/ascii/s/sleeperos_small.txt | 7 + src/logo/ascii/s/slitaz.txt | 17 + src/logo/ascii/s/smartos.txt | 17 + src/logo/ascii/s/snigdhaos.txt | 20 + src/logo/ascii/s/soda.txt | 15 + src/logo/ascii/s/solaris.txt | 8 + src/logo/ascii/s/solaris_small.txt | 5 + src/logo/ascii/s/solus.txt | 20 + src/logo/ascii/s/source_mage.txt | 21 + src/logo/ascii/s/sparky.txt | 20 + src/logo/ascii/s/spoinkos.txt | 5 + src/logo/ascii/s/star.txt | 19 + src/logo/ascii/s/steamdeck.txt | 24 + src/logo/ascii/s/steamdeck_small.txt | 5 + src/logo/ascii/s/steamos.txt | 18 + src/logo/ascii/s/stock_linux.txt | 19 + src/logo/ascii/s/sulin.txt | 20 + src/logo/ascii/s/summitos.txt | 22 + src/logo/ascii/s/suse.txt | 11 + src/logo/ascii/s/swagarch.txt | 15 + src/logo/ascii/t.inc | 152 + src/logo/ascii/t/t2.txt | 14 + src/logo/ascii/t/t2_small.txt | 6 + src/logo/ascii/t/tails.txt | 19 + src/logo/ascii/t/tatra.txt | 15 + src/logo/ascii/t/tearch.txt | 21 + src/logo/ascii/t/templeos.txt | 20 + src/logo/ascii/t/tileos.txt | 20 + src/logo/ascii/t/torizoncore.txt | 29 + src/logo/ascii/t/trisquel.txt | 17 + src/logo/ascii/t/truenas_scale.txt | 18 + src/logo/ascii/t/tuxedo_os.txt | 16 + src/logo/ascii/t/twister.txt | 23 + src/logo/ascii/u.inc | 316 + src/logo/ascii/u/ublinux.txt | 19 + src/logo/ascii/u/ublinux_small.txt | 11 + src/logo/ascii/u/ubuntu.txt | 20 + src/logo/ascii/u/ubuntu_budgie.txt | 20 + src/logo/ascii/u/ubuntu_cinnamon.txt | 20 + src/logo/ascii/u/ubuntu_gnome.txt | 16 + src/logo/ascii/u/ubuntu_kylin.txt | 20 + src/logo/ascii/u/ubuntu_mate.txt | 20 + src/logo/ascii/u/ubuntu_old.txt | 20 + src/logo/ascii/u/ubuntu_old2.txt | 18 + src/logo/ascii/u/ubuntu_old2_small.txt | 6 + src/logo/ascii/u/ubuntu_small.txt | 11 + src/logo/ascii/u/ubuntu_studio.txt | 20 + src/logo/ascii/u/ubuntu_sway.txt | 20 + src/logo/ascii/u/ubuntu_touch.txt | 7 + src/logo/ascii/u/ubuntu_unity.txt | 25 + src/logo/ascii/u/ultramarine.txt | 20 + src/logo/ascii/u/ultramarine_small.txt | 14 + src/logo/ascii/u/unifi.txt | 6 + src/logo/ascii/u/univalent.txt | 18 + src/logo/ascii/u/univention.txt | 20 + src/logo/ascii/u/uos.txt | 20 + src/logo/ascii/u/urukos.txt | 20 + src/logo/ascii/u/uwuntu.txt | 20 + src/logo/ascii/u/uzbek.txt | 11 + src/logo/ascii/v.inc | 170 + src/logo/ascii/v/valhalla.txt | 10 + src/logo/ascii/v/vanilla.txt | 18 + src/logo/ascii/v/vanilla2.txt | 16 + src/logo/ascii/v/vanilla_small.txt | 7 + src/logo/ascii/v/venom.txt | 19 + src/logo/ascii/v/venom_small.txt | 11 + src/logo/ascii/v/vincentos.txt | 16 + src/logo/ascii/v/vnux.txt | 20 + src/logo/ascii/v/void.txt | 18 + src/logo/ascii/v/void2.txt | 25 + src/logo/ascii/v/void2_small.txt | 7 + src/logo/ascii/v/void_small.txt | 7 + src/logo/ascii/v/vzlinux.txt | 20 + src/logo/ascii/w.inc | 123 + src/logo/ascii/w/wii_linux.txt | 16 + src/logo/ascii/w/windows.txt | 16 + src/logo/ascii/w/windows_11.txt | 17 + src/logo/ascii/w/windows_11_small.txt | 7 + src/logo/ascii/w/windows_2025.txt | 17 + src/logo/ascii/w/windows_8.txt | 19 + src/logo/ascii/w/windows_95.txt | 18 + src/logo/ascii/w/wolfos.txt | 5 + src/logo/ascii/x.inc | 132 + src/logo/ascii/x/xcp_ng.txt | 15 + src/logo/ascii/x/xenia.txt | 15 + src/logo/ascii/x/xenia_old.txt | 21 + src/logo/ascii/x/xeroarch.txt | 19 + src/logo/ascii/x/xferience.txt | 19 + src/logo/ascii/x/ximper.txt | 13 + src/logo/ascii/x/xinux.txt | 14 + src/logo/ascii/x/xj380.txt | 19 + src/logo/ascii/x/xray_os.txt | 21 + src/logo/ascii/x/xubuntu.txt | 19 + src/logo/ascii/y.inc | 31 + src/logo/ascii/y/yakubos.txt | 20 + src/logo/ascii/y/yiffos.txt | 19 + src/logo/ascii/z.inc | 42 + src/logo/ascii/z/zorin.txt | 17 + src/logo/ascii/z/zos.txt | 7 + src/logo/ascii/z/zraxyl.txt | 10 + src/logo/builtin.c | 67 + src/logo/image/im6.c | 27 + src/logo/image/im7.c | 28 + src/logo/image/image.c | 1032 ++ src/logo/image/image.h | 43 + src/logo/logo.c | 813 ++ src/logo/logo.h | 44 + src/logo/logo.svg | 39 + src/modules/battery/battery.c | 326 + src/modules/battery/battery.h | 11 + src/modules/battery/option.h | 14 + src/modules/bios/bios.c | 148 + src/modules/bios/bios.h | 11 + src/modules/bios/option.h | 9 + src/modules/bluetooth/bluetooth.c | 170 + src/modules/bluetooth/bluetooth.h | 11 + src/modules/bluetooth/option.h | 13 + src/modules/bluetoothradio/bluetoothradio.c | 210 + src/modules/bluetoothradio/bluetoothradio.h | 11 + src/modules/bluetoothradio/option.h | 10 + src/modules/board/board.c | 125 + src/modules/board/board.h | 11 + src/modules/board/option.h | 9 + src/modules/bootmgr/bootmgr.c | 121 + src/modules/bootmgr/bootmgr.h | 11 + src/modules/bootmgr/option.h | 9 + src/modules/break/break.c | 36 + src/modules/break/break.h | 11 + src/modules/break/option.h | 8 + src/modules/brightness/brightness.c | 210 + src/modules/brightness/brightness.h | 11 + src/modules/brightness/option.h | 14 + src/modules/btrfs/btrfs.c | 224 + src/modules/btrfs/btrfs.h | 11 + src/modules/btrfs/option.h | 12 + src/modules/camera/camera.c | 134 + src/modules/camera/camera.h | 11 + src/modules/camera/option.h | 9 + src/modules/chassis/chassis.c | 127 + src/modules/chassis/chassis.h | 11 + src/modules/chassis/option.h | 9 + src/modules/codec/codec.c | 268 + src/modules/codec/codec.h | 11 + src/modules/codec/option.h | 20 + src/modules/colors/colors.c | 305 + src/modules/colors/colors.h | 11 + src/modules/colors/option.h | 35 + src/modules/command/command.c | 167 + src/modules/command/command.h | 13 + src/modules/command/option.h | 16 + src/modules/cpu/cpu.c | 262 + src/modules/cpu/cpu.h | 11 + src/modules/cpu/option.h | 14 + src/modules/cpucache/cpucache.c | 238 + src/modules/cpucache/cpucache.h | 11 + src/modules/cpucache/option.h | 11 + src/modules/cpuusage/cpuusage.c | 202 + src/modules/cpuusage/cpuusage.h | 13 + src/modules/cpuusage/option.h | 14 + src/modules/cursor/cursor.c | 115 + src/modules/cursor/cursor.h | 11 + src/modules/cursor/option.h | 9 + src/modules/custom/custom.c | 45 + src/modules/custom/custom.h | 11 + src/modules/custom/option.h | 9 + src/modules/datetime/datetime.c | 189 + src/modules/datetime/datetime.h | 11 + src/modules/datetime/option.h | 9 + src/modules/de/de.c | 98 + src/modules/de/de.h | 11 + src/modules/de/option.h | 9 + src/modules/disk/disk.c | 490 + src/modules/disk/disk.h | 11 + src/modules/disk/option.h | 33 + src/modules/diskio/diskio.c | 193 + src/modules/diskio/diskio.h | 13 + src/modules/diskio/option.h | 13 + src/modules/display/display.c | 457 + src/modules/display/display.h | 11 + src/modules/display/option.h | 27 + src/modules/dns/dns.c | 148 + src/modules/dns/dns.h | 11 + src/modules/dns/option.h | 18 + src/modules/editor/editor.c | 121 + src/modules/editor/editor.h | 11 + src/modules/editor/option.h | 9 + src/modules/font/font.c | 113 + src/modules/font/font.h | 11 + src/modules/font/option.h | 9 + src/modules/gamepad/gamepad.c | 206 + src/modules/gamepad/gamepad.h | 11 + src/modules/gamepad/option.h | 13 + src/modules/gpu/gpu.c | 488 + src/modules/gpu/gpu.h | 11 + src/modules/gpu/option.h | 32 + src/modules/host/host.c | 156 + src/modules/host/host.h | 11 + src/modules/host/option.h | 9 + src/modules/icons/icons.c | 109 + src/modules/icons/icons.h | 11 + src/modules/icons/option.h | 9 + src/modules/initsystem/initsystem.c | 120 + src/modules/initsystem/initsystem.h | 11 + src/modules/initsystem/option.h | 9 + src/modules/kernel/kernel.c | 81 + src/modules/kernel/kernel.h | 11 + src/modules/kernel/option.h | 9 + src/modules/keyboard/keyboard.c | 164 + src/modules/keyboard/keyboard.h | 11 + src/modules/keyboard/option.h | 12 + src/modules/lm/lm.c | 122 + src/modules/lm/lm.h | 11 + src/modules/lm/option.h | 9 + src/modules/loadavg/loadavg.c | 164 + src/modules/loadavg/loadavg.h | 11 + src/modules/loadavg/option.h | 13 + src/modules/locale/locale.c | 81 + src/modules/locale/locale.h | 11 + src/modules/locale/option.h | 9 + src/modules/localip/localip.c | 443 + src/modules/localip/localip.h | 11 + src/modules/localip/option.h | 44 + src/modules/logo/logo.c | 87 + src/modules/logo/logo.h | 11 + src/modules/logo/option.h | 8 + src/modules/media/media.c | 277 + src/modules/media/media.h | 11 + src/modules/media/option.h | 11 + src/modules/memory/memory.c | 135 + src/modules/memory/memory.h | 11 + src/modules/memory/option.h | 12 + src/modules/modules.c | 364 + src/modules/modules.h | 77 + src/modules/monitor/monitor.c | 137 + src/modules/monitor/monitor.h | 11 + src/modules/monitor/option.h | 9 + src/modules/mouse/mouse.c | 167 + src/modules/mouse/mouse.h | 11 + src/modules/mouse/option.h | 12 + src/modules/netio/netio.c | 224 + src/modules/netio/netio.h | 13 + src/modules/netio/option.h | 14 + src/modules/opencl/opencl.c | 136 + src/modules/opencl/opencl.h | 11 + src/modules/opencl/option.h | 9 + src/modules/opengl/opengl.c | 145 + src/modules/opengl/opengl.h | 11 + src/modules/opengl/option.h | 17 + src/modules/options.h | 77 + src/modules/os/option.h | 9 + src/modules/os/os.c | 197 + src/modules/os/os.h | 11 + src/modules/packages/option.h | 55 + src/modules/packages/packages.c | 560 + src/modules/packages/packages.h | 11 + src/modules/physicaldisk/option.h | 34 + src/modules/physicaldisk/physicaldisk.c | 288 + src/modules/physicaldisk/physicaldisk.h | 11 + src/modules/physicalmemory/option.h | 10 + src/modules/physicalmemory/physicalmemory.c | 184 + src/modules/physicalmemory/physicalmemory.h | 11 + src/modules/player/option.h | 9 + src/modules/player/player.c | 126 + src/modules/player/player.h | 12 + src/modules/poweradapter/option.h | 9 + src/modules/poweradapter/poweradapter.c | 129 + src/modules/poweradapter/poweradapter.h | 11 + src/modules/processes/option.h | 9 + src/modules/processes/processes.c | 76 + src/modules/processes/processes.h | 11 + src/modules/publicip/option.h | 13 + src/modules/publicip/publicip.c | 125 + src/modules/publicip/publicip.h | 13 + src/modules/separator/option.h | 11 + src/modules/separator/separator.c | 130 + src/modules/separator/separator.h | 11 + src/modules/shell/option.h | 9 + src/modules/shell/shell.c | 110 + src/modules/shell/shell.h | 11 + src/modules/sound/option.h | 21 + src/modules/sound/sound.c | 219 + src/modules/sound/sound.h | 11 + src/modules/swap/option.h | 13 + src/modules/swap/swap.c | 200 + src/modules/swap/swap.h | 11 + src/modules/terminal/option.h | 9 + src/modules/terminal/terminal.c | 104 + src/modules/terminal/terminal.h | 11 + src/modules/terminalfont/option.h | 9 + src/modules/terminalfont/terminalfont.c | 122 + src/modules/terminalfont/terminalfont.h | 11 + src/modules/terminalsize/option.h | 9 + src/modules/terminalsize/terminalsize.c | 93 + src/modules/terminalsize/terminalsize.h | 11 + src/modules/terminaltheme/option.h | 9 + src/modules/terminaltheme/terminaltheme.c | 110 + src/modules/terminaltheme/terminaltheme.h | 11 + src/modules/theme/option.h | 9 + src/modules/theme/theme.c | 102 + src/modules/theme/theme.h | 11 + src/modules/title/option.h | 14 + src/modules/title/title.c | 201 + src/modules/title/title.h | 11 + src/modules/tpm/option.h | 9 + src/modules/tpm/tpm.c | 98 + src/modules/tpm/tpm.h | 11 + src/modules/uptime/option.h | 9 + src/modules/uptime/uptime.c | 106 + src/modules/uptime/uptime.h | 11 + src/modules/users/option.h | 12 + src/modules/users/users.c | 206 + src/modules/users/users.h | 11 + src/modules/version/option.h | 9 + src/modules/version/version.c | 117 + src/modules/version/version.h | 11 + src/modules/vulkan/option.h | 9 + src/modules/vulkan/vulkan.c | 149 + src/modules/vulkan/vulkan.h | 11 + src/modules/wallpaper/option.h | 9 + src/modules/wallpaper/wallpaper.c | 90 + src/modules/wallpaper/wallpaper.h | 11 + src/modules/weather/option.h | 13 + src/modules/weather/weather.c | 105 + src/modules/weather/weather.h | 13 + src/modules/wifi/option.h | 11 + src/modules/wifi/wifi.c | 239 + src/modules/wifi/wifi.h | 11 + src/modules/wm/option.h | 11 + src/modules/wm/wm.c | 138 + src/modules/wm/wm.h | 11 + src/modules/wmtheme/option.h | 9 + src/modules/wmtheme/wmtheme.c | 75 + src/modules/wmtheme/wmtheme.h | 11 + src/modules/zpool/option.h | 12 + src/modules/zpool/zpool.c | 218 + src/modules/zpool/zpool.h | 11 + src/options/display.c | 1168 ++ src/options/display.h | 97 + src/options/general.c | 134 + src/options/general.h | 27 + src/options/logo.c | 562 + src/options/logo.h | 61 + 1384 files changed, 133162 insertions(+) create mode 100644 src/3rdparty/display-library/adl_defines.h create mode 100644 src/3rdparty/display-library/adl_sdk.h create mode 100644 src/3rdparty/display-library/adl_structures.h create mode 100644 src/3rdparty/display-library/repo.json create mode 100644 src/3rdparty/widecharwidth/repo.json create mode 100644 src/3rdparty/widecharwidth/widechar_width_c.h create mode 100644 src/3rdparty/yyjson/repo.json create mode 100644 src/3rdparty/yyjson/yyjson.c create mode 100644 src/3rdparty/yyjson/yyjson.h create mode 100644 src/common/FFPlatform.h create mode 100644 src/common/FFlist.h create mode 100644 src/common/FFstrbuf.h create mode 100644 src/common/apple/Info.plist.in create mode 100644 src/common/apple/cf_helpers.c create mode 100644 src/common/apple/cf_helpers.h create mode 100644 src/common/apple/osascript.h create mode 100644 src/common/apple/osascript.m create mode 100644 src/common/apple/smc_temps.c create mode 100644 src/common/apple/smc_temps.h create mode 100644 src/common/apple/version.h create mode 100644 src/common/apple/version.m create mode 100644 src/common/argType.h create mode 100644 src/common/arrutil.h create mode 100644 src/common/attributes.h create mode 100644 src/common/base64.h create mode 100644 src/common/binary.h create mode 100644 src/common/color.h create mode 100644 src/common/commandoption.h create mode 100644 src/common/dbus.h create mode 100644 src/common/debug.h create mode 100644 src/common/duration.h create mode 100644 src/common/edidHelper.h create mode 100644 src/common/ffdata.h create mode 100644 src/common/font.h create mode 100644 src/common/format.h create mode 100644 src/common/frequency.h create mode 100644 src/common/haiku/version.cpp create mode 100644 src/common/haiku/version.h create mode 100644 src/common/impl/FFPlatform.c create mode 100644 src/common/impl/FFPlatform_private.h create mode 100644 src/common/impl/FFPlatform_unix.c create mode 100644 src/common/impl/FFPlatform_windows.c create mode 100644 src/common/impl/FFlist.c create mode 100644 src/common/impl/FFstrbuf.c create mode 100644 src/common/impl/base64.c create mode 100644 src/common/impl/binary_apple.c create mode 100644 src/common/impl/binary_linux.c create mode 100644 src/common/impl/binary_windows.c create mode 100644 src/common/impl/commandoption.c create mode 100644 src/common/impl/dbus.c create mode 100644 src/common/impl/debug_windows.c create mode 100644 src/common/impl/duration.c create mode 100644 src/common/impl/edidHelper.c create mode 100644 src/common/impl/font.c create mode 100644 src/common/impl/format.c create mode 100644 src/common/impl/frequency.c create mode 100644 src/common/impl/init.c create mode 100644 src/common/impl/io_unix.c create mode 100644 src/common/impl/io_windows.c create mode 100644 src/common/impl/jsonconfig.c create mode 100644 src/common/impl/kmod_apple.c create mode 100644 src/common/impl/kmod_bsd.c create mode 100644 src/common/impl/kmod_linux.c create mode 100644 src/common/impl/kmod_nbsd.c create mode 100644 src/common/impl/kmod_nosupport.c create mode 100644 src/common/impl/kmod_sunos.c create mode 100644 src/common/impl/kmod_windows.c create mode 100644 src/common/impl/library.c create mode 100644 src/common/impl/lua.c create mode 100644 src/common/impl/memrchr.c create mode 100644 src/common/impl/netif.c create mode 100644 src/common/impl/netif_apple.c create mode 100644 src/common/impl/netif_bsd.c create mode 100644 src/common/impl/netif_gnu.c create mode 100644 src/common/impl/netif_haiku.c create mode 100644 src/common/impl/netif_linux.c create mode 100644 src/common/impl/netif_windows.c create mode 100644 src/common/impl/networking_common.c create mode 100644 src/common/impl/networking_linux.c create mode 100644 src/common/impl/networking_windows.c create mode 100644 src/common/impl/option.c create mode 100644 src/common/impl/parsing.c create mode 100644 src/common/impl/path.c create mode 100644 src/common/impl/percent.c create mode 100644 src/common/impl/printing.c create mode 100644 src/common/impl/processing_linux.c create mode 100644 src/common/impl/processing_windows.c create mode 100644 src/common/impl/properties.c create mode 100644 src/common/impl/settings.c create mode 100644 src/common/impl/size.c create mode 100644 src/common/impl/smbios.c create mode 100644 src/common/impl/strutil.c create mode 100644 src/common/impl/sysctl.c create mode 100644 src/common/impl/temps.c create mode 100644 src/common/impl/time.c create mode 100644 src/common/impl/wcwidth.c create mode 100644 src/common/init.h create mode 100644 src/common/io.h create mode 100644 src/common/jsonconfig.h create mode 100644 src/common/kmod.h create mode 100644 src/common/library.h create mode 100644 src/common/lua.h create mode 100644 src/common/mallocHelper.h create mode 100644 src/common/memrchr.h create mode 100644 src/common/netif.h create mode 100644 src/common/networking.h create mode 100644 src/common/option.h create mode 100644 src/common/parsing.h create mode 100644 src/common/path.h create mode 100644 src/common/percent.h create mode 100644 src/common/printing.h create mode 100644 src/common/processing.h create mode 100644 src/common/properties.h create mode 100644 src/common/settings.h create mode 100644 src/common/size.h create mode 100644 src/common/smbios.h create mode 100644 src/common/strutil.h create mode 100644 src/common/sysctl.h create mode 100644 src/common/temps.h create mode 100644 src/common/textModifier.h create mode 100644 src/common/thread.h create mode 100644 src/common/time.h create mode 100644 src/common/unused.h create mode 100644 src/common/wcwidth.h create mode 100755 src/common/windows/c-logo.sh create mode 100644 src/common/windows/com.c create mode 100644 src/common/windows/com.h create mode 100644 src/common/windows/getline.c create mode 100644 src/common/windows/getline.h create mode 100644 src/common/windows/logo.ico create mode 120000 src/common/windows/logo.svg create mode 100644 src/common/windows/manifest.xml create mode 100644 src/common/windows/nt.h create mode 100644 src/common/windows/perflib_.h create mode 100644 src/common/windows/registry.c create mode 100644 src/common/windows/registry.h create mode 100644 src/common/windows/unicode.c create mode 100644 src/common/windows/unicode.h create mode 100644 src/common/windows/unicode.hpp create mode 100644 src/common/windows/util.hpp create mode 100644 src/common/windows/variant.cpp create mode 100644 src/common/windows/variant.hpp create mode 100644 src/common/windows/version.c create mode 100644 src/common/windows/version.h create mode 100644 src/common/windows/version.rc create mode 100644 src/common/windows/wmi.h create mode 100644 src/detection/battery/battery.h create mode 100644 src/detection/battery/battery_android.c create mode 100644 src/detection/battery/battery_apple.c create mode 100644 src/detection/battery/battery_bsd.c create mode 100644 src/detection/battery/battery_haiku.c create mode 100644 src/detection/battery/battery_linux.c create mode 100644 src/detection/battery/battery_nbsd.c create mode 100644 src/detection/battery/battery_nosupport.c create mode 100644 src/detection/battery/battery_obsd.c create mode 100644 src/detection/battery/battery_windows.c create mode 100644 src/detection/bios/bios.h create mode 100644 src/detection/bios/bios_android.c create mode 100644 src/detection/bios/bios_apple.c create mode 100644 src/detection/bios/bios_bsd.c create mode 100644 src/detection/bios/bios_linux.c create mode 100644 src/detection/bios/bios_nbsd.c create mode 100644 src/detection/bios/bios_nosupport.c create mode 100644 src/detection/bios/bios_windows.c create mode 100644 src/detection/bluetooth/bluetooth.h create mode 100644 src/detection/bluetooth/bluetooth_apple.m create mode 100644 src/detection/bluetooth/bluetooth_bsd.c create mode 100644 src/detection/bluetooth/bluetooth_haiku.cpp create mode 100644 src/detection/bluetooth/bluetooth_linux.c create mode 100644 src/detection/bluetooth/bluetooth_nosupport.c create mode 100644 src/detection/bluetooth/bluetooth_windows.c create mode 100644 src/detection/bluetoothradio/bluetoothradio.c create mode 100644 src/detection/bluetoothradio/bluetoothradio.h create mode 100644 src/detection/bluetoothradio/bluetoothradio_apple.m create mode 100644 src/detection/bluetoothradio/bluetoothradio_linux.c create mode 100644 src/detection/bluetoothradio/bluetoothradio_nosupport.c create mode 100644 src/detection/bluetoothradio/bluetoothradio_windows.c create mode 100644 src/detection/board/board.h create mode 100644 src/detection/board/board_android.c create mode 100644 src/detection/board/board_apple.c create mode 100644 src/detection/board/board_bsd.c create mode 100644 src/detection/board/board_linux.c create mode 100644 src/detection/board/board_nbsd.c create mode 100644 src/detection/board/board_nosupport.c create mode 100644 src/detection/board/board_windows.c create mode 100644 src/detection/bootmgr/bootmgr.c create mode 100644 src/detection/bootmgr/bootmgr.h create mode 100644 src/detection/bootmgr/bootmgr_apple.c create mode 100644 src/detection/bootmgr/bootmgr_bsd.c create mode 100644 src/detection/bootmgr/bootmgr_haiku.cpp create mode 100644 src/detection/bootmgr/bootmgr_linux.c create mode 100644 src/detection/bootmgr/bootmgr_nosupport.c create mode 100644 src/detection/bootmgr/bootmgr_windows.c create mode 100644 src/detection/bootmgr/efi_helper.h create mode 100644 src/detection/brightness/brightness.h create mode 100644 src/detection/brightness/brightness_apple.c create mode 100644 src/detection/brightness/brightness_bsd.c create mode 100644 src/detection/brightness/brightness_haiku.cpp create mode 100644 src/detection/brightness/brightness_linux.c create mode 100644 src/detection/brightness/brightness_nbsd.c create mode 100644 src/detection/brightness/brightness_nosupport.c create mode 100644 src/detection/brightness/brightness_obsd.c create mode 100644 src/detection/brightness/brightness_windows.c create mode 100644 src/detection/btrfs/btrfs.h create mode 100644 src/detection/btrfs/btrfs_linux.c create mode 100644 src/detection/btrfs/btrfs_nosupport.c create mode 100644 src/detection/camera/camera.h create mode 100644 src/detection/camera/camera_android.c create mode 100644 src/detection/camera/camera_apple.m create mode 100644 src/detection/camera/camera_linux.c create mode 100644 src/detection/camera/camera_nosupport.c create mode 100644 src/detection/camera/camera_windows.cpp create mode 100644 src/detection/chassis/chassis.c create mode 100644 src/detection/chassis/chassis.h create mode 100644 src/detection/chassis/chassis_apple.c create mode 100644 src/detection/chassis/chassis_bsd.c create mode 100644 src/detection/chassis/chassis_linux.c create mode 100644 src/detection/chassis/chassis_nbsd.c create mode 100644 src/detection/chassis/chassis_nosupport.c create mode 100644 src/detection/chassis/chassis_windows.c create mode 100644 src/detection/codec/codec.c create mode 100644 src/detection/codec/codec.h create mode 100644 src/detection/codec/codec_android.c create mode 100644 src/detection/codec/codec_apple.c create mode 100644 src/detection/codec/codec_linux.c create mode 100644 src/detection/codec/codec_vulkan.c create mode 100644 src/detection/codec/codec_windows.cpp create mode 100644 src/detection/command/command.c create mode 100644 src/detection/command/command.h create mode 100644 src/detection/cpu/cpu.c create mode 100644 src/detection/cpu/cpu.h create mode 100644 src/detection/cpu/cpu_apple.c create mode 100644 src/detection/cpu/cpu_arm.h create mode 100644 src/detection/cpu/cpu_bsd.c create mode 100644 src/detection/cpu/cpu_haiku.c create mode 100644 src/detection/cpu/cpu_linux.c create mode 100644 src/detection/cpu/cpu_nbsd.c create mode 100644 src/detection/cpu/cpu_nosupport.c create mode 100644 src/detection/cpu/cpu_obsd.c create mode 100644 src/detection/cpu/cpu_sunos.c create mode 100644 src/detection/cpu/cpu_windows.c create mode 100644 src/detection/cpucache/cpucache.h create mode 100644 src/detection/cpucache/cpucache_apple.c create mode 100644 src/detection/cpucache/cpucache_linux.c create mode 100644 src/detection/cpucache/cpucache_nosupport.c create mode 100644 src/detection/cpucache/cpucache_shared.c create mode 100644 src/detection/cpucache/cpucache_windows.c create mode 100644 src/detection/cpuusage/cpuusage.c create mode 100644 src/detection/cpuusage/cpuusage.h create mode 100644 src/detection/cpuusage/cpuusage_apple.c create mode 100644 src/detection/cpuusage/cpuusage_bsd.c create mode 100644 src/detection/cpuusage/cpuusage_haiku.c create mode 100644 src/detection/cpuusage/cpuusage_linux.c create mode 100644 src/detection/cpuusage/cpuusage_nosupport.c create mode 100644 src/detection/cpuusage/cpuusage_sunos.c create mode 100644 src/detection/cpuusage/cpuusage_windows.c create mode 100644 src/detection/cursor/cursor.h create mode 100644 src/detection/cursor/cursor_apple.m create mode 100644 src/detection/cursor/cursor_linux.c create mode 100644 src/detection/cursor/cursor_nosupport.c create mode 100644 src/detection/cursor/cursor_windows.c create mode 100644 src/detection/de/de.h create mode 100644 src/detection/de/de_linux.c create mode 100644 src/detection/de/de_nosupport.c create mode 100644 src/detection/disk/disk.c create mode 100644 src/detection/disk/disk.h create mode 100644 src/detection/disk/disk_bsd.c create mode 100644 src/detection/disk/disk_haiku.cpp create mode 100644 src/detection/disk/disk_linux.c create mode 100644 src/detection/disk/disk_nosupport.c create mode 100644 src/detection/disk/disk_sunos.c create mode 100644 src/detection/disk/disk_windows.c create mode 100644 src/detection/diskio/diskio.c create mode 100644 src/detection/diskio/diskio.h create mode 100644 src/detection/diskio/diskio_apple.c create mode 100644 src/detection/diskio/diskio_bsd.c create mode 100644 src/detection/diskio/diskio_linux.c create mode 100644 src/detection/diskio/diskio_nbsd.c create mode 100644 src/detection/diskio/diskio_nosupport.c create mode 100644 src/detection/diskio/diskio_obsd.c create mode 100644 src/detection/diskio/diskio_sunos.c create mode 100644 src/detection/diskio/diskio_windows.c create mode 100644 src/detection/displayserver/displayserver.c create mode 100644 src/detection/displayserver/displayserver.h create mode 100644 src/detection/displayserver/displayserver_android.c create mode 100644 src/detection/displayserver/displayserver_apple.c create mode 100644 src/detection/displayserver/displayserver_haiku.cpp create mode 100644 src/detection/displayserver/displayserver_windows.c create mode 100644 src/detection/displayserver/linux/common.c create mode 100644 src/detection/displayserver/linux/displayserver_linux.c create mode 100644 src/detection/displayserver/linux/displayserver_linux.h create mode 100644 src/detection/displayserver/linux/drm.c create mode 100644 src/detection/displayserver/linux/wayland/global-output.c create mode 100644 src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h create mode 100644 src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c create mode 100644 src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h create mode 100644 src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c create mode 100644 src/detection/displayserver/linux/wayland/kde-output.c create mode 100644 src/detection/displayserver/linux/wayland/wayland.c create mode 100644 src/detection/displayserver/linux/wayland/wayland.h create mode 100644 src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h create mode 100644 src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c create mode 100644 src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h create mode 100644 src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c create mode 100644 src/detection/displayserver/linux/wayland/zwlr-output.c create mode 100644 src/detection/displayserver/linux/wmde.c create mode 100644 src/detection/displayserver/linux/xcb.c create mode 100644 src/detection/displayserver/linux/xlib.c create mode 100644 src/detection/dns/dns.h create mode 100644 src/detection/dns/dns_apple.c create mode 100644 src/detection/dns/dns_linux.c create mode 100644 src/detection/dns/dns_windows.c create mode 100644 src/detection/editor/editor.c create mode 100644 src/detection/editor/editor.h create mode 100644 src/detection/font/font.c create mode 100644 src/detection/font/font.h create mode 100644 src/detection/font/font_apple.m create mode 100644 src/detection/font/font_haiku.cpp create mode 100644 src/detection/font/font_linux.c create mode 100644 src/detection/font/font_nosupport.c create mode 100644 src/detection/font/font_windows.c create mode 100644 src/detection/gamepad/gamepad.h create mode 100644 src/detection/gamepad/gamepad_apple.c create mode 100644 src/detection/gamepad/gamepad_bsd.c create mode 100644 src/detection/gamepad/gamepad_haiku.cpp create mode 100644 src/detection/gamepad/gamepad_linux.c create mode 100644 src/detection/gamepad/gamepad_nosupport.c create mode 100644 src/detection/gamepad/gamepad_windows.c create mode 100644 src/detection/gpu/adl.h create mode 100644 src/detection/gpu/asahi_drm.h create mode 100644 src/detection/gpu/d3dkmthk.h create mode 100644 src/detection/gpu/gpu.c create mode 100644 src/detection/gpu/gpu.h create mode 100644 src/detection/gpu/gpu_amd.c create mode 100644 src/detection/gpu/gpu_android.c create mode 100644 src/detection/gpu/gpu_apple.c create mode 100644 src/detection/gpu/gpu_apple.m create mode 100644 src/detection/gpu/gpu_bsd.c create mode 100644 src/detection/gpu/gpu_driver_specific.h create mode 100644 src/detection/gpu/gpu_drm.c create mode 100644 src/detection/gpu/gpu_gnu.c create mode 100644 src/detection/gpu/gpu_haiku.c create mode 100644 src/detection/gpu/gpu_intel.c create mode 100644 src/detection/gpu/gpu_linux.c create mode 100644 src/detection/gpu/gpu_mthreads.c create mode 100644 src/detection/gpu/gpu_nbsd.c create mode 100644 src/detection/gpu/gpu_nosupport.c create mode 100644 src/detection/gpu/gpu_nvidia.c create mode 100644 src/detection/gpu/gpu_obsd.c create mode 100644 src/detection/gpu/gpu_pci.c create mode 100644 src/detection/gpu/gpu_sunos.c create mode 100644 src/detection/gpu/gpu_windows.c create mode 100644 src/detection/gpu/gpu_windows.cpp create mode 100644 src/detection/gpu/igcl.h create mode 100644 src/detection/gpu/intel_drm.h create mode 100644 src/detection/gpu/mtml.h create mode 100644 src/detection/gpu/nvapi.h create mode 100644 src/detection/gpu/nvml.h create mode 100644 src/detection/gtk_qt/gtk.c create mode 100644 src/detection/gtk_qt/gtk_qt.h create mode 100644 src/detection/gtk_qt/qt.c create mode 100644 src/detection/host/host.h create mode 100644 src/detection/host/host_android.c create mode 100644 src/detection/host/host_apple.c create mode 100644 src/detection/host/host_bsd.c create mode 100644 src/detection/host/host_linux.c create mode 100644 src/detection/host/host_mac.c create mode 100644 src/detection/host/host_nbsd.c create mode 100644 src/detection/host/host_nosupport.c create mode 100644 src/detection/host/host_obsd.c create mode 100644 src/detection/host/host_windows.c create mode 100644 src/detection/icons/icons.h create mode 100644 src/detection/icons/icons_linux.c create mode 100644 src/detection/icons/icons_nosupport.c create mode 100644 src/detection/icons/icons_windows.c create mode 100644 src/detection/initsystem/initsystem.h create mode 100644 src/detection/initsystem/initsystem_haiku.c create mode 100644 src/detection/initsystem/initsystem_linux.c create mode 100644 src/detection/initsystem/initsystem_nosupport.c create mode 100644 src/detection/keyboard/keyboard.h create mode 100644 src/detection/keyboard/keyboard_apple.c create mode 100644 src/detection/keyboard/keyboard_bsd.c create mode 100644 src/detection/keyboard/keyboard_haiku.cpp create mode 100644 src/detection/keyboard/keyboard_linux.c create mode 100644 src/detection/keyboard/keyboard_nosupport.c create mode 100644 src/detection/keyboard/keyboard_windows.c create mode 100644 src/detection/libc/libc.h create mode 100644 src/detection/libc/libc_android.c create mode 100644 src/detection/libc/libc_apple.c create mode 100644 src/detection/libc/libc_bsd.c create mode 100644 src/detection/libc/libc_linux.c create mode 100644 src/detection/libc/libc_nosupport.c create mode 100644 src/detection/libc/libc_windows.cpp create mode 100644 src/detection/lm/lm.h create mode 100644 src/detection/lm/lm_linux.c create mode 100644 src/detection/lm/lm_nosupport.c create mode 100644 src/detection/loadavg/loadavg.h create mode 100644 src/detection/loadavg/loadavg_bsd.c create mode 100644 src/detection/loadavg/loadavg_linux.c create mode 100644 src/detection/loadavg/loadavg_nosupport.c create mode 100644 src/detection/loadavg/loadavg_sunos.c create mode 100644 src/detection/locale/locale.h create mode 100644 src/detection/locale/locale_linux.c create mode 100644 src/detection/locale/locale_windows.c create mode 100644 src/detection/localip/localip.h create mode 100644 src/detection/localip/localip_linux.c create mode 100644 src/detection/localip/localip_windows.c create mode 100644 src/detection/media/media.c create mode 100644 src/detection/media/media.h create mode 100644 src/detection/media/media_apple.m create mode 100644 src/detection/media/media_linux.c create mode 100644 src/detection/media/media_nosupport.c create mode 100644 src/detection/media/media_windows.cpp create mode 100644 src/detection/memory/memory.h create mode 100644 src/detection/memory/memory_apple.c create mode 100644 src/detection/memory/memory_bsd.c create mode 100644 src/detection/memory/memory_haiku.c create mode 100644 src/detection/memory/memory_linux.c create mode 100644 src/detection/memory/memory_nbsd.c create mode 100644 src/detection/memory/memory_nosupport.c create mode 100644 src/detection/memory/memory_obsd.c create mode 100644 src/detection/memory/memory_sunos.c create mode 100644 src/detection/memory/memory_windows.c create mode 100644 src/detection/mouse/mouse.h create mode 100644 src/detection/mouse/mouse_apple.c create mode 100644 src/detection/mouse/mouse_bsd.c create mode 100644 src/detection/mouse/mouse_haiku.cpp create mode 100644 src/detection/mouse/mouse_linux.c create mode 100644 src/detection/mouse/mouse_nosupport.c create mode 100644 src/detection/mouse/mouse_windows.c create mode 100644 src/detection/netio/netio.c create mode 100644 src/detection/netio/netio.h create mode 100644 src/detection/netio/netio_apple.c create mode 100644 src/detection/netio/netio_bsd.c create mode 100644 src/detection/netio/netio_haiku.cpp create mode 100644 src/detection/netio/netio_linux.c create mode 100644 src/detection/netio/netio_nosupport.c create mode 100644 src/detection/netio/netio_sunos.c create mode 100644 src/detection/netio/netio_windows.c create mode 100644 src/detection/opencl/opencl.c create mode 100644 src/detection/opencl/opencl.h create mode 100644 src/detection/opengl/opengl.h create mode 100644 src/detection/opengl/opengl_apple.c create mode 100644 src/detection/opengl/opengl_haiku.cpp create mode 100644 src/detection/opengl/opengl_linux.c create mode 100644 src/detection/opengl/opengl_shared.c create mode 100644 src/detection/opengl/opengl_windows.c create mode 100644 src/detection/os/os.c create mode 100644 src/detection/os/os.h create mode 100644 src/detection/os/os_android.c create mode 100644 src/detection/os/os_apple.m create mode 100644 src/detection/os/os_haiku.c create mode 100644 src/detection/os/os_linux.c create mode 100644 src/detection/os/os_nbsd.c create mode 100644 src/detection/os/os_obsd.c create mode 100644 src/detection/os/os_sunos.c create mode 100644 src/detection/os/os_windows.c create mode 100644 src/detection/packages/packages.c create mode 100644 src/detection/packages/packages.h create mode 100644 src/detection/packages/packages_apple.c create mode 100644 src/detection/packages/packages_bsd.c create mode 100644 src/detection/packages/packages_haiku.c create mode 100644 src/detection/packages/packages_linux.c create mode 100644 src/detection/packages/packages_nbsd.c create mode 100644 src/detection/packages/packages_nix.c create mode 100644 src/detection/packages/packages_nosupport.c create mode 100644 src/detection/packages/packages_obsd.c create mode 100644 src/detection/packages/packages_sunos.c create mode 100644 src/detection/packages/packages_windows.c create mode 100644 src/detection/physicaldisk/physicaldisk.h create mode 100644 src/detection/physicaldisk/physicaldisk_apple.c create mode 100644 src/detection/physicaldisk/physicaldisk_bsd.c create mode 100644 src/detection/physicaldisk/physicaldisk_haiku.c create mode 100644 src/detection/physicaldisk/physicaldisk_linux.c create mode 100644 src/detection/physicaldisk/physicaldisk_nbsd.c create mode 100644 src/detection/physicaldisk/physicaldisk_nosupport.c create mode 100644 src/detection/physicaldisk/physicaldisk_obsd.c create mode 100644 src/detection/physicaldisk/physicaldisk_sunos.c create mode 100644 src/detection/physicaldisk/physicaldisk_windows.c create mode 100644 src/detection/physicalmemory/physicalmemory.c create mode 100644 src/detection/physicalmemory/physicalmemory.h create mode 100644 src/detection/physicalmemory/physicalmemory_apple.m create mode 100644 src/detection/physicalmemory/physicalmemory_linux.c create mode 100644 src/detection/physicalmemory/physicalmemory_nosupport.c create mode 100644 src/detection/poweradapter/poweradapter.h create mode 100644 src/detection/poweradapter/poweradapter_apple.c create mode 100644 src/detection/poweradapter/poweradapter_linux.c create mode 100644 src/detection/poweradapter/poweradapter_nosupport.c create mode 100644 src/detection/processes/processes.h create mode 100644 src/detection/processes/processes_bsd.c create mode 100644 src/detection/processes/processes_haiku.c create mode 100644 src/detection/processes/processes_linux.c create mode 100644 src/detection/processes/processes_nbsd.c create mode 100644 src/detection/processes/processes_nosupport.c create mode 100644 src/detection/processes/processes_obsd.c create mode 100644 src/detection/processes/processes_windows.c create mode 100644 src/detection/publicip/publicip.c create mode 100644 src/detection/publicip/publicip.h create mode 100644 src/detection/sound/audio_oss_sunos.h create mode 100644 src/detection/sound/sound.h create mode 100644 src/detection/sound/sound_apple.c create mode 100644 src/detection/sound/sound_bsd.c create mode 100644 src/detection/sound/sound_haiku.cpp create mode 100644 src/detection/sound/sound_linux.c create mode 100644 src/detection/sound/sound_nbsd.c create mode 100644 src/detection/sound/sound_nosupport.c create mode 100644 src/detection/sound/sound_obsd.c create mode 100644 src/detection/sound/sound_sunos.c create mode 100644 src/detection/sound/sound_windows.cpp create mode 100644 src/detection/swap/swap.h create mode 100644 src/detection/swap/swap_apple.c create mode 100644 src/detection/swap/swap_bsd.c create mode 100644 src/detection/swap/swap_haiku.c create mode 100644 src/detection/swap/swap_linux.c create mode 100644 src/detection/swap/swap_nosupport.c create mode 100644 src/detection/swap/swap_obsd.c create mode 100644 src/detection/swap/swap_sunos.c create mode 100644 src/detection/swap/swap_windows.c create mode 100644 src/detection/terminalfont/terminalfont.c create mode 100644 src/detection/terminalfont/terminalfont.h create mode 100644 src/detection/terminalfont/terminalfont_android.c create mode 100644 src/detection/terminalfont/terminalfont_apple.m create mode 100644 src/detection/terminalfont/terminalfont_linux.c create mode 100644 src/detection/terminalfont/terminalfont_windows.c create mode 100644 src/detection/terminalshell/terminalshell.c create mode 100644 src/detection/terminalshell/terminalshell.h create mode 100644 src/detection/terminalshell/terminalshell_linux.c create mode 100644 src/detection/terminalshell/terminalshell_windows.c create mode 100644 src/detection/terminalsize/terminalsize.h create mode 100644 src/detection/terminalsize/terminalsize_linux.c create mode 100644 src/detection/terminalsize/terminalsize_windows.c create mode 100644 src/detection/terminaltheme/terminaltheme.c create mode 100644 src/detection/terminaltheme/terminaltheme.h create mode 100644 src/detection/theme/theme.h create mode 100644 src/detection/theme/theme_apple.c create mode 100644 src/detection/theme/theme_linux.c create mode 100644 src/detection/theme/theme_nosupport.c create mode 100644 src/detection/theme/theme_windows.c create mode 100644 src/detection/tpm/tpm.h create mode 100644 src/detection/tpm/tpm_apple.c create mode 100644 src/detection/tpm/tpm_bsd.c create mode 100644 src/detection/tpm/tpm_linux.c create mode 100644 src/detection/tpm/tpm_nosupport.c create mode 100644 src/detection/tpm/tpm_windows.c create mode 100644 src/detection/uptime/uptime.h create mode 100644 src/detection/uptime/uptime_bsd.c create mode 100644 src/detection/uptime/uptime_haiku.c create mode 100644 src/detection/uptime/uptime_linux.c create mode 100644 src/detection/uptime/uptime_sunos.c create mode 100644 src/detection/uptime/uptime_windows.c create mode 100644 src/detection/users/users.h create mode 100644 src/detection/users/users_linux.c create mode 100644 src/detection/users/users_nosupport.c create mode 100644 src/detection/users/users_obsd.c create mode 100644 src/detection/users/users_windows.c create mode 100644 src/detection/version/version.c create mode 100644 src/detection/version/version.h create mode 100644 src/detection/vulkan/vulkan.c create mode 100644 src/detection/vulkan/vulkan.h create mode 100644 src/detection/wallpaper/wallpaper.h create mode 100644 src/detection/wallpaper/wallpaper_apple.m create mode 100644 src/detection/wallpaper/wallpaper_haiku.cpp create mode 100644 src/detection/wallpaper/wallpaper_linux.c create mode 100644 src/detection/wallpaper/wallpaper_nosupport.c create mode 100644 src/detection/wallpaper/wallpaper_windows.c create mode 100644 src/detection/weather/weather.c create mode 100644 src/detection/weather/weather.h create mode 100644 src/detection/wifi/wifi.h create mode 100644 src/detection/wifi/wifi_android.c create mode 100644 src/detection/wifi/wifi_apple.m create mode 100644 src/detection/wifi/wifi_bsd.c create mode 100644 src/detection/wifi/wifi_linux.c create mode 100644 src/detection/wifi/wifi_nbsd.c create mode 100644 src/detection/wifi/wifi_nosupport.c create mode 100644 src/detection/wifi/wifi_obsd.c create mode 100644 src/detection/wifi/wifi_windows.c create mode 100644 src/detection/wm/wm.h create mode 100644 src/detection/wm/wm_apple.m create mode 100644 src/detection/wm/wm_linux.c create mode 100644 src/detection/wm/wm_nosupport.c create mode 100644 src/detection/wm/wm_windows.c create mode 100644 src/detection/wmtheme/wmtheme.h create mode 100644 src/detection/wmtheme/wmtheme_apple.m create mode 100644 src/detection/wmtheme/wmtheme_haiku.cpp create mode 100644 src/detection/wmtheme/wmtheme_linux.c create mode 100644 src/detection/wmtheme/wmtheme_nosupport.c create mode 100644 src/detection/wmtheme/wmtheme_windows.c create mode 100644 src/detection/zpool/libzfs_simplified.h create mode 100644 src/detection/zpool/zpool.c create mode 100644 src/detection/zpool/zpool.h create mode 100644 src/fastfetch.c create mode 100644 src/fastfetch.h create mode 100644 src/fastfetch_config.h.in create mode 100644 src/fastfetch_datatext.h.in create mode 100644 src/flashfetch.c create mode 100644 src/logo/ascii/_/unknown.txt create mode 100644 src/logo/ascii/a.inc create mode 100644 src/logo/ascii/a/adelie.txt create mode 100644 src/logo/ascii/a/aeon.txt create mode 100644 src/logo/ascii/a/aeros.txt create mode 100644 src/logo/ascii/a/aerynos.txt create mode 100644 src/logo/ascii/a/aerynos_old.txt create mode 100644 src/logo/ascii/a/afterglow.txt create mode 100644 src/logo/ascii/a/aix.txt create mode 100644 src/logo/ascii/a/almalinux.txt create mode 100644 src/logo/ascii/a/alpine.txt create mode 100644 src/logo/ascii/a/alpine2.txt create mode 100644 src/logo/ascii/a/alpine2_small.txt create mode 100644 src/logo/ascii/a/alpine3_small.txt create mode 100644 src/logo/ascii/a/alpine_small.txt create mode 100644 src/logo/ascii/a/alter.txt create mode 100644 src/logo/ascii/a/altlinux.txt create mode 100644 src/logo/ascii/a/amazon.txt create mode 100644 src/logo/ascii/a/amazon_linux.txt create mode 100644 src/logo/ascii/a/amiga.txt create mode 100644 src/logo/ascii/a/amogos.txt create mode 100644 src/logo/ascii/a/anarchy.txt create mode 100644 src/logo/ascii/a/android.txt create mode 100644 src/logo/ascii/a/android_small.txt create mode 100644 src/logo/ascii/a/anduinos.txt create mode 100644 src/logo/ascii/a/antergos.txt create mode 100644 src/logo/ascii/a/antix.txt create mode 100644 src/logo/ascii/a/anushos.txt create mode 100644 src/logo/ascii/a/aoscos.txt create mode 100644 src/logo/ascii/a/aoscos_old.txt create mode 100644 src/logo/ascii/a/aoscosretro.txt create mode 100644 src/logo/ascii/a/aoscosretro_small.txt create mode 100644 src/logo/ascii/a/aperture.txt create mode 100644 src/logo/ascii/a/apricity.txt create mode 100644 src/logo/ascii/a/arch.txt create mode 100644 src/logo/ascii/a/arch2.txt create mode 100644 src/logo/ascii/a/arch3.txt create mode 100644 src/logo/ascii/a/arch_old.txt create mode 100644 src/logo/ascii/a/arch_small.txt create mode 100644 src/logo/ascii/a/archbox.txt create mode 100644 src/logo/ascii/a/archcraft.txt create mode 100644 src/logo/ascii/a/archcraft2.txt create mode 100644 src/logo/ascii/a/archlabs.txt create mode 100644 src/logo/ascii/a/archstrike.txt create mode 100644 src/logo/ascii/a/arco.txt create mode 100644 src/logo/ascii/a/arco_small.txt create mode 100644 src/logo/ascii/a/arkane.txt create mode 100644 src/logo/ascii/a/armbian.txt create mode 100644 src/logo/ascii/a/armbian2.txt create mode 100644 src/logo/ascii/a/arselinux.txt create mode 100644 src/logo/ascii/a/artix.txt create mode 100644 src/logo/ascii/a/artix2.txt create mode 100644 src/logo/ascii/a/artix2_small.txt create mode 100644 src/logo/ascii/a/artix_small.txt create mode 100644 src/logo/ascii/a/arya.txt create mode 100644 src/logo/ascii/a/asahi.txt create mode 100644 src/logo/ascii/a/asahi2.txt create mode 100644 src/logo/ascii/a/aster.txt create mode 100644 src/logo/ascii/a/asteroidos.txt create mode 100644 src/logo/ascii/a/astos.txt create mode 100644 src/logo/ascii/a/astra_linux.txt create mode 100644 src/logo/ascii/a/athenaos.txt create mode 100644 src/logo/ascii/a/athenaos_old.txt create mode 100644 src/logo/ascii/a/aurora.txt create mode 100644 src/logo/ascii/a/axos.txt create mode 100644 src/logo/ascii/a/azos.txt create mode 100644 src/logo/ascii/b.inc create mode 100644 src/logo/ascii/b/bedrock.txt create mode 100644 src/logo/ascii/b/bedrock_small.txt create mode 100644 src/logo/ascii/b/berserkarch.txt create mode 100644 src/logo/ascii/b/biglinux.txt create mode 100644 src/logo/ascii/b/bitrig.txt create mode 100644 src/logo/ascii/b/blackarch.txt create mode 100644 src/logo/ascii/b/blackmesa.txt create mode 100644 src/logo/ascii/b/blackpanther.txt create mode 100644 src/logo/ascii/b/blag.txt create mode 100644 src/logo/ascii/b/blankon.txt create mode 100644 src/logo/ascii/b/bluelight.txt create mode 100644 src/logo/ascii/b/bodhi.txt create mode 100644 src/logo/ascii/b/bonsai.txt create mode 100644 src/logo/ascii/b/bredos.txt create mode 100644 src/logo/ascii/b/bsd.txt create mode 100644 src/logo/ascii/b/bunsenlabs.txt create mode 100644 src/logo/ascii/c.inc create mode 100644 src/logo/ascii/c/cachyos.txt create mode 100644 src/logo/ascii/c/cachyos_small.txt create mode 100644 src/logo/ascii/c/calculate.txt create mode 100644 src/logo/ascii/c/calinixos.txt create mode 100644 src/logo/ascii/c/calinixos_small.txt create mode 100644 src/logo/ascii/c/carbs.txt create mode 100644 src/logo/ascii/c/cbl_mariner.txt create mode 100644 src/logo/ascii/c/celos.txt create mode 100644 src/logo/ascii/c/center.txt create mode 100644 src/logo/ascii/c/centos.txt create mode 100644 src/logo/ascii/c/centos_small.txt create mode 100644 src/logo/ascii/c/cereus.txt create mode 100644 src/logo/ascii/c/chakra.txt create mode 100644 src/logo/ascii/c/chaletos.txt create mode 100644 src/logo/ascii/c/chapeau.txt create mode 100644 src/logo/ascii/c/chimera_linux.txt create mode 100644 src/logo/ascii/c/chonkysealos.txt create mode 100644 src/logo/ascii/c/chrom.txt create mode 100644 src/logo/ascii/c/cleanjaro.txt create mode 100644 src/logo/ascii/c/cleanjaro_small.txt create mode 100644 src/logo/ascii/c/clear_linux.txt create mode 100644 src/logo/ascii/c/clearos.txt create mode 100644 src/logo/ascii/c/clover.txt create mode 100644 src/logo/ascii/c/cobalt.txt create mode 100644 src/logo/ascii/c/codex.txt create mode 100644 src/logo/ascii/c/condres.txt create mode 100644 src/logo/ascii/c/cosmic.txt create mode 100644 src/logo/ascii/c/crux.txt create mode 100644 src/logo/ascii/c/crux_small.txt create mode 100644 src/logo/ascii/c/crystal.txt create mode 100644 src/logo/ascii/c/cucumber.txt create mode 100644 src/logo/ascii/c/cuerdos.txt create mode 100644 src/logo/ascii/c/cutefishos.txt create mode 100644 src/logo/ascii/c/cuteos.txt create mode 100644 src/logo/ascii/c/cyberos.txt create mode 100644 src/logo/ascii/c/cycledream.txt create mode 100644 src/logo/ascii/d.inc create mode 100644 src/logo/ascii/d/dahlia.txt create mode 100644 src/logo/ascii/d/darkos.txt create mode 100644 src/logo/ascii/d/debian.txt create mode 100644 src/logo/ascii/d/debian_small.txt create mode 100644 src/logo/ascii/d/deepin.txt create mode 100644 src/logo/ascii/d/desaos.txt create mode 100644 src/logo/ascii/d/devuan.txt create mode 100644 src/logo/ascii/d/devuan_small.txt create mode 100644 src/logo/ascii/d/dietpi.txt create mode 100644 src/logo/ascii/d/dracos.txt create mode 100644 src/logo/ascii/d/dragonfly.txt create mode 100644 src/logo/ascii/d/dragonfly_old.txt create mode 100644 src/logo/ascii/d/dragonfly_small.txt create mode 100644 src/logo/ascii/d/drauger.txt create mode 100644 src/logo/ascii/d/droidian.txt create mode 100644 src/logo/ascii/e.inc create mode 100644 src/logo/ascii/e/elbrus.txt create mode 100644 src/logo/ascii/e/elementary.txt create mode 100644 src/logo/ascii/e/elementary_small.txt create mode 100644 src/logo/ascii/e/elive.txt create mode 100644 src/logo/ascii/e/emmabuntus.txt create mode 100644 src/logo/ascii/e/emperoros.txt create mode 100644 src/logo/ascii/e/encryptos.txt create mode 100644 src/logo/ascii/e/endeavouros.txt create mode 100644 src/logo/ascii/e/endeavouros_small.txt create mode 100644 src/logo/ascii/e/endless.txt create mode 100644 src/logo/ascii/e/enos.txt create mode 100644 src/logo/ascii/e/enso.txt create mode 100644 src/logo/ascii/e/eshanizedos.txt create mode 100644 src/logo/ascii/e/eurolinux.txt create mode 100644 src/logo/ascii/e/evolutionos.txt create mode 100644 src/logo/ascii/e/evolutionos_old.txt create mode 100644 src/logo/ascii/e/evolutionos_small.txt create mode 100644 src/logo/ascii/e/eweos.txt create mode 100644 src/logo/ascii/e/exherbo.txt create mode 100644 src/logo/ascii/e/exodia_predator.txt create mode 100644 src/logo/ascii/f.inc create mode 100644 src/logo/ascii/f/fastfetch.txt create mode 100644 src/logo/ascii/f/fedora.txt create mode 100644 src/logo/ascii/f/fedora2_small.txt create mode 100644 src/logo/ascii/f/fedora_coreos.txt create mode 100644 src/logo/ascii/f/fedora_kinoite.txt create mode 100644 src/logo/ascii/f/fedora_old.txt create mode 100644 src/logo/ascii/f/fedora_sericea.txt create mode 100644 src/logo/ascii/f/fedora_silverblue.txt create mode 100644 src/logo/ascii/f/fedora_small.txt create mode 100644 src/logo/ascii/f/femboyos.txt create mode 100644 src/logo/ascii/f/feren.txt create mode 100644 src/logo/ascii/f/filotimo.txt create mode 100644 src/logo/ascii/f/finnix.txt create mode 100644 src/logo/ascii/f/floflis.txt create mode 100644 src/logo/ascii/f/freebsd.txt create mode 100644 src/logo/ascii/f/freebsd_small.txt create mode 100644 src/logo/ascii/f/freemint.txt create mode 100644 src/logo/ascii/f/frugalware.txt create mode 100644 src/logo/ascii/f/funtoo.txt create mode 100644 src/logo/ascii/f/furreto.txt create mode 100644 src/logo/ascii/g.inc create mode 100644 src/logo/ascii/g/galliumos.txt create mode 100644 src/logo/ascii/g/garuda.txt create mode 100644 src/logo/ascii/g/garuda_dragon.txt create mode 100644 src/logo/ascii/g/garuda_small.txt create mode 100644 src/logo/ascii/g/gentoo.txt create mode 100644 src/logo/ascii/g/gentoo_small.txt create mode 100644 src/logo/ascii/g/ghostbsd.txt create mode 100644 src/logo/ascii/g/ghostfreak.txt create mode 100644 src/logo/ascii/g/glaucus.txt create mode 100644 src/logo/ascii/g/gnewsense.txt create mode 100644 src/logo/ascii/g/gnome.txt create mode 100644 src/logo/ascii/g/gnu.txt create mode 100644 src/logo/ascii/g/gobolinux.txt create mode 100644 src/logo/ascii/g/goldendoglinux.txt create mode 100644 src/logo/ascii/g/grapheneos.txt create mode 100644 src/logo/ascii/g/grombyang.txt create mode 100644 src/logo/ascii/g/guix.txt create mode 100644 src/logo/ascii/g/guix_small.txt create mode 100644 src/logo/ascii/g/gxde.txt create mode 100644 src/logo/ascii/h.inc create mode 100644 src/logo/ascii/h/haiku.txt create mode 100644 src/logo/ascii/h/haiku2.txt create mode 100644 src/logo/ascii/h/haiku_small.txt create mode 100644 src/logo/ascii/h/hamonikr.txt create mode 100644 src/logo/ascii/h/hardclanz.txt create mode 100644 src/logo/ascii/h/harmonyos.txt create mode 100644 src/logo/ascii/h/hash.txt create mode 100644 src/logo/ascii/h/hce.txt create mode 100644 src/logo/ascii/h/heliumos.txt create mode 100644 src/logo/ascii/h/huayra.txt create mode 100644 src/logo/ascii/h/hybrid.txt create mode 100644 src/logo/ascii/h/hydroos.txt create mode 100644 src/logo/ascii/h/hyperbola.txt create mode 100644 src/logo/ascii/h/hyperbola_small.txt create mode 100644 src/logo/ascii/h/hypros.txt create mode 100644 src/logo/ascii/i.inc create mode 100644 src/logo/ascii/i/iglunix.txt create mode 100644 src/logo/ascii/i/instantos.txt create mode 100644 src/logo/ascii/i/interix.txt create mode 100644 src/logo/ascii/i/irix.txt create mode 100644 src/logo/ascii/i/ironclad.txt create mode 100644 src/logo/ascii/i/itc.txt create mode 100644 src/logo/ascii/j.inc create mode 100644 src/logo/ascii/j/januslinux.txt create mode 100644 src/logo/ascii/k.inc create mode 100644 src/logo/ascii/k/kaisen.txt create mode 100644 src/logo/ascii/k/kali.txt create mode 100644 src/logo/ascii/k/kali_small.txt create mode 100644 src/logo/ascii/k/kalpa_desktop.txt create mode 100644 src/logo/ascii/k/kaos.txt create mode 100644 src/logo/ascii/k/kdelinux.txt create mode 100644 src/logo/ascii/k/kdeneon.txt create mode 100644 src/logo/ascii/k/kernelos.txt create mode 100644 src/logo/ascii/k/kibaos.txt create mode 100644 src/logo/ascii/k/kibojoe.txt create mode 100644 src/logo/ascii/k/kiss.txt create mode 100644 src/logo/ascii/k/kiss2.txt create mode 100644 src/logo/ascii/k/kogaion.txt create mode 100644 src/logo/ascii/k/korora.txt create mode 100644 src/logo/ascii/k/krassos.txt create mode 100644 src/logo/ascii/k/kslinux.txt create mode 100644 src/logo/ascii/k/kubuntu.txt create mode 100644 src/logo/ascii/k/kylin.txt create mode 100644 src/logo/ascii/l.inc create mode 100644 src/logo/ascii/l/lainos.txt create mode 100644 src/logo/ascii/l/langitketujuh.txt create mode 100644 src/logo/ascii/l/laxeros.txt create mode 100644 src/logo/ascii/l/lede.txt create mode 100644 src/logo/ascii/l/lfs.txt create mode 100644 src/logo/ascii/l/libreelec.txt create mode 100644 src/logo/ascii/l/lilidog.txt create mode 100644 src/logo/ascii/l/limeos.txt create mode 100644 src/logo/ascii/l/lingmo.txt create mode 100644 src/logo/ascii/l/linspire.txt create mode 100644 src/logo/ascii/l/linux.txt create mode 100644 src/logo/ascii/l/linux_small.txt create mode 100644 src/logo/ascii/l/linuxlite.txt create mode 100644 src/logo/ascii/l/linuxlite_small.txt create mode 100644 src/logo/ascii/l/linuxmint.txt create mode 100644 src/logo/ascii/l/linuxmint2.txt create mode 100644 src/logo/ascii/l/linuxmint_old.txt create mode 100644 src/logo/ascii/l/linuxmint_small.txt create mode 100644 src/logo/ascii/l/live_raizo.txt create mode 100644 src/logo/ascii/l/lliurex.txt create mode 100644 src/logo/ascii/l/lmde.txt create mode 100644 src/logo/ascii/l/locos.txt create mode 100644 src/logo/ascii/l/lubuntu.txt create mode 100644 src/logo/ascii/l/lunar.txt create mode 100644 src/logo/ascii/m.inc create mode 100644 src/logo/ascii/m/macaronios.txt create mode 100644 src/logo/ascii/m/macos.txt create mode 100644 src/logo/ascii/m/macos2.txt create mode 100644 src/logo/ascii/m/macos2_small.txt create mode 100644 src/logo/ascii/m/macos3.txt create mode 100644 src/logo/ascii/m/macos_small.txt create mode 100644 src/logo/ascii/m/mageia.txt create mode 100644 src/logo/ascii/m/mageia_small.txt create mode 100644 src/logo/ascii/m/magix.txt create mode 100644 src/logo/ascii/m/magpieos.txt create mode 100644 src/logo/ascii/m/mainsailos.txt create mode 100644 src/logo/ascii/m/mainsailos_small.txt create mode 100644 src/logo/ascii/m/mandriva.txt create mode 100644 src/logo/ascii/m/manjaro.txt create mode 100644 src/logo/ascii/m/manjaro_small.txt create mode 100644 src/logo/ascii/m/massos.txt create mode 100644 src/logo/ascii/m/matuusos.txt create mode 100644 src/logo/ascii/m/maui.txt create mode 100644 src/logo/ascii/m/mauna.txt create mode 100644 src/logo/ascii/m/meowix.txt create mode 100644 src/logo/ascii/m/mer.txt create mode 100644 src/logo/ascii/m/midnightbsd.txt create mode 100644 src/logo/ascii/m/midos.txt create mode 100644 src/logo/ascii/m/midos_old.txt create mode 100644 src/logo/ascii/m/minimal.txt create mode 100644 src/logo/ascii/m/minix.txt create mode 100644 src/logo/ascii/m/miracle_linux.txt create mode 100644 src/logo/ascii/m/mos.txt create mode 100644 src/logo/ascii/m/msys2.txt create mode 100644 src/logo/ascii/m/mx.txt create mode 100644 src/logo/ascii/m/mx2.txt create mode 100644 src/logo/ascii/m/mx_small.txt create mode 100644 src/logo/ascii/n.inc create mode 100644 src/logo/ascii/n/namib.txt create mode 100644 src/logo/ascii/n/nebios.txt create mode 100644 src/logo/ascii/n/nekos.txt create mode 100644 src/logo/ascii/n/neptune.txt create mode 100644 src/logo/ascii/n/netbsd.txt create mode 100644 src/logo/ascii/n/netbsd2.txt create mode 100644 src/logo/ascii/n/netbsd_small.txt create mode 100644 src/logo/ascii/n/nethydra.txt create mode 100644 src/logo/ascii/n/netrunner.txt create mode 100644 src/logo/ascii/n/nexalinux.txt create mode 100644 src/logo/ascii/n/nitrux.txt create mode 100644 src/logo/ascii/n/nixos.txt create mode 100644 src/logo/ascii/n/nixos2.txt create mode 100644 src/logo/ascii/n/nixos_old.txt create mode 100644 src/logo/ascii/n/nixos_old_small.txt create mode 100644 src/logo/ascii/n/nixos_small.txt create mode 100644 src/logo/ascii/n/nobara.txt create mode 100644 src/logo/ascii/n/nomadbsd.txt create mode 100644 src/logo/ascii/n/nuros.txt create mode 100644 src/logo/ascii/n/nurunner.txt create mode 100644 src/logo/ascii/n/nutyx.txt create mode 100644 src/logo/ascii/o.inc create mode 100644 src/logo/ascii/o/obarun.txt create mode 100644 src/logo/ascii/o/obrevenge.txt create mode 100644 src/logo/ascii/o/obsidianos.txt create mode 100644 src/logo/ascii/o/omnios.txt create mode 100644 src/logo/ascii/o/openbsd.txt create mode 100644 src/logo/ascii/o/openbsd_small.txt create mode 100644 src/logo/ascii/o/openeuler.txt create mode 100644 src/logo/ascii/o/openindiana.txt create mode 100644 src/logo/ascii/o/openkylin.txt create mode 100644 src/logo/ascii/o/openmamba.txt create mode 100644 src/logo/ascii/o/openmandriva.txt create mode 100644 src/logo/ascii/o/openruyi.txt create mode 100644 src/logo/ascii/o/openstage.txt create mode 100644 src/logo/ascii/o/opensuse.txt create mode 100644 src/logo/ascii/o/opensuse_leap.txt create mode 100644 src/logo/ascii/o/opensuse_leap_old.txt create mode 100644 src/logo/ascii/o/opensuse_microos.txt create mode 100644 src/logo/ascii/o/opensuse_slowroll.txt create mode 100644 src/logo/ascii/o/opensuse_small.txt create mode 100644 src/logo/ascii/o/opensuse_tumbleweed.txt create mode 100644 src/logo/ascii/o/opensuse_tumbleweed2.txt create mode 100644 src/logo/ascii/o/opensuse_tumbleweed_old.txt create mode 100644 src/logo/ascii/o/opensuse_tumbleweed_small.txt create mode 100644 src/logo/ascii/o/openwrt.txt create mode 100644 src/logo/ascii/o/openwrt_old.txt create mode 100644 src/logo/ascii/o/openwrt_small.txt create mode 100644 src/logo/ascii/o/opnsense.txt create mode 100644 src/logo/ascii/o/oracle.txt create mode 100644 src/logo/ascii/o/orchid.txt create mode 100644 src/logo/ascii/o/orchid_small.txt create mode 100644 src/logo/ascii/o/oreon.txt create mode 100644 src/logo/ascii/o/origami.txt create mode 100644 src/logo/ascii/o/origami_small.txt create mode 100644 src/logo/ascii/o/os2warp.txt create mode 100644 src/logo/ascii/o/os_elbrus.txt create mode 100644 src/logo/ascii/o/osmc.txt create mode 100644 src/logo/ascii/p.inc create mode 100644 src/logo/ascii/p/pacbsd.txt create mode 100644 src/logo/ascii/p/panwah.txt create mode 100644 src/logo/ascii/p/parabola.txt create mode 100644 src/logo/ascii/p/parabola_small.txt create mode 100644 src/logo/ascii/p/parch.txt create mode 100644 src/logo/ascii/p/pardus.txt create mode 100644 src/logo/ascii/p/parrot.txt create mode 100644 src/logo/ascii/p/parsix.txt create mode 100644 src/logo/ascii/p/pcbsd.txt create mode 100644 src/logo/ascii/p/pclinuxos.txt create mode 100644 src/logo/ascii/p/pearos.txt create mode 100644 src/logo/ascii/p/pengwin.txt create mode 100644 src/logo/ascii/p/pentoo.txt create mode 100644 src/logo/ascii/p/peppermint.txt create mode 100644 src/logo/ascii/p/peropesis.txt create mode 100644 src/logo/ascii/p/phyos.txt create mode 100644 src/logo/ascii/p/pikaos.txt create mode 100644 src/logo/ascii/p/pisi.txt create mode 100644 src/logo/ascii/p/pnm_linux.txt create mode 100644 src/logo/ascii/p/pop.txt create mode 100644 src/logo/ascii/p/pop_small.txt create mode 100644 src/logo/ascii/p/porteus.txt create mode 100644 src/logo/ascii/p/postmarketos.txt create mode 100644 src/logo/ascii/p/postmarketos_small.txt create mode 100644 src/logo/ascii/p/prismlinux.txt create mode 100644 src/logo/ascii/p/prismlinux_small.txt create mode 100644 src/logo/ascii/p/proxmox.txt create mode 100644 src/logo/ascii/p/puffos.txt create mode 100644 src/logo/ascii/p/puppy.txt create mode 100644 src/logo/ascii/p/pureos.txt create mode 100644 src/logo/ascii/p/pureos_small.txt create mode 100644 src/logo/ascii/q.inc create mode 100644 src/logo/ascii/q/q4os.txt create mode 100644 src/logo/ascii/q/qts.txt create mode 100644 src/logo/ascii/q/quasar.txt create mode 100644 src/logo/ascii/q/qubes.txt create mode 100644 src/logo/ascii/q/qubyt.txt create mode 100644 src/logo/ascii/q/quibian.txt create mode 100644 src/logo/ascii/q/quirinux.txt create mode 100644 src/logo/ascii/r.inc create mode 100644 src/logo/ascii/r/radix.txt create mode 100644 src/logo/ascii/r/raspbian.txt create mode 100644 src/logo/ascii/r/raspbian_small.txt create mode 100644 src/logo/ascii/r/ravynos.txt create mode 100644 src/logo/ascii/r/rebornos.txt create mode 100644 src/logo/ascii/r/rebornos_small.txt create mode 100644 src/logo/ascii/r/redcore.txt create mode 100644 src/logo/ascii/r/redos.txt create mode 100644 src/logo/ascii/r/redos_small.txt create mode 100755 src/logo/ascii/r/redrose.txt create mode 100644 src/logo/ascii/r/redstar.txt create mode 100644 src/logo/ascii/r/refracta.txt create mode 100644 src/logo/ascii/r/regata.txt create mode 100644 src/logo/ascii/r/regolith.txt create mode 100644 src/logo/ascii/r/rengeos.txt create mode 100644 src/logo/ascii/r/rhaymos.txt create mode 100644 src/logo/ascii/r/rhel.txt create mode 100644 src/logo/ascii/r/rhel_old.txt create mode 100644 src/logo/ascii/r/rhel_small.txt create mode 100644 src/logo/ascii/r/rhino.txt create mode 100644 src/logo/ascii/r/rocky.txt create mode 100644 src/logo/ascii/r/rocky_small.txt create mode 100644 src/logo/ascii/r/rosa.txt create mode 100644 src/logo/ascii/s.inc create mode 100644 src/logo/ascii/s/sabayon.txt create mode 100644 src/logo/ascii/s/sabotage.txt create mode 100644 src/logo/ascii/s/sailfish.txt create mode 100644 src/logo/ascii/s/salentos.txt create mode 100644 src/logo/ascii/s/salientos.txt create mode 100644 src/logo/ascii/s/salix.txt create mode 100644 src/logo/ascii/s/sambabox.txt create mode 100644 src/logo/ascii/s/sasanqua.txt create mode 100644 src/logo/ascii/s/scientific.txt create mode 100644 src/logo/ascii/s/secureblue.txt create mode 100644 src/logo/ascii/s/semc.txt create mode 100644 src/logo/ascii/s/septor.txt create mode 100644 src/logo/ascii/s/serene.txt create mode 100644 src/logo/ascii/s/serpent_os.txt create mode 100644 src/logo/ascii/s/sharklinux.txt create mode 100644 src/logo/ascii/s/shastraos.txt create mode 100644 src/logo/ascii/s/shebang.txt create mode 100644 src/logo/ascii/s/siduction.txt create mode 100644 src/logo/ascii/s/skiffos.txt create mode 100644 src/logo/ascii/s/slackel.txt create mode 100644 src/logo/ascii/s/slackware.txt create mode 100644 src/logo/ascii/s/slackware_small.txt create mode 100644 src/logo/ascii/s/sleeperos.txt create mode 100644 src/logo/ascii/s/sleeperos_small.txt create mode 100644 src/logo/ascii/s/slitaz.txt create mode 100644 src/logo/ascii/s/smartos.txt create mode 100644 src/logo/ascii/s/snigdhaos.txt create mode 100644 src/logo/ascii/s/soda.txt create mode 100644 src/logo/ascii/s/solaris.txt create mode 100644 src/logo/ascii/s/solaris_small.txt create mode 100644 src/logo/ascii/s/solus.txt create mode 100644 src/logo/ascii/s/source_mage.txt create mode 100644 src/logo/ascii/s/sparky.txt create mode 100644 src/logo/ascii/s/spoinkos.txt create mode 100644 src/logo/ascii/s/star.txt create mode 100644 src/logo/ascii/s/steamdeck.txt create mode 100644 src/logo/ascii/s/steamdeck_small.txt create mode 100644 src/logo/ascii/s/steamos.txt create mode 100644 src/logo/ascii/s/stock_linux.txt create mode 100644 src/logo/ascii/s/sulin.txt create mode 100644 src/logo/ascii/s/summitos.txt create mode 100644 src/logo/ascii/s/suse.txt create mode 100644 src/logo/ascii/s/swagarch.txt create mode 100644 src/logo/ascii/t.inc create mode 100644 src/logo/ascii/t/t2.txt create mode 100644 src/logo/ascii/t/t2_small.txt create mode 100644 src/logo/ascii/t/tails.txt create mode 100644 src/logo/ascii/t/tatra.txt create mode 100644 src/logo/ascii/t/tearch.txt create mode 100644 src/logo/ascii/t/templeos.txt create mode 100644 src/logo/ascii/t/tileos.txt create mode 100644 src/logo/ascii/t/torizoncore.txt create mode 100644 src/logo/ascii/t/trisquel.txt create mode 100644 src/logo/ascii/t/truenas_scale.txt create mode 100644 src/logo/ascii/t/tuxedo_os.txt create mode 100644 src/logo/ascii/t/twister.txt create mode 100644 src/logo/ascii/u.inc create mode 100644 src/logo/ascii/u/ublinux.txt create mode 100644 src/logo/ascii/u/ublinux_small.txt create mode 100644 src/logo/ascii/u/ubuntu.txt create mode 100644 src/logo/ascii/u/ubuntu_budgie.txt create mode 100644 src/logo/ascii/u/ubuntu_cinnamon.txt create mode 100644 src/logo/ascii/u/ubuntu_gnome.txt create mode 100644 src/logo/ascii/u/ubuntu_kylin.txt create mode 100644 src/logo/ascii/u/ubuntu_mate.txt create mode 100644 src/logo/ascii/u/ubuntu_old.txt create mode 100644 src/logo/ascii/u/ubuntu_old2.txt create mode 100644 src/logo/ascii/u/ubuntu_old2_small.txt create mode 100644 src/logo/ascii/u/ubuntu_small.txt create mode 100644 src/logo/ascii/u/ubuntu_studio.txt create mode 100644 src/logo/ascii/u/ubuntu_sway.txt create mode 100644 src/logo/ascii/u/ubuntu_touch.txt create mode 100644 src/logo/ascii/u/ubuntu_unity.txt create mode 100644 src/logo/ascii/u/ultramarine.txt create mode 100644 src/logo/ascii/u/ultramarine_small.txt create mode 100644 src/logo/ascii/u/unifi.txt create mode 100644 src/logo/ascii/u/univalent.txt create mode 100644 src/logo/ascii/u/univention.txt create mode 100644 src/logo/ascii/u/uos.txt create mode 100644 src/logo/ascii/u/urukos.txt create mode 100644 src/logo/ascii/u/uwuntu.txt create mode 100644 src/logo/ascii/u/uzbek.txt create mode 100644 src/logo/ascii/v.inc create mode 100644 src/logo/ascii/v/valhalla.txt create mode 100644 src/logo/ascii/v/vanilla.txt create mode 100644 src/logo/ascii/v/vanilla2.txt create mode 100644 src/logo/ascii/v/vanilla_small.txt create mode 100644 src/logo/ascii/v/venom.txt create mode 100644 src/logo/ascii/v/venom_small.txt create mode 100644 src/logo/ascii/v/vincentos.txt create mode 100644 src/logo/ascii/v/vnux.txt create mode 100644 src/logo/ascii/v/void.txt create mode 100644 src/logo/ascii/v/void2.txt create mode 100644 src/logo/ascii/v/void2_small.txt create mode 100644 src/logo/ascii/v/void_small.txt create mode 100644 src/logo/ascii/v/vzlinux.txt create mode 100644 src/logo/ascii/w.inc create mode 100644 src/logo/ascii/w/wii_linux.txt create mode 100644 src/logo/ascii/w/windows.txt create mode 100644 src/logo/ascii/w/windows_11.txt create mode 100644 src/logo/ascii/w/windows_11_small.txt create mode 100644 src/logo/ascii/w/windows_2025.txt create mode 100644 src/logo/ascii/w/windows_8.txt create mode 100644 src/logo/ascii/w/windows_95.txt create mode 100644 src/logo/ascii/w/wolfos.txt create mode 100644 src/logo/ascii/x.inc create mode 100644 src/logo/ascii/x/xcp_ng.txt create mode 100644 src/logo/ascii/x/xenia.txt create mode 100644 src/logo/ascii/x/xenia_old.txt create mode 100644 src/logo/ascii/x/xeroarch.txt create mode 100644 src/logo/ascii/x/xferience.txt create mode 100644 src/logo/ascii/x/ximper.txt create mode 100644 src/logo/ascii/x/xinux.txt create mode 100644 src/logo/ascii/x/xj380.txt create mode 100644 src/logo/ascii/x/xray_os.txt create mode 100644 src/logo/ascii/x/xubuntu.txt create mode 100644 src/logo/ascii/y.inc create mode 100644 src/logo/ascii/y/yakubos.txt create mode 100644 src/logo/ascii/y/yiffos.txt create mode 100644 src/logo/ascii/z.inc create mode 100644 src/logo/ascii/z/zorin.txt create mode 100644 src/logo/ascii/z/zos.txt create mode 100644 src/logo/ascii/z/zraxyl.txt create mode 100644 src/logo/builtin.c create mode 100644 src/logo/image/im6.c create mode 100644 src/logo/image/im7.c create mode 100644 src/logo/image/image.c create mode 100644 src/logo/image/image.h create mode 100644 src/logo/logo.c create mode 100644 src/logo/logo.h create mode 100644 src/logo/logo.svg create mode 100644 src/modules/battery/battery.c create mode 100644 src/modules/battery/battery.h create mode 100644 src/modules/battery/option.h create mode 100644 src/modules/bios/bios.c create mode 100644 src/modules/bios/bios.h create mode 100644 src/modules/bios/option.h create mode 100644 src/modules/bluetooth/bluetooth.c create mode 100644 src/modules/bluetooth/bluetooth.h create mode 100644 src/modules/bluetooth/option.h create mode 100644 src/modules/bluetoothradio/bluetoothradio.c create mode 100644 src/modules/bluetoothradio/bluetoothradio.h create mode 100644 src/modules/bluetoothradio/option.h create mode 100644 src/modules/board/board.c create mode 100644 src/modules/board/board.h create mode 100644 src/modules/board/option.h create mode 100644 src/modules/bootmgr/bootmgr.c create mode 100644 src/modules/bootmgr/bootmgr.h create mode 100644 src/modules/bootmgr/option.h create mode 100644 src/modules/break/break.c create mode 100644 src/modules/break/break.h create mode 100644 src/modules/break/option.h create mode 100644 src/modules/brightness/brightness.c create mode 100644 src/modules/brightness/brightness.h create mode 100644 src/modules/brightness/option.h create mode 100644 src/modules/btrfs/btrfs.c create mode 100644 src/modules/btrfs/btrfs.h create mode 100644 src/modules/btrfs/option.h create mode 100644 src/modules/camera/camera.c create mode 100644 src/modules/camera/camera.h create mode 100644 src/modules/camera/option.h create mode 100644 src/modules/chassis/chassis.c create mode 100644 src/modules/chassis/chassis.h create mode 100644 src/modules/chassis/option.h create mode 100644 src/modules/codec/codec.c create mode 100644 src/modules/codec/codec.h create mode 100644 src/modules/codec/option.h create mode 100644 src/modules/colors/colors.c create mode 100644 src/modules/colors/colors.h create mode 100644 src/modules/colors/option.h create mode 100644 src/modules/command/command.c create mode 100644 src/modules/command/command.h create mode 100644 src/modules/command/option.h create mode 100644 src/modules/cpu/cpu.c create mode 100644 src/modules/cpu/cpu.h create mode 100644 src/modules/cpu/option.h create mode 100644 src/modules/cpucache/cpucache.c create mode 100644 src/modules/cpucache/cpucache.h create mode 100644 src/modules/cpucache/option.h create mode 100644 src/modules/cpuusage/cpuusage.c create mode 100644 src/modules/cpuusage/cpuusage.h create mode 100644 src/modules/cpuusage/option.h create mode 100644 src/modules/cursor/cursor.c create mode 100644 src/modules/cursor/cursor.h create mode 100644 src/modules/cursor/option.h create mode 100644 src/modules/custom/custom.c create mode 100644 src/modules/custom/custom.h create mode 100644 src/modules/custom/option.h create mode 100644 src/modules/datetime/datetime.c create mode 100644 src/modules/datetime/datetime.h create mode 100644 src/modules/datetime/option.h create mode 100644 src/modules/de/de.c create mode 100644 src/modules/de/de.h create mode 100644 src/modules/de/option.h create mode 100644 src/modules/disk/disk.c create mode 100644 src/modules/disk/disk.h create mode 100644 src/modules/disk/option.h create mode 100644 src/modules/diskio/diskio.c create mode 100644 src/modules/diskio/diskio.h create mode 100644 src/modules/diskio/option.h create mode 100644 src/modules/display/display.c create mode 100644 src/modules/display/display.h create mode 100644 src/modules/display/option.h create mode 100644 src/modules/dns/dns.c create mode 100644 src/modules/dns/dns.h create mode 100644 src/modules/dns/option.h create mode 100644 src/modules/editor/editor.c create mode 100644 src/modules/editor/editor.h create mode 100644 src/modules/editor/option.h create mode 100644 src/modules/font/font.c create mode 100644 src/modules/font/font.h create mode 100644 src/modules/font/option.h create mode 100644 src/modules/gamepad/gamepad.c create mode 100644 src/modules/gamepad/gamepad.h create mode 100644 src/modules/gamepad/option.h create mode 100644 src/modules/gpu/gpu.c create mode 100644 src/modules/gpu/gpu.h create mode 100644 src/modules/gpu/option.h create mode 100644 src/modules/host/host.c create mode 100644 src/modules/host/host.h create mode 100644 src/modules/host/option.h create mode 100644 src/modules/icons/icons.c create mode 100644 src/modules/icons/icons.h create mode 100644 src/modules/icons/option.h create mode 100644 src/modules/initsystem/initsystem.c create mode 100644 src/modules/initsystem/initsystem.h create mode 100644 src/modules/initsystem/option.h create mode 100644 src/modules/kernel/kernel.c create mode 100644 src/modules/kernel/kernel.h create mode 100644 src/modules/kernel/option.h create mode 100644 src/modules/keyboard/keyboard.c create mode 100644 src/modules/keyboard/keyboard.h create mode 100644 src/modules/keyboard/option.h create mode 100644 src/modules/lm/lm.c create mode 100644 src/modules/lm/lm.h create mode 100644 src/modules/lm/option.h create mode 100644 src/modules/loadavg/loadavg.c create mode 100644 src/modules/loadavg/loadavg.h create mode 100644 src/modules/loadavg/option.h create mode 100644 src/modules/locale/locale.c create mode 100644 src/modules/locale/locale.h create mode 100644 src/modules/locale/option.h create mode 100644 src/modules/localip/localip.c create mode 100644 src/modules/localip/localip.h create mode 100644 src/modules/localip/option.h create mode 100644 src/modules/logo/logo.c create mode 100644 src/modules/logo/logo.h create mode 100644 src/modules/logo/option.h create mode 100644 src/modules/media/media.c create mode 100644 src/modules/media/media.h create mode 100644 src/modules/media/option.h create mode 100644 src/modules/memory/memory.c create mode 100644 src/modules/memory/memory.h create mode 100644 src/modules/memory/option.h create mode 100644 src/modules/modules.c create mode 100644 src/modules/modules.h create mode 100644 src/modules/monitor/monitor.c create mode 100644 src/modules/monitor/monitor.h create mode 100644 src/modules/monitor/option.h create mode 100644 src/modules/mouse/mouse.c create mode 100644 src/modules/mouse/mouse.h create mode 100644 src/modules/mouse/option.h create mode 100644 src/modules/netio/netio.c create mode 100644 src/modules/netio/netio.h create mode 100644 src/modules/netio/option.h create mode 100644 src/modules/opencl/opencl.c create mode 100644 src/modules/opencl/opencl.h create mode 100644 src/modules/opencl/option.h create mode 100644 src/modules/opengl/opengl.c create mode 100644 src/modules/opengl/opengl.h create mode 100644 src/modules/opengl/option.h create mode 100644 src/modules/options.h create mode 100644 src/modules/os/option.h create mode 100644 src/modules/os/os.c create mode 100644 src/modules/os/os.h create mode 100644 src/modules/packages/option.h create mode 100644 src/modules/packages/packages.c create mode 100644 src/modules/packages/packages.h create mode 100644 src/modules/physicaldisk/option.h create mode 100644 src/modules/physicaldisk/physicaldisk.c create mode 100644 src/modules/physicaldisk/physicaldisk.h create mode 100644 src/modules/physicalmemory/option.h create mode 100644 src/modules/physicalmemory/physicalmemory.c create mode 100644 src/modules/physicalmemory/physicalmemory.h create mode 100644 src/modules/player/option.h create mode 100644 src/modules/player/player.c create mode 100644 src/modules/player/player.h create mode 100644 src/modules/poweradapter/option.h create mode 100644 src/modules/poweradapter/poweradapter.c create mode 100644 src/modules/poweradapter/poweradapter.h create mode 100644 src/modules/processes/option.h create mode 100644 src/modules/processes/processes.c create mode 100644 src/modules/processes/processes.h create mode 100644 src/modules/publicip/option.h create mode 100644 src/modules/publicip/publicip.c create mode 100644 src/modules/publicip/publicip.h create mode 100644 src/modules/separator/option.h create mode 100644 src/modules/separator/separator.c create mode 100644 src/modules/separator/separator.h create mode 100644 src/modules/shell/option.h create mode 100644 src/modules/shell/shell.c create mode 100644 src/modules/shell/shell.h create mode 100644 src/modules/sound/option.h create mode 100644 src/modules/sound/sound.c create mode 100644 src/modules/sound/sound.h create mode 100644 src/modules/swap/option.h create mode 100644 src/modules/swap/swap.c create mode 100644 src/modules/swap/swap.h create mode 100644 src/modules/terminal/option.h create mode 100644 src/modules/terminal/terminal.c create mode 100644 src/modules/terminal/terminal.h create mode 100644 src/modules/terminalfont/option.h create mode 100644 src/modules/terminalfont/terminalfont.c create mode 100644 src/modules/terminalfont/terminalfont.h create mode 100644 src/modules/terminalsize/option.h create mode 100644 src/modules/terminalsize/terminalsize.c create mode 100644 src/modules/terminalsize/terminalsize.h create mode 100644 src/modules/terminaltheme/option.h create mode 100644 src/modules/terminaltheme/terminaltheme.c create mode 100644 src/modules/terminaltheme/terminaltheme.h create mode 100644 src/modules/theme/option.h create mode 100644 src/modules/theme/theme.c create mode 100644 src/modules/theme/theme.h create mode 100644 src/modules/title/option.h create mode 100644 src/modules/title/title.c create mode 100644 src/modules/title/title.h create mode 100644 src/modules/tpm/option.h create mode 100644 src/modules/tpm/tpm.c create mode 100644 src/modules/tpm/tpm.h create mode 100644 src/modules/uptime/option.h create mode 100644 src/modules/uptime/uptime.c create mode 100644 src/modules/uptime/uptime.h create mode 100644 src/modules/users/option.h create mode 100644 src/modules/users/users.c create mode 100644 src/modules/users/users.h create mode 100644 src/modules/version/option.h create mode 100644 src/modules/version/version.c create mode 100644 src/modules/version/version.h create mode 100644 src/modules/vulkan/option.h create mode 100644 src/modules/vulkan/vulkan.c create mode 100644 src/modules/vulkan/vulkan.h create mode 100644 src/modules/wallpaper/option.h create mode 100644 src/modules/wallpaper/wallpaper.c create mode 100644 src/modules/wallpaper/wallpaper.h create mode 100644 src/modules/weather/option.h create mode 100644 src/modules/weather/weather.c create mode 100644 src/modules/weather/weather.h create mode 100644 src/modules/wifi/option.h create mode 100644 src/modules/wifi/wifi.c create mode 100644 src/modules/wifi/wifi.h create mode 100644 src/modules/wm/option.h create mode 100644 src/modules/wm/wm.c create mode 100644 src/modules/wm/wm.h create mode 100644 src/modules/wmtheme/option.h create mode 100644 src/modules/wmtheme/wmtheme.c create mode 100644 src/modules/wmtheme/wmtheme.h create mode 100644 src/modules/zpool/option.h create mode 100644 src/modules/zpool/zpool.c create mode 100644 src/modules/zpool/zpool.h create mode 100644 src/options/display.c create mode 100644 src/options/display.h create mode 100644 src/options/general.c create mode 100644 src/options/general.h create mode 100644 src/options/logo.c create mode 100644 src/options/logo.h (limited to 'src') diff --git a/src/3rdparty/display-library/adl_defines.h b/src/3rdparty/display-library/adl_defines.h new file mode 100644 index 0000000..f24143e --- /dev/null +++ b/src/3rdparty/display-library/adl_defines.h @@ -0,0 +1,2649 @@ +// +// 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 \WIN 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 +/// Defines the maximum of RPC endpoint +#define ADL_MAX_RPC_ENDPOINT 32 +/// @} + +/// \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 +/// The RPC server is in busy state +#define ADL_ERR_SERVER_BUSY -26 +/// The GPU is occupied by application which cannot be powered off +#define ADL_ERR_GPU_IN_USE -27 +/// The general RPC connection error +#define ADL_ERR_RPC -28 + +/// @} +/// + +/// \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 */ +#define ADL_BUSTYPE_PCIE_GEN5 6 /* PCI Express 5th 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. */ +}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_ACTIMING_PARAMETERS_TUNE = 1 << 25, // AC Timing Parameters Tuning + ADL_OD8_OVERDRIVE_INTERFACE = 1 << 26, // Version info feature ID for RSX, do not expose in ADL. + ADL_OD8_AUTO_UV_ENGINE_V2 = 1 << 27, // Auto UV 2.0 with actual stress testing. + ADL_OD8_POWER_GAUGE = 1 << 28 //Power Gauge +}; + +typedef enum ADLOD8SettingId +{ + OD8_GFXCLK_FMAX = 0, + OD8_GFXCLK_FMIN, + OD8_GFXCLK_FREQ1, + OD8_GFXCLK_VOLTAGE1, + OD8_GFXCLK_FREQ2, + OD8_GFXCLK_VOLTAGE2, + OD8_GFXCLK_FREQ3, + OD8_GFXCLK_VOLTAGE3, + OD8_UCLK_FMAX, + OD8_POWER_PERCENTAGE, + OD8_FAN_MIN_SPEED, //10 + OD8_FAN_ACOUSTIC_LIMIT, + OD8_FAN_TARGET_TEMP, + OD8_OPERATING_TEMP_MAX, + OD8_AC_TIMING, + OD8_FAN_ZERORPM_CONTROL, + OD8_AUTO_UV_ENGINE_CONTROL, + OD8_AUTO_OC_ENGINE_CONTROL, + OD8_AUTO_OC_MEMORY_CONTROL, + OD8_FAN_CURVE_TEMPERATURE_1, + OD8_FAN_CURVE_SPEED_1, //20 + OD8_FAN_CURVE_TEMPERATURE_2, + OD8_FAN_CURVE_SPEED_2, + OD8_FAN_CURVE_TEMPERATURE_3, + OD8_FAN_CURVE_SPEED_3, + OD8_FAN_CURVE_TEMPERATURE_4, + OD8_FAN_CURVE_SPEED_4, + OD8_FAN_CURVE_TEMPERATURE_5, + OD8_FAN_CURVE_SPEED_5, + OD8_WS_FAN_AUTO_FAN_ACOUSTIC_LIMIT, + OD8_GFXCLK_CURVE_COEFFICIENT_A, // 30 As part of the agreement with UI team, the min/max voltage limits for the + OD8_GFXCLK_CURVE_COEFFICIENT_B, // quadratic curve graph will be stored in the min and max limits of + OD8_GFXCLK_CURVE_COEFFICIENT_C, // coefficient a, b and c. A, b and c themselves do not have limits. + OD8_GFXCLK_CURVE_VFT_FMIN, + 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, //40 + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_3, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_4, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_5, + 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_FULL_CONTROL_MODE_GFXCLK, + OD8_FULL_CONTROL_MODE_UCLK, // 50, + OD8_IDLE_POWER_SAVING_FEATURE_CONTROL, + OD8_RUNTIME_POWER_SAVING_FEATURE_CONTROL, + OD8_FULL_CONTROL_MODE_FEATURE_CONTROL, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_1, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_2, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_3, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_4, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_5, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_6, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_VOLTAGE_LIMIT, //60 + OD8_ACTIMING_PARAMETER_TRRDS, + OD8_ACTIMING_PARAMETER_TCL, + OD8_ACTIMING_PARAMETER_TCWL, + OD8_ACTIMING_PARAMETER_TRCDRD, + OD8_ACTIMING_PARAMETER_TRCDWR, + OD8_ACTIMING_PARAMETER_TRAS, + OD8_ACTIMING_PARAMETER_TRPAB, + OD8_ACTIMING_PARAMETER_TRFC, + OD8_ACTIMING_PARAMETER_TRFCPB, + OD8_ACTIMING_PARAMETER_TRREFD, //70 + OD8_ACTIMING_PARAMETER_TREF, + OD8_ACTIMING_PARAMETER_TWR, + OD8_ACTIMING_PARAMETER_TWTRS, + OD8_OVERDRIVE_INTERFACE_ID, //74 + OD8_AUTO_UV_ENGINE_V2_ID, //75 + OD8_POWER_GAUGE, + OD8_COUNT +} ADLOD8SettingId; + + +//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_THROTTLE_PERCENTAGE_TEMP_GFX = 52, + PMLOG_THROTTLE_PERCENTAGE_TEMP_MEM = 53, + PMLOG_THROTTLE_PERCENTAGE_TEMP_VR = 54, + PMLOG_THROTTLE_PERCENTAGE_POWER = 55, + PMLOG_THROTTLE_PERCENTAGE_TDC = 56, + PMLOG_THROTTLE_PERCENTAGE_VMAX = 57, + PMLOG_BUS_CURR_MAX_SPEED = 58, + PMLOG_RESERVED_1 = 59, //Currently Unused + PMLOG_RESERVED_2 = 60, //Currently Unused + PMLOG_RESERVED_3 = 61, //Currently Unused + PMLOG_RESERVED_4 = 62, //Currently Unused + PMLOG_RESERVED_5 = 63, //Currently Unused + PMLOG_RESERVED_6 = 64, //Currently Unused + PMLOG_RESERVED_7 = 65, //Currently Unused + PMLOG_RESERVED_8 = 66, //Currently Unused + PMLOG_RESERVED_9 = 67, //Currently Unused + PMLOG_RESERVED_10 = 68, //Currently Unused + PMLOG_RESERVED_11 = 69, //Currently Unused + PMLOG_RESERVED_12 = 70, //Currently Unused + PMLOG_RESERVED_13 = 71, //Currently Unused + PMLOG_RESERVED_14 = 72, + PMLOG_BOARD_POWER = 73, + PMLOG_MAX_SENSORS_REAL +} ADLSensorType; + + +//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_THROTTLE_PERCENTAGE_TEMP_GFX = 52, + ADL_PMLOG_THROTTLE_PERCENTAGE_TEMP_MEM = 53, + ADL_PMLOG_THROTTLE_PERCENTAGE_TEMP_VR = 54, + ADL_PMLOG_THROTTLE_PERCENTAGE_POWER = 55, + ADL_PMLOG_THROTTLE_PERCENTAGE_TDC = 56, + ADL_PMLOG_THROTTLE_PERCENTAGE_VMAX = 57, + ADL_PMLOG_BUS_CURR_MAX_SPEED = 58, + ADL_PMLOG_RESERVED_1 = 59, //Currently Unused + ADL_PMLOG_RESERVED_2 = 60, //Currently Unused + ADL_PMLOG_RESERVED_3 = 61, //Currently Unused + ADL_PMLOG_RESERVED_4 = 62, //Currently Unused + ADL_PMLOG_RESERVED_5 = 63, //Currently Unused + ADL_PMLOG_RESERVED_6 = 64, //Currently Unused + ADL_PMLOG_RESERVED_7 = 65, //Currently Unused + ADL_PMLOG_RESERVED_8 = 66, //Currently Unused + ADL_PMLOG_RESERVED_9 = 67, //Currently Unused + ADL_PMLOG_RESERVED_10 = 68, //Currently Unused + ADL_PMLOG_RESERVED_11 = 69, //Currently Unused + ADL_PMLOG_RESERVED_12 = 70, //Currently Unused + ADL_PMLOG_CLK_NPUCLK = 71, //NPU frequency + ADL_PMLOG_NPU_BUSY_AVG = 72, //NPU busy + ADL_PMLOG_BOARD_POWER = 73, + ADL_PMLOG_TEMPERATURE_INTAKE = 74, + ADL_PMLOG_MAX_SENSORS_REAL +} ADL_PMLOG_SENSORS; + +/// \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_DELAGNEXT = 16, + ADL_UIFEATURES_GROUP_RTBOOST = 17, + ADL_UIFEATURES_GROUP_UAI = 18 + +} 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_RTBOOST_PROFILE=1 << 8, + ADL_USER_SETTINGS_USU2_PROFILE = 1 << 9, //notify USU2 settings change + } ADL_USER_SETTINGS; + +//FRAME_TIMESTAMPS_SHARED_MEMORY type +typedef enum FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE +{ + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_UNKNOWN = 0, + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_LEGACY, + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_INDEXED +}FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE; +#define ADL_REG_DEVICE_FUNCTION_1 0x00000001 +#endif /* ADL_DEFINES_H_ */ + + diff --git a/src/3rdparty/display-library/adl_sdk.h b/src/3rdparty/display-library/adl_sdk.h new file mode 100644 index 0000000..f649431 --- /dev/null +++ b/src/3rdparty/display-library/adl_sdk.h @@ -0,0 +1,42 @@ +// +// 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" + +/// Memory Allocation Call back +typedef void* ( __stdcall *ADL_MAIN_MALLOC_CALLBACK )( int ); + +#define ADL_SDK_MAJOR_VERSION 18 +#define ADL_SDK_MINOR_VERSION 0 + +#endif /* ADL_SDK_H_ */ diff --git a/src/3rdparty/display-library/adl_structures.h b/src/3rdparty/display-library/adl_structures.h new file mode 100644 index 0000000..8311fe4 --- /dev/null +++ b/src/3rdparty/display-library/adl_structures.h @@ -0,0 +1,4282 @@ +// +// 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 \WIN platforms.\n Included in ADL SDK +/// +/// All data structures used in AMD Display Library (ADL) public interfaces should be defined in this header file. +/// + +#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 +{ +/// \WIN_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. + 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) */ + +} AdapterInfo, *LPAdapterInfo; + +///////////////////////////////////////////////////////////////////////////////////////////// +///\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; + + // Display screen width/height + int ulScreenWidth; + int ulScreenHeight; + + // Reserved for future use + int iReserved[2]; +} 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 BOOST Settings +/// +/// Elements of BOOST settings. +/// \nosubgrouping +//////////////////////////////////////////////////////////////////////////////////////////// +typedef struct ADL_BOOST_SETTINGSX2 +{ + int Hotkey; // Hotkey value + int GlobalEnable; //Global enable value + int GlobalMinRes; //Gloabl Min Resolution value + int GlobalMinRes_MinLimit; //Gloabl Min Resolution slider min limit value + int GlobalMinRes_MaxLimit; //Gloabl Min Resolution slider max limit value + int GlobalMinRes_Step; //Gloabl Min Resolution step value + int VsrSupported; //Allows for interop with Upscaling/RSR +}ADL_BOOST_SETTINGSX2; + + +///////////////////////////////////////////////////////////////////////////////////////////// +///\brief Structure containing information about ProVSR Settings change reason +/// +/// 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 +{ + /// \WIN_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 +/// \deprecated This structure has been deprecated. +//////////////////////////////////////////////////////////////////////////////////////////// + +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_ */ \ No newline at end of file diff --git a/src/3rdparty/display-library/repo.json b/src/3rdparty/display-library/repo.json new file mode 100644 index 0000000..d318cb6 --- /dev/null +++ b/src/3rdparty/display-library/repo.json @@ -0,0 +1,6 @@ +{ + "home": "https://github.com/GPUOpen-LibrariesAndSDKs/display-library", + "license": "MIT (embeded in source)", + "version": "ADL SDK 18.1", + "author": "Advanced Micro Devices, Inc" +} diff --git a/src/3rdparty/widecharwidth/repo.json b/src/3rdparty/widecharwidth/repo.json new file mode 100644 index 0000000..d87eb5f --- /dev/null +++ b/src/3rdparty/widecharwidth/repo.json @@ -0,0 +1,6 @@ +{ + "home": "https://github.com/ridiculousfish/widecharwidth", + "license": "Public domain", + "version": "Unicode 17", + "author": "ridiculousfish" +} diff --git a/src/3rdparty/widecharwidth/widechar_width_c.h b/src/3rdparty/widecharwidth/widechar_width_c.h new file mode 100644 index 0000000..f13e897 --- /dev/null +++ b/src/3rdparty/widecharwidth/widechar_width_c.h @@ -0,0 +1,1590 @@ +/** + * 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 diff --git a/src/3rdparty/yyjson/repo.json b/src/3rdparty/yyjson/repo.json new file mode 100644 index 0000000..bfa523f --- /dev/null +++ b/src/3rdparty/yyjson/repo.json @@ -0,0 +1,6 @@ +{ + "home": "https://github.com/ibireme/yyjson", + "license": "MIT ( embed in source )", + "version": "0.12.0", + "author": "ibireme" +} diff --git a/src/3rdparty/yyjson/yyjson.c b/src/3rdparty/yyjson/yyjson.c new file mode 100644 index 0000000..c16d925 --- /dev/null +++ b/src/3rdparty/yyjson/yyjson.c @@ -0,0 +1,11065 @@ +/*============================================================================== + 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 */ diff --git a/src/3rdparty/yyjson/yyjson.h b/src/3rdparty/yyjson/yyjson.h new file mode 100644 index 0000000..5eb6d46 --- /dev/null +++ b/src/3rdparty/yyjson/yyjson.h @@ -0,0 +1,8230 @@ +/*============================================================================== + 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 */ diff --git a/src/common/FFPlatform.h b/src/common/FFPlatform.h new file mode 100644 index 0000000..cf8224d --- /dev/null +++ b/src/common/FFPlatform.h @@ -0,0 +1,37 @@ +#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); diff --git a/src/common/FFlist.h b/src/common/FFlist.h new file mode 100644 index 0000000..80cef0b --- /dev/null +++ b/src/common/FFlist.h @@ -0,0 +1,141 @@ +#pragma once + +#include "common/attributes.h" + +#include +#include +#include +#include + +#define FF_LIST_DEFAULT_ALLOC 16 + +typedef struct FFlist { + uint8_t* data; + uint32_t length; + uint32_t capacity; +} FFlist; + +void* ffListAdd(FFlist* list, uint32_t elementSize); + +// Removes the first element, and copy its value to `*result` +bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result); +// Removes the last element, and copy its value to `*result` +bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result); + +static inline void ffListInit(FFlist* list) { + list->capacity = 0; + list->length = 0; + list->data = NULL; +} + +static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) { + ffListInit(list); + list->capacity = capacity; + list->data = __builtin_expect(capacity == 0, 0) ? NULL : (uint8_t*) malloc((size_t) capacity * elementSize); +} + +FF_A_NODISCARD static inline FFlist ffListCreate() { + FFlist result; + ffListInit(&result); + return result; +} + +FF_A_NODISCARD static inline FFlist ffListCreateA(uint32_t elementSize, uint32_t capacity) { + FFlist result; + ffListInitA(&result, elementSize, capacity); + return result; +} + +FF_A_NODISCARD static inline void* ffListGet(const FFlist* list, uint32_t elementSize, uint32_t index) { + assert(list->capacity > index); + return list->data + (index * elementSize); +} + +FF_A_NODISCARD static inline uint32_t ffListFirstIndexComp(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { + for (uint32_t i = 0; i < list->length; i++) { + if (compFunc(ffListGet(list, elementSize, i), compElement)) { + return i; + } + } + + return list->length; +} + +FF_A_NODISCARD static inline bool ffListContains(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { + return ffListFirstIndexComp(list, elementSize, compElement, compFunc) != list->length; +} + +static inline void ffListSort(FFlist* list, uint32_t elementSize, int (*compar)(const void*, const void*)) { + qsort(list->data, list->length, elementSize, compar); +} + +// Move the contents of `src` into `list`, and left `src` empty +static inline void ffListInitMove(FFlist* list, FFlist* src) { + if (src) { + list->capacity = src->capacity; + list->length = src->length; + list->data = src->data; + ffListInit(src); + } else { + ffListInit(list); + } +} + +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 elementSize, uint32_t newCapacity) { + if (__builtin_expect(newCapacity <= list->capacity, false)) { + return; + } + + list->data = (uint8_t*) realloc(list->data, (size_t) newCapacity * elementSize); + list->capacity = newCapacity; +} + +#define FF_LIST_FOR_EACH(itemType, itemVarName, listVar) \ + for (itemType* itemVarName = (itemType*) (listVar).data; \ + itemVarName - (itemType*) (listVar).data < (intptr_t) (listVar).length; \ + ++itemVarName) + +#define FF_LIST_AUTO_DESTROY FFlist FF_A_CLEANUP(ffListDestroy) + +#define FF_LIST_GET(itemType, listVar, index) \ + ({ \ + assert((listVar).capacity > (index)); \ + (itemType*) (listVar).data + (index); \ + }) + +#define FF_LIST_ADD(itemType, listVar) (itemType*) ffListAdd(&(listVar), (uint32_t) sizeof(itemType)) + +#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)); \ + }) + +#define FF_LIST_CONTAINS(listVar, pCompElement, compFunc) \ + ({ \ + typedef __typeof__(*(pCompElement)) compElementType; \ + typedef bool compFuncType(const compElementType*, const compElementType*); \ + static_assert(__builtin_types_compatible_p(__typeof__(compFunc), compFuncType), "Incompatible callback function"); \ + ffListContains(&(listVar), (uint32_t) sizeof(*(pCompElement)), (pCompElement), (bool (*)(const void*, const void*)) compFunc); \ + }) + +#define FF_LIST_SHIFT(listVar, pResult) \ + ffListShift(&(listVar), (uint32_t) sizeof(*(pResult)), (pResult)) +#define FF_LIST_POP(listVar, pResult) \ + ffListPop(&(listVar), (uint32_t) sizeof(*(pResult)), (pResult)) diff --git a/src/common/FFstrbuf.h b/src/common/FFstrbuf.h new file mode 100644 index 0000000..2279152 --- /dev/null +++ b/src/common/FFstrbuf.h @@ -0,0 +1,584 @@ +#pragma once + +#include "common/attributes.h" + +#include +#include +#include +#include +#include +#include +#include +#include "common/memrchr.h" + +#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_A_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_A_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[]); + +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_A_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_A_NODISCARD double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue); +FF_A_NODISCARD int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue); +FF_A_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_A_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_A_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_A_NODISCARD static inline FFstrbuf ffStrbufCreateMove(FFstrbuf* src) { + FFstrbuf strbuf; + ffStrbufInitMove(&strbuf, src); + return strbuf; +} + +FF_A_NODISCARD static inline FFstrbuf ffStrbufCreateVF(const char* format, va_list arguments) { + FFstrbuf strbuf; + ffStrbufInitVF(&strbuf, format, arguments); + return strbuf; +} + +FF_A_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_A_PRINTF(1, 2) FF_A_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); +} + +// Despite the name, this function resets strbuf to the initial/unallocated state +static inline void ffStrbufDestroy(FFstrbuf* strbuf) { + if (strbuf->allocated > 0) { + free(strbuf->chars); + } + + ffStrbufInit(strbuf); +} + +FF_A_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_A_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_A_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_A_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_A_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_A_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_A_NODISCARD bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { + return ffStrbufComp(strbuf, comp) == 0; +} + +static inline FF_A_NODISCARD int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) { + return strcmp(strbuf->chars, comp); +} + +static inline FF_A_NODISCARD bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) { + return ffStrbufCompS(strbuf, comp) == 0; +} + +static inline FF_A_NODISCARD int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) { + return strcasecmp(strbuf->chars, comp); +} + +static inline FF_A_NODISCARD bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) { + return ffStrbufIgnCaseCompS(strbuf, comp) == 0; +} + +static inline FF_A_NODISCARD int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { + return ffStrbufIgnCaseCompS(strbuf, comp->chars); +} + +static inline FF_A_NODISCARD bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { + return ffStrbufIgnCaseComp(strbuf, comp) == 0; +} + +static inline FF_A_NODISCARD bool ffStrbufContainC(const FFstrbuf* strbuf, char c) { + return memchr(strbuf->chars, c, strbuf->length) != NULL; +} + +static inline FF_A_NODISCARD bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) { + return strstr(strbuf->chars, str) != NULL; +} + +static inline FF_A_NODISCARD bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) { + return ffStrbufContainS(strbuf, str->chars); +} + +static inline FF_A_NODISCARD bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) { + return strcasestr(strbuf->chars, str) != NULL; +} + +static inline FF_A_NODISCARD bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) { + return ffStrbufContainIgnCaseS(strbuf, str->chars); +} + +FF_A_NODISCARD static inline 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; +} + +FF_A_NODISCARD static inline 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; +} + +FF_A_NODISCARD static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { + assert(start <= strbuf->length); + + const char* ptr = (const char*) memrchr(strbuf->chars, c, start + 1); + return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length; +} + +static inline FF_A_NODISCARD uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) { + return ffStrbufNextIndexC(strbuf, 0, c); +} + +static inline FF_A_NODISCARD uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) { + return ffStrbufNextIndexS(strbuf, 0, searched->chars); +} + +static inline FF_A_NODISCARD uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) { + return ffStrbufNextIndexS(strbuf, 0, str); +} + +static inline FF_A_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_A_NODISCARD bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) { + return strbuf->chars[0] == c; +} + +static inline FF_A_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_A_NODISCARD bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) { + return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start)); +} + +static inline FF_A_NODISCARD bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) { + return ffStrbufStartsWithSN(strbuf, start->chars, start->length); +} + +static inline FF_A_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_A_NODISCARD bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) { + return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start); +} + +static inline FF_A_NODISCARD bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) { + return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars); +} + +static inline FF_A_NODISCARD bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) { + return strbuf->length == 0 ? false : strbuf->chars[strbuf->length - 1] == c; +} + +static inline FF_A_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_A_NODISCARD bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) { + return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end); +} + +static inline FF_A_NODISCARD bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) { + return strbuf->length == 0 ? false : fn(strbuf->chars[strbuf->length - 1]); +} + +static inline FF_A_NODISCARD bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) { + return ffStrbufEndsWithNS(strbuf, end->length, end->chars); +} + +static inline FF_A_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_A_NODISCARD bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) { + return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end); +} + +static inline FF_A_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); +} + +// Returns true if the strbuf is modified +bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf); + +#define FF_STRBUF_AUTO_DESTROY FFstrbuf FF_A_CLEANUP(ffStrbufDestroy) diff --git a/src/common/apple/Info.plist.in b/src/common/apple/Info.plist.in new file mode 100644 index 0000000..5220053 --- /dev/null +++ b/src/common/apple/Info.plist.in @@ -0,0 +1,16 @@ + + + + + CFBundleIdentifier + fastfetch + CFBundleName + @PROJECT_NAME@ + CFBundleShortVersionString + @PROJECT_VERSION@ + CFBundleDevelopmentRegion + English + NSBluetoothAlwaysUsageDescription + For detecting Bluetooth devices + + diff --git a/src/common/apple/cf_helpers.c b/src/common/apple/cf_helpers.c new file mode 100644 index 0000000..60e1a1c --- /dev/null +++ b/src/common/apple/cf_helpers.c @@ -0,0 +1,225 @@ +#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(*result)) { + return "Data length is not sizeof(int32_t)"; + } + CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(*result)), (uint8_t*) result); + return NULL; + } + + return "TypeID is neither 'CFNumber' nor 'CFData'"; +} + +const char* ffCfNumGetDouble(CFTypeRef cf, double* result) { + if (CFGetTypeID(cf) == CFNumberGetTypeID()) { + if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberDoubleType, result) && + !CFNumberGetValue((CFNumberRef) cf, kCFNumberFloatType, result)) { + return "Number type is not Double or Float"; + } + return NULL; + } + + return "TypeID is neither 'CFNumber'"; +} + +const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result) { + if (CFGetTypeID(cf) != CFDateGetTypeID()) { + return "TypeID is not 'CFDate'"; + } + + CFAbsoluteTime absTime = CFDateGetAbsoluteTime((CFDateRef) cf); + // Convert from seconds to milliseconds and add the difference between 1970 and 2001 in milliseconds + *result = (uint64_t) ((absTime + 978307200 /*kCFAbsoluteTimeIntervalSince1970*/) * 1000); + return NULL; +} + +const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) { + ffStrbufClear(result); + if (!cf) { + 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* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result) { + CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); + if (cf == NULL) { + return "CFDictionaryGetValue() failed"; + } + + return ffCfNumGetDouble(cf, result); +} + +const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length) { + CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); + if (cf == NULL) { + 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; +} + +const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result) { + CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); + if (cf == NULL) { + return "CFDictionaryGetValue() failed"; + } + + return ffCfDateGetEpoch(cf, result); +} diff --git a/src/common/apple/cf_helpers.h b/src/common/apple/cf_helpers.h new file mode 100644 index 0000000..a929f03 --- /dev/null +++ b/src/common/apple/cf_helpers.h @@ -0,0 +1,43 @@ +#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* ffCfNumGetDouble(CFTypeRef cf, double* result); +const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result); +const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result); +const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); +const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result); +const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result); +const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result); +const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result); +const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length); +const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); +const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result); +const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result); + +static inline CFNumberRef ffCfCreateInt(int value) { + return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value); +} + +static inline void cfReleaseWrapper(void* type) { + assert(type); + if (*(CFTypeRef*) type) { + CFRelease(*(CFTypeRef*) type); + } +} + +#define FF_CFTYPE_AUTO_RELEASE FF_A_CLEANUP(cfReleaseWrapper) + +static inline void wrapIoObjectRelease(io_object_t* service) { + assert(service); + if (*service) { + IOObjectRelease(*service); + } +} +#define FF_IOOBJECT_AUTO_RELEASE FF_A_CLEANUP(wrapIoObjectRelease) diff --git a/src/common/apple/osascript.h b/src/common/apple/osascript.h new file mode 100644 index 0000000..164d8d7 --- /dev/null +++ b/src/common/apple/osascript.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +bool ffOsascript(const char* input, FFstrbuf* result); diff --git a/src/common/apple/osascript.m b/src/common/apple/osascript.m new file mode 100644 index 0000000..f17af03 --- /dev/null +++ b/src/common/apple/osascript.m @@ -0,0 +1,15 @@ +#include "osascript.h" + +#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; +} diff --git a/src/common/apple/smc_temps.c b/src/common/apple/smc_temps.c new file mode 100644 index 0000000..6669c31 --- /dev/null +++ b/src/common/apple/smc_temps.c @@ -0,0 +1,465 @@ +#include "smc_temps.h" +#include "common/apple/cf_helpers.h" +#include "common/strutil.h" + +#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; +} diff --git a/src/common/apple/smc_temps.h b/src/common/apple/smc_temps.h new file mode 100644 index 0000000..6783bb9 --- /dev/null +++ b/src/common/apple/smc_temps.h @@ -0,0 +1,32 @@ +#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); diff --git a/src/common/apple/version.h b/src/common/apple/version.h new file mode 100644 index 0000000..d15acb4 --- /dev/null +++ b/src/common/apple/version.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* retVersion); diff --git a/src/common/apple/version.m b/src/common/apple/version.m new file mode 100644 index 0000000..133ec5b --- /dev/null +++ b/src/common/apple/version.m @@ -0,0 +1,43 @@ +#include "common/apple/version.h" +#include "common/strutil.h" + +#import + +#define APP_CONTENTS_MACOS ".app/Contents/MacOS" + +bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* retVersion) { + char* lastSlash = strrchr(exePath, '/'); + if (!lastSlash) { + return false; + } + if ((size_t) (lastSlash - exePath) > strlen("X" APP_CONTENTS_MACOS) && memcmp(lastSlash - strlen(APP_CONTENTS_MACOS), APP_CONTENTS_MACOS, strlen(APP_CONTENTS_MACOS)) != 0) { + return false; + } + + lastSlash -= strlen("MacOS"); + char infoPlistPath[PATH_MAX]; + memcpy(infoPlistPath, exePath, lastSlash - exePath); + memcpy(infoPlistPath + (lastSlash - exePath), "Info.plist", sizeof("Info.plist")); // X.app/Contents/Info.plist + NSError* error; + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(infoPlistPath)] + error:&error]; + if (!dict) { + return false; + } + + if (retName) { + NSString* bundleName = dict[@"CFBundleDisplayName"] ?: dict[@"CFBundleName"]; + if (bundleName) { + ffStrbufSetS(retName, bundleName.UTF8String); + } + } + + if (retVersion) { + NSString* bundleVersion = dict[@"CFBundleShortVersionString"]; + if (bundleVersion) { + ffStrbufSetS(retVersion, bundleVersion.UTF8String); + } + } + + return true; +} diff --git a/src/common/argType.h b/src/common/argType.h new file mode 100644 index 0000000..81217c7 --- /dev/null +++ b/src/common/argType.h @@ -0,0 +1,44 @@ +#pragma once + +#include "common/FFstrbuf.h" + +// For ffRegReadValue(s) +// If length is 0, data will be allocated with malloc() and must be freed by the caller +// Otherwise, data must point to a buffer of at least length bytes, and the actual length of the data will be written to length. +typedef struct FFArgBuffer { + void* data; + uint32_t length; +} FFArgBuffer; + +typedef enum FF_A_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, + FF_ARG_TYPE_BUFFER, +} 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, \ + FFArgBuffer: FF_ARG_TYPE_BUFFER), \ + _Generic((variable), char*: (variable), const char*: (variable), default: &(variable)), \ + (var_name) } diff --git a/src/common/arrutil.h b/src/common/arrutil.h new file mode 100644 index 0000000..83a49d6 --- /dev/null +++ b/src/common/arrutil.h @@ -0,0 +1,16 @@ +#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 diff --git a/src/common/attributes.h b/src/common/attributes.h new file mode 100644 index 0000000..ff1e660 --- /dev/null +++ b/src/common/attributes.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef __has_attribute + #error Unsupported compiler + #define __attribute__(x) +#endif + +#define FF_A_FALLTHROUGH __attribute__((__fallthrough__)) +#define FF_A_DEPRECATED __attribute__((__deprecated__)) +#define FF_A_CLEANUP(func) __attribute__((__cleanup__(func))) +#define FF_A_NODISCARD __attribute__((__warn_unused_result__)) +#define FF_A_PRINTF(formatStrIndex, argsStartIndex) __attribute__((__format__(printf, formatStrIndex, argsStartIndex))) +#define FF_A_SCANF(formatStrIndex, argsStartIndex) __attribute__((__format__(scanf, formatStrIndex, argsStartIndex))) +#define FF_A_NONNULL(argIndex, ...) __attribute__((__nonnull__(argIndex, ##__VA_ARGS__))) +#define FF_A_RETURNS_NONNULL __attribute__((__returns_nonnull__)) +#define FF_A_UNUSED __attribute__((__unused__)) +#define FF_A_PACKED __attribute__((__packed__)) +#define FF_A_WEAK_IMPORT __attribute__((__weak_import__)) +#define FF_A_ALWAYS_INLINE __attribute__((__always_inline__)) diff --git a/src/common/base64.h b/src/common/base64.h new file mode 100644 index 0000000..d17da5c --- /dev/null +++ b/src/common/base64.h @@ -0,0 +1,21 @@ +#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; +} diff --git a/src/common/binary.h b/src/common/binary.h new file mode 100644 index 0000000..2d2d784 --- /dev/null +++ b/src/common/binary.h @@ -0,0 +1,18 @@ +#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); diff --git a/src/common/color.h b/src/common/color.h new file mode 100644 index 0000000..7f8c539 --- /dev/null +++ b/src/common/color.h @@ -0,0 +1,55 @@ +#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;" diff --git a/src/common/commandoption.h b/src/common/commandoption.h new file mode 100644 index 0000000..5f64701 --- /dev/null +++ b/src/common/commandoption.h @@ -0,0 +1,8 @@ +#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); diff --git a/src/common/dbus.h b/src/common/dbus.h new file mode 100644 index 0000000..e0689c3 --- /dev/null +++ b/src/common/dbus.h @@ -0,0 +1,46 @@ +#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, uint64_t* result); +DBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method, const char* arg1, const char* arg2); +DBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property); +bool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result); +bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int64_t* result); +bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint64_t* result); +void ffDBusDestroyData(FFDBusData* data); + +static inline DBusMessage* ffDBusGetAllProperties(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface) { + return ffDBusGetMethodReply(dbus, busName, objectPath, "org.freedesktop.DBus.Properties", "GetAll", interface, NULL); +} + + #define FF_DBUS_AUTO_DESTROY_DATA FF_A_CLEANUP(ffDBusDestroyData) + +#endif // FF_HAVE_DBUS diff --git a/src/common/debug.h b/src/common/debug.h new file mode 100644 index 0000000..a378fc8 --- /dev/null +++ b/src/common/debug.h @@ -0,0 +1,37 @@ +#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(DWORD errorCode); +const char* ffDebugNtStatus(NTSTATUS status); +const char* ffDebugHResult(HRESULT hr); +#endif diff --git a/src/common/duration.h b/src/common/duration.h new file mode 100644 index 0000000..30733c9 --- /dev/null +++ b/src/common/duration.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result); diff --git a/src/common/edidHelper.h b/src/common/edidHelper.h new file mode 100644 index 0000000..dc4e4fb --- /dev/null +++ b/src/common/edidHelper.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/common/ffdata.h b/src/common/ffdata.h new file mode 100644 index 0000000..189c0fa --- /dev/null +++ b/src/common/ffdata.h @@ -0,0 +1,22 @@ +#pragma once + +#include "common/FFstrbuf.h" + +typedef enum FF_A_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; diff --git a/src/common/font.h b/src/common/font.h new file mode 100644 index 0000000..f9d25e7 --- /dev/null +++ b/src/common/font.h @@ -0,0 +1,25 @@ +#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); +} diff --git a/src/common/format.h b/src/common/format.h new file mode 100644 index 0000000..5be5dff --- /dev/null +++ b/src/common/format.h @@ -0,0 +1,14 @@ +#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); +bool 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)); diff --git a/src/common/frequency.h b/src/common/frequency.h new file mode 100644 index 0000000..7a3bdab --- /dev/null +++ b/src/common/frequency.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result); diff --git a/src/common/haiku/version.cpp b/src/common/haiku/version.cpp new file mode 100644 index 0000000..f8e65c3 --- /dev/null +++ b/src/common/haiku/version.cpp @@ -0,0 +1,26 @@ +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; +} diff --git a/src/common/haiku/version.h b/src/common/haiku/version.h new file mode 100644 index 0000000..4f047a2 --- /dev/null +++ b/src/common/haiku/version.h @@ -0,0 +1,3 @@ +#include "common/FFstrbuf.h" + +bool ffGetFileVersion(const char* filePath, FFstrbuf* version); diff --git a/src/common/impl/FFPlatform.c b/src/common/impl/FFPlatform.c new file mode 100644 index 0000000..ff4c45c --- /dev/null +++ b/src/common/impl/FFPlatform.c @@ -0,0 +1,94 @@ +#include "FFPlatform_private.h" +#include "common/strutil.h" +#include "common/io.h" +#include "detection/version/version.h" + +void ffPlatformInit(FFPlatform* platform) { + ffStrbufInit(&platform->homeDir); + ffStrbufInit(&platform->cacheDir); + ffListInit(&platform->configDirs); + ffListInit(&platform->dataDirs); + 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); + info->pageSize = 0; + + 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 (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) { + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *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) && !FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) { + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer); + } +} diff --git a/src/common/impl/FFPlatform_private.h b/src/common/impl/FFPlatform_private.h new file mode 100644 index 0000000..e9dfd71 --- /dev/null +++ b/src/common/impl/FFPlatform_private.h @@ -0,0 +1,8 @@ +#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); diff --git a/src/common/impl/FFPlatform_unix.c b/src/common/impl/FFPlatform_unix.c new file mode 100644 index 0000000..de69f6b --- /dev/null +++ b/src/common/impl/FFPlatform_unix.c @@ -0,0 +1,321 @@ +#include "FFPlatform_private.h" +#include "common/FFstrbuf.h" +#include "common/arrutil.h" +#include "common/strutil.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 + #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, + (pid_t) 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, (pid_t) 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); +} diff --git a/src/common/impl/FFPlatform_windows.c b/src/common/impl/FFPlatform_windows.c new file mode 100644 index 0000000..083d20a --- /dev/null +++ b/src/common/impl/FFPlatform_windows.c @@ -0,0 +1,313 @@ +#include "FFPlatform_private.h" +#include "common/io.h" +#include "common/library.h" +#include "common/strutil.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 (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) { + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *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) && !FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) { + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *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); + } + + NTSYSAPI NTSTATUS NTAPI LsaGetUserName( + _Outptr_ PLSA_UNICODE_STRING * UserName, + _Outptr_opt_ PLSA_UNICODE_STRING * DomainName); + PLSA_UNICODE_STRING userName = NULL; + if (NT_SUCCESS(LsaGetUserName(&userName, NULL))) { + ffStrbufSetNWS(&platform->userName, userName->Length / sizeof(wchar_t), userName->Buffer); + RtlFreeUnicodeString(userName); // Required. userName.Buffer is allocated separately + LsaFreeMemory(userName); + } 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); + + PPEB_FULL peb = ffGetPeb(); + + ffStrbufSetF(&info->release, + "%u.%u.%u.%u", + (unsigned) peb->OSMajorVersion, + (unsigned) peb->OSMinorVersion, + (unsigned) peb->OSBuildNumber, + (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); +} diff --git a/src/common/impl/FFlist.c b/src/common/impl/FFlist.c new file mode 100644 index 0000000..eddd0fa --- /dev/null +++ b/src/common/impl/FFlist.c @@ -0,0 +1,34 @@ +#include "common/FFlist.h" + +#include +#include + +void* ffListAdd(FFlist* list, uint32_t elementSize) { + if (list->length == list->capacity) { + ffListReserve(list, elementSize, list->capacity == 0 ? FF_LIST_DEFAULT_ALLOC : list->capacity * 2); + } + + ++list->length; + return ffListGet(list, elementSize, list->length - 1); +} + +bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result) { + if (list->length == 0) { + return false; + } + + memcpy(result, list->data, elementSize); + memmove(list->data, list->data + elementSize, (size_t) elementSize * (list->length - 1)); + --list->length; + return true; +} + +bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result) { + if (list->length == 0) { + return false; + } + + memcpy(result, ffListGet(list, elementSize, list->length - 1), elementSize); + --list->length; + return true; +} diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c new file mode 100644 index 0000000..1f6477b --- /dev/null +++ b/src/common/impl/FFstrbuf.c @@ -0,0 +1,903 @@ +#include "common/FFstrbuf.h" +#include "common/mallocHelper.h" +#include "common/strutil.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) { + char* newBuf = malloc(sizeof(char) * (length + 1)); + memcpy(newBuf, value, length); + if (strbuf->allocated > 0) { + free(strbuf->chars); + } + strbuf->chars = newBuf; + strbuf->allocated = length + 1; + } else { + memmove(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]); + } +} + +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, (size_t) (comp + compLength - p)); + if (colon == NULL) { + uint32_t remainingLen = (uint32_t) (comp + compLength - p); + return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0; + } + + uint32_t substrLength = (uint32_t) (colon - p); + 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, (size_t) (comp + compLength - p)); + if (colon == NULL) { + uint32_t remainingLen = (uint32_t) (comp + compLength - p); + return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0; + } + + uint32_t substrLength = (uint32_t) (colon - p); + 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; +} + +bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) { + assert(strbuf); + + if (strbuf->length < 4) { + return false; + } + + // Static string must be converted first. + assert(strbuf->allocated > 0); + + bool changed = false; + uint32_t read = 0; + uint32_t write = 0; + + while (read < strbuf->length) { + if ( + read + 3 < strbuf->length && + strbuf->chars[read] == '\\' && + strbuf->chars[read + 1] == 'x') { + int8_t hi = ffHexCharToInt(strbuf->chars[read + 2]); + int8_t lo = ffHexCharToInt(strbuf->chars[read + 3]); + if (hi >= 0 && lo >= 0) { + strbuf->chars[write++] = (char) ((hi << 4) | lo); + read += 4; + changed = true; + continue; + } + } + + strbuf->chars[write++] = strbuf->chars[read++]; + } + + strbuf->length = write; + strbuf->chars[write] = '\0'; + return changed; +} diff --git a/src/common/impl/base64.c b/src/common/impl/base64.c new file mode 100644 index 0000000..98a415f --- /dev/null +++ b/src/common/impl/base64.c @@ -0,0 +1,110 @@ +#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]; + +static 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; +} diff --git a/src/common/impl/binary_apple.c b/src/common/impl/binary_apple.c new file mode 100644 index 0000000..8478169 --- /dev/null +++ b/src/common/impl/binary_apple.c @@ -0,0 +1,303 @@ +#include "common/binary.h" +#include "common/io.h" +#include "common/strutil.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 + +typedef struct { + const uint8_t* data; + size_t length; +} FFMemoryMapping; + +static inline void wrapMunmap(FFMemoryMapping* mapping) { + assert(mapping); + if (mapping->data == NULL || mapping->data == MAP_FAILED) { + return; + } + munmap((void*) mapping->data, mapping->length); +} + +/** + * Helper function to access data from a memory-mapped file at a specific offset + */ +static inline const void* readData(const FFMemoryMapping* mapping, size_t size, off_t offset) { + if (offset < 0) { + return NULL; + } + + size_t start = (size_t) offset; + if (start > mapping->length || size > mapping->length - start) { + return NULL; + } + + return mapping->data + start; +} + +/** + * Handles a Mach-O section by extracting strings from the __cstring section + * + * @param mapping Memory mapping of 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(const FFMemoryMapping* mapping, 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; + } + + const char* data = readData(mapping, size, offset); + if (!data) { + return true; + } + + for (size_t off = 0; off < size; ++off) { + const char* p = data + off; + if (*p == '\0') { + continue; + } + uint32_t len = (uint32_t) strnlen(p, size - off); + if (len < minLength) { + off += len; // Skip short strings + 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 mapping Memory mapping of 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(const FFMemoryMapping* mapping, 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) { + const struct mach_header_64* header = readData(mapping, sizeof(struct mach_header_64), offset); + if (!header) { + return "read mach header failed"; + } + + ncmds = header->ncmds; + loadCommandsOffset += sizeof(*header); + } else { + const struct mach_header* header = readData(mapping, sizeof(struct mach_header), offset); + if (!header) { + return "read mach header failed"; + } + + ncmds = header->ncmds; + loadCommandsOffset += sizeof(*header); + } + + off_t commandOffset = loadCommandsOffset; + const struct load_command* cmd = NULL; + for (uint32_t i = 0U; i < ncmds; i++, commandOffset += cmd->cmdsize) { + cmd = readData(mapping, sizeof(*cmd), commandOffset); + if (!cmd) { + break; + } + + if (cmd->cmdsize < sizeof(*cmd)) { + break; + } + + if (cmd->cmd == LC_SEGMENT_64) { + const struct segment_command_64* segment = readData(mapping, sizeof(struct segment_command_64), commandOffset); + if (!segment) { + continue; + } + + if (!ffStrEquals(segment->segname, "__TEXT")) { + continue; + } + + for (uint32_t j = 0U; j < segment->nsects; j++) { + off_t sectionOffset = commandOffset + (off_t) sizeof(*segment) + (off_t) (j * sizeof(struct section_64)); + const struct section_64* section = readData(mapping, sizeof(struct section_64), sectionOffset); + if (!section) { + continue; + } + + if (!handleMachSection(mapping, section->sectname, (off_t) section->offset, (size_t) section->size, cb, userdata, minLength)) { + return NULL; + } + } + } else if (cmd->cmd == LC_SEGMENT) { + const struct segment_command* segment = readData(mapping, sizeof(struct segment_command), commandOffset); + if (!segment) { + continue; + } + + if (!ffStrEquals(segment->segname, "__TEXT")) { + continue; + } + + for (uint32_t j = 0; j < segment->nsects; j++) { + off_t sectionOffset = commandOffset + (off_t) sizeof(*segment) + (off_t) (j * sizeof(struct section)); + const struct section* section = readData(mapping, sizeof(struct section), sectionOffset); + if (!section) { + continue; + } + + if (!handleMachSection(mapping, section->sectname, (off_t) section->offset, (size_t) section->size, cb, userdata, minLength)) { + 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 mapping Memory mapping of 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(const FFMemoryMapping* mapping, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) { + const struct fat_header* headerRaw = readData(mapping, sizeof(struct fat_header), 0); + if (!headerRaw) { + return "read fat header failed"; + } + + struct fat_header header = *headerRaw; + + 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) { + off_t archOffset = (off_t) sizeof(struct fat_header) + (off_t) (i * sizeof(struct fat_arch)); + const struct fat_arch* archRaw = readData(mapping, sizeof(struct fat_arch), archOffset); + if (!archRaw) { + continue; + } + + struct fat_arch arch = *archRaw; + + if (needSwap) { + swap_fat_arch(&arch, 1, NX_UnknownByteOrder); + } + machHeaderOffset = (off_t) arch.offset; + } else { + off_t archOffset = (off_t) sizeof(struct fat_header) + (off_t) (i * sizeof(struct fat_arch_64)); + const struct fat_arch_64* archRaw = readData(mapping, sizeof(struct fat_arch_64), archOffset); + if (!archRaw) { + continue; + } + + struct fat_arch_64 arch = *archRaw; + + if (needSwap) { + swap_fat_arch_64(&arch, 1, NX_UnknownByteOrder); + } + + machHeaderOffset = (off_t) arch.offset; + } + + const uint32_t* magic = readData(mapping, sizeof(uint32_t), machHeaderOffset); + if (!magic) { + continue; + } + + if (*magic == MH_MAGIC_64 || *magic == MH_MAGIC) { + return dumpMachHeader(mapping, machHeaderOffset, *magic == MH_MAGIC_64, cb, userdata, minLength); + } + } + 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_FD int fd = open(machoFile, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return "File could not be opened"; + } + + struct stat st; + if (fstat(fd, &st) != 0 || st.st_size <= 0) { + return "Failed to stat file"; + } + + FF_A_CLEANUP(wrapMunmap) FFMemoryMapping mapping = { + .data = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_PRIVATE, fd, 0), + .length = (size_t) st.st_size, + }; + if (mapping.data == MAP_FAILED) { + return "mmap failed"; + } + + // Read the magic number to determine the type of binary + const uint32_t* magic = readData(&mapping, sizeof(uint32_t), 0); + if (!magic) { + 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(&mapping, cb, userdata, minLength); + } else { + return dumpMachHeader(&mapping, 0, *magic == MH_MAGIC_64, cb, userdata, minLength); + } +} diff --git a/src/common/impl/binary_linux.c b/src/common/impl/binary_linux.c new file mode 100644 index 0000000..9a24217 --- /dev/null +++ b/src/common/impl/binary_linux.c @@ -0,0 +1,147 @@ +#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/strutil.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) strnlen(p, data->d_size - off); + if (len < minLength) { + off += len; + 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 diff --git a/src/common/impl/binary_windows.c b/src/common/impl/binary_windows.c new file mode 100644 index 0000000..918cad5 --- /dev/null +++ b/src/common/impl/binary_windows.c @@ -0,0 +1,71 @@ +#include "common/binary.h" +#include "common/io.h" +#include "common/strutil.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 hSection = NULL; + if (!NT_SUCCESS(NtCreateSection(&hSection, SECTION_MAP_READ, NULL, NULL, PAGE_READONLY, SEC_COMMIT, hFile))) { + return "NtCreateSection() failed"; + } + + PVOID base = NULL; + SIZE_T viewSize = 0; + if (!NT_SUCCESS(NtMapViewOfSection(hSection, NtCurrentProcess(), &base, 0, 0, NULL, &viewSize, ViewUnmap, 0, PAGE_READONLY))) { + return "NtMapViewOfSection() failed"; + } + + PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(base); + if (!ntHeaders) { + NtUnmapViewOfSection(NtCurrentProcess(), 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) strnlen(p, section->SizeOfRawData - off); + if (len < minLength) { + off += len; + continue; + } + // Only process printable ASCII characters + if (*p >= ' ' && *p <= '~') // Ignore control characters + { + if (!cb(p, len, userdata)) { + break; + } + } + off += len; + } + } + } + + NtUnmapViewOfSection(NtCurrentProcess(), base); + return NULL; +} diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c new file mode 100644 index 0000000..2f548df --- /dev/null +++ b/src/common/impl/commandoption.c @@ -0,0 +1,261 @@ +#include "common/commandoption.h" +#include "common/color.h" +#include "common/printing.h" +#include "common/time.h" +#include "common/jsonconfig.h" +#include "common/strutil.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]) { + #if !FF_MODULE_DISABLE_CPUUSAGE + case 'C': + case 'c': + FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME) + ffPrepareCPUUsage(); + break; + #endif + + #if !FF_MODULE_DISABLE_DISKIO + case 'D': + case 'd': + FF_IF_MODULE_MATCH(FF_DISKIO_MODULE_NAME) { + FF_A_CLEANUP(ffDestroyDiskIOOptions) FFDiskIOOptions options; + ffInitDiskIOOptions(&options); + ffPrepareDiskIO(&options); + } + break; + #endif + + #if !FF_MODULE_DISABLE_NETIO + case 'N': + case 'n': + FF_IF_MODULE_MATCH(FF_NETIO_MODULE_NAME) { + FF_A_CLEANUP(ffDestroyNetIOOptions) FFNetIOOptions options; + ffInitNetIOOptions(&options); + ffPrepareNetIO(&options); + } + break; + #endif + + #if !FF_MODULE_DISABLE_PUBLICIP + case 'P': + case 'p': + FF_IF_MODULE_MATCH(FF_PUBLICIP_MODULE_NAME) { + FF_A_CLEANUP(ffDestroyPublicIpOptions) FFPublicIPOptions options; + ffInitPublicIpOptions(&options); + ffPreparePublicIp(&options); + } + break; + #endif + + #if !FF_MODULE_DISABLE_WEATHER + case 'W': + case 'w': + FF_IF_MODULE_MATCH(FF_WEATHER_MODULE_NAME) { + FF_A_CLEANUP(ffDestroyWeatherOptions) FFWeatherOptions options; + ffInitWeatherOptions(&options); + ffPrepareWeather(&options); + } + break; + #endif + } + +#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 bool 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 (data->resultDoc != NULL) { + fn(data, baseInfo, optionBuf); + } else { + baseInfo->printModule(optionBuf); + } + baseInfo->destroyOptions(optionBuf); + return true; + } + } + } + + if (data->resultDoc) { + yyjson_mut_doc* doc = data->resultDoc; + yyjson_mut_val* module = yyjson_mut_arr_add_obj(doc, doc->root); + yyjson_mut_obj_add_str(doc, module, "type", line); + yyjson_mut_obj_add_str(doc, module, "error", "Unknown module type"); + } else { + ffPrintError(line, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, ""); + } + return false; +} + +void ffPrintCommandOption(FFdata* data) { + // 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 - 1, 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); + } +} diff --git a/src/common/impl/dbus.c b/src/common/impl/dbus.c new file mode 100644 index 0000000..473af2d --- /dev/null +++ b/src/common/impl/dbus.c @@ -0,0 +1,334 @@ +#include "common/dbus.h" + +#ifdef FF_HAVE_DBUS + + #include "common/thread.h" + #include "common/strutil.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 true; + } + + 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 + + int subArgType = dbus->lib->ffdbus_message_iter_get_arg_type(&subIter); + if (subArgType == DBUS_TYPE_INVALID) { + return false; + } + + if (subArgType == DBUS_TYPE_BYTE) { + while (true) { + uint8_t value; + dbus->lib->ffdbus_message_iter_get_basic(&subIter, &value); + ffStrbufAppendC(result, (char) value); + + if (!dbus->lib->ffdbus_message_iter_next(&subIter)) { + break; + } + } + + return true; + } + + bool foundAValue = false; + + while (true) { + 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, uint64_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) { + uint32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = value; + return true; + } + + if (argType == DBUS_TYPE_UINT64) { + dbus->lib->ffdbus_message_iter_get_basic(iter, result); + return true; + } + + 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, int64_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) { + int32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = value; + return true; + } + + if (argType == DBUS_TYPE_INT64) { + dbus->lib->ffdbus_message_iter_get_basic(iter, result); + return true; + } + + 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 = (int32_t) value; + return true; + } + + if (argType == DBUS_TYPE_UINT32) { + uint32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = (int32_t) value; + return true; + } + + if (argType == DBUS_TYPE_UINT64) { + uint64_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + if (value > INT64_MAX) { + return false; + } + *result = (int64_t) value; + 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, uint64_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 diff --git a/src/common/impl/debug_windows.c b/src/common/impl/debug_windows.c new file mode 100644 index 0000000..fb76a65 --- /dev/null +++ b/src/common/impl/debug_windows.c @@ -0,0 +1,55 @@ +#include "common/debug.h" +#include "common/windows/nt.h" + +#include + +const char* ffDebugWin32Error(DWORD errorCode) { + static char buffer[512]; + + wchar_t bufferW[256]; + ULONG len = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + (DWORD) errorCode, + 0, + bufferW, + ARRAY_SIZE(bufferW), + NULL); + + if (len == 0) { + snprintf(buffer, sizeof(buffer), "Unknown error code (%lu)", errorCode); + } else { + // Remove trailing newline + while (len > 0 && (bufferW[len - 1] == '\r' || bufferW[len - 1] == '\n')) { + --len; + } + + if (NT_SUCCESS(RtlUnicodeToUTF8N(buffer, sizeof(buffer), &len, bufferW, len * sizeof(wchar_t)))) { + snprintf(buffer + len, sizeof(buffer) - len, " (%lu)", errorCode); + } else { + snprintf(buffer, sizeof(buffer), "Unknown error (%lu)", errorCode); + } + } + + return buffer; +} + +const char* ffDebugNtStatus(NTSTATUS status) { + return ffDebugWin32Error(RtlNtStatusToDosError(status)); +} + +static inline DWORD HRESULTToWin32Error(HRESULT hr) { + if (SUCCEEDED(hr)) { + return ERROR_SUCCESS; + } + + if (HRESULT_FACILITY(hr) == FACILITY_WIN32) { + return HRESULT_CODE(hr); + } + + return ERROR_INTERNAL_ERROR; +} + +const char* ffDebugHResult(HRESULT hr) { + return ffDebugWin32Error(HRESULTToWin32Error(hr)); +} diff --git a/src/common/impl/duration.c b/src/common/impl/duration.c new file mode 100644 index 0000000..a6a4965 --- /dev/null +++ b/src/common/impl/duration.c @@ -0,0 +1,87 @@ +#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"); + } + } +} diff --git a/src/common/impl/edidHelper.c b/src/common/impl/edidHelper.c new file mode 100644 index 0000000..1e3310e --- /dev/null +++ b/src/common/impl/edidHelper.c @@ -0,0 +1,132 @@ +#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; +} diff --git a/src/common/impl/font.c b/src/common/impl/font.c new file mode 100644 index 0000000..93feff1 --- /dev/null +++ b/src/common/impl/font.c @@ -0,0 +1,513 @@ +#include "fastfetch.h" +#include "common/FFlist.h" +#include "common/FFstrbuf.h" +#include "common/strutil.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); +} + +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 = FF_LIST_ADD(FFstrbuf, 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 = FF_LIST_ADD(FFstrbuf, 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 = FF_LIST_ADD(FFstrbuf, 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") || ffStrStartsWithIgnCase(keyStart, "pointsize")))) { + if (sizeEmpty && ffCharIsDigit(value.chars[0])) { + ffStrbufAppend(&font->size, &value); + ffStrbufAppendS(&font->size, + (keyLen == 9 && ffStrStartsWithIgnCase(keyStart, "pixelsize")) ? "px" : "pt"); + } + } 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, ' '); + if (style->length == 0) { + ffStrbufDestroy(style); + --font->styles.length; + } + } + } + } + + 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); +} diff --git a/src/common/impl/format.c b/src/common/impl/format.c new file mode 100644 index 0000000..db414bc --- /dev/null +++ b/src/common/impl/format.c @@ -0,0 +1,761 @@ +#include "fastfetch.h" +#include "common/format.h" +#include "common/parsing.h" +#include "common/textModifier.h" +#include "common/strutil.h" +#include "common/library.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; + } + case FF_ARG_TYPE_BUFFER: { + // Placeholder for binary data, just print the size for now + const FFArgBuffer* argBuffer = (const FFArgBuffer*) formatarg->value; + ffStrbufAppendF(buffer, "buffer(%u bytes)", argBuffer->length); + 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 && *(int32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)); +} + +FF_A_UNUSED static inline void normalizeArgName(FFstrbuf* dst, const char* src) { + ffStrbufClear(dst); + bool flag = false; + for (const char* p = src; *p; ++p) { + if (*p == '-') { + flag = true; + } else if (flag) { + ffStrbufAppendC(dst, (char) toupper((unsigned char) *p)); + flag = false; + } else { + ffStrbufAppendC(dst, *p); + } + } +} + +#if FF_HAVE_LUA + #include "common/lua.h" + +static void appendLuaError(FFstrbuf* buffer, const char* prefix, lua_State* L) { + const char* err = lua_tolstring(L, -1, NULL); + if (err) { + const char* tmp = strchr(err, ':'); + if (tmp) { + err = tmp + 1; + while (*err == ' ') { + ++err; + } + } + } + ffStrbufAppendF(buffer, "%s: %s", prefix, err ? err : "unknown"); +} + +static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) { + const char* err = ffLuaLoadState(); + if (err) { + ffStrbufAppendF(buffer, "Lua init error: %s", err); + return false; + } + + FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate(); + bool ret = false; + + lua_State* L = luaData.L; + // Clear stack and load chunk + lua_settop(L, 0); + if (luaL_loadbuffer(L, script, scriptLen, "") != LUA_OK) { + appendLuaError(buffer, "Lua load error", L); + } else { + // Build args table for name lookup only. + lua_createtable(L, 0, 0); + + for (uint32_t i = 0; i < numArgs; ++i) { + const FFformatarg* arg = &arguments[i]; + switch (arg->type) { + case FF_ARG_TYPE_INT: + lua_pushinteger(L, (lua_Integer) * (int32_t*) arg->value); + break; + case FF_ARG_TYPE_UINT: + lua_pushinteger(L, (lua_Integer) * (uint32_t*) arg->value); + break; + case FF_ARG_TYPE_UINT64: + lua_pushinteger(L, (lua_Integer) * (uint64_t*) arg->value); + break; + case FF_ARG_TYPE_UINT16: + lua_pushinteger(L, (lua_Integer) * (uint16_t*) arg->value); + break; + case FF_ARG_TYPE_UINT8: + lua_pushinteger(L, (lua_Integer) * (uint8_t*) arg->value); + break; + case FF_ARG_TYPE_FLOAT: + lua_pushnumber(L, (lua_Number) * (float*) arg->value); + break; + case FF_ARG_TYPE_DOUBLE: + lua_pushnumber(L, (lua_Number) * (double*) arg->value); + break; + case FF_ARG_TYPE_BOOL: + lua_pushboolean(L, *(bool*) arg->value); + break; + case FF_ARG_TYPE_STRING: + lua_pushlstring(L, (const char*) arg->value, strlen((const char*) arg->value)); + break; + case FF_ARG_TYPE_STRBUF: { + const FFstrbuf* sb = (const FFstrbuf*) arg->value; + lua_pushlstring(L, sb->chars, sb->length); + break; + } + case FF_ARG_TYPE_LIST: { + const FFlist* list = (const FFlist*) arg->value; + lua_createtable(L, 0, 0); + for (uint32_t li = 0; li < list->length; ++li) { + const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li); + lua_pushlstring(L, item->chars, item->length); + lua_seti(L, -2, (lua_Integer) (li + 1)); + } + break; + } + default: + lua_pushnil(L); + break; + } + if (arg->name && arg->name[0]) { + normalizeArgName(&argNameBuf, arg->name); + } else { + ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1); + } + lua_setfield(L, -2, argNameBuf.chars); + } + + if (lua_pcall(L, 1, LUA_MULTRET, 0) != LUA_OK) { + appendLuaError(buffer, "Lua runtime error", L); + } else { + int nresults = lua_gettop(L); + if (nresults == 0) { + ffStrbufAppendS(buffer, "Lua result error: no result"); + } else { + // Convert first result to string + const char* res = lua_tolstring(L, 1, NULL); + if (res) { + ffStrbufAppendS(buffer, res); + } else { + luaL_tolstring(L, 1, NULL); + const char* sval = lua_tolstring(L, -1, NULL); + if (sval) { + ffStrbufAppendS(buffer, sval); + } + } + ret = true; + } + } + } + lua_settop(L, 0); + return ret; +} +#endif + +#if FF_HAVE_QUICKJS + #include + +struct FFQuickJSData { + FF_LIBRARY_SYMBOL(JS_NewRuntime) + FF_LIBRARY_SYMBOL(JS_NewContext) + FF_LIBRARY_SYMBOL(JS_FreeRuntime) + FF_LIBRARY_SYMBOL(JS_EvalThis) + FF_LIBRARY_SYMBOL(JS_GetException) + FF_LIBRARY_SYMBOL(JS_ToCStringLen2) + FF_LIBRARY_SYMBOL(JS_FreeCString) + FF_LIBRARY_SYMBOL(JS_NewStringLen) + FF_LIBRARY_SYMBOL(JS_NewArray) + FF_LIBRARY_SYMBOL(JS_NewBigUint64) + FF_LIBRARY_SYMBOL(JS_SetPropertyUint32) + FF_LIBRARY_SYMBOL(JS_NewObject) + FF_LIBRARY_SYMBOL(JS_SetPropertyStr) + FF_LIBRARY_SYMBOL(JS_FreeValue) + + JSRuntime* rt; + JSContext* ctx; + bool inited; +} qjsData; + +static const char* loadQuickJSState(void) { + if (qjsData.inited) { + if (qjsData.ctx == NULL) { + return "QuickJS is not available"; + } + return NULL; + } + + qjsData.inited = true; + #ifdef _WIN32 + FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs-0" FF_LIBRARY_EXTENSION, 0) + #else + FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs" FF_LIBRARY_EXTENSION, 0) + #endif + + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewRuntime) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewContext) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeRuntime) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_EvalThis) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_GetException) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_ToCStringLen2) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeCString) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewStringLen) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewArray) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyUint32) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewObject) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyStr) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeValue) + + qjsData.rt = qjsData.ffJS_NewRuntime(); + if (qjsData.rt == NULL) { + return "JS_NewRuntime() failed"; + } + + qjsData.ctx = qjsData.ffJS_NewContext(qjsData.rt); + if (qjsData.ctx == NULL) { + qjsData.ffJS_FreeRuntime(qjsData.rt); + qjsData.rt = NULL; + return "JS_NewContext() failed"; + } + + libqjs = NULL; // don't close quickjs + + return NULL; +} + +static bool parseQuickJSString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) { + const char* err = loadQuickJSState(); + if (err) { + ffStrbufAppendF(buffer, "Qjs init error: %s", err); + return false; + } + JSContext* ctx = qjsData.ctx; + JSValue argsObj = qjsData.ffJS_NewObject(ctx); + FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate(); + + for (uint32_t i = 0; i < numArgs; ++i) { + const FFformatarg* arg = &arguments[i]; + + JSValue value; + switch (arg->type) { + case FF_ARG_TYPE_INT: + value = JS_NewInt32(ctx, *(int32_t*) arg->value); + break; + case FF_ARG_TYPE_UINT: + value = JS_NewUint32(ctx, *(uint32_t*) arg->value); + break; + case FF_ARG_TYPE_UINT64: { + uint64_t val = *(uint64_t*) arg->value; + if (val <= INT32_MAX) { + value = JS_NewInt32(ctx, (int32_t) val); + } else { + value = JS_NewFloat64(ctx, (double) val); + } + break; + } + case FF_ARG_TYPE_UINT16: + value = JS_NewUint32(ctx, *(uint16_t*) arg->value); + break; + case FF_ARG_TYPE_UINT8: + value = JS_NewUint32(ctx, *(uint8_t*) arg->value); + break; + case FF_ARG_TYPE_FLOAT: + value = JS_NewFloat64(ctx, *(float*) arg->value); + break; + case FF_ARG_TYPE_DOUBLE: + value = JS_NewFloat64(ctx, *(double*) arg->value); + break; + case FF_ARG_TYPE_BOOL: + value = JS_NewBool(ctx, *(bool*) arg->value); + break; + case FF_ARG_TYPE_STRING: + value = qjsData.ffJS_NewStringLen(ctx, (const char*) arg->value, strlen((const char*) arg->value)); + break; + case FF_ARG_TYPE_STRBUF: { + const FFstrbuf* sb = (const FFstrbuf*) arg->value; + value = qjsData.ffJS_NewStringLen(ctx, sb->chars, sb->length); + break; + } + case FF_ARG_TYPE_LIST: { + const FFlist* list = (const FFlist*) arg->value; + JSValue arr = qjsData.ffJS_NewArray(ctx); + for (uint32_t li = 0; li < list->length; ++li) { + const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li); + JSValue itemValue = qjsData.ffJS_NewStringLen(ctx, item->chars, item->length); + qjsData.ffJS_SetPropertyUint32(ctx, arr, li, itemValue); + } + + value = arr; + break; + } + default: + value = JS_UNDEFINED; + break; + } + + if (arg->name && arg->name[0]) { + normalizeArgName(&argNameBuf, arg->name); + } else { + ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1); + } + qjsData.ffJS_SetPropertyStr(ctx, argsObj, argNameBuf.chars, value); + } + JSValue result = qjsData.ffJS_EvalThis(ctx, argsObj, script, scriptLen, "", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT); + + qjsData.ffJS_FreeValue(ctx, argsObj); + + bool ret = false; + if (JS_IsException(result)) { + JSValue exc = qjsData.ffJS_GetException(ctx); + const char* message = qjsData.ffJS_ToCStringLen2(ctx, NULL, exc, false); + qjsData.ffJS_FreeValue(ctx, exc); + ffStrbufAppendF(buffer, "Qjs runtime error: %s", message ?: "unknown"); + if (message) { + qjsData.ffJS_FreeCString(ctx, message); + } + } else if (JS_IsUndefined(result)) { + ffStrbufAppendS(buffer, "Qjs result error: undefined result"); + } else { + size_t len; + const char* res = qjsData.ffJS_ToCStringLen2(ctx, &len, result, false); + if (res) { + ffStrbufAppendNS(buffer, (uint32_t) len, res); + qjsData.ffJS_FreeCString(ctx, res); + } + ret = true; + } + + qjsData.ffJS_FreeValue(ctx, result); + return ret; +} +#endif + +static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape) { + if (__builtin_expect(in->chars[0] == '\e' && in->chars[1] == '[', false)) { + // skip ANSI escape codes at the start of the string for truncation + const char* p = in->chars + 2; + while (*p && (ffCharIsDigit(*p) || *p == ';')) { + ++p; + } + if (*p && isascii(*p)) { + ++p; + } + uint32_t prefixLen = (uint32_t) (p - in->chars); + ffStrbufAppendNS(out, prefixLen, in->chars); + ffStrbufSubstrAfter(in, prefixLen - 1); + + if (trailingEscape) { + // likely have a `CSI m` reset at the end of the string + uint32_t iLastEscape = ffStrbufLastIndexC(in, '\e'); + if (iLastEscape != in->length) { + ffStrbufSetNS(trailingEscape, in->length - iLastEscape, in->chars + iLastEscape); + ffStrbufSubstrBefore(in, iLastEscape); + } + } + return true; + } + return false; +} + +static bool parseFormatString(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; + + // unmatched trailing '{' + if (i >= formatstr->length) { + ffStrbufAppendC(buffer, '{'); + break; + } + + // 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 != '~') { + ++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]); + FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate(); + skipAnsiEscape(&tempString, buffer, &trailingEscape); + + 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 (trailingEscape.length > 0) { + ffStrbufAppend(buffer, &trailingEscape); + } + + 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]); + FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate(); + skipAnsiEscape(&tempString, buffer, &trailingEscape); + + 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 == '<') { // left align + ffStrbufAppend(buffer, &tempString); + ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); + } else if (cSep == '>') { // right align + ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); + ffStrbufAppend(buffer, &tempString); + } else if (cSep == '|') { // center align + uint32_t padding = ((uint32_t) truncLength - tempString.length) / 2; + ffStrbufAppendNC(buffer, padding, ' '); + ffStrbufAppend(buffer, &tempString); + ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length - padding, ' '); + } + } + + if (trailingEscape.length > 0) { + ffStrbufAppend(buffer, &trailingEscape); + } + } + } + + if (!instance.config.display.pipe) { + ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); + } + + return true; +} + +bool ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) { +#if FF_HAVE_QUICKJS + if (ffStrbufStartsWithS(formatstr, "qjs:")) { + return parseQuickJSString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments); + } +#endif + +#if FF_HAVE_LUA + if (ffStrbufStartsWithS(formatstr, "lua:")) { + // If outputFormat starts with "lua:", treat the rest as a Lua script + return parseLuaString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments); + } +#endif + + return parseFormatString(buffer, formatstr, numArgs, arguments); +} diff --git a/src/common/impl/frequency.c b/src/common/impl/frequency.c new file mode 100644 index 0000000..4838df8 --- /dev/null +++ b/src/common/impl/frequency.c @@ -0,0 +1,26 @@ +#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; +} diff --git a/src/common/impl/init.c b/src/common/impl/init.c new file mode 100644 index 0000000..f0fc0a2 --- /dev/null +++ b/src/common/impl/init.c @@ -0,0 +1,300 @@ +#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; + + #if !FF_MODULE_DISABLE_TERMINALTHEME + { + // 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; + } + } + #endif +} + +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 = false; +static volatile bool ffHideCursor = false; +#ifdef _WIN32 +static volatile UINT oldCp = CP_UTF8; +#endif + +static void resetConsole(void) { + if (ffDisableLinewrap) { + fputs("\033[?7h", stdout); + } + + if (ffHideCursor) { + fputs("\033[?25h", stdout); + } + + if (instance.state.dynamicInterval > 0) { + fputs("\033[?1049l", stdout); // Disable alternate buffer + } + +#if defined(_WIN32) + fflush(stdout); + + if (oldCp != CP_UTF8) { + SetConsoleOutputCP(oldCp); + } +#endif +} + +#ifdef _WIN32 +BOOL WINAPI consoleHandler(FF_A_UNUSED DWORD signal) { + resetConsole(); + exit(0); +} +#else +static void exitSignalHandler(FF_A_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; + if (GetConsoleMode(hStdout, &mode)) { + SetConsoleMode(hStdout, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + oldCp = GetConsoleOutputCP(); + if (oldCp != CP_UTF8) { + SetConsoleOutputCP(CP_UTF8); + } + } +#else + if (instance.config.display.noBuffer) { + setvbuf(stdout, NULL, _IONBF, 0); + } + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = exitSignalHandler; + sigaction(SIGINT, &action, NULL); + sigaction(SIGTERM, &action, NULL); + sigaction(SIGQUIT, &action, NULL); + 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(); +} + +#if FF_HAVE_LUA + #include +#endif +#if FF_HAVE_QUICKJS + #include + #define FF_STR_INDIR(x) #x + #define FF_STR(x) FF_STR_INDIR(x) +#endif + +// 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_EET + "eet\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_VA + "va\n" +#endif +#if FF_HAVE_VDPAU + "vdpau\n" +#endif +#if FF_USE_SYSTEM_YYJSON + "System yyjson\n" +#endif +#if FF_HAVE_LINUX_VIDEODEV2 + "linux/videodev2\n" +#endif +#if FF_HAVE_EMBEDDED_PCIIDS + "Embedded pciids\n" +#endif +#if FF_ENABLE_WCWIDTH + "Embedded wcwidth\n" +#endif +#if FF_HAVE_WINRT + "WinRT headers\n" +#endif +#if FF_WIN81_COMPAT + "Windows 8.1 Compatibility\n" +#endif +#if FF_APPLE_MEMSIZE_USABLE + "Apple memsize_usable\n" +#endif +#if FF_HAVE_LUA + LUA_VERSION "\n" +#endif +#if FF_HAVE_QUICKJS + "QuickJS " FF_STR(QJS_VERSION_MAJOR) "." FF_STR(QJS_VERSION_MINOR) "." FF_STR(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX "\n" +#endif + "", + stdout); +} diff --git a/src/common/impl/io_unix.c b/src/common/impl/io_unix.c new file mode 100644 index 0000000..e603186 --- /dev/null +++ b/src/common/impl/io_unix.c @@ -0,0 +1,333 @@ +#include "common/io.h" +#include "fastfetch.h" +#include "common/strutil.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; +} + +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) { + int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC | O_DIRECTORY); // Ownership of dfd will be transformed to dir + if (dfd < 0) { + return; + } + + FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd); + if (dir == NULL) { + close(dfd); + return; // Should not happen + } + + 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); + } +} + +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; +} diff --git a/src/common/impl/io_windows.c b/src/common/impl/io_windows.c new file mode 100644 index 0000000..24b8d05 --- /dev/null +++ b/src/common/impl/io_windows.c @@ -0,0 +1,501 @@ +#include "fastfetch.h" +#include "common/io.h" +#include "common/strutil.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; +} + +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, int oflag) { + 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), !!(oflag & O_DIRECTORY)); +} + +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 = 0; + bool hasInputMode = !!GetConsoleMode(hInput, &inputMode); + if (!hasInputMode) { + hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL); + hInput = hConin; + hasInputMode = !!GetConsoleMode(hInput, &inputMode); + } + SetConsoleMode(hInput, 0); + + 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, FALSE, &(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 + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) { + va_end(args); + return "ReadFile() failed"; + } + + bytesRead += bytes; + if (__builtin_expect(bytesRead >= sizeof(buffer) - 1, false)) { + va_end(args); + return "terminal response buffer overflow"; + } + buffer[bytesRead] = '\0'; + + va_list cargs; + 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; + } + } + + if (hasInputMode) { + 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; +} diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c new file mode 100644 index 0000000..fccc56c --- /dev/null +++ b/src/common/impl/jsonconfig.c @@ -0,0 +1,365 @@ +#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/strutil.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]) { + #if !FF_MODULE_DISABLE_CPUUSAGE + case 'c': + case 'C': { + if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) { + ffPrepareCPUUsage(); + } else if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) { + FF_A_CLEANUP(ffDestroyCommandOptions) FFCommandOptions options; + ffInitCommandOptions(&options); + if (module) { + ffCommandModuleInfo.parseJsonObject(&options, module); + } + ffPrepareCommand(&options); + } + break; + } + #endif + + #if !FF_MODULE_DISABLE_DISKIO + case 'd': + case 'D': { + if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) { + FF_A_CLEANUP(ffDestroyDiskIOOptions) FFDiskIOOptions options; + ffInitDiskIOOptions(&options); + if (module) { + ffDiskIOModuleInfo.parseJsonObject(&options, module); + } + ffPrepareDiskIO(&options); + } + break; + } + #endif + + #if !FF_MODULE_DISABLE_NETIO + case 'n': + case 'N': { + if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME)) { + FF_A_CLEANUP(ffDestroyNetIOOptions) FFNetIOOptions options; + ffInitNetIOOptions(&options); + if (module) { + ffNetIOModuleInfo.parseJsonObject(&options, module); + } + ffPrepareNetIO(&options); + } + break; + } + #endif + + #if !FF_MODULE_DISABLE_PUBLICIP + case 'p': + case 'P': { + if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME)) { + FF_A_CLEANUP(ffDestroyPublicIpOptions) FFPublicIPOptions options; + ffInitPublicIpOptions(&options); + if (module) { + ffPublicIPModuleInfo.parseJsonObject(&options, module); + } + ffPreparePublicIp(&options); + } + break; + } + #endif + + #if !FF_MODULE_DISABLE_WEATHER + case 'w': + case 'W': { + if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME)) { + FF_A_CLEANUP(ffDestroyWeatherOptions) FFWeatherOptions options; + ffInitWeatherOptions(&options); + if (module) { + ffWeatherModuleInfo.parseJsonObject(&options, module); + } + ffPrepareWeather(&options); + } + break; + } + #endif + } +} + +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 'condition' 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 - 1, 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); + } + } +} diff --git a/src/common/impl/kmod_apple.c b/src/common/impl/kmod_apple.c new file mode 100644 index 0000000..82edc49 --- /dev/null +++ b/src/common/impl/kmod_apple.c @@ -0,0 +1,12 @@ +#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); +} diff --git a/src/common/impl/kmod_bsd.c b/src/common/impl/kmod_bsd.c new file mode 100644 index 0000000..996d4ab --- /dev/null +++ b/src/common/impl/kmod_bsd.c @@ -0,0 +1,7 @@ +#include "common/kmod.h" +#include +#include + +bool ffKmodLoaded(const char* modName) { + return modfind(modName) >= 0; +} diff --git a/src/common/impl/kmod_linux.c b/src/common/impl/kmod_linux.c new file mode 100644 index 0000000..204ab35 --- /dev/null +++ b/src/common/impl/kmod_linux.c @@ -0,0 +1,25 @@ +#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; +} diff --git a/src/common/impl/kmod_nbsd.c b/src/common/impl/kmod_nbsd.c new file mode 100644 index 0000000..f1b450a --- /dev/null +++ b/src/common/impl/kmod_nbsd.c @@ -0,0 +1,40 @@ +#include "common/kmod.h" +#include "common/strutil.h" + +#include +#include + +typedef struct FF_A_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; +} diff --git a/src/common/impl/kmod_nosupport.c b/src/common/impl/kmod_nosupport.c new file mode 100644 index 0000000..c254ba7 --- /dev/null +++ b/src/common/impl/kmod_nosupport.c @@ -0,0 +1,5 @@ +#include "common/kmod.h" + +bool ffKmodLoaded(FF_A_UNUSED const char* modName) { + return true; // Don't generate kernel module related errors +} diff --git a/src/common/impl/kmod_sunos.c b/src/common/impl/kmod_sunos.c new file mode 100644 index 0000000..da392e2 --- /dev/null +++ b/src/common/impl/kmod_sunos.c @@ -0,0 +1,23 @@ +#include "common/kmod.h" +#include "common/strutil.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); +} diff --git a/src/common/impl/kmod_windows.c b/src/common/impl/kmod_windows.c new file mode 100644 index 0000000..081e5a7 --- /dev/null +++ b/src/common/impl/kmod_windows.c @@ -0,0 +1,28 @@ +#include "common/kmod.h" +#include "common/windows/nt.h" +#include "common/mallocHelper.h" +#include "common/strutil.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; +} diff --git a/src/common/impl/library.c b/src/common/impl/library.c new file mode 100644 index 0000000..6f96e96 --- /dev/null +++ b/src/common/impl/library.c @@ -0,0 +1,180 @@ +#include "fastfetch.h" +#include "common/library.h" +#include "common/debug.h" + +#if _WIN32 + #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); + + #if _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) { + FF_DEBUG("dlopen(\"%s\"): %s", path, dlerror()); + } + + 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; + } else { + FF_DEBUG("dlopen(\"%s\"): %s", pathbuf.chars, dlerror()); + } + + 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_A_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 diff --git a/src/common/impl/lua.c b/src/common/impl/lua.c new file mode 100644 index 0000000..ffc1d83 --- /dev/null +++ b/src/common/impl/lua.c @@ -0,0 +1,233 @@ +#if FF_HAVE_LUA + + #include "common/lua.h" + #include "common/mallocHelper.h" + +struct FFLuaData luaData; + +static yyjson_mut_val* lua2yyjson(lua_State* L, int idx, yyjson_mut_doc* doc, int depth) { + if (__builtin_expect(depth > 15, false)) { + yyjson_mut_doc_free(doc); + lua_pushlstring( + L, "yyjson: recursion depth exceeded; possible circular reference", strlen("yyjson: recursion depth exceeded; possible circular reference")); + lua_error(L); // noreturn + __builtin_unreachable(); + } + + if (idx < 0) { + idx = lua_gettop(L) + idx + 1; + } + int type = lua_type(L, idx); + + switch (type) { + case LUA_TNIL: + return yyjson_mut_null(doc); + + case LUA_TBOOLEAN: + return yyjson_mut_bool(doc, lua_toboolean(L, idx)); + + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) { + lua_Integer i = lua_tointeger(L, idx); + return yyjson_mut_sint(doc, (int64_t) i); + } else { + return yyjson_mut_real(doc, lua_tonumber(L, idx)); + } + } + + case LUA_TSTRING: { + size_t len; + const char* str = lua_tolstring(L, idx, &len); + return yyjson_mut_strncpy(doc, str, len); + } + + case LUA_TTABLE: { + lua_Unsigned len = lua_rawlen(L, idx); + int is_array = 1; + + if (len == 0) { + lua_pushnil(L); + is_array = 0; + if (lua_next(L, idx) != 0) { + lua_pop(L, 2); + } + } else { + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + if (lua_type(L, -2) != LUA_TNUMBER) { + is_array = 0; + lua_pop(L, 2); + break; + } + int isnum = false; + lua_Integer k = lua_tointegerx(L, -2, &isnum); + if (!isnum || (lua_Unsigned) k > len) { + is_array = 0; + lua_pop(L, 2); + break; + } + lua_pop(L, 1); + } + } + + if (is_array) { + yyjson_mut_val* arr = yyjson_mut_arr(doc); + for (lua_Unsigned i = 1; i <= len; i++) { + lua_rawgeti(L, idx, (lua_Integer) i); + yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1); + yyjson_mut_arr_append(arr, val); + lua_pop(L, 1); + } + return arr; + } else { + yyjson_mut_val* obj = yyjson_mut_obj(doc); + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + size_t klen; + const char* key_str = luaL_tolstring(L, -2, &klen); + yyjson_mut_val* key = yyjson_mut_strncpy(doc, key_str, klen); + lua_pop(L, 1); + + yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1); + yyjson_mut_obj_add(obj, key, val); + lua_pop(L, 1); + } + return obj; + } + } + + default: + return yyjson_mut_null(doc); + } +} + +static int yyjsonEncode(lua_State* L) { + luaL_checkany(L, 1); + + bool pretty = false; + if (lua_isboolean(L, 2)) { + pretty = (int) lua_toboolean(L, 2); + } + + yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL); + if (__builtin_expect(!doc, false)) { + lua_pushlstring(L, "yyjson: yyjson_mut_doc_new() failed", strlen("yyjson: yyjson_mut_doc_new() failed")); + return lua_error(L); + } + + yyjson_mut_val* root = lua2yyjson(L, 1, doc, 0); + yyjson_mut_doc_set_root(doc, root); + + size_t jsonLen; + yyjson_write_err err = {}; + FF_AUTO_FREE const char* jsonStr = yyjson_mut_write_opts(doc, YYJSON_WRITE_ALLOW_INF_AND_NAN | (pretty ? YYJSON_WRITE_PRETTY_TWO_SPACES : 0), NULL, &jsonLen, &err); + + if (__builtin_expect(jsonStr != NULL, true)) { + lua_pushlstring(L, jsonStr, jsonLen); + yyjson_mut_doc_free(doc); + return 1; + } else { + yyjson_mut_doc_free(doc); + { + FF_STRBUF_AUTO_DESTROY errBuf = ffStrbufCreateStatic("yyjson: yyjson_mut_write_opts() failed: "); + ffStrbufAppendS(&errBuf, err.msg); + lua_pushlstring(L, errBuf.chars, errBuf.length); + } + + return lua_error(L); // longjmp + } +} + +const char* ffLuaLoadState(void) { + if (luaData.inited) { + if (luaData.L == NULL) { + return "Lua library is not available"; + } + return NULL; + } + + luaData.inited = true; + // clang-format off + #ifdef _WIN32 + #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \ + "lua5" #version FF_LIBRARY_EXTENSION, 0) + #else + #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \ + "liblua5." #version FF_LIBRARY_EXTENSION, 0, \ + "liblua-5." #version FF_LIBRARY_EXTENSION, 0, \ + "liblua5." #version FF_LIBRARY_EXTENSION ".5." #version, 0) + #endif + // clang-format on + #if LUA_VERSION_NUM == 505 + FF_LOAD_LIBLUA(5) + #elif LUA_VERSION_NUM == 504 + FF_LOAD_LIBLUA(4) + #elif LUA_VERSION_NUM == 503 + FF_LOAD_LIBLUA(3) + #else + #error "Unsupported Lua version" + #endif + #undef FF_LOAD_LIBLUA + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_newstate) + #if LUA_VERSION_NUM >= 505 + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_openselectedlibs) + #else + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_requiref) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_base) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_math) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_string) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_table) + #endif + + #if !FF_DISABLE_DLOPEN + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_checkany) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_loadbufferx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_tolstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_callk) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_createtable) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_error) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_gettop) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_isinteger) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_next) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pcallk) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushboolean) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushcclosure) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushinteger) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushlstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnil) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnumber) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushvalue) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawgeti) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawlen) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setfield) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setglobal) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_seti) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_settop) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_toboolean) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tointegerx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tolstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tonumberx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_type) + #endif + + lua_State* L = ffluaL_newstate(); + if (L == NULL) { + return "luaL_newstate() failed"; + } + #if LUA_VERSION_NUM >= 505 + ffluaL_openselectedlibs(L, LUA_GLIBK | LUA_MATHLIBK | LUA_STRLIBK | LUA_TABLIBK, 0); + #else + ffluaL_requiref(L, LUA_GNAME, ffluaopen_base, 1); + ffluaL_requiref(L, LUA_MATHLIBNAME, ffluaopen_math, 1); + ffluaL_requiref(L, LUA_STRLIBNAME, ffluaopen_string, 1); + ffluaL_requiref(L, LUA_TABLIBNAME, ffluaopen_table, 1); + lua_settop(L, 0); + #endif + lua_pushcfunction(L, yyjsonEncode); + lua_setglobal(L, "json_encode"); + luaData.L = L; + liblua = NULL; // don't close lua + return NULL; +} + +#endif diff --git a/src/common/impl/memrchr.c b/src/common/impl/memrchr.c new file mode 100644 index 0000000..aaf97e6 --- /dev/null +++ b/src/common/impl/memrchr.c @@ -0,0 +1,21 @@ +#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; +} diff --git a/src/common/impl/netif.c b/src/common/impl/netif.c new file mode 100644 index 0000000..1525a18 --- /dev/null +++ b/src/common/impl/netif.c @@ -0,0 +1,22 @@ +#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; +} diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c new file mode 100644 index 0000000..2d42735 --- /dev/null +++ b/src/common/impl/netif_apple.c @@ -0,0 +1,218 @@ +#include "common/netif.h" +#include "common/io.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; + } + + bool gotResponse = false; + while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0) { + if (rtmsg.hdr.rtm_seq == 1 && rtmsg.hdr.rtm_pid == (pid_t) pid) { + gotResponse = true; + break; + } + } + if (!gotResponse) { + return false; + } + +#ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason + if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY)) +#endif + { + struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(&rtmsg.hdr, RTA_IFP); + if (sdl +#ifndef __sun + && sdl->sdl_len +#endif + && sdl->sdl_family == AF_LINK) { + if (sdl->sdl_nlen > IF_NAMESIZE) { + return false; + } + memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); + result->ifName[sdl->sdl_nlen] = '\0'; + result->ifIndex = sdl->sdl_index; + + // 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; + } + + bool gotResponse = false; + while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0) { + if (rtmsg.hdr.rtm_seq == 2 && rtmsg.hdr.rtm_pid == (pid_t) pid) { + gotResponse = true; + break; + } + } + if (!gotResponse) { + return false; + } + +#ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason + if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY)) +#endif + { + struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(&rtmsg.hdr, RTA_IFP); + if (sdl +#ifndef __sun + && sdl->sdl_len +#endif + && sdl->sdl_family == AF_LINK) { + if (sdl->sdl_nlen > IF_NAMESIZE) { + return false; + } + memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); + result->ifName[sdl->sdl_nlen] = '\0'; + result->ifIndex = sdl->sdl_index; + + return true; + } + return false; + } + + return false; +} diff --git a/src/common/impl/netif_bsd.c b/src/common/impl/netif_bsd.c new file mode 100644 index 0000000..2b59c6d --- /dev/null +++ b/src/common/impl/netif_bsd.c @@ -0,0 +1,124 @@ +#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) { + if (sdl->sdl_nlen > IF_NAMESIZE) { + continue; + } + memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); + result->ifName[sdl->sdl_nlen] = '\0'; + result->ifIndex = sdl->sdl_index; + + // 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) { + if (sdl->sdl_nlen > IF_NAMESIZE) { + continue; + } + memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); + result->ifName[sdl->sdl_nlen] = '\0'; + result->ifIndex = sdl->sdl_index; + + return true; + } + } + } + + return false; +} diff --git a/src/common/impl/netif_gnu.c b/src/common/impl/netif_gnu.c new file mode 100644 index 0000000..d2feed5 --- /dev/null +++ b/src/common/impl/netif_gnu.c @@ -0,0 +1,36 @@ +#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; +} diff --git a/src/common/impl/netif_haiku.c b/src/common/impl/netif_haiku.c new file mode 100644 index 0000000..a32138c --- /dev/null +++ b/src/common/impl/netif_haiku.c @@ -0,0 +1,125 @@ +#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_INET6, 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; +} diff --git a/src/common/impl/netif_linux.c b/src/common/impl/netif_linux.c new file mode 100644 index 0000000..67e4868 --- /dev/null +++ b/src/common/impl/netif_linux.c @@ -0,0 +1,443 @@ +#include "common/netif.h" +#include "common/io.h" +#include "common/mallocHelper.h" +#include "common/debug.h" + +#include +#include +#include + +static uint32_t ffNetifGetNetlinkPortId(int sock_fd) { + struct sockaddr_nl addr = {}; + socklen_t addrLen = sizeof(addr); + if (getsockname(sock_fd, (struct sockaddr*) &addr, &addrLen) < 0) { + FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno)); + return instance.state.platform.pid; + } else { + FF_DEBUG("Netlink port ID: %u", addr.nl_pid); + return addr.nl_pid; + } +} + +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); + + // 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"); + + uint32_t pid = ffNetifGetNetlinkPortId(sock_fd); + + struct FF_A_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_A_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); + + // 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"); + + uint32_t pid = ffNetifGetNetlinkPortId(sock_fd); + + struct FF_A_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_A_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_A_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_A_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; +} diff --git a/src/common/impl/netif_windows.c b/src/common/impl/netif_windows.c new file mode 100644 index 0000000..7531ae0 --- /dev/null +++ b/src/common/impl/netif_windows.c @@ -0,0 +1,92 @@ +#include "common/netif.h" + +#include // AF_INET6, IN6_IS_ADDR_UNSPECIFIED +#include + +bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { + PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; + + if (!NETIO_SUCCESS(GetIpForwardTable2(AF_INET, &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_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; + } + } + } + } + + FreeMibTable(pIpForwardTable); + + return foundDefault; +} + +bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { + PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; + + if (!NETIO_SUCCESS(GetIpForwardTable2(AF_INET6, &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 && + 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; +} diff --git a/src/common/impl/networking_common.c b/src/common/impl/networking_common.c new file mode 100644 index 0000000..e63979b --- /dev/null +++ b/src/common/impl/networking_common.c @@ -0,0 +1,192 @@ +#include "fastfetch.h" +#include "common/library.h" +#include "common/networking.h" +#include "common/strutil.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); + } + + // Check for decompression errors before using result + if (result != Z_STREAM_END) { + FF_DEBUG("Decompression failed with zlib error: %d", result); + zlibData.ffinflateEnd(&zs); + return false; + } + + zlibData.ffinflateEnd(&zs); + + // Calculate decompressed size (from the last inflate call) + uint32_t decompressedSize = (uint32_t) (availableOut - zs.avail_out); + decompressedBuffer.length += decompressedSize; + decompressedBuffer.chars[decompressedBuffer.length] = '\0'; + FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length); + + // Modify Content-Length header and remove Content-Encoding header + // Use decompressedBuffer.length (total) not decompressedSize (last chunk only) + FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedBuffer.length + 64); + + char* line = NULL; + size_t len = 0; + 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 diff --git a/src/common/impl/networking_linux.c b/src/common/impl/networking_linux.c new file mode 100644 index 0000000..e253451 --- /dev/null +++ b/src/common/impl/networking_linux.c @@ -0,0 +1,502 @@ +#include "fastfetch.h" +#include "common/networking.h" +#include "common/time.h" +#include "common/library.h" +#include "common/strutil.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, %s)", + errno == 0 ? "succeeded" : "was in progress", + sent, + strerror(errno)); + freeaddrinfo(state->addr); + state->addr = NULL; + ffStrbufDestroy(&state->command); + return NULL; + } + + FF_DEBUG( + #ifdef __APPLE__ + "connectx()" + #else + "sendto()" + #endif + " failed: %s", + strerror(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", strerror(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", strerror(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", strerror(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_A_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", strerror(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 + + if (shutdown(state->sockfd, SHUT_WR) == -1) { + FF_DEBUG("Failed to shutdown socket send: %s", strerror(errno)); + // Not a critical error, continue anyway + } + + FF_DEBUG("Starting data reception"); + FF_A_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", strerror(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 + 15, NULL, 10); + if (contentLength > 0) { + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); + // Ensure buffer is large enough, adding header size and some margin + 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 (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + FF_DEBUG("Invalid response: %.40s...", buffer->chars); + return "Invalid response"; + } + FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length); + + if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { + FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); + return "Content length mismatch"; + } + +// If compression was used, try to decompress +#ifdef FF_HAVE_ZLIB + 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; +} diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c new file mode 100644 index 0000000..d3a7752 --- /dev/null +++ b/src/common/impl/networking_windows.c @@ -0,0 +1,376 @@ +#include +#include + +// Must be included after +#include "fastfetch.h" +#include "common/networking.h" +#include "common/strutil.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())); + } + if (CancelIoEx((HANDLE) state->sockfd, &state->overlapped)) { + WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, 10, TRUE); + } + 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 (shutdown(state->sockfd, SD_SEND) == SOCKET_ERROR) { + FF_DEBUG("Failed to shutdown socket send: %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_A_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 + 15, NULL, 10); + if (contentLength > 0) { + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); + // Ensure buffer is large enough, adding header size and some margin + 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 (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + FF_DEBUG("Invalid response: %.40s...", buffer->chars); + return "Invalid response"; + } + FF_DEBUG("Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes", + contentLength, + buffer->length); + + if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { + FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); + return "Content length mismatch"; + } + +// If compression was used, try to decompress +#ifdef FF_HAVE_ZLIB + 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; +} diff --git a/src/common/impl/option.c b/src/common/impl/option.c new file mode 100644 index 0000000..3d592d0 --- /dev/null +++ b/src/common/impl/option.c @@ -0,0 +1,185 @@ +#include "fastfetch.h" +#include "common/option.h" +#include "common/color.h" +#include "common/strutil.h" + +#include + +// Return start position of the inner key if the argument key belongs to the module specified, NULL otherwise +const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName) { + assert(argumentKey && moduleName); + + const char* subKey = argumentKey; + if (!(subKey[0] == '-' && subKey[1] == '-')) { + return NULL; + } + + 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; + unsigned long num = strtoul(value, &end, 10); + if (value[0] == '-' || *end != '\0' || num > UINT32_MAX) { + fprintf(stderr, "Error: usage: %s \n", argumentKey); + exit(479); + } + + return (uint32_t) num; +} + +int32_t ffOptionParseInt32(const char* argumentKey, const char* value) { + if (value == NULL) { + fprintf(stderr, "Error: usage: %s \n", argumentKey); + exit(480); + } + + char* end; + long num = strtol(value, &end, 10); + if (*end != '\0' || num < INT32_MIN || num > INT32_MAX) { + fprintf(stderr, "Error: usage: %s \n", argumentKey); + exit(479); + } + + return (int32_t) 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 + } +} diff --git a/src/common/impl/parsing.c b/src/common/impl/parsing.c new file mode 100644 index 0000000..e88d10c --- /dev/null +++ b/src/common/impl/parsing.c @@ -0,0 +1,136 @@ +#include "fastfetch.h" +#include "common/parsing.h" + +#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 (gtk2->length > 0 && gtk4->length > 0) { + if (ffStrbufIgnCaseEqual(gtk2, gtk4)) { + ffStrbufAppend(buffer, gtk4); + ffStrbufAppendS(buffer, " [GTK2/4]"); + } else { + ffStrbufAppend(buffer, gtk2); + ffStrbufAppendS(buffer, " [GTK2], "); + ffStrbufAppend(buffer, gtk4); + ffStrbufAppendS(buffer, " [GTK4]"); + } + } else if (gtk3->length > 0 && gtk4->length > 0) { + if (ffStrbufIgnCaseEqual(gtk3, gtk4)) { + ffStrbufAppend(buffer, gtk4); + 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 diff --git a/src/common/impl/path.c b/src/common/impl/path.c new file mode 100644 index 0000000..c97210e --- /dev/null +++ b/src/common/impl/path.c @@ -0,0 +1,277 @@ +#include "common/path.h" +#include "common/io.h" +#include "common/arrutil.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; + } + } 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; +} + +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 diff --git a/src/common/impl/percent.c b/src/common/impl/percent.c new file mode 100644 index 0000000..266588c --- /dev/null +++ b/src/common/impl/percent.c @@ -0,0 +1,293 @@ +#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/strutil.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; + + 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; + + uint8_t blocksPercent = (uint8_t) (percent / 100.0 * options->barWidth + 0.5); + assert(blocksPercent <= options->barWidth); + + bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, "auto"); + + bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed; + 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); + } + } + } + if (percent == -DBL_MAX) { + ffStrbufAppendS(buffer, "-"); + } else { + ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); + } + + if (colored && !options->pipe) { + ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); + 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); +} diff --git a/src/common/impl/printing.c b/src/common/impl/printing.c new file mode 100644 index 0000000..9f372d9 --- /dev/null +++ b/src/common/impl/printing.c @@ -0,0 +1,156 @@ +#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); + } + } +} + +bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + bool success; + if (__builtin_expect(moduleArgs != NULL, 1)) { + success = ffParseFormatString(&buffer, &moduleArgs->outputFormat, numArgs, arguments); + } else { + ffStrbufSetStatic(&buffer, "undefined format"); + success = false; + } + + if (success) { + ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType); + ffStrbufPutTo(&buffer, stdout); + } else { + ffPrintError(moduleName, moduleIndex, moduleArgs, printType, "%s", buffer.chars); + } + + return success; +} + +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); + } +} diff --git a/src/common/impl/processing_linux.c b/src/common/impl/processing_linux.c new file mode 100644 index 0000000..2b87c3b --- /dev/null +++ b/src/common/impl/processing_linux.c @@ -0,0 +1,672 @@ +#include "fastfetch.h" +#include "common/processing.h" +#include "common/io.h" +#include "common/strutil.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]); + if (ret == ENOENT) { + return "command not found"; + } + 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; +} diff --git a/src/common/impl/processing_windows.c b/src/common/impl/processing_windows.c new file mode 100644 index 0000000..846276b --- /dev/null +++ b/src/common/impl/processing_windows.c @@ -0,0 +1,281 @@ +#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; +} + +static void terminateChildProcess(HANDLE hProcess, HANDLE hChildPipeRead, HANDLE hReadEvent, IO_STATUS_BLOCK* piosb) { + IO_STATUS_BLOCK cancelIosb = {}; + if (NT_SUCCESS(NtCancelIoFileEx(hChildPipeRead, piosb, &cancelIosb))) { + if (hReadEvent) { + NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = -100000 }); // wait for cancellation to complete + } + } + NtTerminateProcess(hProcess, 1); +} + +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, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) { + case STATUS_WAIT_0: + status = iosb.Status; + break; + + case STATUS_TIMEOUT: { + terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb); + return "NtReadFile(hChildPipeRead) timed out"; + } + + default: + terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb); + return "NtWaitForSingleObject(hReadEvent) failed"; + } + } + + if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE) { + goto exit; + } + + if (!NT_SUCCESS(status)) { + terminateChildProcess(hProcess, hChildPipeRead, NULL, &iosb); + 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; +} diff --git a/src/common/impl/properties.c b/src/common/impl/properties.c new file mode 100644 index 0000000..c13ed1c --- /dev/null +++ b/src/common/impl/properties.c @@ -0,0 +1,172 @@ +#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 quoted 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; +} diff --git a/src/common/impl/settings.c b/src/common/impl/settings.c new file mode 100644 index 0000000..e7d246b --- /dev/null +++ b/src/common/impl/settings.c @@ -0,0 +1,609 @@ +#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) { + int64_t value; + if (ffDBusGetInt(&dbus, &rootIterator, &value)) { + dbus.lib->ffdbus_message_unref(reply); + return (FFvariant) { .intValue = (int32_t) value }; + } + dbus.lib->ffdbus_message_unref(reply); + 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 + } + dbus.lib->ffdbus_message_unref(reply); + 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 }; + } + } + + dbus.lib->ffdbus_message_unref(reply); + 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) { + int64_t value; + if (ffDBusGetInt(&dbus, &dictIterator, &value)) { + dbus.lib->ffdbus_message_unref(reply); + return (FFvariant) { .intValue = (int32_t) value }; + } + dbus.lib->ffdbus_message_unref(reply); + 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 + } + dbus.lib->ffdbus_message_unref(reply); + 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 }; + } + } + + dbus.lib->ffdbus_message_unref(reply); + return FF_VARIANT_NULL; + } + + dbus.lib->ffdbus_message_unref(reply); + 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 + +#ifdef FF_HAVE_EET + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wsign-conversion" + #pragma GCC diagnostic ignored "-Wfloat-conversion" + #include + #pragma GCC diagnostic pop + +typedef struct E_Font_Default { + char* text_class; + char* font; + int size; +} E_Font_Default; + +typedef struct E_Config { + char* theme_default_border_style; + char* icon_theme; + int use_e_cursor; + int cursor_size; + char* desktop_default_background; + Eina_List* font_defaults; +} E_Config; // Must be the same name as the top level struct in e.cfg + + #define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(clas, type) \ + (ffeet_eina_file_data_descriptor_class_set(clas, sizeof(*(clas)), #type, sizeof(type))) + #define FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, struct_type, member, type) \ + do { \ + struct_type ___ett; \ + ffeet_data_descriptor_element_add(edd, #member, type, EET_G_UNKNOWN, (char*) (&(___ett.member)) - (char*) (&(___ett)), 0, /* 0, */ NULL, NULL); \ + } while (0) + #define FF_EET_DATA_DESCRIPTOR_ADD_LIST(edd, struct_type, member, subtype) \ + do { \ + struct_type ___ett; \ + ffeet_data_descriptor_element_add(edd, #member, EET_T_UNKNOW, EET_G_LIST, (char*) (&(___ett.member)) - (char*) (&(___ett)), 0, /* 0, */ NULL, subtype); \ + } while (0) + +bool ffSettingsGetEnlightenmentProperty(ffEnlightenmentSettings* result) { + FF_LIBRARY_LOAD(libeet, false, "libeet" FF_LIBRARY_EXTENSION, 1); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_init, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_open, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_file_new, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_read, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_close, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_shutdown, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_free, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_eina_file_data_descriptor_class_set, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_element_add, false); + + if (ffeet_init() == 0) { + return false; + } + + FF_STRBUF_AUTO_DESTROY fileName = ffStrbufCreateCopy(&instance.state.platform.homeDir); + ffStrbufAppendS(&fileName, ".e/e/config/standard/e.cfg"); + + Eet_File* ef = ffeet_open(fileName.chars, EET_FILE_MODE_READ); + if (!ef) { + ffeet_shutdown(); + return false; + } + + Eet_Data_Descriptor_Class fontDdc; + FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&fontDdc, E_Font_Default); + Eet_Data_Descriptor* fontDdd = ffeet_data_descriptor_file_new(&fontDdc); + if (!fontDdd) { + ffeet_close(ef); + ffeet_shutdown(); + return false; + } + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, text_class, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, font, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, size, EET_T_INT); + + Eet_Data_Descriptor_Class eddc; + FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, E_Config); + Eet_Data_Descriptor* edd = ffeet_data_descriptor_file_new(&eddc); + if (!edd) { + ffeet_data_descriptor_free(fontDdd); + ffeet_close(ef); + ffeet_shutdown(); + return false; + } + + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, theme_default_border_style, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, icon_theme, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, use_e_cursor, EET_T_INT); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, cursor_size, EET_T_INT); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, desktop_default_background, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_LIST(edd, E_Config, font_defaults, fontDdd); + + E_Config* parsed = ffeet_data_read(ef, edd, "config"); + + if (parsed) { + // TODO: find a better method to get the main theme name + result->theme = parsed->theme_default_border_style; + result->icon_theme = parsed->icon_theme; + result->use_e_cursor = !!parsed->use_e_cursor; + result->cursor_size = parsed->cursor_size; + result->desktop_default_background = parsed->desktop_default_background; + + E_Font_Default* firstFont = eina_list_data_get(parsed->font_defaults); + if (firstFont) { + result->font = firstFont->font; + } + } + + ffeet_close(ef); + ffeet_data_descriptor_free(edd); + ffeet_data_descriptor_free(fontDdd); + if (!parsed) { + // We don't shutdown eet so that `result->*` are not freed + ffeet_shutdown(); + } + + return !!parsed; +} +#else +bool ffSettingsGetEnlightenmentProperty(FF_A_UNUSED ffEnlightenmentSettings* result) { + return false; +} +#endif diff --git a/src/common/impl/size.c b/src/common/impl/size.c new file mode 100644 index 0000000..4e8d1e3 --- /dev/null +++ b/src/common/impl/size.c @@ -0,0 +1,42 @@ +#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; + } +} diff --git a/src/common/impl/smbios.c b/src/common/impl/smbios.c new file mode 100644 index 0000000..028bcd9 --- /dev/null +++ b/src/common/impl/smbios.c @@ -0,0 +1,805 @@ +#include "common/smbios.h" +#include "common/io.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, "System SKU#") && + !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") && ({ + // Some SMBIOS implementations use "0x0000" to indicate an unset value, even for strings. + bool zero = ffStrbufStartsWithS(value, "0x0"); + if (zero) { + for (size_t i = 2; i < value->length; i++) { + char c = value->chars[i]; + if (c != '0') { + zero = false; + break; + } + } + } + !zero; + }); +} + +static bool smbiosTableInitialized = false; +static FFSmbiosHeaderTable smbiosTable; + +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); +} + +static bool parseSmbiosTable(const uint8_t* data, uint32_t length) { + const FFSmbiosHeader* endOfTable = NULL; + + FF_DEBUG("Parsing SMBIOS table structures with length %u bytes", length); + FF_A_UNUSED int structureCount = 0, totalCount = 0; + for ( + const FFSmbiosHeader* header = (const FFSmbiosHeader*) data; + (const uint8_t*) header + sizeof(FFSmbiosHeader) < (const uint8_t*) data + length; + header = ffSmbiosNextEntry(header)) { + ++totalCount; + endOfTable = header; + + if (header->Length < sizeof(FFSmbiosHeader)) { + FF_DEBUG("Invalid SMBIOS structure length %u at offset 0x%lx", + header->Length, + (unsigned long) ((const uint8_t*) header - data)); + break; + } + + if (header->Handle >= 0xFF00) { + FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx", + header->Handle, + (unsigned long) ((const uint8_t*) header - data)); + break; + } + + // This doesn't verify the entire structure (e.g. string section can still be truncated), + // but at least ensures the formatted section is valid and prevents infinite loops + // when the table is severely malformed. + if (__builtin_expect((const uint8_t*) header + header->Length > (const uint8_t*) data + length, false)) { + FF_DEBUG("Truncated SMBIOS structure at offset 0x%lx: length %u is too small", + (unsigned long) ((const uint8_t*) header - data), + header->Length); + break; + } + + if (header->Type < FF_SMBIOS_TYPE_END_OF_TABLE) { + if (!smbiosTable[header->Type]) { + smbiosTable[header->Type] = header; + FF_DEBUG("Found SMBIOS structure type %u, handle 0x%04X, length %u", + header->Type, + header->Handle, + header->Length); + structureCount++; + } else { + FF_DEBUG("Duplicate SMBIOS structure type %u, handle 0x%04X, length %u", + header->Type, + header->Handle, + header->Length); + } + } else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) { + FF_DEBUG("Reached SMBIOS end of type %u, handle 0x%04X, length %u", + header->Type, + header->Handle, + header->Length); + break; + } else { + FF_DEBUG("Found custom SMBIOS structure type %u, handle 0x%04X, length %u; ignoring", + header->Type, + header->Handle, + header->Length); + } + } + + if (!endOfTable) { + FF_DEBUG("No SMBIOS structures found in table"); + return false; + } + + FF_DEBUG("Parsed %d/%d SMBIOS structures, end-of-table (Type 127) %s", + structureCount, + totalCount, + endOfTable->Type == FF_SMBIOS_TYPE_END_OF_TABLE ? "found." : "not found! SMBIOS data may be malformed."); + smbiosTable[FF_SMBIOS_TYPE_END_OF_TABLE] = endOfTable; + + return true; +} + +#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" + #elif defined(__NetBSD__) + #include "common/sysctl.h" + #endif + + #ifdef __linux__ +bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer) { + // /sys/class/dmi/id/* are all pseudo-files with very small content + // so reading the whole file at once is efficient + ffStrbufEnsureFixedLengthFree(buffer, 127); + + ssize_t len = ffReadFileData(devicesPath, buffer->allocated - 1, buffer->chars); + if (len > 0) { + assert(len < buffer->allocated); + buffer->chars[len] = '\0'; + buffer->length = (uint32_t) len; + ffStrbufTrimRightSpace(buffer); + if (ffIsSmbiosValueSet(buffer)) { + return true; + } + } + + len = ffReadFileData(classPath, buffer->allocated - 1, buffer->chars); + if (len > 0) { + assert(len < buffer->allocated); + buffer->chars[len] = '\0'; + buffer->length = (uint32_t) len; + ffStrbufTrimRightSpace(buffer); + if (ffIsSmbiosValueSet(buffer)) { + return true; + } + } + + ffStrbufClear(buffer); + return false; +} + #endif + +static bool readPhysicalMemory(int fd, off_t address, size_t length, void* buffer) { + #if !defined(__FreeBSD__) // Either causes kernel panic or returns EFAULT + // -1: unknown, 0: failed before (stop trying), 1: succeeded before + static int preadState = -1; + if (preadState != 0) { + ssize_t bytesRead = pread(fd, buffer, length, address); + if (bytesRead == (ssize_t) length) { + preadState = 1; + return true; + } + + FF_DEBUG("pread failed at address 0x%lx for %zu bytes: %s. Falling back to mmap%s", + (unsigned long) address, + length, + strerror(errno), + preadState < 0 ? " and caching failure" : ""); + preadState = 0; + } else { + FF_DEBUG("Skipping pread due to cached failure; using mmap"); + } + #endif + + off_t alignedAddress = address & ~((off_t) instance.state.platform.sysinfo.pageSize - 1); + size_t pageOffset = (size_t) (address - alignedAddress); + size_t mapLength = pageOffset + length; + + void* p = mmap(NULL, mapLength, PROT_READ, MAP_SHARED, fd, alignedAddress); + if (p == MAP_FAILED) { + FF_DEBUG("mmap failed at aligned address 0x%lx for %zu bytes: %s", + (unsigned long) alignedAddress, + mapLength, + strerror(errno)); + return false; + } + + memcpy(buffer, (const uint8_t*) p + pageOffset, length); + munmap(p, mapLength); + return true; +} + +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; +} FF_A_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; +} FF_A_PACKED FFSmbios30EntryPoint; + +static_assert(offsetof(FFSmbios30EntryPoint, StructureTableAddress) == 0x10, + "FFSmbios30EntryPoint: Wrong struct alignment"); + +typedef union FFSmbiosEntryPoint { + FFSmbios20EntryPoint Smbios20; + FFSmbios30EntryPoint Smbios30; +} FFSmbiosEntryPoint; + +static bool fillTableBufferFallback(FFstrbuf* buffer) { + const char* devMem = + #if __HAIKU__ + "/dev/misc/mem"; + #else + "/dev/mem"; // kern.securelevel must be -1 + #endif + FF_DEBUG("Using physical memory searching implementation: %s", devMem); + + uint32_t tableLength = 0; + off_t tableAddress = 0; + FF_AUTO_CLOSE_FD int fd = open(devMem, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + FF_DEBUG("Failed to open memory device: %s", strerror(errno)); + return false; + } + 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 (!readPhysicalMemory(fd, 0xF0000, 0x10000, smBiosBase)) { + FF_DEBUG("Failed to read SMBIOS memory region"); + return false; + } + 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 phyaddr 0x%05lX", (unsigned long) (0xF0000 + 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 false; + } + 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 phyaddr 0x%05lX", (unsigned long) (0xF0000 + 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 false; + } + 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 false; + } + + ffStrbufClear(buffer); + ffStrbufEnsureFixedLengthFree(buffer, tableLength); + FF_DEBUG("Attempting to read SMBIOS table data: %u bytes at 0x%lx", tableLength, (unsigned long) tableAddress); + if (readPhysicalMemory(fd, (off_t) tableAddress, tableLength, buffer->chars)) { + 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"); + return false; + } + + return true; +} + + #ifdef __OpenBSD__ +static bool detectSmbiosTableLength(const uint8_t* data, uint32_t bufferLength, uint32_t* tableLength) { + const uint8_t* p = data; + const uint8_t* end = data + bufferLength; + + while (p + sizeof(FFSmbiosHeader) <= end) { + const FFSmbiosHeader* header = (const FFSmbiosHeader*) p; + if (header->Length < sizeof(*header)) { + FF_DEBUG("Invalid SMBIOS structure length %u at offset 0x%lx", + header->Length, + (unsigned long) (p - data)); + return false; + } + + if (header->Handle >= 0xFF00) { + FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx", + header->Handle, + (unsigned long) (p - data)); + return false; + } + + const uint8_t* formattedEnd = p + header->Length; + if (formattedEnd > end) { + FF_DEBUG("Truncated SMBIOS structure at offset 0x%lx: length %u is too small", + (unsigned long) (p - data), + header->Length); + return false; + } + + const char* string = (const char*) formattedEnd; + const char* stringEnd = (const char*) end; + while (true) { + size_t remaining = (size_t) (stringEnd - string); + if (remaining == 0) { + return false; + } + + const char* nul = memchr(string, '\0', remaining); + if (!nul) { + return false; + } + + string = nul + 1; + if (string >= stringEnd) { + return false; + } + + if (*string == '\0') { + ++string; + break; + } + } + + if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) { + *tableLength = (uint32_t) (string - (const char*) data); + return true; + } + + p = (const uint8_t*) string; + } + + return false; +} + +static bool fillTableBufferPlatform(FFstrbuf* buffer) { + FF_DEBUG("Using OpenBSD /var/run/dmesg.boot implementation"); + + char dmesg[2048]; + ssize_t size = ffReadFileData("/var/run/dmesg.boot", sizeof(dmesg) - 1, dmesg); + if (size <= 0) { + FF_DEBUG("Failed to read /var/run/dmesg.boot"); + return false; + } else { + FF_DEBUG("Successfully read %zd bytes from /var/run/dmesg.boot", size); + } + dmesg[size] = '\0'; + + const char* const needle = "\nbios0 at mainbus0: SMBIOS rev. "; + size_t needleLen = strlen(needle); + char* line = memmem(dmesg, (size_t) size, needle, needleLen); + if (!line) { + FF_DEBUG("Failed to find SMBIOS line in /var/run/dmesg.boot"); + return false; + } + line += needleLen; + + char* lineEnd = memchr(line, '\n', (size_t) ((dmesg + size) - line)); + if (!lineEnd) { + lineEnd = dmesg + size; + } + + char* address = memchr(line, '@', (size_t) (lineEnd - line)); + if (!address) { + FF_DEBUG("Failed to find SMBIOS table address in dmesg line"); + return false; + } + + do { + ++address; + } while (address < lineEnd && (*address == ' ' || *address == '\t')); + + errno = 0; + char* addressEnd = NULL; + unsigned long long parsedAddress = strtoull(address, &addressEnd, 16); + if (errno != 0 || addressEnd == address || parsedAddress == 0) { + FF_DEBUG("Failed to parse OpenBSD SMBIOS table address from line: %.*s", + (int) (lineEnd - line), + line); + return false; + } + + off_t tableAddress = (off_t) parsedAddress; + FF_DEBUG("Parsed OpenBSD SMBIOS table address: 0x%llx", parsedAddress); + + 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 false; + } + + uint32_t readLength = 0x10000; + ffStrbufClear(buffer); + ffStrbufEnsureFixedLengthFree(buffer, readLength); + + FF_DEBUG("Attempting to read OpenBSD SMBIOS table data: %u bytes at 0x%lx", + readLength, + (unsigned long) tableAddress); + + if (!readPhysicalMemory(fd, tableAddress, readLength, buffer->chars)) { + FF_DEBUG("Failed to read OpenBSD SMBIOS table data"); + return false; + } + + uint32_t detectedLength = 0; + if (detectSmbiosTableLength((const uint8_t*) buffer->chars, readLength, &detectedLength)) { + buffer->length = detectedLength; + buffer->chars[buffer->length] = '\0'; + FF_DEBUG("Determined OpenBSD SMBIOS table length: %u bytes", detectedLength); + return true; + } + + FF_DEBUG("SMBIOS end-of-table marker not found within first %u bytes; give up", readLength); + ffStrbufClear(buffer); + return false; +} + #else +static bool fillTableBufferPlatform(FFstrbuf* buffer) { + #if __HAIKU__ && __GNU__ + return false; + #elif defined(__linux__) + FF_DEBUG("Using Linux implementation - trying /sys/firmware/dmi/tables/DMI"); + if (ffAppendFileBuffer("/sys/firmware/dmi/tables/DMI", buffer)) { + return true; + } + + FF_DEBUG("Failed to read /sys/firmware/dmi/tables/DMI, falling back to memory-mapped implementation"); + #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 false; // non-UEFI systems + } + 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 false; + } + if (!ffParsePropLines(systab.chars, "SMBIOS3=", &strEntryAddress) && + !ffParsePropLines(systab.chars, "SMBIOS=", &strEntryAddress)) { + FF_DEBUG("Failed to find SMBIOS entry in systab"); + return false; + } + FF_DEBUG("Found SMBIOS entry in systab: %s", strEntryAddress.chars); + } + #endif + + off_t entryAddress = (off_t) strtol(strEntryAddress.chars, NULL, 16); + if (entryAddress == 0) { + FF_DEBUG("Invalid SMBIOS entry address: 0"); + return false; + } + 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 false; + } + 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 (!readPhysicalMemory(fd, entryAddress, sizeof(entryPoint), &entryPoint)) { + return false; + } + FF_DEBUG("Successfully read SMBIOS entry point data"); + #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 false; + } + 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 false; + } + 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 false; + } + 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 false; + } + FF_DEBUG("Successfully read SMBIOS entry point"); + #endif + #endif + + uint32_t tableLength = 0; + off_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 false; + } + tableLength = entryPoint.Smbios20.StructureTableLength; + tableAddress = (off_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 false; + } + tableLength = entryPoint.Smbios30.StructureTableMaximumSize; + tableAddress = (off_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 false; // Dragonfly goes here + } + + ffStrbufClear(buffer); + ffStrbufEnsureFixedLengthFree(buffer, tableLength); + FF_DEBUG("Attempting to read SMBIOS table data: %u bytes at 0x%lx", tableLength, (unsigned long) tableAddress); + if (readPhysicalMemory(fd, tableAddress, tableLength, buffer->chars)) { + buffer->length = tableLength; + buffer->chars[buffer->length] = '\0'; + FF_DEBUG("Successfully read SMBIOS table data: %u bytes", tableLength); + } else { + ffStrbufClear(buffer); + return false; + } + } + + return true; +} + #endif + +const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable() { + static FFstrbuf buffer; + if (!smbiosTableInitialized) { + smbiosTableInitialized = true; + FF_DEBUG("Initializing SMBIOS buffer"); + ffStrbufInit(&buffer); + + if (!fillTableBufferPlatform(&buffer)) { + FF_DEBUG("Platform specific SMBIOS retrieval failed, trying fallback method"); + if (!fillTableBufferFallback(&buffer)) { + FF_DEBUG("Fallback SMBIOS retrieval also failed"); + ffStrbufDestroy(&buffer); + return NULL; + } + } + + if (!parseSmbiosTable((const uint8_t*) buffer.chars, buffer.length)) { + ffStrbufClear(&buffer); + } + } + + if (buffer.length == 0) { + FF_DEBUG("No valid SMBIOS data available"); + return NULL; + } + + return &smbiosTable; +} +#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; + + if (!smbiosTableInitialized) { + smbiosTableInitialized = true; + 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); + + if (!parseSmbiosTable(rawData->SMBIOSTableData, rawData->Length)) { + free(buffer); + buffer = NULL; + return NULL; + } + } + + if (!buffer) { + FF_DEBUG("No valid SMBIOS data available"); + return NULL; + } + return &smbiosTable; +} +#elif defined(__APPLE__) + #include "common/apple/cf_helpers.h" + +const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable() { + static CFDataRef smbiosDataBuffer; + + if (!smbiosTableInitialized) { + smbiosTableInitialized = true; + FF_DEBUG("Initializing SMBIOS buffer on Apple platform"); + + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t registryEntry = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMBIOS")); + + if (!registryEntry) { + FF_DEBUG("IOServiceGetMatchingService() failed to find AppleSMBIOS"); + return NULL; + } + + FF_DEBUG("AppleSMBIOS service found, retrieving SMBIOS data"); + smbiosDataBuffer = IORegistryEntryCreateCFProperty(registryEntry, CFSTR("SMBIOS"), kCFAllocatorDefault, kNilOptions); + if (!smbiosDataBuffer) { + FF_DEBUG("IORegistryEntryCreateCFProperty() failed to get SMBIOS data"); + return NULL; + } + if (CFGetTypeID(smbiosDataBuffer) != CFDataGetTypeID()) { + FF_DEBUG("Unexpected SMBIOS data type: expected CFData"); + CFRelease(smbiosDataBuffer); + smbiosDataBuffer = NULL; + return NULL; + } + + FF_DEBUG("Successfully retrieved SMBIOS data: %lu bytes", CFDataGetLength(smbiosDataBuffer)); + if (!parseSmbiosTable((const uint8_t*) CFDataGetBytePtr(smbiosDataBuffer), (uint32_t) CFDataGetLength(smbiosDataBuffer))) { + CFRelease(smbiosDataBuffer); + smbiosDataBuffer = NULL; + return NULL; + } + } + + if (!smbiosDataBuffer) { + FF_DEBUG("No valid SMBIOS data available"); + return NULL; + } + + return &smbiosTable; +} +#endif diff --git a/src/common/impl/strutil.c b/src/common/impl/strutil.c new file mode 100644 index 0000000..3a801f7 --- /dev/null +++ b/src/common/impl/strutil.c @@ -0,0 +1,80 @@ +#include "common/strutil.h" + +uint8_t ffUtf8CharLenWidth(const char* str, uint32_t length, uint8_t* width) { + if (__builtin_expect(length == 0 || *str == '\0', false)) { + if (width) { + *width = 0; + } + return 0; + } + + unsigned char first = (unsigned char) *str; + if (__builtin_expect(first < 0x80, true)) { + if (width) { + *width = 1; + } + return 1; + } + + uint8_t bytes; + if ((first & 0xE0) == 0xC0) { + bytes = 2; + } else if ((first & 0xF0) == 0xE0) { + bytes = 3; + } else if ((first & 0xF8) == 0xF0) { + bytes = 4; + } else { + if (width) { + *width = 1; + } + return 1; + } + + if (length < bytes) { + if (width) { + *width = 1; + } + return 1; + } + + for (uint8_t i = 1; i < bytes; ++i) { + unsigned char continuation = (unsigned char) str[i]; + if (continuation == '\0' || (continuation & 0xC0) != 0x80) { + if (width) { + *width = 1; + } + return 1; + } + } + + uint32_t ucs = (uint32_t) (first & ((1U << (8 - bytes)) - 1)); + for (uint8_t i = 1; i < bytes; ++i) { + ucs <<= 6; + ucs |= (uint32_t) ((unsigned char) str[i] & 0x3F); + } + + int wcWidth = mk_wcwidth(ucs); + if (width) { + *width = (uint8_t) (wcWidth < 0 ? 0 : wcWidth); + } + return bytes; +} + +uint32_t ffUtf8StrWidth(const char* str, uint32_t length) { + uint32_t result = 0; + const char* ptr = str; + + while (length > 0 && *ptr != '\0') { + uint8_t width = 0; + uint8_t bytes = ffUtf8CharLenWidth(ptr, length, &width); + if (__builtin_expect(bytes == 0, false)) { + break; + } + + result += width; + ptr += bytes; + length -= bytes; + } + + return result > 0 ? result : (uint32_t) (ptr - str); +} diff --git a/src/common/impl/sysctl.c b/src/common/impl/sysctl.c new file mode 100644 index 0000000..5974698 --- /dev/null +++ b/src/common/impl/sysctl.c @@ -0,0 +1,93 @@ +#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 "sysctl() length query failed"; + } + + ffStrbufEnsureFree(result, (uint32_t) neededLength - 1); + + if (sysctl((int[]) { mib1, mib2 }, 2, result->chars + result->length, &neededLength, NULL, 0) != 0) { + return "sysctl() failed to retrieve string data"; + } + + 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) { + return "sysctlbyname() failed to retrieve string data"; + } + + 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 (sysctl(request, requestLength, data, resultLength, NULL, 0) != 0) { + free(data); + return NULL; + } + + return data; +} diff --git a/src/common/impl/temps.c b/src/common/impl/temps.c new file mode 100644 index 0000000..652f3fb --- /dev/null +++ b/src/common/impl/temps.c @@ -0,0 +1,165 @@ +#include "fastfetch.h" +#include "common/temps.h" +#include "common/textModifier.h" +#include "common/strutil.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) { + if (!yyjson_is_int(greenVal)) { + fputs("Error: usage: temp.green must be an integer between 0 and 100\n", stderr); + exit(480); + } + + int num = unsafe_yyjson_get_int(greenVal); + if (num < 0 || num > 100) { + fputs("Error: usage: temp.green must be between 0 and 100\n", stderr); + exit(480); + } + config->green = (uint8_t) num; + } + + yyjson_val* yellowVal = yyjson_obj_get(value, "yellow"); + if (yellowVal) { + if (!yyjson_is_int(yellowVal)) { + fputs("Error: usage: temp.yellow must be an integer between 0 and 100\n", stderr); + exit(480); + } + + int num = unsafe_yyjson_get_int(yellowVal); + if (num < 0 || num > 100) { + fputs("Error: usage: temp.yellow must be between 0 and 100\n", stderr); + exit(480); + } + 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); + } +} diff --git a/src/common/impl/time.c b/src/common/impl/time.c new file mode 100644 index 0000000..c60d1fe --- /dev/null +++ b/src/common/impl/time.c @@ -0,0 +1,97 @@ +#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; + RtlQueryPerformanceFrequency(&frequency); + ffQpcMultiplier = 1000. / (double) frequency.QuadPart; +} +#endif diff --git a/src/common/impl/wcwidth.c b/src/common/impl/wcwidth.c new file mode 100644 index 0000000..4453510 --- /dev/null +++ b/src/common/impl/wcwidth.c @@ -0,0 +1,31 @@ +#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 0; + default: + // Use the width widechar_width gave us. + return width; + } +} diff --git a/src/common/init.h b/src/common/init.h new file mode 100644 index 0000000..c2ea4c0 --- /dev/null +++ b/src/common/init.h @@ -0,0 +1,7 @@ +#pragma once + +void ffInitInstance(void); +void ffStart(void); +void ffFinish(void); +void ffDestroyInstance(void); +void ffListFeatures(void); diff --git a/src/common/io.h b/src/common/io.h new file mode 100644 index 0000000..f7aec0b --- /dev/null +++ b/src/common/io.h @@ -0,0 +1,300 @@ +#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 + #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 + +#ifdef _WIN32 + #ifndef O_CLOEXEC + #define O_CLOEXEC 0 + #endif + #ifndef O_RDONLY + #define O_RDONLY 0 + #endif + #ifndef O_DIRECTORY + #define O_DIRECTORY 0200000 + #endif + +// Only O_RDONLY is supported +HANDLE openat(HANDLE dfd, const char* fileName, int oflag); +HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory); +#endif + +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_A_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 FF_A_CLEANUP(wrapClose) + +static inline FFNativeFD FFUnixFD2NativeFD(int unixfd) { +#ifndef _WIN32 + return unixfd; +#else + return (FFNativeFD) _get_osfhandle(unixfd); +#endif +} + +FF_A_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_A_NONNULL(2) static inline bool ffWriteFDBuffer(FFNativeFD fd, const FFstrbuf* content) { + return ffWriteFDData(fd, content->length, content->chars); +} + +FF_A_NONNULL(1, 3) bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data); + +FF_A_NONNULL(1, 2) static inline bool ffWriteFileBuffer(const char* fileName, const FFstrbuf* buffer) { + return ffWriteFileData(fileName, buffer->length, buffer->chars); +} + +FF_A_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_A_NONNULL(2) bool ffAppendFDBuffer(FFNativeFD fd, FFstrbuf* buffer); + +FF_A_NONNULL(1, 3) static inline ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data) { + FFNativeFD FF_AUTO_CLOSE_FD fd = +#ifndef _WIN32 + open(fileName, O_RDONLY | O_CLOEXEC); +#else + CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (!ffIsValidNativeFD(fd)) { + return -1; + } + + return ffReadFDData(fd, dataSize, data); +} + +FF_A_NONNULL(2, 4) static inline ssize_t ffReadFileDataRelative(FFNativeFD dfd, const char* fileName, size_t dataSize, void* data) { + FFNativeFD FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); + if (!ffIsValidNativeFD(fd)) { + return -1; + } + + return ffReadFDData(fd, dataSize, data); +} + +FF_A_NONNULL(1, 2) static inline bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) { + FFNativeFD FF_AUTO_CLOSE_FD fd = +#ifndef _WIN32 + open(fileName, O_RDONLY | O_CLOEXEC); +#else + CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (!ffIsValidNativeFD(fd)) { + return false; + } + + return ffAppendFDBuffer(fd, buffer); +} + +FF_A_NONNULL(2, 3) static inline bool ffAppendFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) { + FFNativeFD FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); + if (!ffIsValidNativeFD(fd)) { + return false; + } + + return ffAppendFDBuffer(fd, buffer); +} + +FF_A_NONNULL(2) static inline bool ffReadFDBuffer(FFNativeFD fd, FFstrbuf* buffer) { + ffStrbufClear(buffer); + return ffAppendFDBuffer(fd, buffer); +} + +FF_A_NONNULL(1, 2) static inline bool ffReadFileBuffer(const char* fileName, FFstrbuf* buffer) { + ffStrbufClear(buffer); + return ffAppendFileBuffer(fileName, buffer); +} + +FF_A_NONNULL(2, 3) static inline bool ffReadFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) { + ffStrbufClear(buffer); + return ffAppendFileBufferRelative(dfd, fileName, buffer); +} + +typedef enum FF_A_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_A_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_A_NONNULL(1, 2) bool ffPathExpandEnv(const char* in, FFstrbuf* out); + +#define FF_IO_TERM_RESP_WAIT_MS 100 // #554 + +FF_A_SCANF(3, 4) +FF_A_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 FF_A_CLEANUP(ffUnsuppressIO) FF_A_UNUSED io_suppressed__ = ffSuppressIO(true) + +void ffListFilesRecursively(const char* path, bool pretty); + +FF_A_NONNULL(1) static inline bool wrapFclose(FILE** pfile) { + assert(pfile); + if (!*pfile) { + return false; + } + fclose(*pfile); + return true; +} +#define FF_AUTO_CLOSE_FILE FF_A_CLEANUP(wrapFclose) + +FF_A_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 FF_A_CLEANUP(wrapClosedir) + +FF_A_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); diff --git a/src/common/jsonconfig.h b/src/common/jsonconfig.h new file mode 100644 index 0000000..a78aafd --- /dev/null +++ b/src/common/jsonconfig.h @@ -0,0 +1,27 @@ +#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); diff --git a/src/common/kmod.h b/src/common/kmod.h new file mode 100644 index 0000000..013340b --- /dev/null +++ b/src/common/kmod.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +bool ffKmodLoaded(const char* modName); diff --git a/src/common/library.h b/src/common/library.h new file mode 100644 index 0000000..97500ac --- /dev/null +++ b/src/common/library.h @@ -0,0 +1,109 @@ +#pragma once + +#include "fastfetch.h" + +#ifndef FF_DISABLE_DLOPEN + + #if defined(_WIN32) + #define FF_DLOPEN_FLAGS 0 +FF_A_NODISCARD void* dlopen(const char* path, int mode); +FF_A_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* FF_A_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_A_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_A_UNUSED __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff##symbolName, symbolName, returnValue); + + #define FF_LIBRARY_LOAD_SYMBOL_LAZY(library, symbolName) \ + FF_A_UNUSED __auto_type ff##symbolName = (__typeof__(&symbolName)) &symbolName; + + #define FF_LIBRARY_LOAD_SYMBOL_MESSAGE(library, symbolName) \ + FF_A_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 diff --git a/src/common/lua.h b/src/common/lua.h new file mode 100644 index 0000000..06343c4 --- /dev/null +++ b/src/common/lua.h @@ -0,0 +1,187 @@ +#pragma once + +#include "fastfetch.h" + +#if FF_HAVE_LUA + #if !FF_DISABLE_DLOPEN + // Hack. LUA_API is defined as extern which prevents us from implementing the functions ourselves. + #include + #undef LUA_API + #undef LUALIB_API + #undef LUAMOD_API + #define LUA_API static inline + #define LUALIB_API LUA_API + #define LUAMOD_API LUA_API + + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + + #include + #include + #include + + #ifndef LUA_GNAME + #define LUA_GNAME "_G" + #endif + + #include "common/library.h" + +extern struct FFLuaData { + #if !FF_DISABLE_DLOPEN + FF_LIBRARY_SYMBOL(luaL_checkany) + FF_LIBRARY_SYMBOL(luaL_loadbufferx) + FF_LIBRARY_SYMBOL(luaL_tolstring) + FF_LIBRARY_SYMBOL(lua_callk) + FF_LIBRARY_SYMBOL(lua_createtable) + FF_LIBRARY_SYMBOL(lua_error) + FF_LIBRARY_SYMBOL(lua_gettop) + FF_LIBRARY_SYMBOL(lua_isinteger) + FF_LIBRARY_SYMBOL(lua_next) + FF_LIBRARY_SYMBOL(lua_pcallk) + FF_LIBRARY_SYMBOL(lua_pushboolean) + FF_LIBRARY_SYMBOL(lua_pushcclosure) + FF_LIBRARY_SYMBOL(lua_pushinteger) + FF_LIBRARY_SYMBOL(lua_pushlstring) + FF_LIBRARY_SYMBOL(lua_pushnil) + FF_LIBRARY_SYMBOL(lua_pushnumber) + FF_LIBRARY_SYMBOL(lua_pushvalue) + FF_LIBRARY_SYMBOL(lua_rawgeti) + FF_LIBRARY_SYMBOL(lua_rawlen) + FF_LIBRARY_SYMBOL(lua_setfield) + FF_LIBRARY_SYMBOL(lua_setglobal) + FF_LIBRARY_SYMBOL(lua_seti) + FF_LIBRARY_SYMBOL(lua_settop) + FF_LIBRARY_SYMBOL(lua_toboolean) + FF_LIBRARY_SYMBOL(lua_tointegerx) + FF_LIBRARY_SYMBOL(lua_tolstring) + FF_LIBRARY_SYMBOL(lua_tonumberx) + FF_LIBRARY_SYMBOL(lua_type) + #endif + + lua_State* L; + bool inited; +} luaData; + + #if !FF_DISABLE_DLOPEN +FF_A_ALWAYS_INLINE void(lua_settop)(lua_State* L, int idx) { + return luaData.fflua_settop(L, idx); +} + +FF_A_ALWAYS_INLINE int(luaL_loadbufferx)(lua_State* L, const char* buff, size_t sz, const char* name, const char* mode) { + return luaData.ffluaL_loadbufferx(L, buff, sz, name, mode); +} + +FF_A_ALWAYS_INLINE const char*(lua_tolstring) (lua_State * L, int idx, size_t* len) { + return luaData.fflua_tolstring(L, idx, len); +} + +FF_A_ALWAYS_INLINE void(lua_createtable)(lua_State* L, int narr, int nrec) { + return luaData.fflua_createtable(L, narr, nrec); +} + +FF_A_ALWAYS_INLINE void(lua_pushinteger)(lua_State* L, lua_Integer n) { + return luaData.fflua_pushinteger(L, n); +} + +FF_A_ALWAYS_INLINE void(lua_pushnumber)(lua_State* L, lua_Number n) { + return luaData.fflua_pushnumber(L, n); +} + +FF_A_ALWAYS_INLINE void(lua_pushboolean)(lua_State* L, int b) { + return luaData.fflua_pushboolean(L, b); +} + +FF_A_ALWAYS_INLINE const char*(lua_pushlstring) (lua_State * L, const char* s, size_t len) { + return luaData.fflua_pushlstring(L, s, len); +} + +FF_A_ALWAYS_INLINE void(lua_pushvalue)(lua_State* L, int idx) { + return luaData.fflua_pushvalue(L, idx); +} + +FF_A_ALWAYS_INLINE void(lua_seti)(lua_State* L, int idx, lua_Integer n) { + return luaData.fflua_seti(L, idx, n); +} + +FF_A_ALWAYS_INLINE void(lua_pushnil)(lua_State* L) { + return luaData.fflua_pushnil(L); +} + +FF_A_ALWAYS_INLINE void(lua_setfield)(lua_State* L, int idx, const char* k) { + return luaData.fflua_setfield(L, idx, k); +} + +FF_A_ALWAYS_INLINE int(lua_pcallk)(lua_State* L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k) { + return luaData.fflua_pcallk(L, nargs, nresults, errfunc, ctx, k); +} + +FF_A_ALWAYS_INLINE int(lua_gettop)(lua_State* L) { + return luaData.fflua_gettop(L); +} + +FF_A_ALWAYS_INLINE const char*(luaL_tolstring) (lua_State * L, int idx, size_t* len) { + return luaData.ffluaL_tolstring(L, idx, len); +} + +FF_A_ALWAYS_INLINE int(lua_error)(lua_State* L) { + return luaData.fflua_error(L); +} + +FF_A_ALWAYS_INLINE void(lua_pushcclosure)(lua_State* L, lua_CFunction fn, int n) { + return luaData.fflua_pushcclosure(L, fn, n); +} + +FF_A_ALWAYS_INLINE void(luaL_checkany)(lua_State* L, int idx) { + return luaData.ffluaL_checkany(L, idx); +} + +FF_A_ALWAYS_INLINE void(lua_callk)(lua_State* L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k) { + return luaData.fflua_callk(L, nargs, nresults, ctx, k); +} + +FF_A_ALWAYS_INLINE int(lua_isinteger)(lua_State* L, int idx) { + return luaData.fflua_isinteger(L, idx); +} + +FF_A_ALWAYS_INLINE int(lua_next)(lua_State* L, int idx) { + return luaData.fflua_next(L, idx); +} + +FF_A_ALWAYS_INLINE int(lua_rawgeti)(lua_State* L, int idx, lua_Integer n) { + return luaData.fflua_rawgeti(L, idx, n); +} + +FF_A_ALWAYS_INLINE + #if LUA_VERSION_NUM > 503 +lua_Unsigned + #else +size_t + #endif + (lua_rawlen)(lua_State* L, int idx) { + return luaData.fflua_rawlen(L, idx); +} + +FF_A_ALWAYS_INLINE void(lua_setglobal)(lua_State* L, const char* name) { + return luaData.fflua_setglobal(L, name); +} + +FF_A_ALWAYS_INLINE int(lua_toboolean)(lua_State* L, int idx) { + return luaData.fflua_toboolean(L, idx); +} + +FF_A_ALWAYS_INLINE lua_Integer(lua_tointegerx)(lua_State* L, int idx, int* isnum) { + return luaData.fflua_tointegerx(L, idx, isnum); +} + +FF_A_ALWAYS_INLINE lua_Number(lua_tonumberx)(lua_State* L, int idx, int* isnum) { + return luaData.fflua_tonumberx(L, idx, isnum); +} + +FF_A_ALWAYS_INLINE int(lua_type)(lua_State* L, int idx) { + return luaData.fflua_type(L, idx); +} + #endif + +const char* ffLuaLoadState(void); + +#endif diff --git a/src/common/mallocHelper.h b/src/common/mallocHelper.h new file mode 100644 index 0000000..5b24df4 --- /dev/null +++ b/src/common/mallocHelper.h @@ -0,0 +1,38 @@ +#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 FF_A_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 +} diff --git a/src/common/memrchr.h b/src/common/memrchr.h new file mode 100644 index 0000000..6905a33 --- /dev/null +++ b/src/common/memrchr.h @@ -0,0 +1,15 @@ +#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 diff --git a/src/common/netif.h b/src/common/netif.h new file mode 100644 index 0000000..d3984df --- /dev/null +++ b/src/common/netif.h @@ -0,0 +1,30 @@ +#pragma once + +#include "fastfetch.h" + +#ifndef _WIN32 + #include + #include +#endif + +typedef enum FF_A_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); diff --git a/src/common/networking.h b/src/common/networking.h new file mode 100644 index 0000000..2554e22 --- /dev/null +++ b/src/common/networking.h @@ -0,0 +1,38 @@ +#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 diff --git a/src/common/option.h b/src/common/option.h new file mode 100644 index 0000000..ce0a0ab --- /dev/null +++ b/src/common/option.h @@ -0,0 +1,96 @@ +#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 FF_A_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_A_NODISCARD uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value); +FF_A_NODISCARD int32_t ffOptionParseInt32(const char* argumentKey, const char* value); +FF_A_NODISCARD int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]); +FF_A_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 diff --git a/src/common/parsing.h b/src/common/parsing.h new file mode 100644 index 0000000..c3a4bee --- /dev/null +++ b/src/common/parsing.h @@ -0,0 +1,24 @@ +#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); diff --git a/src/common/path.h b/src/common/path.h new file mode 100644 index 0000000..705b1c0 --- /dev/null +++ b/src/common/path.h @@ -0,0 +1,21 @@ +#pragma once + +#include "common/FFstrbuf.h" +#include "common/strutil.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 diff --git a/src/common/percent.h b/src/common/percent.h new file mode 100644 index 0000000..c2eeba6 --- /dev/null +++ b/src/common/percent.h @@ -0,0 +1,43 @@ +#pragma once + +#include "common/FFstrbuf.h" +#include "common/parsing.h" +#include "common/option.h" + +typedef enum FF_A_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); diff --git a/src/common/printing.h b/src/common/printing.h new file mode 100644 index 0000000..f20a244 --- /dev/null +++ b/src/common/printing.h @@ -0,0 +1,21 @@ +#pragma once + +#include "fastfetch.h" +#include "common/format.h" + +typedef enum FF_A_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); +bool 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_A_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); diff --git a/src/common/processing.h b/src/common/processing.h new file mode 100644 index 0000000..51105c8 --- /dev/null +++ b/src/common/processing.h @@ -0,0 +1,55 @@ +#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 diff --git a/src/common/properties.h b/src/common/properties.h new file mode 100644 index 0000000..789a757 --- /dev/null +++ b/src/common/properties.h @@ -0,0 +1,47 @@ +#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 } }); +} diff --git a/src/common/settings.h b/src/common/settings.h new file mode 100644 index 0000000..f2143cf --- /dev/null +++ b/src/common/settings.h @@ -0,0 +1,47 @@ +#pragma once + +#include "fastfetch.h" + +typedef enum FF_A_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); + +typedef struct { + char* theme; + char* icon_theme; + bool use_e_cursor; + int cursor_size; + char* desktop_default_background; + char* font; +} ffEnlightenmentSettings; +bool ffSettingsGetEnlightenmentProperty(ffEnlightenmentSettings* result); + +#ifdef __ANDROID__ +bool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result); +#elif defined(__FreeBSD__) +bool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result); +#endif diff --git a/src/common/size.h b/src/common/size.h new file mode 100644 index 0000000..7f1da02 --- /dev/null +++ b/src/common/size.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result); diff --git a/src/common/smbios.h b/src/common/smbios.h new file mode 100644 index 0000000..24edbda --- /dev/null +++ b/src/common/smbios.h @@ -0,0 +1,103 @@ +#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.9.0.pdf + +typedef enum FF_A_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, + FF_SMBIOS_TYPE__MAX, + // system- and OEM-specific information 128~256 +} FFSmbiosType; +static_assert(sizeof(FFSmbiosType) == 1, "FFSmbiosType should be 1 byte"); + +typedef struct FFSmbiosHeader { + // Type of SMBIOS structure. Do NOT test `Type == FF_SMBIOS_END_OF_TABLE` to determine the end of the table, + // as malformed tables may be missing the end-of-table marker. + // **Use FFSmbiosHeaderTable[FF_SMBIOS_TYPE_END_OF_TABLE] pointer instead.** + FFSmbiosType Type; + // Length of formatted section, excluding unformatted string section + // Must be at least 4 (sizeof(FFSmbiosHeader)) to be valid + uint8_t Length; + // Unique handle, used to reference this structure from other structures. + // Not guaranteed to be consistent across reboots or even multiple reads of the same table. + // Must be less than 0xFF00 + uint16_t Handle; +} FF_A_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__MAX]; + +const FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header); +const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable(void); + +#ifdef __linux__ +bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer); +#endif diff --git a/src/common/strutil.h b/src/common/strutil.h new file mode 100644 index 0000000..00a8a08 --- /dev/null +++ b/src/common/strutil.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/wcwidth.h" + +#ifdef _WIN32 +// #include +__stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch); + #define strcasestr StrStrIA +#endif + +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'; +} + +// Parse one UTF-8 character, returning consumed byte count and display width. +// Invalid / incomplete sequence falls back to one-byte width=1. +// If the Unicode codepoint is non-printable, width becomes 0. +uint8_t ffUtf8CharLenWidth(const char* str, uint32_t length, uint8_t* width); + +uint32_t ffUtf8StrWidth(const char* str, uint32_t length); + +static inline bool ffCharIsHexDigit(char c) { + return ffCharIsDigit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); +} + +static inline int8_t ffHexCharToInt(char c) { + if (ffCharIsDigit(c)) { + return (int8_t) (c - '0'); + } else if ('a' <= c && c <= 'f') { + return (int8_t) (c - 'a' + 10); + } else if ('A' <= c && c <= 'F') { + return (int8_t) (c - 'A' + 10); + } else { + return -1; + } +} + +// 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; +} diff --git a/src/common/sysctl.h b/src/common/sysctl.h new file mode 100644 index 0000000..1baabe6 --- /dev/null +++ b/src/common/sysctl.h @@ -0,0 +1,17 @@ +#pragma once + +#include "fastfetch.h" + +#include +#include + +#ifdef __OpenBSD__ +const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result); +FF_A_NODISCARD int ffSysctlGetInt(int mib1, int mib2, int defaultValue); +FF_A_NODISCARD int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue); +#else +const char* ffSysctlGetString(const char* propName, FFstrbuf* result); +FF_A_NODISCARD int ffSysctlGetInt(const char* propName, int defaultValue); +FF_A_NODISCARD int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue); +#endif +FF_A_NODISCARD void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength); diff --git a/src/common/temps.h b/src/common/temps.h new file mode 100644 index 0000000..f2f8eda --- /dev/null +++ b/src/common/temps.h @@ -0,0 +1,9 @@ +#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); diff --git a/src/common/textModifier.h b/src/common/textModifier.h new file mode 100644 index 0000000..77d6e55 --- /dev/null +++ b/src/common/textModifier.h @@ -0,0 +1,5 @@ +#pragma once + +#define FASTFETCH_TEXT_MODIFIER_BOLT "\033[1m" +#define FASTFETCH_TEXT_MODIFIER_ERROR "\033[1;31m" +#define FASTFETCH_TEXT_MODIFIER_RESET "\033[m" diff --git a/src/common/thread.h b/src/common/thread.h new file mode 100644 index 0000000..64d8478 --- /dev/null +++ b/src/common/thread.h @@ -0,0 +1,115 @@ +#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, FALSE, 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_A_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(FF_A_UNUSED FFThreadMutex* mutex) {} +static inline void ffThreadMutexUnlock(FF_A_UNUSED FFThreadMutex* mutex) {} + #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) +#endif // FF_HAVE_THREADS diff --git a/src/common/time.h b/src/common/time.h new file mode 100644 index 0000000..e2d86ab --- /dev/null +++ b/src/common/time.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#ifdef _WIN32 + #include + #include "common/windows/nt.h" + #include +#elif defined(__HAIKU__) + #include +#endif + +#include "common/arrutil.h" + +static inline double ffTimeGetTick(void) // In msec +{ +#ifdef _WIN32 + extern double ffQpcMultiplier; + LARGE_INTEGER start; + RtlQueryPerformanceCounter(&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); diff --git a/src/common/unused.h b/src/common/unused.h new file mode 100644 index 0000000..98e21a2 --- /dev/null +++ b/src/common/unused.h @@ -0,0 +1,6 @@ +#pragma once + +static inline void ffUnused(int dummy, ...) { + (void) dummy; +} +#define FF_UNUSED(...) ffUnused(0, __VA_ARGS__); diff --git a/src/common/wcwidth.h b/src/common/wcwidth.h new file mode 100644 index 0000000..8d093da --- /dev/null +++ b/src/common/wcwidth.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#if FF_ENABLE_WCWIDTH +int mk_wcwidth(uint32_t wc); +#else +static inline int mk_wcwidth(uint32_t wc) { + (void) wc; + return 1; +} +#endif diff --git a/src/common/windows/c-logo.sh b/src/common/windows/c-logo.sh new file mode 100755 index 0000000..e4d7863 --- /dev/null +++ b/src/common/windows/c-logo.sh @@ -0,0 +1,10 @@ +#!/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 diff --git a/src/common/windows/com.c b/src/common/windows/com.c new file mode 100644 index 0000000..a400ca9 --- /dev/null +++ b/src/common/windows/com.c @@ -0,0 +1,67 @@ +#include "com.h" + +#include + +#if FF_HAVE_WINRT + #include + +static void RoUninitializeWrap(void) { + RoUninitialize(); +} + +static const char* doInitCom() { + HRESULT res = RoInitialize(RO_INIT_MULTITHREADED); + if (FAILED(res)) { + switch (res) { + case E_INVALIDARG: + return "RoInitialize() failed: invalid argument"; + case E_OUTOFMEMORY: + return "RoInitialize() failed: out of memory"; + case E_UNEXPECTED: + return "RoInitialize() failed: unexpected error"; + case RPC_E_CHANGED_MODE: + // COM was already initialized with a different concurrency model + return NULL; + default: + return "RoInitialize() failed: unknown error"; + } + } + + atexit(RoUninitializeWrap); + return NULL; +} +#else + #include + +static void CoUninitializeWrap(void) { + CoUninitialize(); +} + +static const char* doInitCom() { + HRESULT res = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(res)) { + switch (res) { + case E_INVALIDARG: + return "CoInitializeEx() failed: invalid argument"; + case E_OUTOFMEMORY: + return "CoInitializeEx() failed: out of memory"; + case RPC_E_CHANGED_MODE: + // COM was already initialized with a different concurrency model + return NULL; + default: + return "CoInitializeEx() failed: unknown error"; + } + } + + atexit(CoUninitializeWrap); + return NULL; +} +#endif + +const char* ffInitCom(void) { + static const char* error = ""; + if (error && error[0] == '\0') { + error = doInitCom(); + } + return error; +} diff --git a/src/common/windows/com.h b/src/common/windows/com.h new file mode 100644 index 0000000..57b8c6d --- /dev/null +++ b/src/common/windows/com.h @@ -0,0 +1,23 @@ +#pragma once + +#include "common/attributes.h" +#include +#include + +// Initialize COM & WinRT +const char* ffInitCom(void); + +static inline void ffReleaseComObject(void* ppUnknown) { + assert(ppUnknown); + IUnknown* pUnknown = *(IUnknown**) ppUnknown; + if (pUnknown) { +#ifdef __cplusplus + pUnknown->Release(); +#else + pUnknown->lpVtbl->Release(pUnknown); +#endif + *(IUnknown**) ppUnknown = NULL; + } +} + +#define FF_AUTO_RELEASE_COM_OBJECT FF_A_CLEANUP(ffReleaseComObject) diff --git a/src/common/windows/getline.c b/src/common/windows/getline.c new file mode 100644 index 0000000..3d43d55 --- /dev/null +++ b/src/common/windows/getline.c @@ -0,0 +1,58 @@ +#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; +} diff --git a/src/common/windows/getline.h b/src/common/windows/getline.h new file mode 100644 index 0000000..49ba5db --- /dev/null +++ b/src/common/windows/getline.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +ssize_t getline(char** lineptr, size_t* n, FILE* stream); diff --git a/src/common/windows/logo.ico b/src/common/windows/logo.ico new file mode 100644 index 0000000..cdd89ad Binary files /dev/null and b/src/common/windows/logo.ico differ diff --git a/src/common/windows/logo.svg b/src/common/windows/logo.svg new file mode 120000 index 0000000..068d3f6 --- /dev/null +++ b/src/common/windows/logo.svg @@ -0,0 +1 @@ +../../logo/logo.svg \ No newline at end of file diff --git a/src/common/windows/manifest.xml b/src/common/windows/manifest.xml new file mode 100644 index 0000000..2113c07 --- /dev/null +++ b/src/common/windows/manifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + UTF-8 + true/pm + PerMonitor + SegmentHeap + + + diff --git a/src/common/windows/nt.h b/src/common/windows/nt.h new file mode 100644 index 0000000..2f476b8 --- /dev/null +++ b/src/common/windows/nt.h @@ -0,0 +1,1325 @@ +#pragma once + +#include +#include +#include +#include +#include + +enum { + SystemModuleInformation = 11, + SystemFirmwareTableInformation = 76, + SystemBootEnvironmentInformation = 90, + SystemLogicalProcessorAndGroupInformation = 107, + SystemSecureBootInformation = 146, +}; + +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); + +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); +NTSTATUS NTAPI RtlStringFromGUIDEx(IN GUID* Guid, OUT PCUNICODE_STRING GuidString, _In_ BOOLEAN AllocateGuidString); + +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; + +#ifdef __aarch64__ + #define SharedUserData ({ \ + __auto_type shared_user_data = (const volatile KUSER_SHARED_DATA*) (uintptr_t) 0x7FFE0000UL; \ + __asm__("" : "+r"(shared_user_data)); /* https://github.com/lhmouse/mcfgthread/issues/330 */ \ + shared_user_data; \ + }) +#else + #define SharedUserData ((const volatile KUSER_SHARED_DATA*) (uintptr_t) 0x7FFE0000UL) +#endif + +static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime) { +#if _WIN64 + + // This is safe even if pTime is not 8-byte aligned + // See https://learn.microsoft.com/en-us/windows/win32/winprog64/fault-alignments + return *(const volatile uint64_t*) pTime; + +#else + + uint32_t low, high1, high2; + + do { + high1 = (uint32_t) pTime->High1Time; + low = (uint32_t) pTime->LowPart; + high2 = (uint32_t) 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 SharedUserData->NtMajorVersion > 10 || + (SharedUserData->NtMajorVersion == 10 && 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 KERNEL_CALLBACK_TABLE* PKERNEL_CALLBACK_TABLE; +typedef struct API_SET_NAMESPACE* PAPI_SET_NAMESPACE; +typedef struct RTL_BITMAP* PRTL_BITMAP; +typedef struct SILO_USER_SHARED_DATA* PSILO_USER_SHARED_DATA; +typedef struct CPTABLEINFO* PCPTABLEINFO; +typedef struct NLSTABLEINFO* PNLSTABLEINFO; +typedef struct GDI_HANDLE_ENTRY* PGDI_HANDLE_ENTRY; + +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; + + // + // Pointer to a critical section used to synchronize access to the PEB. + // + PRTL_CRITICAL_SECTION FastPebLock; + + // + // Pointer to a singly linked list used by ATL. + // + PSLIST_HEADER AtlThunkSListPtr; + + // + // Handle to the Image File Execution Options key. + // + HANDLE IFEOKey; + + // + // Cross process flags. + // + union { + ULONG CrossProcessFlags; + struct + { + ULONG ProcessInJob : 1; // The process is part of a job. + ULONG ProcessInitializing : 1; // The process is initializing. + ULONG ProcessUsingVEH : 1; // The process is using VEH. + ULONG ProcessUsingVCH : 1; // The process is using VCH. + ULONG ProcessUsingFTH : 1; // The process is using FTH. + ULONG ProcessPreviouslyThrottled : 1; // The process was previously throttled. + ULONG ProcessCurrentlyThrottled : 1; // The process is currently throttled. + ULONG ProcessImagesHotPatched : 1; // The process images are hot patched. // RS5 + ULONG ReservedBits0 : 24; + }; + }; + + // + // User32 KERNEL_CALLBACK_TABLE (ntuser.h) + // + union { + PKERNEL_CALLBACK_TABLE KernelCallbackTable; + PVOID UserSharedInfoPtr; + }; + + // + // Reserved. + // + ULONG SystemReserved; + + // + // Pointer to the Active Template Library (ATL) singly linked list (32-bit) + // + ULONG AtlThunkSListPtr32; + + // + // Pointer to the API Set Schema. + // + PAPI_SET_NAMESPACE ApiSetMap; + + // + // Counter for TLS expansion. + // + ULONG TlsExpansionCounter; + + // + // Pointer to the TLS bitmap. + // + PRTL_BITMAP TlsBitmap; + + // + // Bits for the TLS bitmap. + // + ULONG TlsBitmapBits[2]; + + // + // Reserved for CSRSS. + // + PVOID ReadOnlySharedMemoryBase; + + // + // Pointer to the USER_SHARED_DATA for the current SILO. + // + PSILO_USER_SHARED_DATA SharedData; + + // + // Reserved for CSRSS. + // + PVOID* ReadOnlyStaticServerData; + + // + // Pointer to the ANSI code page data. + // + PCPTABLEINFO AnsiCodePageData; + + // + // Pointer to the OEM code page data. + // + PCPTABLEINFO OemCodePageData; + + // + // Pointer to the Unicode case table data. + // + PNLSTABLEINFO UnicodeCaseTableData; + + // + // The total number of system processors. + // + ULONG NumberOfProcessors; + + // + // Global flags for the system. + // + union { + ULONG NtGlobalFlag; + struct + { + ULONG StopOnException : 1; // FLG_STOP_ON_EXCEPTION + ULONG ShowLoaderSnaps : 1; // FLG_SHOW_LDR_SNAPS + ULONG DebugInitialCommand : 1; // FLG_DEBUG_INITIAL_COMMAND + ULONG StopOnHungGUI : 1; // FLG_STOP_ON_HUNG_GUI + ULONG HeapEnableTailCheck : 1; // FLG_HEAP_ENABLE_TAIL_CHECK + ULONG HeapEnableFreeCheck : 1; // FLG_HEAP_ENABLE_FREE_CHECK + ULONG HeapValidateParameters : 1; // FLG_HEAP_VALIDATE_PARAMETERS + ULONG HeapValidateAll : 1; // FLG_HEAP_VALIDATE_ALL + ULONG ApplicationVerifier : 1; // FLG_APPLICATION_VERIFIER + ULONG MonitorSilentProcessExit : 1; // FLG_MONITOR_SILENT_PROCESS_EXIT + ULONG PoolEnableTagging : 1; // FLG_POOL_ENABLE_TAGGING + ULONG HeapEnableTagging : 1; // FLG_HEAP_ENABLE_TAGGING + ULONG UserStackTraceDb : 1; // FLG_USER_STACK_TRACE_DB + ULONG KernelStackTraceDb : 1; // FLG_KERNEL_STACK_TRACE_DB + ULONG MaintainObjectTypeList : 1; // FLG_MAINTAIN_OBJECT_TYPELIST + ULONG HeapEnableTagByDll : 1; // FLG_HEAP_ENABLE_TAG_BY_DLL + ULONG DisableStackExtension : 1; // FLG_DISABLE_STACK_EXTENSION + ULONG EnableCsrDebug : 1; // FLG_ENABLE_CSRDEBUG + ULONG EnableKDebugSymbolLoad : 1; // FLG_ENABLE_KDEBUG_SYMBOL_LOAD + ULONG DisablePageKernelStacks : 1; // FLG_DISABLE_PAGE_KERNEL_STACKS + ULONG EnableSystemCritBreaks : 1; // FLG_ENABLE_SYSTEM_CRIT_BREAKS + ULONG HeapDisableCoalescing : 1; // FLG_HEAP_DISABLE_COALESCING + ULONG EnableCloseExceptions : 1; // FLG_ENABLE_CLOSE_EXCEPTIONS + ULONG EnableExceptionLogging : 1; // FLG_ENABLE_EXCEPTION_LOGGING + ULONG EnableHandleTypeTagging : 1; // FLG_ENABLE_HANDLE_TYPE_TAGGING + ULONG HeapPageAllocs : 1; // FLG_HEAP_PAGE_ALLOCS + ULONG DebugInitialCommandEx : 1; // FLG_DEBUG_INITIAL_COMMAND_EX + ULONG DisableDbgPrint : 1; // FLG_DISABLE_DBGPRINT + ULONG CritSecEventCreation : 1; // FLG_CRITSEC_EVENT_CREATION + ULONG LdrTopDown : 1; // FLG_LDR_TOP_DOWN + ULONG EnableHandleExceptions : 1; // FLG_ENABLE_HANDLE_EXCEPTIONS + ULONG DisableProtDlls : 1; // FLG_DISABLE_PROTDLLS + } NtGlobalFlags; + }; + + // + // Timeout for critical sections. + // + LARGE_INTEGER CriticalSectionTimeout; + + // + // Reserved size for heap segments. + // + SIZE_T HeapSegmentReserve; + + // + // Committed size for heap segments. + // + SIZE_T HeapSegmentCommit; + + // + // Threshold for decommitting total free heap. + // + SIZE_T HeapDeCommitTotalFreeThreshold; + + // + // Threshold for decommitting free heap blocks. + // + SIZE_T HeapDeCommitFreeBlockThreshold; + + // + // Number of process heaps. + // + ULONG NumberOfHeaps; + + // + // Maximum number of process heaps. + // + ULONG MaximumNumberOfHeaps; + + // + // Pointer to an array of process heaps. ProcessHeaps is initialized + // to point to the first free byte after the PEB and MaximumNumberOfHeaps + // is computed from the page size used to hold the PEB, less the fixed + // size of this data structure. + // + PVOID* ProcessHeaps; + + // + // Pointer to the system GDI shared handle table. + // + PGDI_HANDLE_ENTRY GdiSharedHandleTable; + + // + // Pointer to the process starter helper. + // + PVOID ProcessStarterHelper; + + // + // The maximum number of GDI function calls during batch operations (GdiSetBatchLimit) + // + ULONG GdiDCAttributeList; + + // + // Pointer to the loader lock critical section. + // + PRTL_CRITICAL_SECTION LoaderLock; + + // + // Major version of the operating system. + // + ULONG OSMajorVersion; + + // + // Minor version of the operating system. + // + ULONG OSMinorVersion; + + // + // Build number of the operating system. + // + USHORT OSBuildNumber; + + // + // CSD version of the operating system. + // + USHORT OSCSDVersion; + + // + // Platform ID of the operating system. + // + ULONG OSPlatformId; + + // + // Subsystem version of the current process image (PE Headers). + // + ULONG ImageSubsystem; + + // + // Major version of the current process image subsystem (PE Headers). + // + ULONG ImageSubsystemMajorVersion; + + // + // Minor version of the current process image subsystem (PE Headers). + // + ULONG ImageSubsystemMinorVersion; + + // ... +} 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 RtlOpenCurrentUser( + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE CurrentUserKey); + +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); + +typedef enum _SECTION_INHERIT { + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT; + +NTSYSAPI NTSTATUS NTAPI NtCreateSection( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PLARGE_INTEGER MaximumSize, + _In_ ULONG SectionPageProtection, + _In_ ULONG AllocationAttributes, + _In_opt_ HANDLE FileHandle); + +NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection( + _In_ HANDLE SectionHandle, + _In_ HANDLE ProcessHandle, + _Inout_ _At_(*BaseAddress, _Readable_bytes_(*ViewSize) _Writable_bytes_(*ViewSize) _Post_readable_byte_size_(*ViewSize)) PVOID* BaseAddress, + _In_ ULONG_PTR ZeroBits, + _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, + _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, + _In_ ULONG AllocationType, + _In_ ULONG PageProtection); + +NTSYSAPI NTSTATUS NTAPI NtUnmapViewOfSection( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress); + +NTSYSAPI LOGICAL NTAPI RtlQueryPerformanceCounter( + _Out_ PLARGE_INTEGER PerformanceCounter); + +NTSYSAPI LOGICAL NTAPI RtlQueryPerformanceFrequency( + _Out_ PLARGE_INTEGER PerformanceFrequency); + +NTSYSAPI NTSTATUS NTAPI NtCancelIoFileEx( + _In_ HANDLE FileHandle, + _In_opt_ PIO_STATUS_BLOCK IoRequestToCancel, + _Out_ PIO_STATUS_BLOCK IoStatusBlock); + +NTSYSAPI NTSTATUS NTAPI NtTerminateProcess( + _In_opt_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus); diff --git a/src/common/windows/perflib_.h b/src/common/windows/perflib_.h new file mode 100644 index 0000000..f30ed54 --- /dev/null +++ b/src/common/windows/perflib_.h @@ -0,0 +1,134 @@ +#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); diff --git a/src/common/windows/registry.c b/src/common/windows/registry.c new file mode 100644 index 0000000..3618760 --- /dev/null +++ b/src/common/windows/registry.c @@ -0,0 +1,388 @@ +#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: { + NTSTATUS status = RtlOpenCurrentUser(KEY_READ, &result); + if (!NT_SUCCESS(status)) { + FF_DEBUG("RtlOpenCurrentUser() failed: %s", ffDebugNtStatus(status)); + return NULL; + } + 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) { + goto type_mismatch; + } + + FFlist* list = (FFlist*) arg->value; + ffListClear(list); + + 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; + } + break; + } + + case FF_ARG_TYPE_BUFFER: { + FFArgBuffer* buffer = (FFArgBuffer*) arg->value; + if (buffer->length == 0) { + buffer->data = malloc(regDataLen); + } else if (buffer->length < regDataLen) { + if (error) { + FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); + ffStrbufAppendF(error, "ffRegReadValues(%s) buffer too small (%u): expected %u", nameA.chars, (unsigned) buffer->length, (unsigned) regDataLen); + } + return false; + } + buffer->length = regDataLen; + memcpy(buffer->data, regData, 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; +} diff --git a/src/common/windows/registry.h b/src/common/windows/registry.h new file mode 100644 index 0000000..9745372 --- /dev/null +++ b/src/common/windows/registry.h @@ -0,0 +1,66 @@ +#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, FFArgBuffer* buffer, FFstrbuf* error) { + return ffRegReadValue(hKey, &(FFRegValueArg) { + .type = FF_ARG_TYPE_BUFFER, + .value = buffer, + .name = valueNameW, + }, + error); +} diff --git a/src/common/windows/unicode.c b/src/common/windows/unicode.c new file mode 100644 index 0000000..06008ba --- /dev/null +++ b/src/common/windows/unicode.c @@ -0,0 +1,44 @@ +#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'; +} diff --git a/src/common/windows/unicode.h b/src/common/windows/unicode.h new file mode 100644 index 0000000..f58b425 --- /dev/null +++ b/src/common/windows/unicode.h @@ -0,0 +1,46 @@ +#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); +} diff --git a/src/common/windows/unicode.hpp b/src/common/windows/unicode.hpp new file mode 100644 index 0000000..f9c355a --- /dev/null +++ b/src/common/windows/unicode.hpp @@ -0,0 +1,27 @@ +#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 diff --git a/src/common/windows/util.hpp b/src/common/windows/util.hpp new file mode 100644 index 0000000..939f6bb --- /dev/null +++ b/src/common/windows/util.hpp @@ -0,0 +1,21 @@ +#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; +}; diff --git a/src/common/windows/variant.cpp b/src/common/windows/variant.cpp new file mode 100644 index 0000000..d5610d3 --- /dev/null +++ b/src/common/windows/variant.cpp @@ -0,0 +1,20 @@ +#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; +} diff --git a/src/common/windows/variant.hpp b/src/common/windows/variant.hpp new file mode 100644 index 0000000..58e46b7 --- /dev/null +++ b/src/common/windows/variant.hpp @@ -0,0 +1,164 @@ +#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; +}; +} // namespace diff --git a/src/common/windows/version.c b/src/common/windows/version.c new file mode 100644 index 0000000..4d61c78 --- /dev/null +++ b/src/common/windows/version.c @@ -0,0 +1,77 @@ +#include "common/debug.h" +#include "common/mallocHelper.h" +#include "common/windows/version.h" +#include "common/windows/unicode.h" + +#include + +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) { + FF_DEBUG("GetFileVersionInfoSizeW failed: %s", ffDebugWin32Error(GetLastError())); + 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)) { + FF_DEBUG("GetFileVersionInfoW failed: %s", ffDebugWin32Error(GetLastError())); + 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; + } + + struct { + WORD language; + WORD codePage; + }* translations; + UINT translationsLen; + + if (VerQueryValueW(versionData, L"\\VarFileInfo\\Translation", (void**) &translations, &translationsLen) && + translationsLen >= sizeof(*translations)) { + wchar_t subBlock[128]; + 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); + + wchar_t* value; + UINT valueLen; // Number of characters, including null terminator + if (VerQueryValueW(versionData, subBlock, (void**) &value, &valueLen) && valueLen > 0) { + ffStrbufSetNWS(version, valueLen - 1, 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; +} diff --git a/src/common/windows/version.h b/src/common/windows/version.h new file mode 100644 index 0000000..457c053 --- /dev/null +++ b/src/common/windows/version.h @@ -0,0 +1,14 @@ +#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); diff --git a/src/common/windows/version.rc b/src/common/windows/version.rc new file mode 100644 index 0000000..f01ca7f --- /dev/null +++ b/src/common/windows/version.rc @@ -0,0 +1,37 @@ +#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 diff --git a/src/common/windows/wmi.h b/src/common/windows/wmi.h new file mode 100644 index 0000000..aa50c00 --- /dev/null +++ b/src/common/windows/wmi.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include + +/** + * The WmiOpenBlock function opens the WMI data block object for the specified WMI class. + * + * \param Guid Specifies the GUID for WMI class. + * \param DesiredAccess Specifies the desired access rights to the data block object. + * \param DataBlockHandle Pointer to a memory location where the routine returns a handle to the data block object. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiOpenBlock( + _In_ LPCGUID Guid, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE DataBlockHandle); + +/** + * The WmiQueryAllDataW function returns all WMI data blocks that implement a given WMI class (Unicode). + * + * \param DataBlockHandle Handle to a WMI data block object. + * \param BufferLength Pointer to a memory location that specifies the size of the buffer. + * \param Buffer Pointer to the buffer where the routine returns the WMI data. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiQueryAllDataW( + _In_ HANDLE DataBlockHandle, + _Inout_ PULONG BufferLength, + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer); + +/** + * The WmiCloseBlock function closes a WMI data block object. + * + * \param DataBlockHandle Handle to the data block object to be closed. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiCloseBlock( + _In_ HANDLE DataBlockHandle); + +static inline void ffCloseWmiBlock(HANDLE* hBlock) { + assert(hBlock); + if (*hBlock) { + WmiCloseBlock(*hBlock); + } +} + +#define FF_AUTO_CLOSE_WMI_BLOCK __attribute__((cleanup(ffCloseWmiBlock))) + +// MOF: https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/wmicore.mof diff --git a/src/detection/battery/battery.h b/src/detection/battery/battery.h new file mode 100644 index 0000000..1a3b4e9 --- /dev/null +++ b/src/detection/battery/battery.h @@ -0,0 +1,32 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/battery/option.h" + +#define FF_BATTERY_TEMP_UNSET (-DBL_MAX) + +typedef enum FFBatteryStatus { + FF_BATTERY_STATUS_NONE = 0, + FF_BATTERY_STATUS_UNKNOWN = 1 << 0, + FF_BATTERY_STATUS_AC_CONNECTED = 1 << 1, + FF_BATTERY_STATUS_USB_CONNECTED = 1 << 2, + FF_BATTERY_STATUS_WIRELESS_CONNECTED = 1 << 3, + FF_BATTERY_STATUS_CHARGING = 1 << 4, + FF_BATTERY_STATUS_DISCHARGING = 1 << 5, + FF_BATTERY_STATUS_CRITICAL = 1 << 6, +} FFBatteryStatus; + +typedef struct FFBatteryResult { + FFstrbuf manufacturer; + FFstrbuf manufactureDate; + FFstrbuf modelName; + FFstrbuf technology; + FFstrbuf serial; + FFBatteryStatus status; + double capacity; + double temperature; + uint32_t cycleCount; + int32_t timeRemaining; // in seconds, -1 if unknown +} FFBatteryResult; + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results); diff --git a/src/detection/battery/battery_android.c b/src/detection/battery/battery_android.c new file mode 100644 index 0000000..d76c4ad --- /dev/null +++ b/src/detection/battery/battery_android.c @@ -0,0 +1,160 @@ +#include "fastfetch.h" +#include "battery.h" +#include "common/strutil.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* FF_A_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 = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + 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")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_USB")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_WIRELESS")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + } + const char* status = yyjson_get_str(yyjson_obj_get(root, "status")); + if (status) { + if (ffStrEquals(status, "CHARGING")) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } else if (ffStrEquals(status, "DISCHARGING")) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + } + + 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 = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->capacity = 0; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + + if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + 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; +} diff --git a/src/detection/battery/battery_apple.c b/src/detection/battery/battery_apple.c new file mode 100644 index 0000000..c22aa6d --- /dev/null +++ b/src/detection/battery/battery_apple.c @@ -0,0 +1,117 @@ +#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 = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + battery->capacity = currentCapacity * 100.0 / maxCapacity; + battery->cycleCount = 0; + battery->timeRemaining = -1; + + 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) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else { + battery->status |= FF_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) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (ffCfDictGetBool(properties, CFSTR(kIOPMPSAtCriticalLevelKey), &boolValue) == NULL && boolValue) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + + 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; +} diff --git a/src/detection/battery/battery_bsd.c b/src/detection/battery/battery_bsd.c new file mode 100644 index 0000000..336a17e --- /dev/null +++ b/src/detection/battery/battery_bsd.c @@ -0,0 +1,90 @@ +#include "fastfetch.h" +#include "common/sysctl.h" +#include "common/io.h" +#include "battery.h" + +#include +#include +#include +#include + +const char* ffDetectBattery(FF_A_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 = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + 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) { + battery->status |= FF_BATTERY_STATUS_UNKNOWN; + } else { + if (battio.battinfo.state & ACPI_BATT_STAT_DISCHARG) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (battio.battinfo.state & ACPI_BATT_STAT_CRITICAL) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + } + + int acadStatus; + if (ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acadStatus) >= 0 && acadStatus) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + +#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; +} diff --git a/src/detection/battery/battery_haiku.c b/src/detection/battery/battery_haiku.c new file mode 100644 index 0000000..aa620e6 --- /dev/null +++ b/src/detection/battery/battery_haiku.c @@ -0,0 +1,71 @@ +#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 = FF_LIST_ADD(FFBatteryResult, *results); + ffStrbufInitS(&battery->modelName, extended.model_number); + ffStrbufInitS(&battery->manufacturer, extended.oem_info); + ffStrbufInit(&battery->manufactureDate); + ffStrbufInitS(&battery->technology, extended.type); // extended.technology? + ffStrbufInitS(&battery->serial, extended.serial_number); + battery->status = FF_BATTERY_STATUS_NONE; + 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) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (basic.state & BATTERY_CHARGING) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (basic.state & BATTERY_CRITICAL_STATE) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + if (basic.state & BATTERY_NOT_CHARGING || basic.state & BATTERY_CHARGING) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + + return NULL; +} + +const char* ffDetectBattery(FF_A_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; +} diff --git a/src/detection/battery/battery_linux.c b/src/detection/battery/battery_linux.c new file mode 100644 index 0000000..6c1f711 --- /dev/null +++ b/src/detection/battery/battery_linux.c @@ -0,0 +1,203 @@ +#include "battery.h" +#include "common/io.h" +#include "common/strutil.h" +#include "common/debug.h" + +#include +#include +#include + +// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power + +static bool parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFlist* results, bool* acConnected) { + FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate(); + + { + char present = '\0'; + if (ffReadFileDataRelative(dfd, "present", 1, &present) && present == '0') { + FF_DEBUG("Battery \"%s\": Not present", id); + return false; + } + } + + // type must exist + if (!ffReadFileBufferRelative(dfd, "type", &tmpBuffer)) { + FF_DEBUG("Battery \"%s\": No type file", id); + return false; + } + ffStrbufTrimRightSpace(&tmpBuffer); + if (ffStrbufEqualS(&tmpBuffer, "Mains")) { + if (*acConnected) { + FF_DEBUG("Battery \"%s\": Type is Mains, but AC is already connected", id); + return false; + } + + char online = '\0'; + if (ffReadFileDataRelative(dfd, "online", 1, &online) == 1 && online == '1') { + *acConnected = true; + } + FF_DEBUG("Battery \"%s\": Type is Mains, AC Connected: %s", id, *acConnected ? "Yes" : "No"); + return false; + } else if (!ffStrbufEqualS(&tmpBuffer, "Battery")) { + FF_DEBUG("Battery \"%s\": Type is not Battery or Mains, but \"%s\"", id, tmpBuffer.chars); + return false; + } + + // scope may not exist or must not be "Device" + if (ffReadFileBufferRelative(dfd, "scope", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + + FF_DEBUG("Battery \"%s\": Scope is \"%s\"", id, tmpBuffer.chars); + if (ffStrbufEqualS(&tmpBuffer, "Device")) { + return false; + } + } + + // `capacity` must exist + // This is expensive in my laptop + if (!ffReadFileBufferRelative(dfd, "capacity", &tmpBuffer)) { + FF_DEBUG("Battery \"%s\": No capacity file", id); + return false; + } + + FFBatteryResult* result = FF_LIST_ADD(FFBatteryResult, *results); + ffStrbufInit(&result->manufacturer); + ffStrbufInit(&result->modelName); + ffStrbufInit(&result->technology); + ffStrbufInit(&result->serial); + ffStrbufInit(&result->manufactureDate); + result->status = FF_BATTERY_STATUS_NONE; + result->capacity = ffStrbufToDouble(&tmpBuffer, 0); + result->cycleCount = 0; + result->temperature = FF_BATTERY_TEMP_UNSET; + result->timeRemaining = -1; + + // At this point, we have a battery. Try to get as much values as possible. + + if (ffReadFileBufferRelative(dfd, "manufacturer", &result->manufacturer)) { + ffStrbufTrimRightSpace(&result->manufacturer); + } else if (ffStrEquals(id, "macsmc-battery")) { // asahi + ffStrbufSetStatic(&result->manufacturer, "Apple Inc."); + } + + if (ffReadFileBufferRelative(dfd, "model_name", &result->modelName)) { + ffStrbufTrimRightSpace(&result->modelName); + } + + if (ffReadFileBufferRelative(dfd, "technology", &result->technology)) { + ffStrbufTrimRightSpace(&result->technology); + } + + if (ffReadFileBufferRelative(dfd, "status", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + } + + // Unknown, Charging, Discharging, Not charging, Full + + if (ffStrbufEqualS(&tmpBuffer, "Discharging")) { + result->status |= FF_BATTERY_STATUS_DISCHARGING; + FF_STRBUF_AUTO_DESTROY now = ffStrbufCreate(); + if (ffReadFileBufferRelative(dfd, "time_to_empty_now", &now)) { + result->timeRemaining = (int32_t) ffStrbufToSInt(&now, 0); + } else { + if (ffReadFileBufferRelative(dfd, "charge_now", &now)) { + int64_t chargeNow = ffStrbufToSInt(&now, 0); + if (chargeNow > 0) { + if (ffReadFileBufferRelative(dfd, "current_now", &now)) { + int64_t currentNow = ffStrbufToSInt(&now, INT64_MIN); + if (currentNow < 0) { + currentNow = -currentNow; + } + if (currentNow > 0) { + result->timeRemaining = (int32_t) ((chargeNow * 3600) / currentNow); + } + } + } + } + } + } else if (ffStrbufEqualS(&tmpBuffer, "Charging")) { + result->status |= FF_BATTERY_STATUS_CHARGING; + } else if (ffStrbufEqualS(&tmpBuffer, "Unknown")) { + result->status |= FF_BATTERY_STATUS_UNKNOWN; + } + + if (ffReadFileBufferRelative(dfd, "capacity_level", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + if (ffStrbufEqualS(&tmpBuffer, "Critical")) { + result->status |= FF_BATTERY_STATUS_CRITICAL; + } + } + + 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; + } + + 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); + } + } + } + } + } + } + + 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; + } + } + } + + FF_DEBUG("Battery \"%s\": Capacity: %.2f%%, Status: \"%x\", Time Remaining: %d seconds, Temperature: %.1f°C, Cycle Count: %u", + id, + result->capacity, + result->status, + result->timeRemaining, + result->temperature, + result->cycleCount); + return true; +} + +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"; + } + + bool acConnected = false; + + 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, &acConnected); + } + } + + if (acConnected) { + FF_LIST_FOR_EACH (FFBatteryResult, batt, *results) { + batt->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + } + + return NULL; +} diff --git a/src/detection/battery/battery_nbsd.c b/src/detection/battery/battery_nbsd.c new file mode 100644 index 0000000..2e8b426 --- /dev/null +++ b/src/detection/battery/battery_nbsd.c @@ -0,0 +1,109 @@ +#include "battery.h" +#include "common/io.h" +#include "common/FFstrbuf.h" +#include "common/strutil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char* ffDetectBattery(FF_A_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 = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + battery->timeRemaining = -1; + + battery->capacity = (double) curr / (double) max * 100.; + if (charging) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } else if (dischargeRate) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + battery->timeRemaining = (int32_t) ((double) curr / dischargeRate * 3600); + } + if (critical) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + if (acConnected) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + } + + prop_object_iterator_release(iter); + } + prop_object_iterator_release(itKey); + prop_object_release(root); + + return NULL; +} diff --git a/src/detection/battery/battery_nosupport.c b/src/detection/battery/battery_nosupport.c new file mode 100644 index 0000000..efd0f73 --- /dev/null +++ b/src/detection/battery/battery_nosupport.c @@ -0,0 +1,7 @@ +#include "fastfetch.h" +#include "battery.h" + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_UNUSED(options, results) + return "Not supported on this platform"; +} diff --git a/src/detection/battery/battery_obsd.c b/src/detection/battery/battery_obsd.c new file mode 100644 index 0000000..ee664ce --- /dev/null +++ b/src/detection/battery/battery_obsd.c @@ -0,0 +1,60 @@ +#include "battery.h" +#include "common/io.h" + +#include +#include +#include +#include + +const char* ffDetectBattery(FF_A_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 = FF_LIST_ADD(FFBatteryResult, *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->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + + if (info.ac_state == APM_AC_ON || info.ac_state == APM_AC_BACKUP) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else if (info.ac_state == APM_AC_OFF) { + battery->timeRemaining = (int) info.minutes_left * 60; + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + + if (info.battery_state == APM_BATT_CRITICAL || info.battery_state == APM_BATT_CHARGING || info.battery_state == APM_BATT_UNKNOWN) { + switch (info.battery_state) { + case APM_BATT_UNKNOWN: + battery->status |= FF_BATTERY_STATUS_UNKNOWN; + break; + case APM_BATT_CHARGING: + battery->status |= FF_BATTERY_STATUS_CHARGING; + break; + case APM_BATT_CRITICAL: + battery->status |= FF_BATTERY_STATUS_CRITICAL; + break; + } + } + + return NULL; +} diff --git a/src/detection/battery/battery_windows.c b/src/detection/battery/battery_windows.c new file mode 100644 index 0000000..84f2a90 --- /dev/null +++ b/src/detection/battery/battery_windows.c @@ -0,0 +1,384 @@ +#define INITGUID + +#include "battery.h" + +#include "common/debug.h" +#include "common/mallocHelper.h" +#include "common/windows/unicode.h" +#include "common/windows/wmi.h" + +#include + +typedef void(WINAPI* PINTERFACE_REFERENCE)(PVOID Context); +typedef void(WINAPI* PINTERFACE_DEREFERENCE)(PVOID Context); +typedef struct _DEVICE_OBJECT* PDEVICE_OBJECT; +typedef struct _IRP* PIRP; +#ifdef _WINDOWS_ + #undef _WINDOWS_ +#endif + +#include + +#pragma GCC diagnostic ignored "-Wmultichar" + +typedef struct FFBatteryWmiEntry { + ULONG tag; + FFBatteryResult* result; +} FFBatteryWmiEntry; + +static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULONG tag) { + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, *entries) { + if (entry->tag == tag) { + return entry; + } + } + + FFBatteryWmiEntry* entry = FF_LIST_ADD(FFBatteryWmiEntry, *entries); + entry->tag = tag; + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + entry->result = battery; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->manufactureDate); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + battery->status = FF_BATTERY_STATUS_NONE; + battery->capacity = -1; + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + return entry; +} + +static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) { + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: WmiOpenBlock() failed for %s: %s", guidStr, ffDebugWin32Error(status)); + return "WmiOpenBlock() failed"; + } + + status = WmiQueryAllDataW(hBlock, pBufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW() failed: %s", ffDebugWin32Error(status)); + return "WmiQueryAllDataW(NULL) failed"; + } + + if (*pBufferSize == 0) { + return "WmiQueryAllDataW(NULL) returned no data"; + } + + if (*pBufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: WmiQueryAllDataW() returned insufficient buffer size: %lu", *pBufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } + + *pAllData = (PWNODE_ALL_DATA) malloc(*pBufferSize); + + status = WmiQueryAllDataW(hBlock, pBufferSize, *pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed: %s", ffDebugWin32Error(status)); + free(*pAllData); + *pAllData = NULL; + return "WmiQueryAllDataW(*pAllData) failed"; + } + + return NULL; +} + +static bool getInstanceData(const PWNODE_ALL_DATA allData, ULONG bufferSize, ULONG index, const uint8_t** instanceData, ULONG* instanceLength) { + ULONG dataOffset = 0; + ULONG dataLength = 0; + + if (allData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = allData->FixedInstanceSize; + dataOffset = allData->DataBlockOffset + index * dataLength; + } else { + dataOffset = allData->OffsetInstanceDataAndLength[index].OffsetInstanceData; + dataLength = allData->OffsetInstanceDataAndLength[index].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + return false; + } + + *instanceData = (const uint8_t*) allData + dataOffset; + *instanceLength = dataLength; + return true; +} + +static void detectStaticData(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStaticData"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATIC_DATA_WMI_GUID, "BATTERY_STATIC_DATA_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < offsetof(BATTERY_WMI_STATIC_DATA, Strings)) { + continue; + } + + const BATTERY_WMI_STATIC_DATA* data = (const BATTERY_WMI_STATIC_DATA*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry); +#if __BIG_ENDIAN__ + #define htobe32(x) (x) // Should not happen on Windows, but just in case +#else + #define htobe32(x) __builtin_bswap32(x) +#endif + switch (data->Chemistry) { + case htobe32('PbAc'): + ffStrbufSetStatic(&entry->result->technology, "Lead Acid"); + break; + case htobe32('LION'): + case htobe32('Li-I'): + ffStrbufSetStatic(&entry->result->technology, "Lithium Ion"); + break; + case htobe32('NiCd'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium"); + break; + case htobe32('NiMH'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride"); + break; + case htobe32('NiZn'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc"); + break; + case htobe32('RAM\0'): + ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese"); + break; + default: + ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable"); + break; + } +#undef htobe32 + + const BATTERY_MANUFACTURE_DATE* manufactureDate = (const BATTERY_MANUFACTURE_DATE*) data->ManufactureDate; + if (manufactureDate->Year > 0 && manufactureDate->Month >= 1 && manufactureDate->Month <= 12 && manufactureDate->Day >= 1 && manufactureDate->Day <= 31) { + uint16_t year = manufactureDate->Year; + ffStrbufSetF(&entry->result->manufactureDate, "%.4u-%.2u-%.2u", (unsigned) (year < 1000 ? (year + 1900) : year), (unsigned) manufactureDate->Month, (unsigned) manufactureDate->Day); + } + + // Device Name, Manufacture Name, Serial Number, UniqueID + const struct { + uint16_t size; // in bytes, including the null terminator + wchar_t value[]; + }* cursor = (const void*) data->Strings; + + FFstrbuf* strings[] = { + &entry->result->modelName, + &entry->result->manufacturer, + &entry->result->serial, + }; + + for (size_t i = 0; i < ARRAY_SIZE(strings); ++i) { + if (cursor->size > sizeof(wchar_t)) { + ffStrbufSetNWS(strings[i], cursor->size / sizeof(wchar_t) - 1, cursor->value); + } + cursor = (const void*) ((const uint8_t*) cursor + sizeof(uint16_t) + cursor->size); + } + } +} + +static void detectStatus(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStatus"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATUS_WMI_GUID, "BATTERY_STATUS_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_STATUS)) { + continue; + } + + const BATTERY_WMI_STATUS* data = (const BATTERY_WMI_STATUS*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { + entry->result->capacity = data->RemainingCapacity; + } + + entry->result->status = FF_BATTERY_STATUS_NONE; + if (data->PowerOnline) { + entry->result->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (data->Charging) { + entry->result->status |= FF_BATTERY_STATUS_CHARGING; + } + if (data->Discharging) { + entry->result->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (data->Critical) { + entry->result->status |= FF_BATTERY_STATUS_CRITICAL; + } + } +} + +static void detectRuntime(FFlist* entries, FFlist* results) { + FF_DEBUG("detectRuntime"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_RUNTIME_WMI_GUID, "BATTERY_RUNTIME_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_RUNTIME)) { + continue; + } + + const BATTERY_WMI_RUNTIME* data = (const BATTERY_WMI_RUNTIME*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->EstimatedRuntime != BATTERY_UNKNOWN_TIME) { + entry->result->timeRemaining = (int32_t) data->EstimatedRuntime; + } + } +} + +static void detectFullChargedCapacity(FFlist* entries, FFlist* results) { + FF_DEBUG("detectFullChargedCapacity"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID, "BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_FULL_CHARGED_CAPACITY)) { + continue; + } + + const BATTERY_WMI_FULL_CHARGED_CAPACITY* data = (const BATTERY_WMI_FULL_CHARGED_CAPACITY*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + if (data->FullChargedCapacity != BATTERY_UNKNOWN_CAPACITY && entry->result->capacity >= 0) { + entry->result->capacity *= 100; + entry->result->capacity /= data->FullChargedCapacity; + } + } +} + +static void detectCycleCount(FFlist* entries, FFlist* results) { + FF_DEBUG("detectCycleCount"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_CYCLE_COUNT_WMI_GUID, "BATTERY_CYCLE_COUNT_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_CYCLE_COUNT)) { + continue; + } + + const BATTERY_WMI_CYCLE_COUNT* data = (const BATTERY_WMI_CYCLE_COUNT*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->cycleCount = data->CycleCount; + } +} + +static void detectTemperature(FFlist* entries, FFlist* results) { + FF_DEBUG("detectTemperature"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_TEMPERATURE_WMI_GUID, "BATTERY_TEMPERATURE_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_TEMPERATURE)) { + continue; + } + + const BATTERY_WMI_TEMPERATURE* data = (const BATTERY_WMI_TEMPERATURE*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->temperature = data->Temperature / 10.0 - 273.15; + } +} + +static const char* detectWithNtApi(FFBatteryResult* battery) { + // Reports summary battery information, not per battery + FF_DEBUG("NtApi: start detection"); + SYSTEM_BATTERY_STATE info; + NTSTATUS status = NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info)); + if (!NT_SUCCESS(status)) { + FF_DEBUG("NtApi: NtPowerInformation(SystemBatteryState) failed: %s", ffDebugNtStatus(status)); + return "NtPowerInformation(SystemBatteryState) failed"; + } + if (!info.BatteryPresent) { + FF_DEBUG("NtApi reports no battery present"); + return "No battery present"; + } + + if (info.MaxCapacity != BATTERY_UNKNOWN_CAPACITY && info.RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { + battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity; + } + battery->status = FF_BATTERY_STATUS_NONE; + if (info.AcOnLine) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (info.Charging) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (info.Discharging) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (info.DefaultAlert1 > 0 && info.RemainingCapacity <= info.DefaultAlert1) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime; + return NULL; +} + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_DEBUG("WMI: start detection"); + + FF_LIST_AUTO_DESTROY entries = ffListCreate(); + detectStaticData(&entries, results); + if (results->length == 0) { + return NULL; + } else if (results->length == 1) { + // Fast path for single battery + detectWithNtApi(FF_LIST_FIRST(FFBatteryWmiEntry, entries)->result); + } else { + detectStatus(&entries, results); + detectFullChargedCapacity(&entries, results); + detectRuntime(&entries, results); + } + detectCycleCount(&entries, results); + if (options->temp) { + detectTemperature(&entries, results); + } + + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, entries) { + FF_DEBUG( + "WMI: detected battery tag=%lu, name='%s', charge=%.2f%%, status=0x%x, runtime=%d seconds", + entry->tag, + entry->result->modelName.length ? entry->result->modelName.chars : "", + entry->result->capacity, + entry->result->status, + entry->result->timeRemaining); + } + + FF_DEBUG("WMI: finished detection, total results=%u", results->length); + return NULL; +} diff --git a/src/detection/bios/bios.h b/src/detection/bios/bios.h new file mode 100644 index 0000000..cdb48c2 --- /dev/null +++ b/src/detection/bios/bios.h @@ -0,0 +1,14 @@ +#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); diff --git a/src/detection/bios/bios_android.c b/src/detection/bios/bios_android.c new file mode 100644 index 0000000..a53db0d --- /dev/null +++ b/src/detection/bios/bios_android.c @@ -0,0 +1,16 @@ +#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; +} diff --git a/src/detection/bios/bios_apple.c b/src/detection/bios/bios_apple.c new file mode 100644 index 0000000..6b046d2 --- /dev/null +++ b/src/detection/bios/bios_apple.c @@ -0,0 +1,58 @@ +#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 CFDataRef manufacturer = IORegistryEntryCreateCFProperty(device, CFSTR("manufacturer"), kCFAllocatorDefault, kNilOptions); + ffCfStrGetString(manufacturer, &bios->vendor); + FF_CFTYPE_AUTO_RELEASE CFDataRef timeStamp = IORegistryEntryCreateCFProperty(device, CFSTR("time-stamp"), kCFAllocatorDefault, kNilOptions); + ffCfStrGetString(timeStamp, &bios->date); + + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceChosen = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen"); + if (deviceChosen) { + FF_CFTYPE_AUTO_RELEASE CFStringRef tag = IORegistryEntryCreateCFProperty(deviceChosen, CFSTR("iboot-stage-one-tag"), kCFAllocatorDefault, kNilOptions) + ?: IORegistryEntryCreateCFProperty(deviceChosen, CFSTR("system-firmware-version"), kCFAllocatorDefault, kNilOptions); + if (tag) { + ffCfStrGetString(tag, &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; +} diff --git a/src/detection/bios/bios_bsd.c b/src/detection/bios/bios_bsd.c new file mode 100644 index 0000000..66e50ea --- /dev/null +++ b/src/detection/bios/bios_bsd.c @@ -0,0 +1,31 @@ +#include "bios.h" + +#include "common/settings.h" +#include "common/sysctl.h" +#include "common/io.h" +#include "common/smbios.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; +} diff --git a/src/detection/bios/bios_linux.c b/src/detection/bios/bios_linux.c new file mode 100644 index 0000000..8776631 --- /dev/null +++ b/src/detection/bios/bios_linux.c @@ -0,0 +1,21 @@ +#include "bios.h" +#include "common/io.h" +#include "common/smbios.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; +} diff --git a/src/detection/bios/bios_nbsd.c b/src/detection/bios/bios_nbsd.c new file mode 100644 index 0000000..3ab43ed --- /dev/null +++ b/src/detection/bios/bios_nbsd.c @@ -0,0 +1,20 @@ +#include "bios.h" +#include "common/sysctl.h" +#include "common/smbios.h" +#include "common/io.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); + } + if (ffSysctlGetString("machdep.bootmethod", &bios->type) != NULL) { + ffStrbufSetStatic(&bios->type, ffPathExists("/dev/efi", FF_PATHTYPE_FILE) ? "UEFI" : "BIOS"); + } + return NULL; +} diff --git a/src/detection/bios/bios_nosupport.c b/src/detection/bios/bios_nosupport.c new file mode 100644 index 0000000..e044835 --- /dev/null +++ b/src/detection/bios/bios_nosupport.c @@ -0,0 +1,5 @@ +#include "bios.h" + +const char* ffDetectBios(FF_A_UNUSED FFBiosResult* bios) { + return "Not supported on this platform"; +} diff --git a/src/detection/bios/bios_windows.c b/src/detection/bios/bios_windows.c new file mode 100644 index 0000000..42b7674 --- /dev/null +++ b/src/detection/bios/bios_windows.c @@ -0,0 +1,107 @@ +#include "bios.h" +#include "common/smbios.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 +#elif __APPLE__ + #include "common/apple/cf_helpers.h" + #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 +} FF_A_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 legacy BIOS only + ffStrbufSetStatic(&bios->type, "BIOS"); +#elif __APPLE__ + // Intel Macs use UEFI from day one + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceEfi = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/efi"); + ffStrbufSetStatic(&bios->type, deviceEfi ? "UEFI" : "BIOS"); +#endif + + return NULL; +} diff --git a/src/detection/bluetooth/bluetooth.h b/src/detection/bluetooth/bluetooth.h new file mode 100644 index 0000000..dde8085 --- /dev/null +++ b/src/detection/bluetooth/bluetooth.h @@ -0,0 +1,14 @@ +#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 */); diff --git a/src/detection/bluetooth/bluetooth_apple.m b/src/detection/bluetooth/bluetooth_apple.m new file mode 100644 index 0000000..9cbf732 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_apple.m @@ -0,0 +1,112 @@ +#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 = FF_LIST_ADD(FFBluetoothResult, *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; +} diff --git a/src/detection/bluetooth/bluetooth_bsd.c b/src/detection/bluetooth/bluetooth_bsd.c new file mode 100644 index 0000000..db9f23c --- /dev/null +++ b/src/detection/bluetooth/bluetooth_bsd.c @@ -0,0 +1,30 @@ +#include "bluetooth.h" + +#define L2CAP_SOCKET_CHECKED +#include + +static int enumDev(FF_A_UNUSED int sockfd, struct bt_devinfo const* dev, FFlist* devices) { + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *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_A_UNUSED FFBluetoothOptions* options, FF_A_UNUSED FFlist* devices /* FFBluetoothResult */) { + // struct hostent* ent = bt_gethostent(); + if (bt_devenum((void*) enumDev, devices) < 0) { + return "bt_devenum() failed"; + } + + return NULL; +} diff --git a/src/detection/bluetooth/bluetooth_haiku.cpp b/src/detection/bluetooth/bluetooth_haiku.cpp new file mode 100644 index 0000000..8ed52c5 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_haiku.cpp @@ -0,0 +1,30 @@ +extern "C" { +#include "bluetooth.h" +#include "common/io.h" +} + +#include + +const char* ffDetectBluetooth(FF_A_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 = FF_LIST_ADD(FFBluetoothResult, *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; +} diff --git a/src/detection/bluetooth/bluetooth_linux.c b/src/detection/bluetooth/bluetooth_linux.c new file mode 100644 index 0000000..fb4e422 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_linux.c @@ -0,0 +1,256 @@ +#include "bluetooth.h" +#include "common/strutil.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")) { + uint64_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 = FF_LIST_ADD(FFBluetoothResult, *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(FFBluetoothOptions* options, 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 (!options->showDisconnected && !device->connected) { + ffStrbufDestroy(&device->name); + ffStrbufDestroy(&device->address); + ffStrbufDestroy(&device->type); + --devices->length; + } + + if (device->name.length == 0) { + ffStrbufSetStatic(&device->name, "Unknown Device"); + } + + if (device->connected && --connectedCount == 0) { + break; + } + } + } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); +} + +static const char* detectBluetooth(FFBluetoothOptions* options, 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(options, 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(FFBluetoothOptions* options, 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(options, devices, connectedCount); +#else + FF_UNUSED(options, devices); + return "Fastfetch was compiled without DBus support"; +#endif +} diff --git a/src/detection/bluetooth/bluetooth_nosupport.c b/src/detection/bluetooth/bluetooth_nosupport.c new file mode 100644 index 0000000..ac93cee --- /dev/null +++ b/src/detection/bluetooth/bluetooth_nosupport.c @@ -0,0 +1,5 @@ +#include "bluetooth.h" + +const char* ffDetectBluetooth(FF_A_UNUSED FFBluetoothOptions* options, FF_A_UNUSED FFlist* devices /* FFBluetoothResult */) { + return "Not supported on this platform"; +} diff --git a/src/detection/bluetooth/bluetooth_windows.c b/src/detection/bluetooth/bluetooth_windows.c new file mode 100644 index 0000000..8932aef --- /dev/null +++ b/src/detection/bluetooth/bluetooth_windows.c @@ -0,0 +1,223 @@ +#include "bluetooth.h" +#include "common/library.h" +#include "common/mallocHelper.h" +#include "common/windows/unicode.h" + +#define INITGUID +#include +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wpointer-sign" + +// https://github.com/wine-mirror/wine/blob/ab6f4584b89f28504b0b277c0b4c723a86b4d6b7/include/ddk/bthguid.h#L4 +/* DEVPROP_TYPE_STRING */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_DeviceAddress, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 1); +/* DEVPROP_TYPE_UINT32 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ClassOfDevice, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 10); +/* DEVPROP_TYPE_FILETIME */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_LastConnectedTime, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 11); +/* DEVPROP_TYPE_GUID */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ServiceGUID, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 2); +/* DEVPROP_TYPE_UINT8 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_BatteryLevel, 0x104ea319, 0x6ee2, 0x4701, 0xbd, 0x47, 0x8d, 0xdb, 0xf4, 0x25, 0xbb, 0xe5, 2); + +// TODO: use CM API to fetch bluetooth devices instead of BluetoothFindFirstDevice, if we find DEVPKEY_Bluetooth_IsConnected or similar +#define GUID_DEVCLASS_BLUETOOTH_STRING L"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}" // Found in +#define GUID_DEVCLASS_MEDIA_STRING L"{4d36e96c-e325-11ce-bfc1-08002be10318}" // Found in + +static const char* ffBluetoothDetectBattery(FFlist* devices) { + ULONG idListLength = 0; + CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_List_SizeW failed"; + } + + if (idListLength == 0) { + return NULL; + } + + wchar_t* FF_AUTO_FREE idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t)); + if (!idList) { + return "malloc() failed"; + } + + status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_ListW failed"; + } + + for (const wchar_t* deviceId = idList; *deviceId; deviceId += wcslen(deviceId) + 1) { + DEVINST devInst = 0; + + // Hands-Free profile service; headsets often expose battery level through this media device node rather than the Bluetooth device node + // BthHFEnum + if (CM_Locate_DevNodeW(&devInst, (DEVINSTID_W) deviceId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { + continue; + } + + uint8_t battery = 0; + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(battery); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_BatteryLevel, &devPropertyType, (PBYTE) &battery, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_BYTE || propertySize != sizeof(battery)) { + continue; + } + } + + WCHAR deviceAddress[13]; // 6 bytes in hex + null terminator + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(deviceAddress); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_DeviceAddress, &devPropertyType, (PBYTE) deviceAddress, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_STRING || propertySize != sizeof(deviceAddress)) { + continue; + } + } + + FF_LIST_FOR_EACH (FFBluetoothResult, bt, *devices) { + if (deviceAddress[0] == bt->address.chars[0] && + deviceAddress[1] == bt->address.chars[1] && + deviceAddress[2] == bt->address.chars[3] && + deviceAddress[3] == bt->address.chars[4] && + deviceAddress[4] == bt->address.chars[6] && + deviceAddress[5] == bt->address.chars[7] && + deviceAddress[6] == bt->address.chars[9] && + deviceAddress[7] == bt->address.chars[10] && + deviceAddress[8] == bt->address.chars[12] && + deviceAddress[9] == bt->address.chars[13] && + deviceAddress[10] == bt->address.chars[15] && + deviceAddress[11] == bt->address.chars[16]) { + bt->battery = battery; + break; + } + } + } + + return NULL; +} + +const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { + FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice) + 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 { + if (!options->showDisconnected && !btdi.fConnected) { + continue; + } + + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *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); + + if (devices->length > 0) { + ffBluetoothDetectBattery(devices); + } + + return NULL; +} diff --git a/src/detection/bluetoothradio/bluetoothradio.c b/src/detection/bluetoothradio/bluetoothradio.c new file mode 100644 index 0000000..117e225 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio.c @@ -0,0 +1,161 @@ +#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"; + } +} diff --git a/src/detection/bluetoothradio/bluetoothradio.h b/src/detection/bluetoothradio/bluetoothradio.h new file mode 100644 index 0000000..a3c1208 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio.h @@ -0,0 +1,18 @@ +#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); diff --git a/src/detection/bluetoothradio/bluetoothradio_apple.m b/src/detection/bluetoothradio/bluetoothradio_apple.m new file mode 100644 index 0000000..c5154ab --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_apple.m @@ -0,0 +1,69 @@ +#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 = FF_LIST_ADD(FFBluetoothRadioResult, *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; +} diff --git a/src/detection/bluetoothradio/bluetoothradio_linux.c b/src/detection/bluetoothradio/bluetoothradio_linux.c new file mode 100644 index 0000000..a7d92a4 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_linux.c @@ -0,0 +1,153 @@ +#include "bluetoothradio.h" +#include "common/strutil.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")) { + uint64_t vendorId; + if (ffDBusGetUint(dbus, &dictIter, &vendorId)) { + ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor((uint32_t) vendorId)); + } + } else if (ffStrEquals(deviceProperty, "Version")) { + uint64_t version; + if (ffDBusGetUint(dbus, &dictIter, &version)) { + device->lmpVersion = (int32_t) version; + } + } else if (ffStrEquals(deviceProperty, "Powered")) { + ffDBusGetBool(dbus, &dictIter, &device->enabled); + } else if (ffStrEquals(deviceProperty, "Discoverable")) { + 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 = FF_LIST_ADD(FFBluetoothRadioResult, *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 +} diff --git a/src/detection/bluetoothradio/bluetoothradio_nosupport.c b/src/detection/bluetoothradio/bluetoothradio_nosupport.c new file mode 100644 index 0000000..08fb6f8 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_nosupport.c @@ -0,0 +1,5 @@ +#include "bluetoothradio.h" + +const char* ffDetectBluetoothRadio(FF_A_UNUSED FFlist* devices /* FFBluetoothRadioResult */) { + return "Not supported on this platform"; +} diff --git a/src/detection/bluetoothradio/bluetoothradio_windows.c b/src/detection/bluetoothradio/bluetoothradio_windows.c new file mode 100644 index 0000000..f0b55d4 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_windows.c @@ -0,0 +1,99 @@ +#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; +} FF_A_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; +} FF_A_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 = FF_LIST_ADD(FFBluetoothRadioResult, *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; +} diff --git a/src/detection/board/board.h b/src/detection/board/board.h new file mode 100644 index 0000000..c092a7f --- /dev/null +++ b/src/detection/board/board.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/board/board_android.c b/src/detection/board/board_android.c new file mode 100644 index 0000000..b5978d1 --- /dev/null +++ b/src/detection/board/board_android.c @@ -0,0 +1,9 @@ +#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; +} diff --git a/src/detection/board/board_apple.c b/src/detection/board/board_apple.c new file mode 100644 index 0000000..e7c4d38 --- /dev/null +++ b/src/detection/board/board_apple.c @@ -0,0 +1,32 @@ +#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; +} diff --git a/src/detection/board/board_bsd.c b/src/detection/board/board_bsd.c new file mode 100644 index 0000000..2e0e417 --- /dev/null +++ b/src/detection/board/board_bsd.c @@ -0,0 +1,15 @@ +#include "board.h" +#include "common/settings.h" +#include "common/smbios.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; +} diff --git a/src/detection/board/board_linux.c b/src/detection/board/board_linux.c new file mode 100644 index 0000000..52b3df2 --- /dev/null +++ b/src/detection/board/board_linux.c @@ -0,0 +1,26 @@ +#include "board.h" +#include "common/io.h" +#include "common/smbios.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; +} diff --git a/src/detection/board/board_nbsd.c b/src/detection/board/board_nbsd.c new file mode 100644 index 0000000..cc3ff93 --- /dev/null +++ b/src/detection/board/board_nbsd.c @@ -0,0 +1,20 @@ +#include "board.h" +#include "common/sysctl.h" +#include "common/smbios.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; +} diff --git a/src/detection/board/board_nosupport.c b/src/detection/board/board_nosupport.c new file mode 100644 index 0000000..befc630 --- /dev/null +++ b/src/detection/board/board_nosupport.c @@ -0,0 +1,5 @@ +#include "board.h" + +const char* ffDetectBoard(FF_A_UNUSED FFBoardResult* board) { + return "Not supported on this platform"; +} diff --git a/src/detection/board/board_windows.c b/src/detection/board/board_windows.c new file mode 100644 index 0000000..079e64d --- /dev/null +++ b/src/detection/board/board_windows.c @@ -0,0 +1,46 @@ +#include "board.h" +#include "common/smbios.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 +} FF_A_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; +} diff --git a/src/detection/bootmgr/bootmgr.c b/src/detection/bootmgr/bootmgr.c new file mode 100644 index 0000000..31838d4 --- /dev/null +++ b/src/detection/bootmgr/bootmgr.c @@ -0,0 +1,44 @@ +#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; +} diff --git a/src/detection/bootmgr/bootmgr.h b/src/detection/bootmgr/bootmgr.h new file mode 100644 index 0000000..055b0ee --- /dev/null +++ b/src/detection/bootmgr/bootmgr.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/bootmgr/bootmgr_apple.c b/src/detection/bootmgr/bootmgr_apple.c new file mode 100644 index 0000000..d2b24e0 --- /dev/null +++ b/src/detection/bootmgr/bootmgr_apple.c @@ -0,0 +1,69 @@ +#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, kNilOptions); + 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"); + } + + #ifdef __aarch64__ + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceChosen = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen"); + if (deviceChosen) { + FF_CFTYPE_AUTO_RELEASE CFStringRef tag = IORegistryEntryCreateCFProperty(deviceChosen, CFSTR("iboot-stage-two-tag"), kCFAllocatorDefault, kNilOptions) + ?: IORegistryEntryCreateCFProperty(deviceChosen, CFSTR("system-firmware-version"), kCFAllocatorDefault, kNilOptions); + if (tag) { + ffCfStrGetString(tag, &result->name); + ffStrbufSubstrBeforeFirstC(&result->name, '-'); + } + } + #endif + + if (!result->name.length) { + ffStrbufSetStatic(&result->name, "iBoot"); + } + + detectSecureBoot(&result->secureBoot); + + return NULL; +} diff --git a/src/detection/bootmgr/bootmgr_bsd.c b/src/detection/bootmgr/bootmgr_bsd.c new file mode 100644 index 0000000..cfaa41d --- /dev/null +++ b/src/detection/bootmgr/bootmgr_bsd.c @@ -0,0 +1,62 @@ +#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; +} diff --git a/src/detection/bootmgr/bootmgr_haiku.cpp b/src/detection/bootmgr/bootmgr_haiku.cpp new file mode 100644 index 0000000..72358fc --- /dev/null +++ b/src/detection/bootmgr/bootmgr_haiku.cpp @@ -0,0 +1,17 @@ +extern "C" { + #include "bootmgr.h" + #include "common/io.h" +} + +const char* ffDetectBootmgr(FFBootmgrResult* result) { + // TODO: glob haiku_loader.* + check EFI partition + if (ffPathExists("/system/haiku_loader.bios_ia32", FF_PATHTYPE_FILE)) { + ffStrbufSetStatic(&result->firmware, "/system/haiku_loader.bios_ia32"); + } + + ffStrbufSetStatic(&result->name, "haiku_loader"); + + // TODO: detectSecureBoot(&result->secureBoot); + + return NULL; +} diff --git a/src/detection/bootmgr/bootmgr_linux.c b/src/detection/bootmgr/bootmgr_linux.c new file mode 100644 index 0000000..0fd019a --- /dev/null +++ b/src/detection/bootmgr/bootmgr_linux.c @@ -0,0 +1,32 @@ +#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; +} diff --git a/src/detection/bootmgr/bootmgr_nosupport.c b/src/detection/bootmgr/bootmgr_nosupport.c new file mode 100644 index 0000000..bb01714 --- /dev/null +++ b/src/detection/bootmgr/bootmgr_nosupport.c @@ -0,0 +1,5 @@ +#include "bootmgr.h" + +const char* ffDetectBootmgr(FF_A_UNUSED FFBootmgrResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/bootmgr/bootmgr_windows.c b/src/detection/bootmgr/bootmgr_windows.c new file mode 100644 index 0000000..e83e1e2 --- /dev/null +++ b/src/detection/bootmgr/bootmgr_windows.c @@ -0,0 +1,74 @@ +#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; +} diff --git a/src/detection/bootmgr/efi_helper.h b/src/detection/bootmgr/efi_helper.h new file mode 100644 index 0000000..b52ec8a --- /dev/null +++ b/src/detection/bootmgr/efi_helper.h @@ -0,0 +1,22 @@ +#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" diff --git a/src/detection/brightness/brightness.h b/src/detection/brightness/brightness.h new file mode 100644 index 0000000..abb9478 --- /dev/null +++ b/src/detection/brightness/brightness.h @@ -0,0 +1,26 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/brightness/option.h" + +typedef struct FFBrightnessResult { + FFstrbuf name; + double min, max, current; + bool builtin; +} FFBrightnessResult; + +enum { + FF_DDC_EDID_ADDR = 0x50u, + FF_DDC_CI_ADDR = 0x37u, + FF_DDC_CI_WRITE_ADDR = FF_DDC_CI_ADDR << 1, + FF_DDC_CI_READ_ADDR = FF_DDC_CI_WRITE_ADDR | 1, + FF_DDC_CI_VCP_COMMAND = 0x51u, + FF_DDC_CI_GET_VCP = 0x01u, + FF_DDC_CI_SET_VCP = 0x03u, + FF_DDC_CI_COMMAND_PACKET = 0x80u, + FF_DDC_CI_LUMINANCE_OPCODE = 0x10u, +}; +#define FF_BRIGHTNESS_DDCCI_SLEEP_SKIP ((uint32_t) -1) +#define FF_DDC_CI_MAKE_HEADER(len) (FF_DDC_CI_COMMAND_PACKET | ((len) & 0x7F)) + +const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult diff --git a/src/detection/brightness/brightness_apple.c b/src/detection/brightness/brightness_apple.c new file mode 100644 index 0000000..5dd12b2 --- /dev/null +++ b/src/detection/brightness/brightness_apple.c @@ -0,0 +1,244 @@ +#include "brightness.h" +#include "detection/displayserver/displayserver.h" +#include "common/apple/cf_helpers.h" +#include "common/edidHelper.h" +#include "common/time.h" + +#include + +// DDC/CI +#ifdef __aarch64__ +typedef CFTypeRef IOAVServiceRef; +extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator) FF_A_WEAK_IMPORT; +extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize) FF_A_WEAK_IMPORT; +#else + // DDC/CI (Intel) + #include + #include + #include +extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service) FF_A_WEAK_IMPORT; +#endif + +// ACPI +extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float* brightness) FF_A_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 = FF_LIST_ADD(FFBrightnessResult, *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_A_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] = { FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE }; + i2cIn[3] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2]; + + for (uint32_t i = 0; i < 2; ++i) { + IOAVServiceWriteI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cIn, ARRAY_SIZE(i2cIn)); + ffTimeSleep(options->ddcciSleep); + } + } + + uint8_t i2cOut[12] = {}; + if (IOAVServiceReadI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, 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 = FF_LIST_ADD(FFBrightnessResult, *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[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 }; + i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3]; + + IOI2CRequest request = { + .commFlags = kNilOptions, + .sendAddress = FF_DDC_CI_WRITE_ADDR, + .sendTransactionType = kIOI2CSimpleTransactionType, + .sendBuffer = (vm_address_t) i2cIn, + .sendBytes = ARRAY_SIZE(i2cIn), + .minReplyDelay = options->ddcciSleep * 1000ULL, + .replyAddress = FF_DDC_CI_READ_ADDR, + .replySubAddress = FF_DDC_CI_VCP_COMMAND, + .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 = FF_LIST_ADD(FFBrightnessResult, *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 (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && displayServer->displays.length > result->length) { + detectWithDdcci(displayServer, options, result); + } + + return NULL; +} diff --git a/src/detection/brightness/brightness_bsd.c b/src/detection/brightness/brightness_bsd.c new file mode 100644 index 0000000..5ff411c --- /dev/null +++ b/src/detection/brightness/brightness_bsd.c @@ -0,0 +1,147 @@ +#include "brightness.h" +#include "common/arrutil.h" +#include "common/io.h" +#include "common/kmod.h" +#include "common/debug.h" +#include "common/time.h" + +#include +#include +#include + +#if __has_include() + #include + +const char* detectWithDdcci(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // FIXME: doesn't work for me + for (char i = '0'; i <= '9'; ++i) { + char path[] = "/dev/iic0"; + path[ARRAY_SIZE(path) - 2] = i; + + FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + int err = errno; + if (err == ENOENT) { + FF_DEBUG("No more I2C devices"); + break; + } else if (i == '0' && !ffKmodLoaded("iicbus")) { + FF_DEBUG("iicbus module is not loaded"); + return "No I2C devices found and iicbus module is not loaded"; + } + FF_DEBUG("open(/dev/iic%c) failed: %s", i, strerror(errno)); + continue; + } + + uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 }; + i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3]; + + int ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_WRITE_ADDR, .flags = IIC_M_WR, .len = ARRAY_SIZE(i2cIn), .buf = i2cIn }, .nmsgs = 1 }); + if (ret < 0) { + FF_DEBUG("First ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno)); + continue; + } + + ffTimeSleep(options->ddcciSleep); + + uint8_t i2cOut[12] = {}; + ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_READ_ADDR, // LSB will be overridden by kernel to set read bit + .flags = IIC_M_RD, + .len = ARRAY_SIZE(i2cOut), + .buf = i2cOut }, + .nmsgs = 1 }); + if (ret < 0) { + FF_DEBUG("Second ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno)); + continue; + } + if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) { + FF_DEBUG("i2c out validation failed: i2cOut[2] = 0x%02x, i2cOut[3] = 0x%02x", i2cOut[2], i2cOut[3]); + 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 = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = max; + brightness->min = 0; + brightness->current = current; + ffStrbufInitS(&brightness->name, path + strlen("/dev/")); + brightness->builtin = false; + } + + return NULL; +} + +#else + +const char* detectWithDdcci(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + FF_DEBUG("DDC/CI support is not available on this system"); + return "DDC/CI is supported only on FreeBSD"; +} + +#endif + +#if __has_include() + #include + +const char* detectWithBacklight(FF_A_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 fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + int err = errno; + if (err == ENOENT) { + FF_DEBUG("No more backlight devices"); + break; + } else if (i == '0' && !ffKmodLoaded("backlight")) { + FF_DEBUG("backlight module is not loaded"); + return "No backlight devices found and backlight module is not loaded"; + } + FF_DEBUG("open(/dev/backlight/backlight%c) failed: %s", i, strerror(errno)); + continue; + } + + struct backlight_props status; + if (ioctl(fd, BACKLIGHTGETSTATUS, &status) < 0) { + continue; + } + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInit(&brightness->name); + + brightness->max = BACKLIGHTMAXLEVELS; + brightness->min = 0; + brightness->current = status.brightness; + brightness->builtin = true; + + struct backlight_info info; + if (ioctl(fd, BACKLIGHTGETINFO, &info) == 0) { + ffStrbufAppendS(&brightness->name, info.name); + } else { + ffStrbufAppendS(&brightness->name, path + strlen("/dev/backlight/")); + } + } + return NULL; +} + +#else + +const char* detectWithBacklight(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + FF_DEBUG("Backlight support is not available on this system"); + return "Backlight is supported only on FreeBSD 13 and newer"; +} + +#endif + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + detectWithBacklight(options, result); + + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length == 0) { + detectWithDdcci(options, result); + } + return NULL; +} diff --git a/src/detection/brightness/brightness_haiku.cpp b/src/detection/brightness/brightness_haiku.cpp new file mode 100644 index 0000000..86fbe6d --- /dev/null +++ b/src/detection/brightness/brightness_haiku.cpp @@ -0,0 +1,46 @@ +extern "C" { +#include "brightness.h" +#include "common/strutil.h" +} + +#include +#include + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + BScreen screen{}; // default screen is the main one + + do { + if (!screen.IsValid()) { + continue; + } + + float value = 1.0f; + status_t status = screen.GetBrightness(&value); + if (status != B_OK) { + continue; + } + + monitor_info monitor; + // WARNING: This is experimental new Haiku API + status_t err = screen.GetMonitorInfo(&monitor); + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + + if (err == B_OK) { + ffStrbufInitF(&brightness->name, "%s %s (%d)", monitor.vendor, monitor.name, screen.ID().id); + ffStrbufTrimRightSpace(&brightness->name); + } else { + ffStrbufInitF(&brightness->name, "Screen %d", screen.ID().id); + } + + brightness->max = 1.0f; + brightness->min = 0.0f; + brightness->current = value; + brightness->builtin = true; + + } while (screen.SetToNext() == B_OK); + + return NULL; +} diff --git a/src/detection/brightness/brightness_linux.c b/src/detection/brightness/brightness_linux.c new file mode 100644 index 0000000..e7125f8 --- /dev/null +++ b/src/detection/brightness/brightness_linux.c @@ -0,0 +1,176 @@ +#include "brightness.h" +#include "common/io.h" +#include "common/edidHelper.h" +#include "common/strutil.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 = FF_LIST_ADD(FFBrightnessResult, *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_A_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 = FF_LIST_ADD(FFBrightnessResult, *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_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + detectWithBacklight(result); + +#ifdef FF_HAVE_DDCUTIL + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + if (result->length < displayServer->displays.length) { + detectWithDdcci(options, result); + } + } +#endif + + return NULL; +} diff --git a/src/detection/brightness/brightness_nbsd.c b/src/detection/brightness/brightness_nbsd.c new file mode 100644 index 0000000..afd6656 --- /dev/null +++ b/src/detection/brightness/brightness_nbsd.c @@ -0,0 +1,26 @@ +#include "brightness.h" + +#include "common/sysctl.h" + +const char* ffDetectBrightness(FF_A_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 = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInitF(&brightness->name, "acpiout%d", i); + + brightness->max = 100; + brightness->min = 0; + brightness->current = value; + brightness->builtin = true; + } + return NULL; +} diff --git a/src/detection/brightness/brightness_nosupport.c b/src/detection/brightness/brightness_nosupport.c new file mode 100644 index 0000000..6e9284e --- /dev/null +++ b/src/detection/brightness/brightness_nosupport.c @@ -0,0 +1,5 @@ +#include "brightness.h" + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/brightness/brightness_obsd.c b/src/detection/brightness/brightness_obsd.c new file mode 100644 index 0000000..102a8c2 --- /dev/null +++ b/src/detection/brightness/brightness_obsd.c @@ -0,0 +1,44 @@ +#include "brightness.h" +#include "common/io.h" + +#include +#include +#include +#include + +const char* ffDetectBrightness(FF_A_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 = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInitF(&brightness->name, "ttyC%c", i); + + brightness->max = param.max; + brightness->min = param.min; + brightness->current = param.curval; + brightness->builtin = true; + } + + return NULL; +} diff --git a/src/detection/brightness/brightness_windows.c b/src/detection/brightness/brightness_windows.c new file mode 100644 index 0000000..70b7f01 --- /dev/null +++ b/src/detection/brightness/brightness_windows.c @@ -0,0 +1,223 @@ +#include "brightness.h" +#include "detection/displayserver/displayserver.h" +#include "common/debug.h" +#include "common/library.h" +#include "common/mallocHelper.h" +#include "common/windows/wmi.h" +#include "common/windows/unicode.h" + +#include +#include +#include + +NTSYSAPI NTSTATUS WINAPI GetPhysicalMonitors( + _In_ UNICODE_STRING* pstrDeviceName, + _In_ DWORD dwPhysicalMonitorArraySize, + _Out_ DWORD* pdwNumPhysicalMonitorHandlesInArray, + _Out_ HANDLE* phPhysicalMonitorArray); + +typedef enum _MC_VCP_CODE_TYPE { + MC_MOMENTARY, + MC_SET_PARAMETER +} MC_VCP_CODE_TYPE, + *LPMC_VCP_CODE_TYPE; + +NTSYSAPI NTSTATUS WINAPI DDCCIGetVCPFeature( + _In_ HANDLE hMonitor, + _In_ DWORD dwVCPCode, + _Out_opt_ LPMC_VCP_CODE_TYPE pvct, + _Out_ DWORD* pdwCurrentValue, + _Out_opt_ DWORD* pdwMaximumValue); + +NTSYSAPI NTSTATUS WINAPI DestroyPhysicalMonitorInternal( + _In_ HANDLE hMonitor); + +NTSTATUS WINAPI GetPhysicalMonitorDescription( + _In_ HANDLE hMonitor, + _In_ DWORD dwPhysicalMonitorDescriptionSizeInChars, + _Out_ LPWSTR szPhysicalMonitorDescription); + +static const char* detectWithWmi(FFlist* result) { + FF_DEBUG("WMI: start detection"); + + // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/wmicore.mof#L21200 + const GUID WmiMonitorBrightnessGuid = { + 0xd43412ac, 0x67f9, 0x4fbb, { 0xa0, 0x81, 0x17, 0x52, 0xa2, 0xc3, 0x3e, 0x84 } + }; + + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + + ULONG status = WmiOpenBlock(&WmiMonitorBrightnessGuid, WMIGUID_QUERY, &hBlock); + if (status != 0) { + FF_DEBUG("WMI: WmiOpenBlock failed: %s", ffDebugWin32Error(status)); + return "WmiOpenBlock() failed"; + } + + ULONG bufferSize = 0; + status = WmiQueryAllDataW(hBlock, &bufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); + return "WmiQueryAllDataW() failed"; + } + + FF_DEBUG("WMI: initial query bufferSize=%lu", bufferSize); + + if (bufferSize == 0) { + FF_DEBUG("WMI: WmiQueryAllDataW returned empty buffer"); + return "WmiQueryAllDataW() returned no data"; + } + + FF_AUTO_FREE PWNODE_ALL_DATA pAllData = (PWNODE_ALL_DATA) malloc(bufferSize); + + status = WmiQueryAllDataW(hBlock, &bufferSize, pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); + return "WmiQueryAllDataW() failed"; + } + + if (bufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: insufficient buffer for WNODE_ALL_DATA, bufferSize=%lu", bufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } + + FF_DEBUG("WMI: instanceCount=%lu, flags=0x%lX", pAllData->InstanceCount, pAllData->WnodeHeader.Flags); + + PULONG pNameOffsets = (PULONG) ((PUCHAR) pAllData + pAllData->OffsetInstanceNameOffsets); + + for (ULONG i = 0; i < pAllData->InstanceCount; i++) { + ULONG dataOffset = 0; + ULONG dataLength = 0; + + if (pAllData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = pAllData->FixedInstanceSize; + dataOffset = pAllData->DataBlockOffset + i * dataLength; + } else { + dataOffset = pAllData->OffsetInstanceDataAndLength[i].OffsetInstanceData; + dataLength = pAllData->OffsetInstanceDataAndLength[i].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + FF_DEBUG("WMI: skip invalid instance %lu (dataOffset=%lu, dataLength=%lu, bufferSize=%lu)", i, dataOffset, dataLength, bufferSize); + continue; + } + + USHORT nameCharsCount = *(PUSHORT) ((PUCHAR) pAllData + pNameOffsets[i]) / sizeof(WCHAR); + PCWSTR pNameChars = (PCWSTR) ((PUCHAR) pAllData + (pNameOffsets[i] + sizeof(USHORT))); + + PUCHAR pDataBlock = (PUCHAR) pAllData + dataOffset; + UCHAR currentBrightness = pDataBlock[0]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = 100; + brightness->min = 0; + brightness->current = currentBrightness; + brightness->builtin = true; + ffStrbufInitNWS(&brightness->name, nameCharsCount, pNameChars); + ffStrbufSubstrAfterFirstC(&brightness->name, '\\'); + ffStrbufSubstrBeforeFirstC(&brightness->name, '\\'); + + FF_DEBUG("WMI: detected builtin display '%s', current=%u", brightness->name.chars, (unsigned) currentBrightness); + } + + FF_DEBUG("WMI: finished detection, total results=%u", result->length); + + return NULL; +} + +static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFlist* result) { + FF_DEBUG("DDC/CI: start detection, displayCount=%u", displayServer->displays.length); + + void* gdi32 = ffLibraryGetModule(L"gdi32.dll"); + if (!gdi32) { + FF_DEBUG("DDC/CI: failed to load gdi32.dll"); + return "ffLibraryGetModule(gdi32.dll) failed"; + } + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DDCCIGetVCPFeature) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DestroyPhysicalMonitorInternal) + + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN) { + FF_DEBUG("DDC/CI: skip builtin display id=%" PRIu64, display->id); + continue; + } + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW((HMONITOR) (uintptr_t) display->id, (LPMONITORINFO) &mi)) { + FF_DEBUG("DDC/CI: GetMonitorInfoW failed for display id=%" PRIu64 ": %s", display->id, ffDebugWin32Error(GetLastError())); + continue; + } + + UNICODE_STRING deviceName = { + .Length = (USHORT) (wcslen(mi.szDevice) * sizeof(wchar_t)), + .MaximumLength = 0, + .Buffer = mi.szDevice, + }; + HANDLE physicalMonitor; + DWORD monitorCount = 0; + NTSTATUS monitorStatus = ffGetPhysicalMonitors(&deviceName, 1, &monitorCount, &physicalMonitor); + if (NT_SUCCESS(monitorStatus) && monitorCount >= 1) { + DWORD curr = 0, max = 0; + if (NT_SUCCESS(ffDDCCIGetVCPFeature(physicalMonitor, FF_DDC_CI_LUMINANCE_OPCODE, NULL, &curr, &max))) { + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + if (display->name.length > 0) { + ffStrbufInitCopy(&brightness->name, &display->name); + } else { + FF_LIBRARY_LOAD_SYMBOL_LAZY(gdi32, GetPhysicalMonitorDescription) + if (ffGetPhysicalMonitorDescription) { + wchar_t description[128 /*MUST be PHYSICAL_MONITOR_DESCRIPTION_SIZE*/]; + if (NT_SUCCESS(ffGetPhysicalMonitorDescription(physicalMonitor, ARRAY_SIZE(description), description))) { + ffStrbufInitWS(&brightness->name, description); + } + } + if (brightness->name.length == 0) { + ffStrbufSetNWS(&brightness->name, deviceName.Length / 2, deviceName.Buffer); + } + } + brightness->max = max; + brightness->min = 0; + brightness->current = curr; + brightness->builtin = false; + + FF_DEBUG("DDC/CI: detected external display '%s', current=%u, max=%u", brightness->name.chars, (unsigned) curr, (unsigned) max); + } else { + FF_DEBUG("DDC/CI: DDCCIGetVCPFeature failed for monitor '%ls': %s", deviceName.Buffer, ffDebugWin32Error(GetLastError())); + } + + ffDestroyPhysicalMonitorInternal(physicalMonitor); + } else { + FF_DEBUG("DDC/CI: GetPhysicalMonitors failed for '%ls', status=0x%08X, monitorCount=%lu, error=%s", deviceName.Buffer, (unsigned) monitorStatus, monitorCount, ffDebugWin32Error(GetLastError())); + } + } + + FF_DEBUG("DDC/CI: finished detection, total results=%u", result->length); + return NULL; +} + +static bool hasBuiltinDisplay(const FFDisplayServerResult* displayServer) { + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { + return true; + } + } + return false; +} + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + FF_DEBUG("start, displayCount=%u", displayServer->displays.length); + + if (hasBuiltinDisplay(displayServer)) { + FF_DEBUG("builtin display detected, trying WMI"); + detectWithWmi(result); + } + + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length < displayServer->displays.length) { + FF_DEBUG("resultCount=%u < displayCount=%u, trying DDC/CI", result->length, displayServer->displays.length); + detectWithDdcci(displayServer, result); + } + + FF_DEBUG("finished, resultCount=%u", result->length); + return NULL; +} diff --git a/src/detection/btrfs/btrfs.h b/src/detection/btrfs/btrfs.h new file mode 100644 index 0000000..08ea4e9 --- /dev/null +++ b/src/detection/btrfs/btrfs.h @@ -0,0 +1,28 @@ +#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 */); diff --git a/src/detection/btrfs/btrfs_linux.c b/src/detection/btrfs/btrfs_linux.c new file mode 100644 index 0000000..41914d7 --- /dev/null +++ b/src/detection/btrfs/btrfs_linux.c @@ -0,0 +1,178 @@ +#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 = FF_LIST_ADD(FFBtrfsResult, *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; +} diff --git a/src/detection/btrfs/btrfs_nosupport.c b/src/detection/btrfs/btrfs_nosupport.c new file mode 100644 index 0000000..b51d2c5 --- /dev/null +++ b/src/detection/btrfs/btrfs_nosupport.c @@ -0,0 +1,5 @@ +#include "btrfs.h" + +const char* ffDetectBtrfs(FF_A_UNUSED FFlist* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/camera/camera.h b/src/detection/camera/camera.h new file mode 100644 index 0000000..52036b5 --- /dev/null +++ b/src/detection/camera/camera.h @@ -0,0 +1,15 @@ +#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 */); diff --git a/src/detection/camera/camera_android.c b/src/detection/camera/camera_android.c new file mode 100644 index 0000000..b09314d --- /dev/null +++ b/src/detection/camera/camera_android.c @@ -0,0 +1,58 @@ +#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_A_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* FF_A_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 = FF_LIST_ADD(FFCameraResult, *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; +} diff --git a/src/detection/camera/camera_apple.m b/src/detection/camera/camera_apple.m new file mode 100644 index 0000000..aa5c1cf --- /dev/null +++ b/src/detection/camera/camera_apple.m @@ -0,0 +1,61 @@ +#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 FF_A_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 = FF_LIST_ADD(FFCameraResult, *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; + case 4 /*AVCaptureColorSpace_AppleLog2*/: ffStrbufInitStatic(&camera->colorspace, "AppleLog2"); 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 +} diff --git a/src/detection/camera/camera_linux.c b/src/detection/camera/camera_linux.c new file mode 100644 index 0000000..2fea2b4 --- /dev/null +++ b/src/detection/camera/camera_linux.c @@ -0,0 +1,94 @@ +#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 = FF_LIST_ADD(FFCameraResult, *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 +} diff --git a/src/detection/camera/camera_nosupport.c b/src/detection/camera/camera_nosupport.c new file mode 100644 index 0000000..ae5b243 --- /dev/null +++ b/src/detection/camera/camera_nosupport.c @@ -0,0 +1,5 @@ +#include "camera.h" + +const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { + return "Not support on this platform"; +} diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp new file mode 100644 index 0000000..1f42306 --- /dev/null +++ b/src/detection/camera/camera_windows.cpp @@ -0,0 +1,154 @@ +extern "C" { +#include "camera.h" +#include "common/library.h" +#include "common/windows/com.h" +} +#include "common/windows/unicode.hpp" +#include "common/windows/util.hpp" + +#include +#include +#include + +extern "C" const char* ffDetectCamera(FF_A_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 = FF_LIST_ADD(FFCameraResult, *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 = nullptr; + BOOL selected; + if (FAILED(pd->GetStreamDescriptorByIndex(0, &selected, &sd))) { + continue; + } + + IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = nullptr; + 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; + case (MFVideoPrimaries) 13: // MFVideoPrimaries_Display_P3 + ffStrbufSetStatic(&camera->colorspace, "Display P3"); + break; + default: + break; + } + } + + MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &camera->width, &camera->height); + break; + } + } + + if (devices) { + CoTaskMemFree(devices); + } + + return nullptr; +} diff --git a/src/detection/chassis/chassis.c b/src/detection/chassis/chassis.c new file mode 100644 index 0000000..d4d1fd3 --- /dev/null +++ b/src/detection/chassis/chassis.c @@ -0,0 +1,81 @@ +#include "chassis.h" + +const char* ffChassisTypeToString(uint32_t type) { + // 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 "Blade Enclosure"; + 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; + } +} diff --git a/src/detection/chassis/chassis.h b/src/detection/chassis/chassis.h new file mode 100644 index 0000000..4e57ad0 --- /dev/null +++ b/src/detection/chassis/chassis.h @@ -0,0 +1,14 @@ +#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); diff --git a/src/detection/chassis/chassis_apple.c b/src/detection/chassis/chassis_apple.c new file mode 100644 index 0000000..1ff4fee --- /dev/null +++ b/src/detection/chassis/chassis_apple.c @@ -0,0 +1,40 @@ +#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; +} diff --git a/src/detection/chassis/chassis_bsd.c b/src/detection/chassis/chassis_bsd.c new file mode 100644 index 0000000..fa13718 --- /dev/null +++ b/src/detection/chassis/chassis_bsd.c @@ -0,0 +1,16 @@ +#include "chassis.h" +#include "common/settings.h" +#include "common/smbios.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; +} diff --git a/src/detection/chassis/chassis_linux.c b/src/detection/chassis/chassis_linux.c new file mode 100644 index 0000000..af7ed0a --- /dev/null +++ b/src/detection/chassis/chassis_linux.c @@ -0,0 +1,39 @@ +#include "chassis.h" +#include "common/io.h" +#include "common/smbios.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; +} diff --git a/src/detection/chassis/chassis_nbsd.c b/src/detection/chassis/chassis_nbsd.c new file mode 100644 index 0000000..5644798 --- /dev/null +++ b/src/detection/chassis/chassis_nbsd.c @@ -0,0 +1,20 @@ +#include "chassis.h" +#include "common/sysctl.h" +#include "common/smbios.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; +} diff --git a/src/detection/chassis/chassis_nosupport.c b/src/detection/chassis/chassis_nosupport.c new file mode 100644 index 0000000..0fc4dd9 --- /dev/null +++ b/src/detection/chassis/chassis_nosupport.c @@ -0,0 +1,5 @@ +#include "chassis.h" + +const char* ffDetectChassis(FF_A_UNUSED FFChassisResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/chassis/chassis_windows.c b/src/detection/chassis/chassis_windows.c new file mode 100644 index 0000000..51a4c6c --- /dev/null +++ b/src/detection/chassis/chassis_windows.c @@ -0,0 +1,54 @@ +#include "chassis.h" +#include "common/smbios.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 +} FF_A_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; +} diff --git a/src/detection/codec/codec.c b/src/detection/codec/codec.c new file mode 100644 index 0000000..c7b3c45 --- /dev/null +++ b/src/detection/codec/codec.c @@ -0,0 +1,14 @@ +#include "codec.h" + +const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + if (options->useVulkan) { +#ifdef FF_HAVE_VULKAN + return ffDetectCodecVulkan(options, result); +#else + FF_UNUSED(options, result); + return "Fastfetch was built without Vulkan support"; +#endif + } + + return ffDetectCodecNative(options, result); +} diff --git a/src/detection/codec/codec.h b/src/detection/codec/codec.h new file mode 100644 index 0000000..d0f48d9 --- /dev/null +++ b/src/detection/codec/codec.h @@ -0,0 +1,43 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/codec/option.h" + +typedef enum FF_A_PACKED FFCodecType { + FF_CODEC_TYPE_NONE = 0, + FF_CODEC_TYPE_UNKNOWN = UINT32_C(1) << 0, + FF_CODEC_TYPE_H261 = UINT32_C(1) << 1, + FF_CODEC_TYPE_H263 = UINT32_C(1) << 2, + FF_CODEC_TYPE_MJPEG = UINT32_C(1) << 3, + FF_CODEC_TYPE_MPEG1 = UINT32_C(1) << 4, + FF_CODEC_TYPE_MPEG2 = UINT32_C(1) << 5, + FF_CODEC_TYPE_DIVX_XVID = UINT32_C(1) << 6, + FF_CODEC_TYPE_H264 = UINT32_C(1) << 7, + FF_CODEC_TYPE_WMV8 = UINT32_C(1) << 8, + FF_CODEC_TYPE_WMV9 = UINT32_C(1) << 9, + FF_CODEC_TYPE_VC1 = UINT32_C(1) << 10, + FF_CODEC_TYPE_VP8 = UINT32_C(1) << 11, + FF_CODEC_TYPE_HEVC = UINT32_C(1) << 12, + FF_CODEC_TYPE_VP9 = UINT32_C(1) << 13, + FF_CODEC_TYPE_AV1 = UINT32_C(1) << 14, + FF_CODEC_TYPE_VVC = UINT32_C(1) << 15, + FF_CODEC_TYPE_DOLBY_VISION_HEVC = UINT32_C(1) << 16, + FF_CODEC_TYPE_PRORES = UINT32_C(1) << 17, + FF_CODEC_TYPE_PRORES_RAW = UINT32_C(1) << 18, + FF_CODEC_TYPE_MAX = FF_CODEC_TYPE_PRORES_RAW, + FF_CODEC_TYPE_FORCE_UNSIGNED = UINT32_MAX, +} FFCodecType; + +typedef struct FFCodecResult { + FFstrbuf gpu; + FFCodecType decoders; + FFCodecType encoders; + const char* platformApi; +} FFCodecResult; + +const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); + +#ifdef FF_HAVE_VULKAN +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); +#endif diff --git a/src/detection/codec/codec_android.c b/src/detection/codec/codec_android.c new file mode 100644 index 0000000..238dab7 --- /dev/null +++ b/src/detection/codec/codec_android.c @@ -0,0 +1,102 @@ +#include "codec.h" + +#include "common/library.h" +#include "common/strutil.h" +#undef __INTRODUCED_IN +#define __INTRODUCED_IN(...) +#include + +static const struct { + const char* mime; + FFCodecType type; +} FF_CODEC_MIME_TO_TYPE[] = { + { "video/3gpp", FF_CODEC_TYPE_H263 }, + { "video/h263", FF_CODEC_TYPE_H263 }, + { "video/mjpeg", FF_CODEC_TYPE_MJPEG }, + { "video/mpeg2", FF_CODEC_TYPE_MPEG2 }, + { "video/mp2v-es", FF_CODEC_TYPE_MPEG2 }, + { "video/mp4v-es", FF_CODEC_TYPE_DIVX_XVID }, + { "video/avc", FF_CODEC_TYPE_H264 }, + { "video/hevc", FF_CODEC_TYPE_HEVC }, + { "video/x-vnd.on2.vp8", FF_CODEC_TYPE_VP8 }, + { "video/x-vnd.on2.vp9", FF_CODEC_TYPE_VP9 }, + { "video/av01", FF_CODEC_TYPE_AV1 }, + { "video/vvc", FF_CODEC_TYPE_VVC }, +}; + +static bool ffCodecIsLikelySoftware(const char* codecName) { + if (!codecName) { + return true; + } + + return ffStrStartsWith(codecName, "OMX.google.") || + ffStrStartsWith(codecName, "c2.android.") || + ffStrStartsWith(codecName, "OMX.ffmpeg.") || + ffStrStartsWith(codecName, "OMX.PV."); +} + +static bool ffCodecIsHardwareAccelerated( + AMediaCodec* codec, + __typeof__(&AMediaCodec_getName) ffAMediaCodec_getName, + __typeof__(&AMediaCodec_releaseName) ffAMediaCodec_releaseName) { + if (!codec) { + return false; + } + + char* codecName = NULL; + media_status_t status = ffAMediaCodec_getName(codec, &codecName); + if (status != AMEDIA_OK || !codecName) { + return false; + } + + bool isHardware = !ffCodecIsLikelySoftware(codecName); + ffAMediaCodec_releaseName(codec, codecName); + return isHardware; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_LIBRARY_LOAD_MESSAGE(mediandk, "libmediandk.so", 0) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createDecoderByType) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createEncoderByType) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_delete) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_getName) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_releaseName) + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_MIME_TO_TYPE); ++i) { + const char* mime = FF_CODEC_MIME_TO_TYPE[i].mime; + FFCodecType type = FF_CODEC_MIME_TO_TYPE[i].type; + + if ((options->showType & FF_CODEC_SHOW_TYPE_DECODER) && !(decoders & type)) { + AMediaCodec* decoder = ffAMediaCodec_createDecoderByType(mime); + if (decoder) { + if (ffCodecIsHardwareAccelerated(decoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) { + decoders |= type; + } + ffAMediaCodec_delete(decoder); + } + } + + if ((options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && !(encoders & type)) { + AMediaCodec* encoder = ffAMediaCodec_createEncoderByType(mime); + if (encoder) { + if (ffCodecIsHardwareAccelerated(encoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) { + encoders |= type; + } + ffAMediaCodec_delete(encoder); + } + } + } + + if (decoders != FF_CODEC_TYPE_NONE || encoders != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitStatic(&item->gpu, "Default"); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "AMediaCodec"; + } + + return NULL; +} diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c new file mode 100644 index 0000000..3778373 --- /dev/null +++ b/src/detection/codec/codec_apple.c @@ -0,0 +1,96 @@ +#include "codec.h" + +#include +#include "common/apple/cf_helpers.h" + +static const struct { + CMVideoCodecType codec; + FFCodecType type; +} FF_CODEC_CODECS[] = { + { 'h263', FF_CODEC_TYPE_H263 }, // kCMVideoCodecType_H263 + { 'jpeg', FF_CODEC_TYPE_MJPEG }, // kCMVideoCodecType_JPEG + { 'dmb1', FF_CODEC_TYPE_MJPEG }, // kCMVideoCodecType_JPEG_OpenDML + { 'mp1v', FF_CODEC_TYPE_MPEG1 }, // kCMVideoCodecType_MPEG1Video + { 'mp2v', FF_CODEC_TYPE_MPEG2 }, // kCMVideoCodecType_MPEG2Video + { 'mp4v', FF_CODEC_TYPE_DIVX_XVID }, // kCMVideoCodecType_MPEG4Video + { 'avc1', FF_CODEC_TYPE_H264 }, // kCMVideoCodecType_H264 + { 'hvc1', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_HEVC + { 'muxa', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_HEVCWithAlpha + { 'dvh1', FF_CODEC_TYPE_DOLBY_VISION_HEVC }, // kCMVideoCodecType_DolbyVisionHEVC + { 'dish', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_DisparityHEVC + { 'deph', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_DepthHEVC + { 'vp09', FF_CODEC_TYPE_VP9 }, // kCMVideoCodecType_VP9 + { 'av01', FF_CODEC_TYPE_AV1 }, // kCMVideoCodecType_AV1 + { 'ap4x', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes4444XQ + { 'ap4h', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes4444 + { 'apch', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422HQ + { 'apcn', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422 + { 'apcs', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422LT + { 'apco', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422Proxy + { 'aprn', FF_CODEC_TYPE_PRORES_RAW }, // kCMVideoCodecType_AppleProResRAW + { 'aprh', FF_CODEC_TYPE_PRORES_RAW }, // kCMVideoCodecType_AppleProResRAWHQ +}; + +static FFCodecType ffCodecCodecToType(CMVideoCodecType codec) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { + if (FF_CODEC_CODECS[i].codec == codec) { + return FF_CODEC_CODECS[i].type; + } + } + return FF_CODEC_TYPE_NONE; +} + +static FFCodecType ffCodecDetectEncoders(void) { + CFArrayRef encoderList = NULL; + if (VTCopyVideoEncoderList(NULL, &encoderList) != noErr || !encoderList) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType types = FF_CODEC_TYPE_NONE; + for (uint32_t i = 0; i < CFArrayGetCount(encoderList); ++i) { + CFDictionaryRef encoder = CFArrayGetValueAtIndex(encoderList, i); + bool isHardwareAccelerated; + int codec; + if (ffCfDictGetBool(encoder, CFSTR("IsHardwareAccelerated"), &isHardwareAccelerated) != NULL || + !isHardwareAccelerated || + ffCfDictGetInt(encoder, CFSTR("CodecType"), &codec) != NULL) { + continue; + } + types |= ffCodecCodecToType((CMVideoCodecType) codec); + } + + return types; +} + +static FFCodecType ffCodecDetectDecoders() { + FFCodecType types = FF_CODEC_TYPE_NONE; + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { + if (types & FF_CODEC_CODECS[i].type) { + continue; + } + + bool supported = VTIsHardwareDecodeSupported(FF_CODEC_CODECS[i].codec); + if (!supported) { + continue; + } + + types |= FF_CODEC_CODECS[i].type; + } + + return types; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FFCodecType decoders = options->showType & FF_CODEC_SHOW_TYPE_DECODER ? ffCodecDetectDecoders() : FF_CODEC_TYPE_NONE; + FFCodecType encoders = options->showType & FF_CODEC_SHOW_TYPE_ENCODER ? ffCodecDetectEncoders() : FF_CODEC_TYPE_NONE; + + if (decoders != FF_CODEC_TYPE_NONE || encoders != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitStatic(&item->gpu, "Default"); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "VideoToolbox"; + } + + return NULL; +} diff --git a/src/detection/codec/codec_linux.c b/src/detection/codec/codec_linux.c new file mode 100644 index 0000000..c25614e --- /dev/null +++ b/src/detection/codec/codec_linux.c @@ -0,0 +1,470 @@ +#include "codec.h" + +#if (FF_HAVE_DRM && FF_HAVE_VA) || FF_HAVE_VDPAU + + #include "common/library.h" + #include "common/mallocHelper.h" + #include "common/io.h" + + #if FF_HAVE_DRM && FF_HAVE_VA + #include "detection/gpu/gpu.h" + + #include + #include + #include + #include + #endif + + #include + + #if FF_HAVE_VDPAU + #include + #include + +VdpStatus vdp_device_create_x11(void* display, int screen, VdpDevice* device, VdpGetProcAddress** get_proc_address); +void* XOpenDisplay(const char* display_name); +int XCloseDisplay(void* display); +int XDefaultScreen(void* display); + #endif + + #if FF_HAVE_DRM && FF_HAVE_VA + +static FFCodecType ffCodecProfileToType(VAProfile profile) { + switch (profile) { + case 11: // VAProfileH263Baseline + return FF_CODEC_TYPE_H263; + + case 12: // VAProfileJPEGBaseline + return FF_CODEC_TYPE_MJPEG; + + case 0: // VAProfileMPEG2Simple + case 1: // VAProfileMPEG2Main + return FF_CODEC_TYPE_MPEG2; + + case 2: // VAProfileMPEG4Simple + case 3: // VAProfileMPEG4AdvancedSimple + case 4: // VAProfileMPEG4Main + return FF_CODEC_TYPE_DIVX_XVID; + + case 5: // VAProfileH264Baseline + case 6: // VAProfileH264Main + case 7: // VAProfileH264High + case 13: // VAProfileH264ConstrainedBaseline + case 15: // VAProfileH264MultiviewHigh + case 16: // VAProfileH264StereoHigh + case 36: // VAProfileH264High10 + case 40: // VAProfileH264High422 + return FF_CODEC_TYPE_H264; + + case 8: // VAProfileVC1Simple + case 9: // VAProfileVC1Main + case 10: // VAProfileVC1Advanced + return FF_CODEC_TYPE_VC1; + + case 14: // VAProfileVP8Version0_3 + return FF_CODEC_TYPE_VP8; + + case 17: // VAProfileHEVCMain + case 18: // VAProfileHEVCMain10 + case 23: // VAProfileHEVCMain12 + case 24: // VAProfileHEVCMain422_10 + case 25: // VAProfileHEVCMain422_12 + case 26: // VAProfileHEVCMain444 + case 27: // VAProfileHEVCMain444_10 + case 28: // VAProfileHEVCMain444_12 + case 29: // VAProfileHEVCSccMain + case 30: // VAProfileHEVCSccMain10 + case 31: // VAProfileHEVCSccMain444 + case 34: // VAProfileHEVCSccMain444_10 + return FF_CODEC_TYPE_HEVC; + + case 19: // VAProfileVP9Profile0 + case 20: // VAProfileVP9Profile1 + case 21: // VAProfileVP9Profile2 + case 22: // VAProfileVP9Profile3 + return FF_CODEC_TYPE_VP9; + + case 32: // VAProfileAV1Profile0 + case 33: // VAProfileAV1Profile1 + case 39: // VAProfileAV1Profile2 + return FF_CODEC_TYPE_AV1; + + case 37: // VAProfileVVCMain10 + case 38: // VAProfileVVCMultilayerMain10 + return FF_CODEC_TYPE_VVC; + + default: + return FF_CODEC_TYPE_UNKNOWN; + } +} + +static FFCodecShowType ffCodecGetEntrypointType(VAEntrypoint entrypoint) { + switch (entrypoint) { + case VAEntrypointVLD: + case VAEntrypointIDCT: + case VAEntrypointMoComp: + return FF_CODEC_SHOW_TYPE_DECODER; + case VAEntrypointEncSlice: + case VAEntrypointEncSliceLP: + case VAEntrypointFEI: + return FF_CODEC_SHOW_TYPE_ENCODER; + default: + return FF_CODEC_SHOW_TYPE_NONE; + } +} + +static bool ffCodecProfileHasOutput( + VADisplay display, + __typeof__(vaQueryConfigEntrypoints)* ffvaQueryConfigEntrypoints, + __typeof__(vaGetConfigAttributes)* ffvaGetConfigAttributes, + int maxEntrypoints, + VAEntrypoint* entrypoints, + VAProfile profile, + FFCodecShowType entrypointType) { + int numEntrypoints = maxEntrypoints; + if (ffvaQueryConfigEntrypoints(display, profile, entrypoints, &numEntrypoints) != VA_STATUS_SUCCESS) { + return false; + } + + for (int i = 0; i < numEntrypoints; ++i) { + if (ffCodecGetEntrypointType(entrypoints[i]) != entrypointType) { + continue; + } + + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + .value = 0, + }; + + if (ffvaGetConfigAttributes(display, profile, entrypoints[i], &attrib, 1) == VA_STATUS_SUCCESS && + attrib.value != VA_ATTRIB_NOT_SUPPORTED && + attrib.value != 0) { + return true; + } + } + + return false; +} + +static void ffCodecFillGpuName(const drmDevice* dev, const char* path, FFstrbuf* name) { + ffStrbufInit(name); + + switch (dev->bustype) { + case DRM_BUS_PCI: { + FFGPUResult gpu = { + .vendor = ffStrbufCreateStatic(ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id)), + .name = ffStrbufCreate(), + }; + + ffGPUFillVendorAndName(0, dev->deviceinfo.pci->vendor_id, dev->deviceinfo.pci->device_id, &gpu); + ffStrbufSetF(name, "%s %s", gpu.vendor.chars, gpu.name.chars); + + ffStrbufDestroy(&gpu.vendor); + ffStrbufDestroy(&gpu.name); + break; + } + case DRM_BUS_PLATFORM: + ffStrbufSetS(name, dev->deviceinfo.platform->compatible[0]); + return; + case DRM_BUS_HOST1X: + ffStrbufSetS(name, dev->deviceinfo.host1x->compatible[0]); + return; + case DRM_BUS_USB: + ffStrbufSetF(name, "0x%04X 0x%04X", dev->deviceinfo.usb->vendor, dev->deviceinfo.usb->product); + return; + default: + ffStrbufSetStatic(name, "Unknown GPU"); + return; + } + + if (!name->length && path && *path) { + const char* base = strrchr(path, '/'); + ffStrbufSetS(name, base ? base + 1 : path); + } + + if (!name->length) { + ffStrbufSetStatic(name, "Unknown GPU"); + } +} + +static const char* ffDetectCodecByVa(FFCodecOptions* options, FFlist* result) { + FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices) + + FF_LIBRARY_LOAD_MESSAGE(libva, "libva" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaInitialize) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaTerminate) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaMaxNumProfiles) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaMaxNumEntrypoints) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaQueryConfigProfiles) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaQueryConfigEntrypoints) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaGetConfigAttributes) + + FF_LIBRARY_LOAD_MESSAGE(libvaDrm, "libva-drm" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvaDrm, vaGetDisplayDRM) + + drmDevicePtr devices[64]; + int numDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices)); + if (numDevices < 0) { + return "drmGetDevices() failed"; + } + if (numDevices == 0) { + return "No DRM devices found"; + } + + const char* error = "No DRM device could initialize VA-API"; + + for (int i = 0; i < numDevices; ++i) { + drmDevice* dev = devices[i]; + + const char* path = NULL; + if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { + path = dev->nodes[DRM_NODE_RENDER]; + } else if (dev->available_nodes & (1 << DRM_NODE_PRIMARY)) { + path = dev->nodes[DRM_NODE_PRIMARY]; + } else { + continue; + } + + FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + fd = open(path, O_RDONLY | O_CLOEXEC); + } + if (fd < 0) { + continue; + } + + VADisplay display = ffvaGetDisplayDRM(fd); + if (!display) { + continue; + } + + int major = 0, minor = 0; + if (ffvaInitialize(display, &major, &minor) != VA_STATUS_SUCCESS) { + continue; + } + error = NULL; + + int maxProfiles = ffvaMaxNumProfiles(display); + int maxEntrypoints = ffvaMaxNumEntrypoints(display); + if (maxProfiles <= 0 || maxEntrypoints <= 0) { + ffvaTerminate(display); + continue; + } + + FF_AUTO_FREE VAProfile* profiles = (VAProfile*) malloc(sizeof(VAProfile) * (size_t) maxProfiles); + FF_AUTO_FREE VAEntrypoint* entrypoints = (VAEntrypoint*) malloc(sizeof(VAEntrypoint) * (size_t) maxEntrypoints); + + int numProfiles = maxProfiles; + if (ffvaQueryConfigProfiles(display, profiles, &numProfiles) != VA_STATUS_SUCCESS) { + ffvaTerminate(display); + continue; + } + + FFCodecType decoderTypes = FF_CODEC_TYPE_NONE; + FFCodecType encoderTypes = FF_CODEC_TYPE_NONE; + for (int j = 0; j < numProfiles; ++j) { + FFCodecType type = ffCodecProfileToType(profiles[j]); + + bool hasDecoder = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) && + !(decoderTypes & type) && + ffCodecProfileHasOutput( + display, + ffvaQueryConfigEntrypoints, + ffvaGetConfigAttributes, + maxEntrypoints, + entrypoints, + profiles[j], + FF_CODEC_SHOW_TYPE_DECODER); + + bool hasEncoder = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && + !(encoderTypes & type) && + ffCodecProfileHasOutput( + display, + ffvaQueryConfigEntrypoints, + ffvaGetConfigAttributes, + maxEntrypoints, + entrypoints, + profiles[j], + FF_CODEC_SHOW_TYPE_ENCODER); + + if (!hasDecoder && !hasEncoder) { + continue; + } + + if (hasDecoder) { + decoderTypes |= type; + } + if (hasEncoder) { + encoderTypes |= type; + } + } + + if (decoderTypes != FF_CODEC_TYPE_NONE || encoderTypes != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffCodecFillGpuName(dev, path, &item->gpu); + item->decoders = decoderTypes; + item->encoders = encoderTypes; + item->platformApi = "VA-API"; + } + + ffvaTerminate(display); + } + + ffdrmFreeDevices(devices, numDevices); + return error; +} + #endif + + #if FF_HAVE_VDPAU + +static const struct FFCodecVdpauCodec { + VdpDecoderProfile profile; + FFCodecType type; +} FF_CODEC_VDPAU_CODECS[] = { + { 0, FF_CODEC_TYPE_MPEG1 }, // VDP_DECODER_PROFILE_MPEG1 + { 1, FF_CODEC_TYPE_MPEG2 }, // VDP_DECODER_PROFILE_MPEG2_SIMPLE + { 2, FF_CODEC_TYPE_MPEG2 }, // VDP_DECODER_PROFILE_MPEG2_MAIN + { 12, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_SP + { 13, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_ASP + { 14, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_QMOBILE + { 15, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_MOBILE + { 16, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HOME_THEATER + { 17, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HD_1080P + { 18, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_QMOBILE + { 19, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_MOBILE + { 20, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HOME_THEATER + { 21, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HD_1080P + { 6, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_BASELINE + { 7, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_MAIN + { 8, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_HIGH + { 22, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE + { 23, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_EXTENDED + { 24, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_PROGRESSIVE_HIGH + { 25, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_CONSTRAINED_HIGH + { 26, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_HIGH_444_PREDICTIVE + { 9, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_SIMPLE + { 10, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_MAIN + { 11, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_ADVANCED + { 100, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN + { 101, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_10 + { 102, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_STILL + { 103, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_12 + { 104, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444 + { 105, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444_10 + { 106, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444_12 + { 27, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_0 + { 28, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_1 + { 29, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_2 + { 30, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_3 + { 107, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_MAIN + { 108, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_HIGH + { 109, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_PROFESSIONAL +}; + +static const char* ffDetectCodecByVdpau(FFCodecOptions* options, FFlist* result) { + if (options->showType == FF_CODEC_SHOW_TYPE_DECODER) { + return "VDPAU only supports decoding"; + } + + FF_LIBRARY_LOAD_MESSAGE(libX11, "libX11" FF_LIBRARY_EXTENSION, 6) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XOpenDisplay) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XCloseDisplay) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XDefaultScreen) + + FF_LIBRARY_LOAD_MESSAGE(libvdpau, "libvdpau" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvdpau, vdp_device_create_x11) + + void* x11Display = ffXOpenDisplay(NULL); + if (!x11Display) { + return "XOpenDisplay() failed"; + } + + VdpDevice device = VDP_INVALID_HANDLE; + VdpGetProcAddress* ffvdp_get_proc_address = NULL; + if (ffvdp_device_create_x11(x11Display, ffXDefaultScreen(x11Display), &device, &ffvdp_get_proc_address) != VDP_STATUS_OK || + device == VDP_INVALID_HANDLE || + ffvdp_get_proc_address == NULL) { + ffXCloseDisplay(x11Display); + return "vdp_device_create_x11() failed"; + } + + VdpDeviceDestroy* ffvdp_device_destroy = NULL; + VdpDecoderQueryCapabilities* ffvdp_decoder_query_capabilities = NULL; + if (ffvdp_get_proc_address(device, VDP_FUNC_ID_DEVICE_DESTROY, (void**) &ffvdp_device_destroy) != VDP_STATUS_OK || + ffvdp_get_proc_address(device, VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, (void**) &ffvdp_decoder_query_capabilities) != VDP_STATUS_OK || + ffvdp_device_destroy == NULL || + ffvdp_decoder_query_capabilities == NULL) { + if (ffvdp_device_destroy) { + ffvdp_device_destroy(device); + } + ffXCloseDisplay(x11Display); + return "ffvdp_get_proc_address() failed"; + } + + FFCodecType decoderTypes = FF_CODEC_TYPE_NONE; + + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_VDPAU_CODECS); ++i) { + const struct FFCodecVdpauCodec* codec = &FF_CODEC_VDPAU_CODECS[i]; + if (decoderTypes & codec->type) { + continue; + } + VdpBool isSupported = VDP_FALSE; + uint32_t maxLevel = 0, maxMacroblocks = 0, maxWidth = 0, maxHeight = 0; + if (ffvdp_decoder_query_capabilities(device, codec->profile, &isSupported, &maxLevel, &maxMacroblocks, &maxWidth, &maxHeight) != VDP_STATUS_OK || + !isSupported) { + continue; + } + + decoderTypes |= codec->type; + } + + ffvdp_device_destroy(device); + ffXCloseDisplay(x11Display); + + if (decoderTypes == FF_CODEC_TYPE_NONE) { + return NULL; + } + + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInit(&item->gpu); + const char* driver = getenv("VDPAU_DRIVER"); + if (driver && *driver) { + ffStrbufSetS(&item->gpu, driver); + } else { + ffStrbufSetStatic(&item->gpu, "Default"); + } + item->decoders = decoderTypes; + item->encoders = FF_CODEC_TYPE_NONE; + item->platformApi = "VDPAU"; + + return NULL; +} + #endif + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FF_SUPPRESS_IO(); + + #if FF_HAVE_DRM && FF_HAVE_VA + if (ffDetectCodecByVa(options, result) == NULL) { + return NULL; + } + #endif + #if FF_HAVE_VDPAU + if (ffDetectCodecByVdpau(options, result) == NULL) { + return NULL; + } + #endif + + return "Both libva and libvdpau fail to initialize"; +} + +#else + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FF_UNUSED(options, result); + return "Fastfetch was built without DRM / VA-API / VDPAU headers"; +} + +#endif diff --git a/src/detection/codec/codec_vulkan.c b/src/detection/codec/codec_vulkan.c new file mode 100644 index 0000000..eabfe26 --- /dev/null +++ b/src/detection/codec/codec_vulkan.c @@ -0,0 +1,270 @@ +#include "codec.h" + +#ifdef FF_HAVE_VULKAN + #include + #include "common/strutil.h" + + #if VK_KHR_video_decode_queue && VK_KHR_video_encode_queue + + #include "common/debug.h" + #include "common/io.h" + #include "common/library.h" + #include "common/mallocHelper.h" + +static FFCodecType ffCodecDecodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00000001 /*VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00000002 /*VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00000004 /*VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + if (operations & 0x00000008 /*VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR*/) { + types |= FF_CODEC_TYPE_VP9; + } + + return types; +} + +static FFCodecType ffCodecEncodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00010000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00020000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00040000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + + return types; +} + +static bool ffCodecHasDeviceExtension(const VkExtensionProperties* extensions, uint32_t extensionCount, const char* extensionName) { + for (uint32_t i = 0; i < extensionCount; ++i) { + if (ffStrEquals(extensions[i].extensionName, extensionName)) { + return true; + } + } + + return false; +} + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_DEBUG("Starting Vulkan codec 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) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumerateDeviceExtensionProperties) + + FF_SUPPRESS_IO(); + + uint32_t apiVersion = VK_API_VERSION_1_0; + PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); + if (ffvkEnumerateInstanceVersion != NULL) { + uint32_t detectedApiVersion = 0; + if (ffvkEnumerateInstanceVersion(&detectedApiVersion) == VK_SUCCESS) { + apiVersion = detectedApiVersion; + } + } + + const uint32_t projectVersion = VK_MAKE_VERSION( + FASTFETCH_PROJECT_VERSION_MAJOR, + FASTFETCH_PROJECT_VERSION_MINOR, + FASTFETCH_PROJECT_VERSION_PATCH); + + VkInstance vkInstance = VK_NULL_HANDLE; + VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &(VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = FASTFETCH_PROJECT_NAME, + .applicationVersion = projectVersion, + .pEngineName = "fastfetch-codec-vulkan", + .engineVersion = projectVersion, + .apiVersion = apiVersion, + }, + }, + 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"; + } + } + + PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties"); + if (!ffvkGetPhysicalDeviceProperties) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceProperties is not available"; + } + + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 ffvkGetPhysicalDeviceQueueFamilyProperties2 = (PFN_vkGetPhysicalDeviceQueueFamilyProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceQueueFamilyProperties2"); + if (!ffvkGetPhysicalDeviceQueueFamilyProperties2) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceQueueFamilyProperties2 is not available"; + } + + uint32_t physicalDeviceCount = 0; + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(count) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + if (physicalDeviceCount == 0) { + ffvkDestroyInstance(vkInstance, NULL); + return "No Vulkan physical devices found"; + } + + FF_AUTO_FREE VkPhysicalDevice* physicalDevices = (VkPhysicalDevice*) malloc(sizeof(VkPhysicalDevice) * (size_t) physicalDeviceCount); + + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(list) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + + bool sawVideoQueueExtension = false; + + for (uint32_t i = 0; i < physicalDeviceCount; ++i) { + VkPhysicalDeviceProperties properties = {}; + ffvkGetPhysicalDeviceProperties(physicalDevices[i], &properties); + + uint32_t extensionCount = 0; + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(count) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + if (extensionCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no extensions", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkExtensionProperties* extensions = (VkExtensionProperties*) malloc(sizeof(VkExtensionProperties) * (size_t) extensionCount); + + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, extensions); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(list) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + bool hasVideoDecode = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME); + bool hasVideoEncode = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME); + + if (!hasVideoDecode && !hasVideoEncode) { + FF_DEBUG("Skipping Vulkan device '%s' because it does not support video queue extensions", properties.deviceName); + continue; + } + sawVideoQueueExtension = true; + + uint32_t queueFamilyCount = 0; + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, NULL); + if (queueFamilyCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no queue families", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkQueueFamilyProperties2* queueFamilyProperties = (VkQueueFamilyProperties2*) malloc(sizeof(VkQueueFamilyProperties2) * (size_t) queueFamilyCount); + FF_AUTO_FREE VkQueueFamilyVideoPropertiesKHR* queueFamilyVideoProperties = (VkQueueFamilyVideoPropertiesKHR*) malloc(sizeof(VkQueueFamilyVideoPropertiesKHR) * (size_t) queueFamilyCount); + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + queueFamilyVideoProperties[queueIndex] = (VkQueueFamilyVideoPropertiesKHR){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR, + }; + queueFamilyProperties[queueIndex] = (VkQueueFamilyProperties2){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2, + .pNext = &queueFamilyVideoProperties[queueIndex], + }; + } + + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, queueFamilyProperties); + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + const VkQueueFlags queueFlags = queueFamilyProperties[queueIndex].queueFamilyProperties.queueFlags; + const VkVideoCodecOperationFlagsKHR operations = queueFamilyVideoProperties[queueIndex].videoCodecOperations; + + if (hasVideoDecode && (queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR)) { + decoders |= ffCodecDecodeOperationsToTypes(operations); + } + + if (hasVideoEncode && (queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR)) { + encoders |= ffCodecEncodeOperationsToTypes(operations); + } + } + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + FF_DEBUG("Skipping Vulkan device '%s' because no supported codec operations were reported", properties.deviceName); + continue; + } + + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitS(&item->gpu, properties.deviceName); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "Vulkan Video"; + FF_DEBUG("Added Vulkan codec result for '%s': decoders=%u encoders=%u", properties.deviceName, (unsigned) decoders, (unsigned) encoders); + } + + ffvkDestroyInstance(vkInstance, NULL); + + if (result->length > 0) { + return NULL; + } + + return sawVideoQueueExtension ? "No supported Vulkan video codec operations found" + : "VK_KHR_video_queue is not supported by any Vulkan physical device"; +} + + #else + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result) { + FF_UNUSED(options, result); + return "Vulkan video queue extensions are not supported by this Vulkan implementation"; +} + + #endif + +#endif diff --git a/src/detection/codec/codec_windows.cpp b/src/detection/codec/codec_windows.cpp new file mode 100644 index 0000000..df01a63 --- /dev/null +++ b/src/detection/codec/codec_windows.cpp @@ -0,0 +1,553 @@ +extern "C" { +#include "codec.h" +#include "common/library.h" +#include "common/windows/com.h" +#include "common/windows/unicode.h" +#include "common/windows/nt.h" +} + +#include +#include +#include +#include +#include +#include +#include + +typedef struct D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC { + UINT NodeIndex; + D3D12_VIDEO_ENCODER_CODEC Codec; + BOOL IsSupported; +} D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC; + +HRESULT MFTEnum2( + _In_ GUID guidCategory, + _In_ UINT32 Flags, + _In_ const MFT_REGISTER_TYPE_INFO* pInputType, + _In_ const MFT_REGISTER_TYPE_INFO* pOutputType, + _In_opt_ IMFAttributes* pAttributes, + _Out_ IMFActivate*** pppMFTActivate, + _Out_ UINT32* pnumMFTActivate); + +// clang-format off +#ifndef MFT_ENUM_ADAPTER_LUID +// {1D39518C-E220-4DA8-A07F-BA172552D6B1} +DEFINE_GUID(MFT_ENUM_ADAPTER_LUID, + 0x1d39518c, 0xe220, 0x4da8, 0xa0, 0x7f, 0xba, 0x17, 0x25, 0x52, 0xd6, 0xb1); +#endif +// clang-format on + +static const DXGI_FORMAT FF_NATIVE_CODEC_FORMATS[] = { + DXGI_FORMAT_420_OPAQUE, + DXGI_FORMAT_NV12, + DXGI_FORMAT_P010, + DXGI_FORMAT_P016, + DXGI_FORMAT_YUY2, + DXGI_FORMAT_Y210, + DXGI_FORMAT_Y216, + DXGI_FORMAT_AYUV, + DXGI_FORMAT_Y410, + DXGI_FORMAT_Y416, +}; + +static FFCodecType ffCodecProfileToTypeDx11(const GUID& profile) { + // clang-format off + if (IsEqualGUID(profile, DXVA_ModeH261_A) || + IsEqualGUID(profile, DXVA_ModeH261_B)) { + return FF_CODEC_TYPE_H261; + } + + if (IsEqualGUID(profile, DXVA_ModeH263_A) || + IsEqualGUID(profile, DXVA_ModeH263_B) || + IsEqualGUID(profile, DXVA_ModeH263_C) || + IsEqualGUID(profile, DXVA_ModeH263_D) || + IsEqualGUID(profile, DXVA_ModeH263_E) || + IsEqualGUID(profile, DXVA_ModeH263_F)) { + return FF_CODEC_TYPE_H263; + } + + if (IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_420) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_422) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_444) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_4444) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_420) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_422) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_444)) { + return FF_CODEC_TYPE_MJPEG; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG1_A) || + IsEqualGUID(profile, DXVA_ModeMPEG1_VLD)) { + return FF_CODEC_TYPE_MPEG1; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG2_A) || + IsEqualGUID(profile, DXVA_ModeMPEG2_B) || + IsEqualGUID(profile, DXVA_ModeMPEG2_C) || + IsEqualGUID(profile, DXVA_ModeMPEG2_D) || + IsEqualGUID(profile, DXVA_ModeMPEG2and1_VLD)) { + return FF_CODEC_TYPE_MPEG2; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_Simple) || + IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_AdvSimple_NoGMC) || + IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_AdvSimple_GMC)) { + return FF_CODEC_TYPE_DIVX_XVID; + } + + if (IsEqualGUID(profile, DXVA_ModeH264_E) || + IsEqualGUID(profile, DXVA_ModeH264_F) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_WithFMOASO_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Stereo_Progressive_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Stereo_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Multiview_NoFGT)) { + return FF_CODEC_TYPE_H264; + } + + if (IsEqualGUID(profile, DXVA_ModeWMV8_A) || + IsEqualGUID(profile, DXVA_ModeWMV8_B)) { + return FF_CODEC_TYPE_WMV8; + } + + if (IsEqualGUID(profile, DXVA_ModeWMV9_A) || + IsEqualGUID(profile, DXVA_ModeWMV9_B) || + IsEqualGUID(profile, DXVA_ModeWMV9_C)) { + return FF_CODEC_TYPE_WMV9; + } + + if (IsEqualGUID(profile, DXVA_ModeVC1_A) || + IsEqualGUID(profile, DXVA_ModeVC1_B) || + IsEqualGUID(profile, DXVA_ModeVC1_C) || + IsEqualGUID(profile, DXVA_ModeVC1_D) || + IsEqualGUID(profile, DXVA_ModeVC1_D2010)) { + return FF_CODEC_TYPE_VC1; + } + + if (IsEqualGUID(profile, DXVA_ModeVP8_VLD)) { + return FF_CODEC_TYPE_VP8; + } + + if (IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_422) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12_422) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_Ext) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main16)) { + return FF_CODEC_TYPE_HEVC; + } + + if (IsEqualGUID(profile, DXVA_ModeVP9_VLD_Profile0) || + IsEqualGUID(profile, DXVA_ModeVP9_VLD_10bit_Profile2)) { + return FF_CODEC_TYPE_VP9; + } + + if (IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile0) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile1) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile2) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_12bit_Profile2) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_12bit_Profile2_420)) { + return FF_CODEC_TYPE_AV1; + } + // clang-format on + + return FF_CODEC_TYPE_UNKNOWN; +} + +static FFCodecType ffCodecProfileToTypeDx12(const GUID& profile) { + // clang-format off + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG1_AND_MPEG2)) { + return FFCodecType(FF_CODEC_TYPE_MPEG1 | FF_CODEC_TYPE_MPEG2); + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG2)) { + return FF_CODEC_TYPE_MPEG2; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_SIMPLE) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_ADVSIMPLE_NOGMC)) { + return FF_CODEC_TYPE_DIVX_XVID; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_STEREO_PROGRESSIVE) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_STEREO) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_MULTIVIEW)) { + return FF_CODEC_TYPE_H264; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VC1) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VC1_D2010)) { + return FF_CODEC_TYPE_VC1; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP8)) { + return FF_CODEC_TYPE_VP8; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10)) { + return FF_CODEC_TYPE_HEVC; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP9) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2)) { + return FF_CODEC_TYPE_VP9; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420)) { + return FF_CODEC_TYPE_AV1; + } + // clang-format on + + return FF_CODEC_TYPE_UNKNOWN; +} + +static bool ffCodecProfileHasNativeOutput(ID3D11VideoDevice* videoDevice, const GUID& profile) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_NATIVE_CODEC_FORMATS); ++i) { + BOOL supported = FALSE; + if (SUCCEEDED(videoDevice->CheckVideoDecoderFormat(&profile, FF_NATIVE_CODEC_FORMATS[i], &supported)) && supported) { + return true; + } + } + + return false; +} + +static bool ffCodecProfileHasNativeOutput(ID3D12VideoDevice* videoDevice, const GUID& profile, UINT nodeIndex) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_NATIVE_CODEC_FORMATS); ++i) { + D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT support = { + .NodeIndex = nodeIndex, + .Configuration = { + .DecodeProfile = profile, + .BitstreamEncryption = D3D12_BITSTREAM_ENCRYPTION_TYPE_NONE, + .InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE, + }, + .Width = 1920, + .Height = 1080, + .DecodeFormat = FF_NATIVE_CODEC_FORMATS[i], + .FrameRate = { + .Numerator = 30, + .Denominator = 1, + }, + .BitRate = 10000000, + .SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE, + .ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE, + .DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED, + }; + + if (SUCCEEDED(videoDevice->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &support, sizeof(support))) && + (support.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED)) { + return true; + } + } + + return false; +} + +static bool ffCodecEncoderSupportedD3d12(ID3D12VideoDevice* videoDevice, D3D12_VIDEO_ENCODER_CODEC codec, UINT nodeIndex) { + D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC encoderCodec = { + .NodeIndex = nodeIndex, + .Codec = codec, + .IsSupported = FALSE, + }; + + return SUCCEEDED(videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_ENCODER_CODEC, + &encoderCodec, + sizeof(encoderCodec))) && + encoderCodec.IsSupported; +} + +typedef struct FFCodecMftEncoderSubtype { + const GUID* subtype; + FFCodecType codecType; +} FFCodecMftEncoderSubtype; + +static const FFCodecMftEncoderSubtype FF_D3D11VA_MFT_ENCODER_SUBTYPES[] = { + { &MFVideoFormat_H264, FF_CODEC_TYPE_H264 }, + { &MFVideoFormat_HEVC, FF_CODEC_TYPE_HEVC }, + { &MFVideoFormat_AV1, FF_CODEC_TYPE_AV1 }, +}; + +static FFCodecType ffDetectD3d11vaDecoders(IDXGIAdapter1* adapter, __typeof__(&D3D11CreateDevice) ffD3D11CreateDevice) { + ID3D11Device* FF_AUTO_RELEASE_COM_OBJECT d3dDevice = nullptr; + D3D_FEATURE_LEVEL featureLevel; + if (FAILED(ffD3D11CreateDevice( + adapter, + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + nullptr, + 0, + D3D11_SDK_VERSION, + &d3dDevice, + &featureLevel, + nullptr)) || + !d3dDevice) { + return FF_CODEC_TYPE_NONE; + } + + ID3D11VideoDevice* FF_AUTO_RELEASE_COM_OBJECT videoDevice = nullptr; + if (FAILED(d3dDevice->QueryInterface(__uuidof(ID3D11VideoDevice), (void**) &videoDevice)) || !videoDevice) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + UINT profileCount = videoDevice->GetVideoDecoderProfileCount(); + for (UINT profileIndex = 0; profileIndex < profileCount; ++profileIndex) { + GUID profile; + if (FAILED(videoDevice->GetVideoDecoderProfile(profileIndex, &profile))) { + continue; + } + + FFCodecType type = ffCodecProfileToTypeDx11(profile); + + if ((decoders & type) || !ffCodecProfileHasNativeOutput(videoDevice, profile)) { + continue; + } + + decoders = (FFCodecType) (((uint32_t) decoders) | ((uint32_t) type)); + } + return decoders; +} + +static FFCodecType ffDetectD3d11MftEncoders(const LUID& adapterLuid, __typeof__(&MFCreateAttributes) ffMFCreateAttributes, __typeof__(&MFTEnum2) ffMFTEnum2) { + IMFAttributes* FF_AUTO_RELEASE_COM_OBJECT attributes = nullptr; + if (FAILED(ffMFCreateAttributes(&attributes, 1)) || !attributes) { + return FF_CODEC_TYPE_NONE; + } + + if (FAILED(attributes->SetBlob(MFT_ENUM_ADAPTER_LUID, (const UINT8*) &adapterLuid, sizeof(adapterLuid)))) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t subtypeIndex = 0; subtypeIndex < ARRAY_SIZE(FF_D3D11VA_MFT_ENCODER_SUBTYPES); ++subtypeIndex) { + const FFCodecMftEncoderSubtype& subtype = FF_D3D11VA_MFT_ENCODER_SUBTYPES[subtypeIndex]; + if (encoders & subtype.codecType) { + continue; + } + + MFT_REGISTER_TYPE_INFO outputType = { + .guidMajorType = MFMediaType_Video, + .guidSubtype = *subtype.subtype, + }; + + IMFActivate** activateList = nullptr; + UINT32 activateCount = 0; + + if (SUCCEEDED(ffMFTEnum2( + MFT_CATEGORY_VIDEO_ENCODER, + MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER, + nullptr, + &outputType, + attributes, + &activateList, + &activateCount)) && + activateCount > 0) { + encoders = (FFCodecType) (((uint32_t) encoders) | ((uint32_t) subtype.codecType)); + } + + for (uint32_t i = 0; i < activateCount; ++i) { + activateList[i]->Release(); + } + CoTaskMemFree(activateList); + } + + return encoders; +} + +static FFCodecType ffCodecEncoderToType(D3D12_VIDEO_ENCODER_CODEC codec) { + switch (codec) { + case D3D12_VIDEO_ENCODER_CODEC_H264: + return FF_CODEC_TYPE_H264; + case D3D12_VIDEO_ENCODER_CODEC_HEVC: + return FF_CODEC_TYPE_HEVC; + case D3D12_VIDEO_ENCODER_CODEC_AV1: + return FF_CODEC_TYPE_AV1; + default: + return FF_CODEC_TYPE_UNKNOWN; + } +} + +template +static void ffEnumHardwareAdapters(IDXGIFactory1* factory, Func&& onAdapter) { + for (UINT adapterIndex = 0;; ++adapterIndex) { + IDXGIAdapter1* FF_AUTO_RELEASE_COM_OBJECT adapter = nullptr; + HRESULT adapterStatus = factory->EnumAdapters1(adapterIndex, &adapter); + if (adapterStatus == DXGI_ERROR_NOT_FOUND) { + break; + } + if (FAILED(adapterStatus) || !adapter) { + continue; + } + + DXGI_ADAPTER_DESC1 desc; + if (FAILED(adapter->GetDesc1(&desc))) { + continue; + } + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { + continue; + } + + onAdapter(adapter, desc); + } +} + +const char* detectD3d11va(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/, IDXGIFactory1* factory) { + FF_LIBRARY_LOAD_MESSAGE(d3d11, "d3d11" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d11, D3D11CreateDevice) + FF_LIBRARY_LOAD_MESSAGE(mfplat, "mfplat" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFCreateAttributes) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFTEnum2) + + ffEnumHardwareAdapters(factory, [&](IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) { + FFCodecType decoders = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) + ? ffDetectD3d11vaDecoders(adapter, ffD3D11CreateDevice) + : FF_CODEC_TYPE_NONE; + FFCodecType encoders = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) + ? ffDetectD3d11MftEncoders(desc.AdapterLuid, ffMFCreateAttributes, ffMFTEnum2) + : FF_CODEC_TYPE_NONE; + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + return; + } + + FFCodecResult* gpuResult = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitWS(&gpuResult->gpu, desc.Description); + gpuResult->decoders = decoders; + gpuResult->encoders = encoders; + gpuResult->platformApi = "D3D11VA+MFT"; + }); + + return nullptr; +} + +const char* detectD3d12va(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/, IDXGIFactory1* factory) { + FF_LIBRARY_LOAD_MESSAGE(d3d12, "d3d12" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d12, D3D12CreateDevice) + + static const GUID FF_D3D12_DECODE_PROFILES[] = { + D3D12_VIDEO_DECODE_PROFILE_MPEG2, + D3D12_VIDEO_DECODE_PROFILE_MPEG1_AND_MPEG2, + D3D12_VIDEO_DECODE_PROFILE_H264, + D3D12_VIDEO_DECODE_PROFILE_H264_STEREO_PROGRESSIVE, + D3D12_VIDEO_DECODE_PROFILE_H264_STEREO, + D3D12_VIDEO_DECODE_PROFILE_H264_MULTIVIEW, + D3D12_VIDEO_DECODE_PROFILE_VC1, + D3D12_VIDEO_DECODE_PROFILE_VC1_D2010, + D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_SIMPLE, + D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_ADVSIMPLE_NOGMC, + D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN, + D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10, + D3D12_VIDEO_DECODE_PROFILE_VP9, + D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_VP8, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420, + }; + + static const D3D12_VIDEO_ENCODER_CODEC FF_D3D12_ENCODER_CODECS[] = { + D3D12_VIDEO_ENCODER_CODEC_H264, + D3D12_VIDEO_ENCODER_CODEC_HEVC, + D3D12_VIDEO_ENCODER_CODEC_AV1, + }; + + const uint32_t resultLengthBefore = result->length; + + ffEnumHardwareAdapters(factory, [&](IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) { + ID3D12Device* FF_AUTO_RELEASE_COM_OBJECT d3dDevice = nullptr; + if (FAILED(ffD3D12CreateDevice( + adapter, + D3D_FEATURE_LEVEL_11_0, + __uuidof(ID3D12Device), + (void**) &d3dDevice)) || + !d3dDevice) { + return; + } + + ID3D12VideoDevice* FF_AUTO_RELEASE_COM_OBJECT videoDevice = nullptr; + if (FAILED(d3dDevice->QueryInterface(__uuidof(ID3D12VideoDevice), (void**) &videoDevice)) || !videoDevice) { + return; + } + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + if (options->showType & FF_CODEC_SHOW_TYPE_DECODER) { + for (uint32_t profileIndex = 0; profileIndex < ARRAY_SIZE(FF_D3D12_DECODE_PROFILES); ++profileIndex) { + const GUID& profile = FF_D3D12_DECODE_PROFILES[profileIndex]; + FFCodecType codecType = ffCodecProfileToTypeDx12(profile); + + if ((decoders & codecType) || !ffCodecProfileHasNativeOutput(videoDevice, profile, 0)) { + continue; + } + + decoders = (FFCodecType) (((uint32_t) decoders) | ((uint32_t) codecType)); + } + } + + FFCodecType encoders = FF_CODEC_TYPE_NONE; + if (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) { + for (uint32_t codecIndex = 0; codecIndex < ARRAY_SIZE(FF_D3D12_ENCODER_CODECS); ++codecIndex) { + D3D12_VIDEO_ENCODER_CODEC codec = FF_D3D12_ENCODER_CODECS[codecIndex]; + FFCodecType codecType = ffCodecEncoderToType(codec); + + if ((encoders & codecType) || !ffCodecEncoderSupportedD3d12(videoDevice, codec, 0)) { + continue; + } + + encoders = (FFCodecType) (((uint32_t) encoders) | ((uint32_t) codecType)); + } + } + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + return; + } + + FFCodecResult* gpuResult = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitWS(&gpuResult->gpu, desc.Description); + gpuResult->decoders = decoders; + gpuResult->encoders = encoders; + gpuResult->platformApi = "D3D12VA"; + }); + + if (result->length == resultLengthBefore) { + return "No D3D12 video acceleration support"; + } + + return nullptr; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_LIBRARY_LOAD_MESSAGE(dxgi, "dxgi" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(dxgi, CreateDXGIFactory1) + + const char* error = ffInitCom(); + if (error) { + return error; + } + + IDXGIFactory1* FF_AUTO_RELEASE_COM_OBJECT factory = nullptr; + if (FAILED(ffCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**) &factory)) || !factory) { + return "CreateDXGIFactory1() failed"; + } + + if (ffIsWindows11OrGreater()) { + // D3D12 video encoding is supported only on Windows 11 + if (detectD3d12va(options, result, factory) == nullptr) { + return nullptr; + } + } + return detectD3d11va(options, result, factory); +} diff --git a/src/detection/command/command.c b/src/detection/command/command.c new file mode 100644 index 0000000..28c6f4b --- /dev/null +++ b/src/detection/command/command.c @@ -0,0 +1,58 @@ +#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; + +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 = FF_LIST_ADD(FFCommandResultBundle, 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 (!FF_LIST_SHIFT(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; +} diff --git a/src/detection/command/command.h b/src/detection/command/command.h new file mode 100644 index 0000000..73acc0a --- /dev/null +++ b/src/detection/command/command.h @@ -0,0 +1,6 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/command/option.h" + +const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result); diff --git a/src/detection/cpu/cpu.c b/src/detection/cpu/cpu.c new file mode 100644 index 0000000..aadcdc4 --- /dev/null +++ b/src/detection/cpu/cpu.c @@ -0,0 +1,519 @@ +#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_A_UNUSED FFCPUResult* cpu) { + // Unsupported system +} + #endif + +#else + +void ffCPUDetectByCpuid(FF_A_UNUSED FFCPUResult* cpu) { + // Unsupported architecture +} + +#endif diff --git a/src/detection/cpu/cpu.h b/src/detection/cpu/cpu.h new file mode 100644 index 0000000..0f7eece --- /dev/null +++ b/src/detection/cpu/cpu.h @@ -0,0 +1,35 @@ +#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); diff --git a/src/detection/cpu/cpu_apple.c b/src/detection/cpu/cpu_apple.c new file mode 100644 index 0000000..f078b0c --- /dev/null +++ b/src/detection/cpu/cpu_apple.c @@ -0,0 +1,158 @@ +#include "cpu.h" +#include "common/sysctl.h" +#include "common/apple/smc_temps.h" +#include "common/strutil.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; +} diff --git a/src/detection/cpu/cpu_arm.h b/src/detection/cpu/cpu_arm.h new file mode 100644 index 0000000..571761a --- /dev/null +++ b/src/detection/cpu/cpu_arm.h @@ -0,0 +1,588 @@ +#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; + } +} diff --git a/src/detection/cpu/cpu_bsd.c b/src/detection/cpu/cpu_bsd.c new file mode 100644 index 0000000..4cca30e --- /dev/null +++ b/src/detection/cpu/cpu_bsd.c @@ -0,0 +1,109 @@ +#include "cpu.h" +#include "common/sysctl.h" +#include "common/strutil.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; +} diff --git a/src/detection/cpu/cpu_haiku.c b/src/detection/cpu/cpu_haiku.c new file mode 100644 index 0000000..9a8acb5 --- /dev/null +++ b/src/detection/cpu/cpu_haiku.c @@ -0,0 +1,68 @@ +#include "cpu.h" +#include "common/mallocHelper.h" + +#include +#include + +const char* ffDetectCPUImpl(FF_A_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; +} diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c new file mode 100644 index 0000000..b228d52 --- /dev/null +++ b/src/detection/cpu/cpu_linux.c @@ -0,0 +1,1098 @@ +#include "cpu.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/properties.h" +#include "common/mallocHelper.h" +#include "common/strutil.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 9965: + name = "2600"; + break; + 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_A_UNUSED FFstrbuf* physicalCoresBuffer, + FF_A_UNUSED FFstrbuf* cpuMHz, + FF_A_UNUSED FFstrbuf* cpuIsa, + FF_A_UNUSED FFstrbuf* cpuUarch, + FF_A_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_A_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_A_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(); + 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) { + *FF_LIST_ADD(uint32_t, 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_A_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_A_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_A_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_A_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 +} diff --git a/src/detection/cpu/cpu_nbsd.c b/src/detection/cpu/cpu_nbsd.c new file mode 100644 index 0000000..9c88c0e --- /dev/null +++ b/src/detection/cpu/cpu_nbsd.c @@ -0,0 +1,109 @@ +#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"; + } + + FF_A_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; +} diff --git a/src/detection/cpu/cpu_nosupport.c b/src/detection/cpu/cpu_nosupport.c new file mode 100644 index 0000000..309883d --- /dev/null +++ b/src/detection/cpu/cpu_nosupport.c @@ -0,0 +1,5 @@ +#include "cpu.h" + +const char* ffDetectCPUImpl(FF_A_UNUSED const FFCPUOptions* options, FF_A_UNUSED FFCPUResult* cpu) { + return "Not supported on this platform"; +} diff --git a/src/detection/cpu/cpu_obsd.c b/src/detection/cpu/cpu_obsd.c new file mode 100644 index 0000000..e5b8d2f --- /dev/null +++ b/src/detection/cpu/cpu_obsd.c @@ -0,0 +1,78 @@ +#include "cpu.h" +#include "common/sysctl.h" +#include "common/strutil.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; +} diff --git a/src/detection/cpu/cpu_sunos.c b/src/detection/cpu/cpu_sunos.c new file mode 100644 index 0000000..9b1cab5 --- /dev/null +++ b/src/detection/cpu/cpu_sunos.c @@ -0,0 +1,154 @@ +#include "cpu.h" +#include "common/processing.h" +#include "common/strutil.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) { + FF_A_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; +} diff --git a/src/detection/cpu/cpu_windows.c b/src/detection/cpu/cpu_windows.c new file mode 100644 index 0000000..86edce9 --- /dev/null +++ b/src/detection/cpu/cpu_windows.c @@ -0,0 +1,320 @@ +#include "cpu.h" +#include "common/windows/registry.h" +#include "common/windows/nt.h" +#include "common/mallocHelper.h" +#include "common/smbios.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 + } + } + + FF_A_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 +} FF_A_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; +} diff --git a/src/detection/cpucache/cpucache.h b/src/detection/cpucache/cpucache.h new file mode 100644 index 0000000..287154b --- /dev/null +++ b/src/detection/cpucache/cpucache.h @@ -0,0 +1,44 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/cpucache/option.h" + +typedef enum FF_A_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 = FF_LIST_ADD(FFCPUCache, *cacheLevel); + *item = (FFCPUCache) { + .size = size, + .num = 1, + .lineSize = lineSize, + .type = type, + }; + return item; +} diff --git a/src/detection/cpucache/cpucache_apple.c b/src/detection/cpucache/cpucache_apple.c new file mode 100644 index 0000000..3d1562c --- /dev/null +++ b/src/detection/cpucache/cpucache_apple.c @@ -0,0 +1,63 @@ +#include "cpucache.h" +#include "common/sysctl.h" +#include "common/strutil.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; +} diff --git a/src/detection/cpucache/cpucache_linux.c b/src/detection/cpucache/cpucache_linux.c new file mode 100644 index 0000000..0657c84 --- /dev/null +++ b/src/detection/cpucache/cpucache_linux.c @@ -0,0 +1,132 @@ +#include "cpucache.h" +#include "common/io.h" +#include "common/strutil.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; +} diff --git a/src/detection/cpucache/cpucache_nosupport.c b/src/detection/cpucache/cpucache_nosupport.c new file mode 100644 index 0000000..51d2256 --- /dev/null +++ b/src/detection/cpucache/cpucache_nosupport.c @@ -0,0 +1,5 @@ +#include "cpucache.h" + +const char* ffDetectCPUCache(FF_A_UNUSED FFCPUCacheResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/cpucache/cpucache_shared.c b/src/detection/cpucache/cpucache_shared.c new file mode 100644 index 0000000..c13e351 --- /dev/null +++ b/src/detection/cpucache/cpucache_shared.c @@ -0,0 +1,82 @@ +#include "cpucache.h" +#include "common/smbios.h" +#include "common/strutil.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 +} FF_A_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"; + } + + const FFSmbiosCacheInfo* endOfTable = (const FFSmbiosCacheInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_END_OF_TABLE]; + for (; data != endOfTable; data = (const FFSmbiosCacheInfo*) ffSmbiosNextEntry(&data->Header)) { + if (data->Header.Type != FF_SMBIOS_TYPE_CACHE_INFO) { + continue; + } + + 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; +} diff --git a/src/detection/cpucache/cpucache_windows.c b/src/detection/cpucache/cpucache_windows.c new file mode 100644 index 0000000..b9d3815 --- /dev/null +++ b/src/detection/cpucache/cpucache_windows.c @@ -0,0 +1,47 @@ +#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; +} diff --git a/src/detection/cpuusage/cpuusage.c b/src/detection/cpuusage/cpuusage.c new file mode 100644 index 0000000..a78ae7b --- /dev/null +++ b/src/detection/cpuusage/cpuusage.c @@ -0,0 +1,74 @@ +#include "fastfetch.h" +#include "detection/cpuusage/cpuusage.h" +#include "common/time.h" + +#include + +static FFlist cpuTimes1; +static uint64_t startTime; + +void ffPrepareCPUUsage(void) { + if (startTime != 0) { + return; // Already prepared + } + + ffListInit(&cpuTimes1); + ffGetCpuUsageInfo(&cpuTimes1); + startTime = ffTimeGetNow(); +} + +const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result) { + const char* error = NULL; + if (startTime == 0) { + ffListInit(&cpuTimes1); + 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(); + 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); + *FF_LIST_ADD(double, *result) = (double) (cpuTime2->inUseAll - cpuTime1->inUseAll) / (double) (cpuTime2->totalAll - cpuTime1->totalAll) * 100; + cpuTime1->inUseAll = cpuTime2->inUseAll; + cpuTime1->totalAll = cpuTime2->totalAll; + } + startTime = ffTimeGetNow(); + return NULL; +} diff --git a/src/detection/cpuusage/cpuusage.h b/src/detection/cpuusage/cpuusage.h new file mode 100644 index 0000000..353b729 --- /dev/null +++ b/src/detection/cpuusage/cpuusage.h @@ -0,0 +1,12 @@ +#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 diff --git a/src/detection/cpuusage/cpuusage_apple.c b/src/detection/cpuusage/cpuusage_apple.c new file mode 100644 index 0000000..50a8b15 --- /dev/null +++ b/src/detection/cpuusage/cpuusage_apple.c @@ -0,0 +1,33 @@ +#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 = FF_LIST_ADD(FFCpuUsageInfo, *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; +} diff --git a/src/detection/cpuusage/cpuusage_bsd.c b/src/detection/cpuusage/cpuusage_bsd.c new file mode 100644 index 0000000..f690d15 --- /dev/null +++ b/src/detection/cpuusage/cpuusage_bsd.c @@ -0,0 +1,58 @@ +#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 = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes); + *info = (FFCpuUsageInfo) { + .inUseAll = inUse, + .totalAll = total, + }; + } + + return NULL; +} diff --git a/src/detection/cpuusage/cpuusage_haiku.c b/src/detection/cpuusage/cpuusage_haiku.c new file mode 100644 index 0000000..f4d3c63 --- /dev/null +++ b/src/detection/cpuusage/cpuusage_haiku.c @@ -0,0 +1,27 @@ +#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 = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes); + info->inUseAll = (uint64_t) cpuInfo[i].active_time; + info->totalAll = uptime; + } + + return NULL; +} diff --git a/src/detection/cpuusage/cpuusage_linux.c b/src/detection/cpuusage/cpuusage_linux.c new file mode 100644 index 0000000..fd2f43b --- /dev/null +++ b/src/detection/cpuusage/cpuusage_linux.c @@ -0,0 +1,46 @@ +#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 = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes); + *info = (FFCpuUsageInfo) { + .inUseAll = inUse, + .totalAll = total, + }; + } else { + break; + } + start = token + 1; + } + + return NULL; +} diff --git a/src/detection/cpuusage/cpuusage_nosupport.c b/src/detection/cpuusage/cpuusage_nosupport.c new file mode 100644 index 0000000..faade7b --- /dev/null +++ b/src/detection/cpuusage/cpuusage_nosupport.c @@ -0,0 +1,6 @@ +#include "fastfetch.h" +#include "detection/cpuusage/cpuusage.h" + +const char* ffGetCpuUsageInfo(FF_A_UNUSED FFlist* cpuTimes) { + return "Not support on this platform"; +} diff --git a/src/detection/cpuusage/cpuusage_sunos.c b/src/detection/cpuusage/cpuusage_sunos.c new file mode 100644 index 0000000..813d74d --- /dev/null +++ b/src/detection/cpuusage/cpuusage_sunos.c @@ -0,0 +1,38 @@ +#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) { + FF_A_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 = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes); + *info = (FFCpuUsageInfo) { + .inUseAll = inUse, + .totalAll = total, + }; + } + return NULL; +} diff --git a/src/detection/cpuusage/cpuusage_windows.c b/src/detection/cpuusage/cpuusage_windows.c new file mode 100644 index 0000000..c23c28a --- /dev/null +++ b/src/detection/cpuusage/cpuusage_windows.c @@ -0,0 +1,169 @@ +#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 = FF_LIST_ADD(FFCpuUsageInfo, *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 = FF_LIST_ADD(FFCpuUsageInfo, *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; +} diff --git a/src/detection/cursor/cursor.h b/src/detection/cursor/cursor.h new file mode 100644 index 0000000..34ef86c --- /dev/null +++ b/src/detection/cursor/cursor.h @@ -0,0 +1,12 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/cursor/option.h" + +typedef struct FFCursorResult { + FFstrbuf theme; + FFstrbuf size; + FFstrbuf error; +} FFCursorResult; + +void ffDetectCursor(FFCursorResult* result); diff --git a/src/detection/cursor/cursor_apple.m b/src/detection/cursor/cursor_apple.m new file mode 100644 index 0000000..610bd1e --- /dev/null +++ b/src/detection/cursor/cursor_apple.m @@ -0,0 +1,63 @@ +#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"); +} diff --git a/src/detection/cursor/cursor_linux.c b/src/detection/cursor/cursor_linux.c new file mode 100644 index 0000000..89dabbc --- /dev/null +++ b/src/detection/cursor/cursor_linux.c @@ -0,0 +1,101 @@ +#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/strutil.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"); + } +} diff --git a/src/detection/cursor/cursor_nosupport.c b/src/detection/cursor/cursor_nosupport.c new file mode 100644 index 0000000..6413d92 --- /dev/null +++ b/src/detection/cursor/cursor_nosupport.c @@ -0,0 +1,5 @@ +#include "cursor.h" + +void ffDetectCursor(FF_A_UNUSED FFCursorResult* result) { + ffStrbufInitS(&result->error, "Not supported on this platform"); +} diff --git a/src/detection/cursor/cursor_windows.c b/src/detection/cursor/cursor_windows.c new file mode 100644 index 0000000..a6814ec --- /dev/null +++ b/src/detection/cursor/cursor_windows.c @@ -0,0 +1,17 @@ +#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)) { + if (ffRegReadStrbuf(hKey, NULL, &result->theme, &result->error)) { + uint32_t cursorBaseSize; + if (ffRegReadUint(hKey, L"CursorBaseSize", &cursorBaseSize, NULL)) { + // Not available on Windows 8.1 + ffStrbufAppendUInt(&result->size, cursorBaseSize); + }; + } + } +} diff --git a/src/detection/de/de.h b/src/detection/de/de.h new file mode 100644 index 0000000..67e8c1c --- /dev/null +++ b/src/detection/de/de.h @@ -0,0 +1,6 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/de/option.h" + +const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options); diff --git a/src/detection/de/de_linux.c b/src/detection/de/de_linux.c new file mode 100644 index 0000000..de7a220 --- /dev/null +++ b/src/detection/de/de_linux.c @@ -0,0 +1,284 @@ +#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_A_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_A_UNUSED FFstrbuf* result) { +#ifdef FF_HAVE_DBUS + FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; + if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) { + return "ffDBusLoadData() failed"; + } + + 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_A_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_A_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_A_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_A_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_A_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_A_UNUSED FFDEOptions* options) { + ffParsePropFileData("budgie/budgie-version.xml", "", result); +} + +static void getUnity(FFstrbuf* result, FF_A_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_A_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_A_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"; +} + +static const char* getEnlightenmentByDbus(FF_A_UNUSED FFstrbuf* result) { +#ifdef FF_HAVE_DBUS + FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; + if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) { + return "ffDBusLoadData() failed"; + } + + DBusMessage* reply = ffDBusGetMethodReply(&dbus, "org.enlightenment.wm.service", "/org/enlightenment/wm/RemoteObject", "org.enlightenment.wm.Core", "Version", NULL, NULL); + if (!reply) { + return "ffDBusGetMethodReply() failed"; + } + + DBusMessageIter rootIterator; + if (!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator)) { + dbus.lib->ffdbus_message_unref(reply); + return "dbus_message_iter_init() failed"; + } + if (!ffDBusGetString(&dbus, &rootIterator, result)) { + dbus.lib->ffdbus_message_unref(reply); + return "ffDBusGetString() failed"; + } + dbus.lib->ffdbus_message_unref(reply); + + return NULL; +#else // FF_HAVE_DBUS + return "ffDBusLoadData() failed: dbus support not compiled in"; +#endif // FF_HAVE_DBUS +} + +static void getEnlightenment(FFstrbuf* result, FF_A_UNUSED FFDEOptions* options) { + getEnlightenmentByDbus(result); + + if (result->length == 0) { + if (ffProcessAppendStdOut(result, (char* const[]) { "enlightenment", "--version", NULL }) == NULL) { // ...\nVersion: 0.27.1\n... + ffStrbufSubstrAfterFirstS(result, "Version: "); + ffStrbufSubstrBeforeFirstC(result, '\n'); + } + } +} +const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options) { + if (!instance.config.general.detectVersion) { + return "Disabled by config"; + } + + 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 if (ffStrbufEqualS(deName, FF_DE_PRETTY_ENLIGHTENMENT)) { + getEnlightenment(result, options); + } else { + return "Unsupported DE"; + } + return NULL; +} diff --git a/src/detection/de/de_nosupport.c b/src/detection/de/de_nosupport.c new file mode 100644 index 0000000..d870ae0 --- /dev/null +++ b/src/detection/de/de_nosupport.c @@ -0,0 +1,5 @@ +#include "de.h" + +const char* ffDetectDEVersion(FF_A_UNUSED const FFstrbuf* deName, FF_A_UNUSED FFstrbuf* result, FF_A_UNUSED FFDEOptions* options) { + return "Not supported on this platform"; +} diff --git a/src/detection/disk/disk.c b/src/detection/disk/disk.c new file mode 100644 index 0000000..c86282d --- /dev/null +++ b/src/detection/disk/disk.c @@ -0,0 +1,55 @@ +#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, sizeof(FFDisk), (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 diff --git a/src/detection/disk/disk.h b/src/detection/disk/disk.h new file mode 100644 index 0000000..57aaf2f --- /dev/null +++ b/src/detection/disk/disk.h @@ -0,0 +1,40 @@ +#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 diff --git a/src/detection/disk/disk_bsd.c b/src/detection/disk/disk_bsd.c new file mode 100644 index 0000000..9156d79 --- /dev/null +++ b/src/detection/disk/disk_bsd.c @@ -0,0 +1,214 @@ +#include "disk.h" +#include "common/mallocHelper.h" +#include "common/strutil.h" + +#include +#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_A_UNUSED struct statfs* fs, FF_A_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]; +} FF_A_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; + } + + alignas(4) 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 = FF_LIST_ADD(FFDisk, *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; +} diff --git a/src/detection/disk/disk_haiku.cpp b/src/detection/disk/disk_haiku.cpp new file mode 100644 index 0000000..0987897 --- /dev/null +++ b/src/detection/disk/disk_haiku.cpp @@ -0,0 +1,74 @@ +extern "C" { +#include "disk.h" +#include "common/strutil.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 = FF_LIST_ADD(FFDisk, *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; +} diff --git a/src/detection/disk/disk_linux.c b/src/detection/disk/disk_linux.c new file mode 100644 index 0000000..7205317 --- /dev/null +++ b/src/detection/disk/disk_linux.c @@ -0,0 +1,337 @@ +#include "disk.h" + +#include "common/io.h" +#include "common/strutil.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; + } + + ffStrbufDecodeHexEscapeSequences(&disk->name); +} + +#ifdef __ANDROID__ + +static void detectType(FF_A_UNUSED const FFlist* disks, FFDisk* currentDisk, FF_A_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 = FF_LIST_ADD(FFDisk, *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; +} diff --git a/src/detection/disk/disk_nosupport.c b/src/detection/disk/disk_nosupport.c new file mode 100644 index 0000000..eabddbc --- /dev/null +++ b/src/detection/disk/disk_nosupport.c @@ -0,0 +1,5 @@ +#include "disk.h" + +const char* ffDetectDisksImpl(FF_A_UNUSED FFDiskOptions* options, FF_A_UNUSED FFlist* disks) { + return "Not supported on this platform"; +} diff --git a/src/detection/disk/disk_sunos.c b/src/detection/disk/disk_sunos.c new file mode 100644 index 0000000..3d811e0 --- /dev/null +++ b/src/detection/disk/disk_sunos.c @@ -0,0 +1,145 @@ +#include "disk.h" +#include "common/io.h" +#include "common/strutil.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); + ffStrbufTrimRightSpace(&disk->name); + + 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 = FF_LIST_ADD(FFDisk, *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; +} diff --git a/src/detection/disk/disk_windows.c b/src/detection/disk/disk_windows.c new file mode 100644 index 0000000..0b3c93d --- /dev/null +++ b/src/detection/disk/disk_windows.c @@ -0,0 +1,128 @@ +#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 = FF_LIST_ADD(FFDisk, *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; +} diff --git a/src/detection/diskio/diskio.c b/src/detection/diskio/diskio.c new file mode 100644 index 0000000..6ff66be --- /dev/null +++ b/src/detection/diskio/diskio.c @@ -0,0 +1,88 @@ +#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); + 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); + 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; +} diff --git a/src/detection/diskio/diskio.h b/src/detection/diskio/diskio.h new file mode 100644 index 0000000..39c665a --- /dev/null +++ b/src/detection/diskio/diskio.h @@ -0,0 +1,15 @@ +#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); diff --git a/src/detection/diskio/diskio_apple.c b/src/detection/diskio/diskio_apple.c new file mode 100644 index 0000000..dc28907 --- /dev/null +++ b/src/detection/diskio/diskio_apple.c @@ -0,0 +1,57 @@ +#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(kIOBlockStorageDriverClass), &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 entryDriver = registryEntry; + + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryMedia = IO_OBJECT_NULL; + if (IORegistryEntryGetChildEntry(entryDriver, kIOServicePlane, &entryMedia) != KERN_SUCCESS) { + continue; + } + + io_name_t deviceName; + if (IORegistryEntryGetName(entryMedia, deviceName) != KERN_SUCCESS) { + continue; + } + + if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0) { + continue; + } + + FF_CFTYPE_AUTO_RELEASE CFDictionaryRef statistics = IORegistryEntryCreateCFProperty(entryDriver, CFSTR(kIOBlockStorageDriverStatisticsKey), kCFAllocatorDefault, kNilOptions); + if (!statistics) { + continue; + } + + FFDiskIOResult* device = FF_LIST_ADD(FFDiskIOResult, *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(entryMedia, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kNilOptions); + if (bsdName) { + ffCfStrGetString(bsdName, &device->devPath); + ffStrbufPrependS(&device->devPath, "/dev/"); + } + } + + return NULL; +} diff --git a/src/detection/diskio/diskio_bsd.c b/src/detection/diskio/diskio_bsd.c new file mode 100644 index 0000000..f5eb4b8 --- /dev/null +++ b/src/detection/diskio/diskio_bsd.c @@ -0,0 +1,120 @@ +#include "diskio.h" + +#if __has_include() + + #include "common/strutil.h" + + #include + #include + #include + #include + #include + #include + +const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { + FF_A_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 = FF_LIST_ADD(FFDiskIOResult, *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 = FF_LIST_ADD(FFDiskIOResult, *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 diff --git a/src/detection/diskio/diskio_linux.c b/src/detection/diskio/diskio_linux.c new file mode 100644 index 0000000..c7ddc55 --- /dev/null +++ b/src/detection/diskio/diskio_linux.c @@ -0,0 +1,99 @@ +#include "diskio.h" +#include "common/io.h" +#include "common/properties.h" +#include "common/strutil.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 = FF_LIST_ADD(FFDiskIOResult, *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; +} diff --git a/src/detection/diskio/diskio_nbsd.c b/src/detection/diskio/diskio_nbsd.c new file mode 100644 index 0000000..9ca9578 --- /dev/null +++ b/src/detection/diskio/diskio_nbsd.c @@ -0,0 +1,50 @@ +#include "diskio.h" +#include "common/io.h" +#include "common/strutil.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"; + } + + char path[64] = "/dev/"; + + 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; + } + + // Skip partitions + char* end = ffStrCopy(&path[5], st->name, ARRAY_SIZE(path) - 8); + *end++ = 'c'; + *end = '\0'; + if (!ffPathExists(path, FF_PATHTYPE_ANY)) { + continue; + } + + FFDiskIOResult* device = FF_LIST_ADD(FFDiskIOResult, *result); + ffStrbufInitNS(&device->devPath, (uint32_t) (end - path), path); + ffStrbufInitS(&device->name, st->name); + device->bytesRead = st->rbytes; + device->readCount = st->rxfer; + device->bytesWritten = st->wbytes; + device->writeCount = st->wxfer; + } + + return NULL; +} diff --git a/src/detection/diskio/diskio_nosupport.c b/src/detection/diskio/diskio_nosupport.c new file mode 100644 index 0000000..f5de15d --- /dev/null +++ b/src/detection/diskio/diskio_nosupport.c @@ -0,0 +1,5 @@ +#include "diskio.h" + +const char* ffDiskIOGetIoCounters(FF_A_UNUSED FFlist* result, FF_A_UNUSED FFDiskIOOptions* options) { + return "Not supported on this platform"; +} diff --git a/src/detection/diskio/diskio_obsd.c b/src/detection/diskio/diskio_obsd.c new file mode 100644 index 0000000..8dbc67e --- /dev/null +++ b/src/detection/diskio/diskio_obsd.c @@ -0,0 +1,39 @@ +#include "diskio.h" +#include "common/strutil.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 = FF_LIST_ADD(FFDiskIOResult, *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; +} diff --git a/src/detection/diskio/diskio_sunos.c b/src/detection/diskio/diskio_sunos.c new file mode 100644 index 0000000..7cda420 --- /dev/null +++ b/src/detection/diskio/diskio_sunos.c @@ -0,0 +1,42 @@ +#include "diskio.h" +#include "common/strutil.h" +#include + +static inline void kstatFreeWrap(kstat_ctl_t** pkc) { + assert(pkc); + if (*pkc) { + kstat_close(*pkc); + } +} + +const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { + FF_A_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 = FF_LIST_ADD(FFDiskIOResult, *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; +} diff --git a/src/detection/diskio/diskio_windows.c b/src/detection/diskio/diskio_windows.c new file mode 100644 index 0000000..80ad94e --- /dev/null +++ b/src/detection/diskio/diskio_windows.c @@ -0,0 +1,101 @@ +#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 = FF_LIST_ADD(FFDiskIOResult, *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; +} diff --git a/src/detection/displayserver/displayserver.c b/src/detection/displayserver/displayserver.c new file mode 100644 index 0000000..58c8c89 --- /dev/null +++ b/src/detection/displayserver/displayserver.c @@ -0,0 +1,67 @@ +#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 = FF_LIST_ADD(FFDisplayResult, 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; + static bool initialized = false; + if (!initialized) { + initialized = true; + ffStrbufInit(&result.wmProcessName); + ffStrbufInit(&result.wmPrettyName); + ffStrbufInit(&result.wmProtocolName); + ffStrbufInit(&result.deProcessName); + ffStrbufInit(&result.dePrettyName); + ffListInit(&result.displays); + ffConnectDisplayServerImpl(&result); + } + return &result; +} diff --git a/src/detection/displayserver/displayserver.h b/src/detection/displayserver/displayserver.h new file mode 100644 index 0000000..fd88f37 --- /dev/null +++ b/src/detection/displayserver/displayserver.h @@ -0,0 +1,123 @@ +#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_DE_PRETTY_NEBIDE "NebiDE" +#define FF_DE_PRETTY_ENLIGHTENMENT "Enlightenment" + +#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_COSMIC_COMP "cosmic-comp" +#define FF_WM_PRETTY_RATPOISON "ratpoison" +#define FF_WM_PRETTY_ENLIGHTENMENT "Enlightenment" + +#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 FF_A_PACKED FFDisplayType { + FF_DISPLAY_TYPE_UNKNOWN, + FF_DISPLAY_TYPE_BUILTIN, + FF_DISPLAY_TYPE_EXTERNAL, +} FFDisplayType; + +typedef enum FF_A_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 FF_A_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); diff --git a/src/detection/displayserver/displayserver_android.c b/src/detection/displayserver/displayserver_android.c new file mode 100644 index 0000000..9b2848a --- /dev/null +++ b/src/detection/displayserver/displayserver_android.c @@ -0,0 +1,209 @@ +#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); +} diff --git a/src/detection/displayserver/displayserver_apple.c b/src/detection/displayserver/displayserver_apple.c new file mode 100644 index 0000000..39ba604 --- /dev/null +++ b/src/detection/displayserver/displayserver_apple.c @@ -0,0 +1,197 @@ +#include "displayserver.h" +#include "common/apple/cf_helpers.h" +#include "common/strutil.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) FF_A_WEAK_IMPORT; +extern Boolean CoreDisplay_Display_IsHDRModeEnabled(CGDirectDisplayID display) FF_A_WEAK_IMPORT; +extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID display) FF_A_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); +} diff --git a/src/detection/displayserver/displayserver_haiku.cpp b/src/detection/displayserver/displayserver_haiku.cpp new file mode 100644 index 0000000..c1ddaf6 --- /dev/null +++ b/src/detection/displayserver/displayserver_haiku.cpp @@ -0,0 +1,71 @@ +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); +} diff --git a/src/detection/displayserver/displayserver_windows.c b/src/detection/displayserver/displayserver_windows.c new file mode 100644 index 0000000..f92c144 --- /dev/null +++ b/src/detection/displayserver/displayserver_windows.c @@ -0,0 +1,219 @@ +#include "displayserver.h" +#include "common/edidHelper.h" +#include "common/windows/registry.h" +#include "common/windows/unicode.h" + +#include +#include + +static inline void freeArgBuffer(FFArgBuffer* buffer) { + if (buffer->data) { + free(buffer->data); + } + buffer->data = NULL; + buffer->length = 0; +} +#define FF_AUTO_FREE_ARG_BUFFER FF_A_CLEANUP(freeArgBuffer) + +// 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_AUTO_FREE_ARG_BUFFER FFArgBuffer edid = {}; + 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 { + edid.length = 0; + 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); +} diff --git a/src/detection/displayserver/linux/common.c b/src/detection/displayserver/linux/common.c new file mode 100644 index 0000000..305766d --- /dev/null +++ b/src/detection/displayserver/linux/common.c @@ -0,0 +1,16 @@ +#include "displayserver_linux.h" +#include "common/strutil.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; +} diff --git a/src/detection/displayserver/linux/displayserver_linux.c b/src/detection/displayserver/linux/displayserver_linux.c new file mode 100644 index 0000000..70d2d40 --- /dev/null +++ b/src/detection/displayserver/linux/displayserver_linux.c @@ -0,0 +1,91 @@ +#include "displayserver_linux.h" +#include "common/io.h" +#include "common/strutil.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); + } +} diff --git a/src/detection/displayserver/linux/displayserver_linux.h b/src/detection/displayserver/linux/displayserver_linux.h new file mode 100644 index 0000000..19ae4e7 --- /dev/null +++ b/src/detection/displayserver/linux/displayserver_linux.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/displayserver/linux/drm.c b/src/detection/displayserver/linux/drm.c new file mode 100644 index 0000000..43c6afc --- /dev/null +++ b/src/detection/displayserver/linux/drm.c @@ -0,0 +1,437 @@ +#include "displayserver_linux.h" +#include "common/io.h" +#include "common/edidHelper.h" +#include "common/strutil.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_A_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_A_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"; +} diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c new file mode 100644 index 0000000..51fb425 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -0,0 +1,173 @@ +#ifdef FF_HAVE_WAYLAND + + #include "wayland.h" + #include "common/strutil.h" + #include "xdg-output-unstable-v1-client-protocol.h" + +static void waylandOutputModeListener(void* data, FF_A_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_A_UNUSED struct wl_output* output, int32_t scale) { + WaylandDisplay* display = data; + display->dpi = 96 * (uint32_t) scale; +} + +static void waylandOutputGeometryListener(void* data, + FF_A_UNUSED struct wl_output* output, + FF_A_UNUSED int32_t x, + FF_A_UNUSED int32_t y, + int32_t physical_width, + int32_t physical_height, + FF_A_UNUSED int32_t subpixel, + FF_A_UNUSED const char* make, + FF_A_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_A_UNUSED struct zxdg_output_v1* _, int32_t width, FF_A_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) { + const char* api = "wayland-global"; + uint32_t bindVersion = min(version, WL_OUTPUT_DESCRIPTION_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, wldata->ffwl_output_interface, bindVersion, name, wldata->ffwl_output_interface->name, bindVersion, NULL); + if (output == NULL) { + return "Failed to create wl_output"; + } + + 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) { + uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION); + struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, bindVersion, NULL, output); + + if (zxdgOutput) { + wldata->ffwl_proxy_add_listener(zxdgOutput, (void (**)(void)) &zxdgOutputListener, &display); + wldata->ffwl_display_roundtrip(wldata->display); + wldata->ffwl_proxy_destroy(zxdgOutput); + api = "wayland-global-zxdg"; + } + } + + 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, + api); + 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) { + uint32_t bindVersion = min(version, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION); + struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, bindVersion, name, zxdg_output_manager_v1_interface.name, bindVersion, NULL); + if (manager == NULL) { + return "Failed to create zxdg_output_manager_v1"; + } + + wldata->zxdgOutputManager = manager; + + return NULL; +} + +#endif diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h new file mode 100644 index 0000000..44ab8ab --- /dev/null +++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h @@ -0,0 +1,1384 @@ +/* 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, + /** + * if this outputdevice supports HDR ICC profiles + * @since 22 + */ + KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE = 0x8000, + /** + * if this outputdevice supports the abm level setting + * @since 23 + */ + KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL = 0x10000, +}; + /** + * @ingroup iface_kde_output_device_v2 + */ + #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 + /** + * @ingroup iface_kde_output_device_v2 + */ + #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE_SINCE_VERSION 22 + /** + * @ingroup iface_kde_output_device_v2 + */ + #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL_SINCE_VERSION 23 +#endif /* KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM */ + +#ifndef KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM + #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 the path to the ICC profile used in SDR mode + * + * + * @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 in SDR mode + * + * + * @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); + /** + * describes the path to the ICC profile used in HDR mode + * + * + * @since 22 + */ + void (*hdr_icc_profile_path)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + const char* profile_path); + /** + * describes which source the compositor uses for the color profile on an output in HDR mode + * + * + * @since 22 + */ + void (*hdr_color_profile_source)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + uint32_t source); + /** + * allowed level of adaptive backlight modulation + * + * Adaptive backlight modulation is a feature that reduces the + * backlight and increases contrast of colors on the screen to + * improve power usage. + * @param level 0 is off, 4 is the maximum level + * @since 23 + */ + void (*abm_level)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + uint32_t level); +}; + +/** + * @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_HDR_ICC_PROFILE_PATH_SINCE_VERSION 22 +/** + * @ingroup iface_kde_output_device_v2 + */ +#define KDE_OUTPUT_DEVICE_V2_HDR_COLOR_PROFILE_SOURCE_SINCE_VERSION 22 +/** + * @ingroup iface_kde_output_device_v2 + */ +#define KDE_OUTPUT_DEVICE_V2_ABM_LEVEL_SINCE_VERSION 23 + +/** + * @ingroup iface_kde_output_device_v2 + */ +#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 diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c new file mode 100644 index 0000000..9dd7556 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c @@ -0,0 +1,128 @@ +#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", + 23, + 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 }, + { "hdr_icc_profile_path", "22s", kde_output_device_v2_types + 0 }, + { "hdr_color_profile_source", "22u", kde_output_device_v2_types + 0 }, + { "abm_level", "23u", kde_output_device_v2_types + 0 }, +}; + +WL_EXPORT const struct wl_interface kde_output_device_v2_interface = { + "kde_output_device_v2", + 23, + 1, + kde_output_device_v2_requests, + 40, + 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", + 22, + 0, + NULL, + 5, + kde_output_device_mode_v2_events, +}; + +#endif diff --git a/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h new file mode 100644 index 0000000..b9928c3 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h @@ -0,0 +1,134 @@ +/* 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 diff --git a/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c b/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c new file mode 100644 index 0000000..00db32a --- /dev/null +++ b/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c @@ -0,0 +1,37 @@ +#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 diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c new file mode 100644 index 0000000..90d5770 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -0,0 +1,300 @@ +#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_A_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_A_UNUSED struct kde_output_device_mode_v2* _, int32_t rate) { + WaylandKdeMode* mode = (WaylandKdeMode*) data; + mode->refreshRate = rate; +} + +static void waylandKdeModePreferredListener(void* data, FF_A_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_A_UNUSED struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) { + WaylandDisplay* wldata = (WaylandDisplay*) data; + if (!wldata->internal) { + return; + } + + WaylandKdeMode* newMode = FF_LIST_ADD(WaylandKdeMode, *(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_A_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_A_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_A_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_A_UNUSED struct kde_output_device_v2* _, int32_t enabled) { + WaylandDisplay* wldata = (WaylandDisplay*) data; + if (!enabled) { + wldata->internal = NULL; + } +} + +static void waylandKdeGeometryListener(void* data, + FF_A_UNUSED struct kde_output_device_v2* kde_output_device_v2, + FF_A_UNUSED int32_t x, + FF_A_UNUSED int32_t y, + int32_t physical_width, + int32_t physical_height, + FF_A_UNUSED int32_t subpixel, + FF_A_UNUSED const char* make, + FF_A_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_A_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_A_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_A_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, +}; + +const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { + uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, bindVersion, name, kde_output_device_v2_interface.name, bindVersion, NULL); + if (output == NULL) { + return "Failed to create kde_output_device_v2"; + } + + FF_LIST_AUTO_DESTROY modes = ffListCreate(); + 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"; + } + // Destroy any mode proxies that were created during the listeners. + // wl proxies created for modes are not automatically freed by destroying + // the parent output proxy, so destroy them explicitly to avoid leaks. + FF_LIST_FOR_EACH (WaylandKdeMode, m, modes) { + if (m->pMode) { + wldata->ffwl_proxy_destroy((struct wl_proxy*) m->pMode); + m->pMode = NULL; + } + } + + 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_A_UNUSED struct kde_output_order_v1* _, const char* output_name) { + uint64_t* id = (uint64_t*) data; + if (*id == 0) { + *id = ffWaylandGenerateIdFromName(output_name); + } +} + +static const struct kde_output_order_v1_listener orderListener = { + .output = waylandKdeOutputOrderListener, + .done = (void*) stubListener, +}; + +const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { + uint32_t bindVersion = min(version, KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_order_v1_interface, bindVersion, name, kde_output_order_v1_interface.name, bindVersion, NULL); + if (output == NULL) { + return "Failed to create kde_output_order_v1"; + } + + 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 diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c new file mode 100644 index 0000000..064c239 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -0,0 +1,340 @@ +#include "../displayserver_linux.h" +#include "common/io.h" +#include "common/edidHelper.h" +#include "common/strutil.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_A_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_A_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_A_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_A_UNUSED FFDisplayServerResult* result) { + return "Fastfetch was compiled without Wayland support"; +} + +#endif diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h new file mode 100644 index 0000000..b103ab9 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -0,0 +1,89 @@ +#pragma once + +#ifdef FF_HAVE_WAYLAND + + #include "common/library.h" + #include "common/strutil.h" + + #include + + #include "../displayserver_linux.h" + +static inline uint32_t min(uint32_t a, uint32_t b) { + return a < b ? a : b; +} + +typedef enum FF_A_PACKED WaylandProtocolType { + FF_WAYLAND_PROTOCOL_TYPE_NONE, + FF_WAYLAND_PROTOCOL_TYPE_GLOBAL, + 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_A_UNUSED void* output, const char* name); +void ffWaylandOutputDescriptionListener(void* data, FF_A_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 diff --git a/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h new file mode 100644 index 0000000..2b0584d --- /dev/null +++ b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h @@ -0,0 +1,1292 @@ +/* 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 diff --git a/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c new file mode 100644 index 0000000..a231fd4 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c @@ -0,0 +1,176 @@ +#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 diff --git a/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h new file mode 100644 index 0000000..51784c6 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h @@ -0,0 +1,414 @@ +/* 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 diff --git a/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c new file mode 100644 index 0000000..76f6257 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c @@ -0,0 +1,76 @@ +#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 diff --git a/src/detection/displayserver/linux/wayland/zwlr-output.c b/src/detection/displayserver/linux/wayland/zwlr-output.c new file mode 100644 index 0000000..632bd19 --- /dev/null +++ b/src/detection/displayserver/linux/wayland/zwlr-output.c @@ -0,0 +1,212 @@ +#ifdef FF_HAVE_WAYLAND + + #include "wayland.h" + #include "wlr-output-management-unstable-v1-client-protocol.h" + +static void waylandZwlrTransformListener(void* data, FF_A_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_A_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_A_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_A_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_A_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_A_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 = FF_LIST_ADD(WaylandZwlrMode, *(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_A_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_A_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_A_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_A_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(); + 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); +} + +static const struct zwlr_output_manager_v1_listener outputListener = { + .head = waylandHandleZwlrHead, + .done = (void*) stubListener, + .finished = (void*) stubListener, +}; + +const char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { + uint32_t bindVersion = min(version, ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zwlr_output_manager_v1_interface, bindVersion, name, zwlr_output_manager_v1_interface.name, bindVersion, NULL); + if (output == NULL) { + return "Failed to bind zwlr_output_manager_v1"; + } + + 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 diff --git a/src/detection/displayserver/linux/wmde.c b/src/detection/displayserver/linux/wmde.c new file mode 100644 index 0000000..f0321c8 --- /dev/null +++ b/src/detection/displayserver/linux/wmde.c @@ -0,0 +1,523 @@ +#include "displayserver_linux.h" +#include "common/io.h" +#include "common/properties.h" +#include "common/strutil.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); + } + + else if (ffStrEqualsIgnCase(name, "Enlightenment")) { + ffStrbufSetS(&result->deProcessName, "enlightenment_start"); + ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT); + } +} + +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, (int) 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); + } +} diff --git a/src/detection/displayserver/linux/xcb.c b/src/detection/displayserver/linux/xcb.c new file mode 100644 index 0000000..8620d71 --- /dev/null +++ b/src/detection/displayserver/linux/xcb.c @@ -0,0 +1,441 @@ +#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/strutil.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 diff --git a/src/detection/displayserver/linux/xlib.c b/src/detection/displayserver/linux/xlib.c new file mode 100644 index 0000000..2beffcc --- /dev/null +++ b/src/detection/displayserver/linux/xlib.c @@ -0,0 +1,365 @@ +#include "displayserver_linux.h" + +#ifdef FF_HAVE_XRANDR + + #include "common/library.h" + #include "common/properties.h" + #include "common/edidHelper.h" + #include "common/strutil.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 diff --git a/src/detection/dns/dns.h b/src/detection/dns/dns.h new file mode 100644 index 0000000..3653f62 --- /dev/null +++ b/src/detection/dns/dns.h @@ -0,0 +1,6 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/dns/option.h" + +const char* ffDetectDNS(FFDNSOptions* options, FFlist* results /* list of FFstrbuf */); diff --git a/src/detection/dns/dns_apple.c b/src/detection/dns/dns_apple.c new file mode 100644 index 0000000..e490ffe --- /dev/null +++ b/src/detection/dns/dns_apple.c @@ -0,0 +1,106 @@ +#include "detection/dns/dns.h" + +#include "common/io.h" +#include "common/mallocHelper.h" +#include "common/strutil.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: %s", path, strerror(errno)); + 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 = FF_LIST_ADD(FFstrbuf, *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 = FF_LIST_ADD(FFstrbuf, *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); +} diff --git a/src/detection/dns/dns_linux.c b/src/detection/dns/dns_linux.c new file mode 100644 index 0000000..41e008c --- /dev/null +++ b/src/detection/dns/dns_linux.c @@ -0,0 +1,143 @@ +#include "detection/dns/dns.h" + +#include "common/io.h" +#include "common/mallocHelper.h" +#include "common/strutil.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: %s", path, strerror(errno)); + 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 = FF_LIST_ADD(FFstrbuf, *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; +} diff --git a/src/detection/dns/dns_windows.c b/src/detection/dns/dns_windows.c new file mode 100644 index 0000000..b28eefb --- /dev/null +++ b/src/detection/dns/dns_windows.c @@ -0,0 +1,63 @@ +#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 = FF_LIST_ADD(FFstrbuf, *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; +} diff --git a/src/detection/editor/editor.c b/src/detection/editor/editor.c new file mode 100644 index 0000000..b84f279 --- /dev/null +++ b/src/detection/editor/editor.c @@ -0,0 +1,160 @@ +#include "editor.h" +#include "common/processing.h" +#include "common/library.h" +#include "common/strutil.h" +#include "common/path.h" +#include "common/binary.h" + +#include + +static bool extractNvimVersionFromBinary(const char* str, FF_A_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_A_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_A_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; +} diff --git a/src/detection/editor/editor.h b/src/detection/editor/editor.h new file mode 100644 index 0000000..ae315c0 --- /dev/null +++ b/src/detection/editor/editor.h @@ -0,0 +1,14 @@ +#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); diff --git a/src/detection/font/font.c b/src/detection/font/font.c new file mode 100644 index 0000000..c651f29 --- /dev/null +++ b/src/detection/font/font.c @@ -0,0 +1,19 @@ +#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"; +} diff --git a/src/detection/font/font.h b/src/detection/font/font.h new file mode 100644 index 0000000..af04c6d --- /dev/null +++ b/src/detection/font/font.h @@ -0,0 +1,20 @@ +#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); diff --git a/src/detection/font/font_apple.m b/src/detection/font/font_apple.m new file mode 100644 index 0000000..e8aff3c --- /dev/null +++ b/src/detection/font/font_apple.m @@ -0,0 +1,37 @@ +#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; +} diff --git a/src/detection/font/font_haiku.cpp b/src/detection/font/font_haiku.cpp new file mode 100644 index 0000000..dc29dd0 --- /dev/null +++ b/src/detection/font/font_haiku.cpp @@ -0,0 +1,54 @@ +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; +} diff --git a/src/detection/font/font_linux.c b/src/detection/font/font_linux.c new file mode 100644 index 0000000..5e82b68 --- /dev/null +++ b/src/detection/font/font_linux.c @@ -0,0 +1,53 @@ +#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; +} diff --git a/src/detection/font/font_nosupport.c b/src/detection/font/font_nosupport.c new file mode 100644 index 0000000..26f9ec6 --- /dev/null +++ b/src/detection/font/font_nosupport.c @@ -0,0 +1,7 @@ +#include "fastfetch.h" +#include "font.h" + +const char* ffDetectFontImpl(FF_A_UNUSED FFFontResult* result) { + FF_UNUSED(result); + return "Not supported on this platform"; +} diff --git a/src/detection/font/font_windows.c b/src/detection/font/font_windows.c new file mode 100644 index 0000000..4fc28d3 --- /dev/null +++ b/src/detection/font/font_windows.c @@ -0,0 +1,63 @@ +#include "font.h" +#include "common/windows/unicode.h" +#include "common/windows/registry.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) { + FF_AUTO_CLOSE_FD HANDLE hKey = NULL; + if (!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Control Panel\\Desktop\\WindowMetrics", &hKey, NULL)) { + return "ffRegOpenKeyForRead(HKEY_CURRENT_USER\\Control Panel\\Desktop\\WindowMetrics) failed"; + } + + LOGFONTW fonts[4]; + FFArgBuffer fontBuffers[4] = { + { .data = &fonts[0], .length = sizeof(fonts[0]) }, + { .data = &fonts[1], .length = sizeof(fonts[1]) }, + { .data = &fonts[2], .length = sizeof(fonts[2]) }, + { .data = &fonts[3], .length = sizeof(fonts[3]) }, + }; + + if (!ffRegReadValues(hKey, 4, (FFRegValueArg[]) { + FF_ARG(fontBuffers[0], L"CaptionFont"), + FF_ARG(fontBuffers[1], L"MenuFont"), + FF_ARG(fontBuffers[2], L"MessageFont"), + FF_ARG(fontBuffers[3], L"StatusFont"), + }, + NULL)) { + return "ffRegReadValues(HKEY_CURRENT_USER\\Control Panel\\Desktop\\WindowMetrics) failed"; + } + + for (uint32_t i = 0; i < ARRAY_SIZE(fonts); ++i) { + if (fontBuffers[i].length != sizeof(LOGFONTW)) { + continue; // Invalid data, skip + } + + LOGFONTW* logFont = &fonts[i]; + + ffStrbufSetWS(&result->fonts[i], logFont->lfFaceName); + if (logFont->lfHeight < 0) { + ffStrbufAppendF(&result->fonts[i], " (%dpt)", (int) -logFont->lfHeight); + } + } + + generateString(result); + + return NULL; +} diff --git a/src/detection/gamepad/gamepad.h b/src/detection/gamepad/gamepad.h new file mode 100644 index 0000000..f26def9 --- /dev/null +++ b/src/detection/gamepad/gamepad.h @@ -0,0 +1,11 @@ +#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 */); diff --git a/src/detection/gamepad/gamepad_apple.c b/src/detection/gamepad/gamepad_apple.c new file mode 100644 index 0000000..c262c86 --- /dev/null +++ b/src/detection/gamepad/gamepad_apple.c @@ -0,0 +1,48 @@ +#include "gamepad.h" +#include "common/apple/cf_helpers.h" +#include "common/mallocHelper.h" + +#include +#include + +static void enumSet(IOHIDDeviceRef value, FFlist* results) { + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *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; +} diff --git a/src/detection/gamepad/gamepad_bsd.c b/src/detection/gamepad/gamepad_bsd.c new file mode 100644 index 0000000..b67b8fd --- /dev/null +++ b/src/detection/gamepad/gamepad_bsd.c @@ -0,0 +1,65 @@ +#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 = FF_LIST_ADD(FFGamepadDevice, *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; +} diff --git a/src/detection/gamepad/gamepad_haiku.cpp b/src/detection/gamepad/gamepad_haiku.cpp new file mode 100644 index 0000000..50b43a0 --- /dev/null +++ b/src/detection/gamepad/gamepad_haiku.cpp @@ -0,0 +1,18 @@ +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 = FF_LIST_ADD(FFGamepadDevice, *devices); + ffStrbufInit(&device->serial); + ffStrbufInitS(&device->name, name); + device->battery = 0; + } + } + return NULL; +} diff --git a/src/detection/gamepad/gamepad_linux.c b/src/detection/gamepad/gamepad_linux.c new file mode 100644 index 0000000..6bb4ee2 --- /dev/null +++ b/src/detection/gamepad/gamepad_linux.c @@ -0,0 +1,97 @@ +#include "gamepad.h" +#include "common/io.h" +#include "common/strutil.h" + +static void detectGamepad(FFlist* devices, FFstrbuf* name, FFstrbuf* path) { + uint32_t baseLen = path->length; + FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *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; +} diff --git a/src/detection/gamepad/gamepad_nosupport.c b/src/detection/gamepad/gamepad_nosupport.c new file mode 100644 index 0000000..c0d791e --- /dev/null +++ b/src/detection/gamepad/gamepad_nosupport.c @@ -0,0 +1,5 @@ +#include "gamepad.h" + +const char* ffDetectGamepad(FF_A_UNUSED FFlist* devices /* List of FFGamepadDevice */) { + return "Not supported on this platform"; +} diff --git a/src/detection/gamepad/gamepad_windows.c b/src/detection/gamepad/gamepad_windows.c new file mode 100644 index 0000000..9cbb561 --- /dev/null +++ b/src/detection/gamepad/gamepad_windows.c @@ -0,0 +1,201 @@ +#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 = FF_LIST_ADD(FFGamepadDevice, *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; +} diff --git a/src/detection/gpu/adl.h b/src/detection/gpu/adl.h new file mode 100644 index 0000000..346eece --- /dev/null +++ b/src/detection/gpu/adl.h @@ -0,0 +1,66 @@ +#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); diff --git a/src/detection/gpu/asahi_drm.h b/src/detection/gpu/asahi_drm.h new file mode 100644 index 0000000..69b537b --- /dev/null +++ b/src/detection/gpu/asahi_drm.h @@ -0,0 +1,1194 @@ +/* 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_ */ diff --git a/src/detection/gpu/d3dkmthk.h b/src/detection/gpu/d3dkmthk.h new file mode 100644 index 0000000..da0087b --- /dev/null +++ b/src/detection/gpu/d3dkmthk.h @@ -0,0 +1,362 @@ +#pragma once + +#include +#include +#include +#include + +#if _WIN32 + #include + #include +#else + #include + #include + #include + +typedef struct _LUID { + uint32_t LowPart; + uint32_t HighPart; +} LUID; + +typedef uint32_t UINT; +typedef uint64_t ULONGLONG; +typedef uint64_t UINT64; +typedef uint32_t UINT32; +typedef uint16_t UINT16; +typedef uint32_t ULONG; +typedef uint16_t USHORT; +typedef uint8_t UCHAR; +typedef void VOID; +typedef char16_t WCHAR; +typedef void* HANDLE; +typedef uint8_t BYTE; +typedef int32_t BOOL; +typedef uint8_t BOOLEAN; +typedef union { + struct { + uint32_t LowPart; + int32_t HighPart; + } u; + int64_t QuadPart; +} LARGE_INTEGER; +typedef int32_t NTSTATUS; // 0 for success, -1 for failure + #define NT_SUCCESS(Status) ((NTSTATUS) (Status) >= 0) + #define _In_range_(low, hi) +#endif + +#define D3DKMT_ALIGN64 alignas(8) + +typedef struct D3DKMT_HANDLE { + union { + struct { + uint32_t Instance : 6; + uint32_t Index : 24; + uint32_t Unique : 2; + }; + uint32_t Value; + }; +} D3DKMT_HANDLE; + +typedef struct _D3DKMT_OPENADAPTERFROMLUID { + LUID AdapterLuid; + D3DKMT_HANDLE hAdapter; +} D3DKMT_OPENADAPTERFROMLUID; + +typedef struct _D3DKMT_ADAPTERINFO { + D3DKMT_HANDLE hAdapter; + LUID AdapterLuid; + ULONG NumOfSources; + BOOL bPrecisePresentRegionsPreferred; +} D3DKMT_ADAPTERINFO; + +#define MAX_ENUM_ADAPTERS 16 +typedef struct _D3DKMT_ENUMADAPTERS { + _In_range_(0, MAX_ENUM_ADAPTERS) ULONG NumAdapters; + D3DKMT_ADAPTERINFO Adapters[MAX_ENUM_ADAPTERS]; +} D3DKMT_ENUMADAPTERS; + +typedef struct _D3DKMT_ENUMADAPTERS2 { + ULONG NumAdapters; // in/out: On input, the count of the pAdapters array buffer. On output, the number of adapters enumerated. + D3DKMT_ADAPTERINFO* pAdapters; // out: Array of enumerated adapters containing NumAdapters elements +} D3DKMT_ENUMADAPTERS2; +static_assert(sizeof(D3DKMT_ENUMADAPTERS2) == +#if SIZE_MAX == UINT64_MAX + 0x10 +#else + 0x08 +#endif + , + "D3DKMT_ENUMADAPTERS2 structure size mismatch"); + +typedef struct _D3DKMT_ADAPTERREGISTRYINFO { + WCHAR AdapterString[260]; + WCHAR BiosString[260]; + WCHAR DacType[260]; + WCHAR ChipType[260]; +} D3DKMT_ADAPTERREGISTRYINFO; + +typedef struct _D3DKMT_CLOSEADAPTER { + D3DKMT_HANDLE hAdapter; // in: adapter handle +} D3DKMT_CLOSEADAPTER; + +typedef struct _D3DKMT_SEGMENTSIZEINFO { + D3DKMT_ALIGN64 ULONGLONG DedicatedVideoMemorySize; + D3DKMT_ALIGN64 ULONGLONG DedicatedSystemMemorySize; + D3DKMT_ALIGN64 ULONGLONG SharedSystemMemorySize; +} D3DKMT_SEGMENTSIZEINFO; + +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 struct _D3DKMT_ADAPTERADDRESS { + UINT BusNumber; // Bus number on which the physical device is located. + UINT DeviceNumber; // Index of the physical device on the bus. + UINT FunctionNumber; // Function number of the adapter on the physical device. +} D3DKMT_ADAPTERADDRESS; + +typedef struct _D3DKMT_DEVICE_IDS { + UINT VendorID; + UINT DeviceID; + UINT SubVendorID; + UINT SubSystemID; + UINT RevisionID; + UINT BusType; +} D3DKMT_DEVICE_IDS; + +typedef struct _D3DKMT_UMD_DRIVER_VERSION { + D3DKMT_ALIGN64 LARGE_INTEGER DriverVersion; +} D3DKMT_UMD_DRIVER_VERSION; + +typedef struct _D3DKMT_QUERY_DEVICE_IDS { + UINT PhysicalAdapterIndex; // in: + D3DKMT_DEVICE_IDS DeviceIds; // out: +} D3DKMT_QUERY_DEVICE_IDS; + +typedef enum _QAI_DRIVERVERSION { + KMT_DRIVERVERSION_WDDM_1_0 = 1000, // Windows Vista + KMT_DRIVERVERSION_WDDM_1_1_PRERELEASE = 1102, // Windows Vista with prereleased Win7 features + KMT_DRIVERVERSION_WDDM_1_1 = 1105, // Windows 7 + KMT_DRIVERVERSION_WDDM_1_2 = 1200, // Windows 8 + KMT_DRIVERVERSION_WDDM_1_3 = 1300, // Windows 8.1 + KMT_DRIVERVERSION_WDDM_2_0 = 2000, // Windows 10 + KMT_DRIVERVERSION_WDDM_2_1 = 2100, // Windows 10 (1607) + KMT_DRIVERVERSION_WDDM_2_2 = 2200, // Windows 10 (1703) + KMT_DRIVERVERSION_WDDM_2_3 = 2300, // Windows 10 (1709) + KMT_DRIVERVERSION_WDDM_2_4 = 2400, // Windows 10 (1803) + KMT_DRIVERVERSION_WDDM_2_5 = 2500, // Windows 10 (1809) + KMT_DRIVERVERSION_WDDM_2_6 = 2600, // Windows 10 (1903) + KMT_DRIVERVERSION_WDDM_2_7 = 2700, // Windows 10 (2004) + KMT_DRIVERVERSION_WDDM_2_8 = 2800, // Windows 11 Insider Preview Manganese + KMT_DRIVERVERSION_WDDM_2_9 = 2900, // Windows 11 Insider Preview Iron + KMT_DRIVERVERSION_WDDM_3_0 = 3000, // Windows 11 (21H2) + KMT_DRIVERVERSION_WDDM_3_1 = 3100, // Windows 11 (22H2) + KMT_DRIVERVERSION_WDDM_3_2 = 3200, // Windows 11 (24H2) +} D3DKMT_DRIVERVERSION; + +typedef struct _D3DKMT_QUERY_ADAPTER_UNIQUE_GUID { + WCHAR AdapterUniqueGUID[40]; +} D3DKMT_QUERY_ADAPTER_UNIQUE_GUID; + +typedef enum _KMTQUERYADAPTERINFOTYPE { + KMTQAITYPE_GETSEGMENTSIZE = 3, + KMTQAITYPE_ADAPTERGUID = 4, + KMTQAITYPE_ADAPTERADDRESS = 6, + KMTQAITYPE_ADAPTERREGISTRYINFO = 8, + KMTQAITYPE_DRIVERVERSION = 13, + KMTQAITYPE_ADAPTERTYPE = 15, // WDDM 1.2, Windows 8 + KMTQAITYPE_UMD_DRIVER_VERSION = 18, + KMTQAITYPE_NODEMETADATA = 25, // WDDM 2.0, Windows 10 + KMTQAITYPE_PHYSICALADAPTERDEVICEIDS = 31, + KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID = 60, // WDDM 2.4, Windows 10 (1803) + KMTQAITYPE_NODEPERFDATA = 61, + KMTQAITYPE_ADAPTERPERFDATA = 62, +} KMTQUERYADAPTERINFOTYPE; + +typedef struct _D3DKMT_QUERYADAPTERINFO { + D3DKMT_HANDLE hAdapter; + KMTQUERYADAPTERINFOTYPE Type; + VOID* pPrivateDriverData; + UINT PrivateDriverDataSize; +} D3DKMT_QUERYADAPTERINFO; + +#define D3DKMT_MAX_SEGMENT_COUNT 32 + +typedef enum _D3DKMT_MEMORY_SEGMENT_GROUP { + D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL = 0, + D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL = 1 +} D3DKMT_MEMORY_SEGMENT_GROUP; + +typedef enum _D3DKMT_QUERYSTATISTICS_TYPE { + D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE = 17, // WDDM 3.1, Windows 11 2022 Update (version 22H2) +} D3DKMT_QUERYSTATISTICS_TYPE; + +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_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_MEMORY_USAGE { + D3DKMT_ALIGN64 UINT64 AllocatedBytes; + D3DKMT_ALIGN64 UINT64 FreeBytes; + D3DKMT_ALIGN64 UINT64 ZeroBytes; + D3DKMT_ALIGN64 UINT64 ModifiedBytes; + D3DKMT_ALIGN64 UINT64 StandbyBytes; +} D3DKMT_QUERYSTATISTICS_MEMORY_USAGE; + +typedef union _D3DKMT_QUERYSTATISTICS_RESULT { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE SegmentGroupUsageInformation; + uint8_t Padding[776]; +} D3DKMT_QUERYSTATISTICS_RESULT; + +typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT_GROUP_USAGE { + UINT16 PhysicalAdapterIndex; + UINT16 SegmentGroup; // D3DKMT_MEMORY_SEGMENT_GROUP +} D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT_GROUP_USAGE; + +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_SEGMENT_GROUP_USAGE QuerySegmentGroupUsage; + }; +} D3DKMT_QUERYSTATISTICS; +static_assert(sizeof(D3DKMT_QUERYSTATISTICS) == +#if SIZE_MAX == UINT64_MAX + 0x328 +#else + 0x320 +#endif + , + "D3DKMT_QUERYSTATISTICS structure size mismatch"); + +#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; +} FF_A_PACKED DXGK_NODEMETADATA; + +typedef struct _D3DKMT_NODEMETADATA { + UINT NodeOrdinalAndAdapterIndex; // WDDMv2: High word is physical adapter index, low word is node ordinal + DXGK_NODEMETADATA NodeData; +} FF_A_PACKED D3DKMT_NODEMETADATA; +static_assert(sizeof(D3DKMT_NODEMETADATA) == 0x4E, "D3DKMT_NODEMETADATA structure size mismatch"); + +// Functions + +#if _WIN32 + +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTOpenAdapterFromLuid(_Inout_ CONST D3DKMT_OPENADAPTERFROMLUID*); +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryAdapterInfo(_Inout_ CONST D3DKMT_QUERYADAPTERINFO*); +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTCloseAdapter(_In_ CONST D3DKMT_CLOSEADAPTER*); +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTEnumAdapters(_Inout_ CONST D3DKMT_ENUMADAPTERS*); +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTEnumAdapters2(_Inout_ CONST D3DKMT_ENUMADAPTERS2*); +EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryStatistics(_In_ CONST D3DKMT_QUERYSTATISTICS*); + +#else + + // Ref: https://github.com/microsoft/WSL2-Linux-Kernel/blob/linux-msft-wsl-6.6.y/include/uapi/misc/d3dkmthk.h + #define LX_DXOPENADAPTERFROMLUID _IOWR(0x47, 0x01, D3DKMT_OPENADAPTERFROMLUID) + #define LX_DXQUERYADAPTERINFO _IOWR(0x47, 0x09, D3DKMT_QUERYADAPTERINFO) + #define LX_DXENUMADAPTERS2 _IOWR(0x47, 0x14, D3DKMT_ENUMADAPTERS2) + #define LX_DXCLOSEADAPTER _IOWR(0x47, 0x15, D3DKMT_CLOSEADAPTER) + #define LX_DXQUERYSTATISTICS _IOWR(0x47, 0x43, D3DKMT_QUERYSTATISTICS) + +extern int dxgfd; // File descriptor for /dev/dxg, initialized in gpu_wsl.c + +static inline NTSTATUS D3DKMTOpenAdapterFromLuid(const D3DKMT_OPENADAPTERFROMLUID* params) { + return ioctl(dxgfd, LX_DXOPENADAPTERFROMLUID, params) < 0 ? -errno : 0; +} + +static inline NTSTATUS D3DKMTQueryAdapterInfo(const D3DKMT_QUERYADAPTERINFO* params) { + return ioctl(dxgfd, LX_DXQUERYADAPTERINFO, params) < 0 ? -errno : 0; +} + +static inline NTSTATUS D3DKMTCloseAdapter(const D3DKMT_CLOSEADAPTER* params) { + return ioctl(dxgfd, LX_DXCLOSEADAPTER, params) < 0 ? -errno : 0; +} + +static inline NTSTATUS D3DKMTEnumAdapters2(D3DKMT_ENUMADAPTERS2* params) { + return ioctl(dxgfd, LX_DXENUMADAPTERS2, params) < 0 ? -errno : 0; +} + +static inline NTSTATUS D3DKMTQueryStatistics(const D3DKMT_QUERYSTATISTICS* params) { + return ioctl(dxgfd, LX_DXQUERYSTATISTICS, params) < 0 ? -errno : 0; +} + +#endif diff --git a/src/detection/gpu/gpu.c b/src/detection/gpu/gpu.c new file mode 100644 index 0000000..a287298 --- /dev/null +++ b/src/detection/gpu/gpu.c @@ -0,0 +1,208 @@ +#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_PARALLELS = "Parallels"; +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* FF_GPU_VENDOR_NAME_QEMU = "QEMU"; + +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: + case 0x05404c42: // PD + return FF_GPU_VENDOR_NAME_PARALLELS; + 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; + case 0x1234: // https://admin.pci-ids.ucw.cz/read/PC/1234 + return FF_GPU_VENDOR_NAME_QEMU; + 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); + + FF_A_CLEANUP(ffDestroyOpenGLOptions) FFOpenGLOptions options; + ffInitOpenGLOptions(&options); + const char* error = ffDetectOpenGL(&options, &result); + FF_DEBUG("OpenGL detection returns: %s", error ?: "success"); + + if (!error) { + FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *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"; +} diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h new file mode 100644 index 0000000..10bd02b --- /dev/null +++ b/src/detection/gpu/gpu.h @@ -0,0 +1,89 @@ +#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_PARALLELS; +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; +extern const char* FF_GPU_VENDOR_NAME_QEMU; + +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; +} diff --git a/src/detection/gpu/gpu_amd.c b/src/detection/gpu/gpu_amd.c new file mode 100644 index 0000000..f396def --- /dev/null +++ b/src/detection/gpu/gpu_amd.c @@ -0,0 +1,460 @@ +#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_A_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* __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; +} diff --git a/src/detection/gpu/gpu_android.c b/src/detection/gpu/gpu_android.c new file mode 100644 index 0000000..08fd48a --- /dev/null +++ b/src/detection/gpu/gpu_android.c @@ -0,0 +1,55 @@ +#include "gpu.h" +#include "common/io.h" +#include "common/strutil.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"; +} diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c new file mode 100644 index 0000000..3b8de20 --- /dev/null +++ b/src/detection/gpu/gpu_apple.c @@ -0,0 +1,208 @@ +#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 = FF_LIST_ADD(FFGPUResult, *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; +} diff --git a/src/detection/gpu/gpu_apple.m b/src/detection/gpu/gpu_apple.m new file mode 100644 index 0000000..e206bdc --- /dev/null +++ b/src/detection/gpu/gpu_apple.m @@ -0,0 +1,87 @@ +#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"; +} diff --git a/src/detection/gpu/gpu_bsd.c b/src/detection/gpu/gpu_bsd.c new file mode 100644 index 0000000..ae29f46 --- /dev/null +++ b/src/detection/gpu/gpu_bsd.c @@ -0,0 +1,230 @@ +#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/strutil.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 = FF_LIST_ADD(FFGPUResult, *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 = FF_LIST_ADD(FFGPUResult, *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); +} diff --git a/src/detection/gpu/gpu_driver_specific.h b/src/detection/gpu/gpu_driver_specific.h new file mode 100644 index 0000000..3ddfb69 --- /dev/null +++ b/src/detection/gpu/gpu_driver_specific.h @@ -0,0 +1,90 @@ +#pragma once + +#include "gpu.h" + +typedef enum FF_A_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); + +#ifndef FF_GPU_DRIVER_DLLNAME_PATH_PREFIX + #define FF_GPU_DRIVER_DLLNAME_PATH_PREFIX +#endif + +FF_A_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 = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "nvml.dll"; +#else + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "libnvidia-ml.so"; +#endif + } else if (vendor == FF_GPU_VENDOR_NAME_MTHREADS) { + *pDetectFn = ffDetectMthreadsGpuInfo; +#ifdef _WIN32 + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "mtml.dll"; +#else + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "libmtml.so"; +#endif + } +#ifdef _WIN32 + else if (vendor == FF_GPU_VENDOR_NAME_INTEL) { + *pDetectFn = ffDetectIntelGpuInfo; + #ifdef _WIN64 + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "ControlLib.dll"; + #else + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "ControlLib32.dll"; + #endif + } else if (vendor == FF_GPU_VENDOR_NAME_AMD) { + *pDetectFn = ffDetectAmdGpuInfo; + #ifdef _WIN64 + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "atiadlxx.dll"; + #else + *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "atiadlxy.dll"; + #endif + } +#endif + else { + *pDetectFn = NULL; + *pDllName = NULL; + return false; + } + + return true; +} diff --git a/src/detection/gpu/gpu_drm.c b/src/detection/gpu/gpu_drm.c new file mode 100644 index 0000000..3a7d381 --- /dev/null +++ b/src/detection/gpu/gpu_drm.c @@ -0,0 +1,380 @@ +#include "gpu.h" + +#if FF_HAVE_DRM + #include + #include + #include + + #include "common/io.h" + #include "common/library.h" + #include "common/mallocHelper.h" + #include "common/strutil.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 && queryItem.length > 0) { // #2259 + 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"; +} diff --git a/src/detection/gpu/gpu_gnu.c b/src/detection/gpu/gpu_gnu.c new file mode 100644 index 0000000..27c774b --- /dev/null +++ b/src/detection/gpu/gpu_gnu.c @@ -0,0 +1,146 @@ +#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_A_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 = FF_LIST_ADD(FFGPUResult, *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; +} diff --git a/src/detection/gpu/gpu_haiku.c b/src/detection/gpu/gpu_haiku.c new file mode 100644 index 0000000..f75f039 --- /dev/null +++ b/src/detection/gpu/gpu_haiku.c @@ -0,0 +1,51 @@ +#include "gpu.h" +#include "common/io.h" + +#include + +const char* ffDetectGPUImpl(FF_A_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 = FF_LIST_ADD(FFGPUResult, *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; +} diff --git a/src/detection/gpu/gpu_intel.c b/src/detection/gpu/gpu_intel.c new file mode 100644 index 0000000..7d94c80 --- /dev/null +++ b/src/detection/gpu/gpu_intel.c @@ -0,0 +1,231 @@ +#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; +} diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c new file mode 100644 index 0000000..3aaa09c --- /dev/null +++ b/src/detection/gpu/gpu_linux.c @@ -0,0 +1,663 @@ +#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/strutil.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_A_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_A_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 = FF_LIST_ADD(FFGPUResult, *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_A_UNUSED static const char* drmDetectAsahiSpecific(FFGPUResult* gpu, const char* name, FF_A_UNUSED FFstrbuf* buffer, FF_A_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 = FF_LIST_ADD(FFGPUResult, *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) { +#if __x86_64__ || __aarch64__ + const char* ffGPUDetectWsl2(const FFGPUOptions* options, FFlist* gpus); + if (ffGPUDetectWsl2(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); +} diff --git a/src/detection/gpu/gpu_mthreads.c b/src/detection/gpu/gpu_mthreads.c new file mode 100644 index 0000000..0b639f5 --- /dev/null +++ b/src/detection/gpu/gpu_mthreads.c @@ -0,0 +1,201 @@ +#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_A_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 +} diff --git a/src/detection/gpu/gpu_nbsd.c b/src/detection/gpu/gpu_nbsd.c new file mode 100644 index 0000000..4f5dab3 --- /dev/null +++ b/src/detection/gpu/gpu_nbsd.c @@ -0,0 +1,120 @@ +#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_A_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 = FF_LIST_ADD(FFGPUResult, *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; +} diff --git a/src/detection/gpu/gpu_nosupport.c b/src/detection/gpu/gpu_nosupport.c new file mode 100644 index 0000000..95fb2f3 --- /dev/null +++ b/src/detection/gpu/gpu_nosupport.c @@ -0,0 +1,6 @@ +#include "gpu.h" + +const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { + FF_UNUSED(options, gpus); + return "Not supported on this platform"; +} diff --git a/src/detection/gpu/gpu_nvidia.c b/src/detection/gpu/gpu_nvidia.c new file mode 100644 index 0000000..85776dd --- /dev/null +++ b/src/detection/gpu/gpu_nvidia.c @@ -0,0 +1,299 @@ +#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 +} diff --git a/src/detection/gpu/gpu_obsd.c b/src/detection/gpu/gpu_obsd.c new file mode 100644 index 0000000..dabaded --- /dev/null +++ b/src/detection/gpu/gpu_obsd.c @@ -0,0 +1,101 @@ +#include "gpu.h" +#include "common/io.h" + +#include +#include +#include +#include +#include +#include + +static inline int pciReadConf(int fd, uint8_t bus, uint8_t device, uint8_t func, uint8_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_A_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 (uint16_t bus = 0; bus <= 255; bus++) { + for (uint8_t dev = 0; dev < 32; dev++) { + uint8_t maxfuncs = 0; + for (uint8_t func = 0; func <= maxfuncs; func++) { + uint32_t pciid, pciclass; + if (pciReadConf(pcifd, (uint8_t) bus, dev, func, PCI_ID_REG, &pciid) != 0) { + continue; + } + + if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) { + continue; + } + + if (pciReadConf(pcifd, (uint8_t) 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, (uint8_t) 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 = FF_LIST_ADD(FFGPUResult, *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; +} diff --git a/src/detection/gpu/gpu_pci.c b/src/detection/gpu/gpu_pci.c new file mode 100644 index 0000000..63cd877 --- /dev/null +++ b/src/detection/gpu/gpu_pci.c @@ -0,0 +1,251 @@ +#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 (vendor == 0x1234 && device == 0x1111 && subclass == 0) { // Not exist in pci.ids + ffStrbufSetStatic(&gpu->name, "Virtual Video Controller"); + return; + } + +#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); +} diff --git a/src/detection/gpu/gpu_sunos.c b/src/detection/gpu/gpu_sunos.c new file mode 100644 index 0000000..13d4998 --- /dev/null +++ b/src/detection/gpu/gpu_sunos.c @@ -0,0 +1,54 @@ +#include "gpu.h" +#include "common/strutil.h" + +#include + +static int walkDevTree(di_node_t node, FF_A_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 = FF_LIST_ADD(FFGPUResult, *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_A_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; +} diff --git a/src/detection/gpu/gpu_windows.c b/src/detection/gpu/gpu_windows.c new file mode 100644 index 0000000..796c1c5 --- /dev/null +++ b/src/detection/gpu/gpu_windows.c @@ -0,0 +1,605 @@ +#include "detection/gpu/gpu.h" +#if __linux__ + #define FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "/usr/lib/wsl/lib/" +#endif +#include "detection/gpu/gpu_driver_specific.h" +#include "common/debug.h" + +#include +#include "d3dkmthk.h" + +#if _WIN32 + #include "common/windows/unicode.h" + #include "common/windows/registry.h" + + #if FF_WIN81_COMPAT + #include "common/mallocHelper.h" + #include + #include + #include + + #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in + +static bool queryDeviceIdsFallback(D3DKMT_ADAPTERADDRESS adapterAddress, D3DKMT_DEVICE_IDS* outDeviceIds) { + FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS failed. Attempting queryDeviceIdsFallback: bus=%u device=%u function=%u", + adapterAddress.BusNumber, + adapterAddress.DeviceNumber, + adapterAddress.FunctionNumber); + + if (adapterAddress.BusNumber == -1u) { + FF_DEBUG("Invalid adapter address, cannot query device IDs"); + return false; + } + + static FFlist deviceIdsCache; + static bool initialized; + typedef struct { + D3DKMT_DEVICE_IDS deviceIds; + D3DKMT_ADAPTERADDRESS adapterAddress; + } CacheEntry; + + if (!initialized) { + initialized = true; + ffListInit(&deviceIdsCache); + + 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 false; + } + + 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 false; + } + + 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'; + } + } + + if (wcsncmp(devId, L"PCI\\", 4) != 0) { + FF_DEBUG("Skipping non-PCI device ID: %ls", devId); + continue; + } + + uint32_t pciBus = 0; + + ULONG pciBufLen = sizeof(pciBus); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, NULL, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { + uint32_t pciAddr = 0; + pciBufLen = sizeof(pciAddr); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, NULL, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { + CacheEntry* entry = FF_LIST_ADD(CacheEntry, deviceIdsCache); + + entry->deviceIds = (D3DKMT_DEVICE_IDS) {}; + // L"PCI\\VEN_10DE&DEV_2782&SUBSYS_513417AA&REV_A1\\4&3674a6b9&0&0008" + if (swscanf(devId + 4, L"VEN_%x&DEV_%x&SUBSYS_%4x%4x&REV_%x", &entry->deviceIds.VendorID, &entry->deviceIds.DeviceID, &entry->deviceIds.SubSystemID, &entry->deviceIds.SubVendorID, &entry->deviceIds.RevisionID) >= 2) { + FF_DEBUG("Parsed PCI IDs - Vendor: 0x%04x, Device: 0x%04x, SubVendor: 0x%04x, SubSystem: 0x%04x, Rev: 0x%04x", entry->deviceIds.VendorID, entry->deviceIds.DeviceID, entry->deviceIds.SubVendorID, entry->deviceIds.SubSystemID, entry->deviceIds.RevisionID); + // I thought it was DXGKMDT_OPM_BUS_TYPE_PCI, but it turns out to be false + // Who TF knows what 1 actually means. It's just reported by most graphic cards + // And yeah, DXGKMDT_OPM_BUS_TYPE_PCIEXPRESS (3) exists + entry->deviceIds.BusType = 1; + } else { + FF_DEBUG("Failed to parse PCI IDs from device ID string"); + deviceIdsCache.length--; // remove the cache entry since it's not valid + continue; + } + + entry->adapterAddress = (D3DKMT_ADAPTERADDRESS) { + .BusNumber = pciBus, + .DeviceNumber = (pciAddr >> 16) & 0xFFFF, + .FunctionNumber = pciAddr & 0xFFFF, + }; + FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID); + } else { + FF_DEBUG("Failed to get PCI address"); + } + } else { + FF_DEBUG("Failed to get PCI bus number"); + } + } + } + + FF_LIST_FOR_EACH (CacheEntry, entry, deviceIdsCache) { + if (memcmp(&entry->adapterAddress, &adapterAddress, sizeof(adapterAddress)) == 0) { + FF_DEBUG("Cache hit for adapter address: bus=%u device=%u function=%u", adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber); + *outDeviceIds = entry->deviceIds; + return true; + } + } + + FF_DEBUG("Cache miss for adapter address: bus=%u device=%u function=%u", adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber); + return false; +} + #endif // FF_WIN81_COMPAT + +static bool queryVendorNameViaRegistry(FFstrbuf* vendor, D3DKMT_HANDLE hAdapter) { + // `KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID` reports the GUID value used by the adapter's registry key (DirectX and Video) + + GUID guid; + NTSTATUS status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = hAdapter, + .Type = KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID, + .pPrivateDriverData = &guid, + .PrivateDriverDataSize = sizeof(guid), + }); + if (!NT_SUCCESS(status)) { + FF_DEBUG("Failed to query adapter unique GUID: %s", ffDebugNtStatus(status)); + return false; + } + + wchar_t path[PATH_MAX]; + swprintf(path, ARRAY_SIZE(path), L"SYSTEM\\CurrentControlSet\\Control\\Video\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\0000", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + + FF_DEBUG("Querying registry: HKEY_LOCAL_MACHINE\\%ls\\ProviderName", path); + FF_AUTO_CLOSE_FD HANDLE key = NULL; + if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, path, &key, NULL)) { + return false; + } + + return ffRegReadStrbuf(key, L"ProviderName", vendor, NULL); +} + +#else + #include + #include + #include + #include + +int dxgfd = -2; + +static void ffStrbufSetWS(FFstrbuf* strbuf, const char16_t* str) { + ffStrbufClear(strbuf); + + mbstate_t state = {}; + while (*str) { + char buf[5]; + size_t len = c16rtomb(buf, *str, &state); + if (len == (size_t) -1) { + ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER + } else if (len > 0) { + ffStrbufAppendNS(strbuf, (uint32_t) len, buf); + } + str++; + } +} + +static void closeDxgfd(void) { + if (dxgfd >= 0) { + close(dxgfd); + dxgfd = 0; + FF_DEBUG("Closed /dev/dxg file descriptor"); + } +} + +FF_A_UNUSED static inline const char* ffDebugNtStatus(NTSTATUS status) { + return status < 0 ? strerror(-status) : "Success"; +} +#endif + +const char* +#if _WIN32 +ffDetectGPUImpl +#else +ffGPUDetectWsl2 +#endif + (const FFGPUOptions* options, FFlist* gpus) { +#if __linux__ + if (dxgfd == -2) { + dxgfd = open("/dev/dxg", O_RDWR); // Windows DXCore/D3DKMT adapter driver for WSL + if (dxgfd < 0) { + if (errno == ENOENT) { + FF_DEBUG("/dev/dxg is not available, WSL DXCore GPU driver not detected"); + return "No DXCore GPU driver detected (no /dev/dxg)"; + } else { + FF_DEBUG("Failed to open /dev/dxg: %s", strerror(errno)); + return "Failed to open /dev/dxg"; + } + } + FF_DEBUG("Opened /dev/dxg successfully"); + atexit(closeDxgfd); + } + if (dxgfd < 0) { + return "Failed to open /dev/dxg"; + } +#endif + +#if FF_WIN81_COMPAT + D3DKMT_ENUMADAPTERS enumAdapters = {}; + D3DKMT_ADAPTERINFO* const adapters = enumAdapters.Adapters; + NTSTATUS status = D3DKMTEnumAdapters(&enumAdapters); +#else + D3DKMT_ADAPTERINFO adapters[64]; + D3DKMT_ENUMADAPTERS2 enumAdapters = { + .NumAdapters = ARRAY_SIZE(adapters), + .pAdapters = adapters, + }; + NTSTATUS status = D3DKMTEnumAdapters2(&enumAdapters); +#endif + if (!NT_SUCCESS(status)) { + FF_DEBUG("D3DKMTEnumAdapters(2) failed: %s", ffDebugNtStatus(status)); + return "Failed to enumerate adapters with D3DKMTEnumAdapters2"; + } + + FF_DEBUG("D3DKMTEnumAdapters(2) succeeded, adapter count: %" PRIu32, (uint32_t) enumAdapters.NumAdapters); + + for (uint32_t i = 0; i < enumAdapters.NumAdapters; i++) { + const D3DKMT_ADAPTERINFO* adapter = &adapters[i]; + FF_DEBUG("Processing adapter #%u", i); + + D3DKMT_ADAPTERTYPE adapterType; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_ADAPTERTYPE, + .pPrivateDriverData = &adapterType, + .PrivateDriverDataSize = sizeof(adapterType), + }); + if (!NT_SUCCESS(status)) { + FF_DEBUG("KMTQAITYPE_ADAPTERTYPE query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); + continue; + } + if (adapterType.SoftwareDevice) { + FF_DEBUG("Skipping software adapter #%u", i); + goto close_adapter; + } + + FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus); + ffStrbufInit(&gpu->vendor); + 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->coreCount = FF_GPU_CORE_COUNT_UNSET; + gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; + 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; + gpu->type = adapterType.HybridIntegrated + ? FF_GPU_TYPE_INTEGRATED + : adapterType.HybridDiscrete + ? FF_GPU_TYPE_DISCRETE + : FF_GPU_TYPE_UNKNOWN; + + D3DKMT_DRIVERVERSION wddmVersion = KMT_DRIVERVERSION_WDDM_2_0; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_DRIVERVERSION, + .pPrivateDriverData = &wddmVersion, + .PrivateDriverDataSize = sizeof(wddmVersion), + }); + if (NT_SUCCESS(status)) { + ffStrbufSetF(&gpu->platformApi, "WDDM %u.%u", (uint32_t) wddmVersion / 1000, ((uint32_t) wddmVersion % 1000) / 100); + FF_DEBUG("Adapter #%u WDDM version: %u", i, (uint32_t) wddmVersion); + } else { + ffStrbufSetStatic(&gpu->platformApi, "WDDM"); + FF_DEBUG("KMTQAITYPE_DRIVERVERSION query failed for adapter #%u", i); + } + + D3DKMT_ADAPTERADDRESS adapterAddress = {}; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_ADAPTERADDRESS, + .pPrivateDriverData = &adapterAddress, + .PrivateDriverDataSize = sizeof(adapterAddress), + }); + if (NT_SUCCESS(status) && adapterAddress.FunctionNumber != 0xFFFF /* non-PCI device */) { + gpu->deviceId = ffGPUPciAddr2Id(0, adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber); + FF_DEBUG("Adapter #%u PCI address: bus=%u device=%u function=%u", + i, + adapterAddress.BusNumber, + adapterAddress.DeviceNumber, + adapterAddress.FunctionNumber); + } else { + adapterAddress.BusNumber = -1u; + gpu->deviceId = ffGPUGeneral2Id(((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart); + FF_DEBUG("KMTQAITYPE_ADAPTERADDRESS query failed for adapter #%u, fallback to LUID-based deviceId: %s", + i, + ffDebugNtStatus(status)); + } + + D3DKMT_QUERY_DEVICE_IDS deviceIds = { .PhysicalAdapterIndex = 0 }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_PHYSICALADAPTERDEVICEIDS, + .pPrivateDriverData = &deviceIds, + .PrivateDriverDataSize = sizeof(deviceIds), + }); + if (NT_SUCCESS(status) +#if FF_WIN81_COMPAT + || queryDeviceIdsFallback(adapterAddress, &deviceIds.DeviceIds) +#endif + ) { + ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(deviceIds.DeviceIds.VendorID)); + FF_DEBUG("Adapter #%u vendor/device IDs: vendor=0x%04x device=0x%04x", + i, + deviceIds.DeviceIds.VendorID, + deviceIds.DeviceIds.DeviceID); + } else { + deviceIds.DeviceIds.VendorID = -1u; + FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + + D3DKMT_UMD_DRIVER_VERSION umdDriverVersion; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_UMD_DRIVER_VERSION, + .pPrivateDriverData = &umdDriverVersion, + .PrivateDriverDataSize = sizeof(umdDriverVersion), + }); + if (NT_SUCCESS(status)) { + ffStrbufSetF(&gpu->driver, + "%u.%u.%u.%u", + (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 48ul & 0xFFFF), + (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 32ul & 0xFFFF), + (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 16ul & 0xFFFF), + (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 0ul & 0xFFFF)); + FF_DEBUG("Adapter #%u UMD driver version: %08" PRIX64, i, (uint64_t) umdDriverVersion.DriverVersion.QuadPart); + } else { + FF_DEBUG("KMTQAITYPE_UMD_DRIVER_VERSION query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + + __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); + FF_A_UNUSED const char* error = detectFn( + &(FFGpuDriverCondition) { + .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID | + (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | + (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), + .pciDeviceId = { + .deviceId = deviceIds.DeviceIds.DeviceID, + .vendorId = deviceIds.DeviceIds.VendorID, + .subSystemId = deviceIds.DeviceIds.SubSystemID, + .revId = deviceIds.DeviceIds.RevisionID, + }, + .pciBusId = { + .domain = 0, + .bus = adapterAddress.BusNumber, + .device = adapterAddress.DeviceNumber, + .func = adapterAddress.FunctionNumber, + }, + .luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart, + }, + (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: %s", error ?: "Success"); + } else if (options->driverSpecific) { + FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); + } + +#if _WIN32 + // Put this after the driver-specific detection, as `getDriverSpecificDetectionFn` never succeeds + if (gpu->vendor.length == 0 && wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { + // For non-PCI devices + FF_DEBUG("Attempting to query vendor name via registry for adapter #%u", i); + queryVendorNameViaRegistry(&gpu->vendor, adapter->hAdapter); + } +#endif + + if (gpu->name.length == 0) { + D3DKMT_ADAPTERREGISTRYINFO registryInfo; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_ADAPTERREGISTRYINFO, + .pPrivateDriverData = ®istryInfo, + .PrivateDriverDataSize = sizeof(registryInfo), + }); + if (NT_SUCCESS(status)) { + ffStrbufSetWS(&gpu->name, registryInfo.AdapterString); + FF_DEBUG("Adapter #%u adapter string: %s", i, gpu->name.chars); + } else { + FF_DEBUG("KMTQAITYPE_ADAPTERREGISTRYINFO query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + + if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) { + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) { + // Supports memory usage query; requires Windows 11 (22H2) or later + D3DKMT_QUERYSTATISTICS queryStatistics = { + .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE, + .AdapterLuid = adapter->AdapterLuid, + .QuerySegmentGroupUsage = { + .PhysicalAdapterIndex = 0, + .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL, + }, + }; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->dedicated.used = used; + gpu->dedicated.total = total; + FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + + queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->shared.used = used; + gpu->shared.total = total; + FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + } else { + // Supports basic segment (total) size query + D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {}; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_GETSEGMENTSIZE, + .pPrivateDriverData = &segmentSizeInfo, + .PrivateDriverDataSize = sizeof(segmentSizeInfo), + }); + if (NT_SUCCESS(status)) { + FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64 + ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64, + i, + (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize, + (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize, + (uint64_t) segmentSizeInfo.SharedSystemMemorySize); + gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize; + gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize; + } else { + FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + } + + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { + if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) { + for (uint32_t nodeIdx = 0;; nodeIdx++) { + D3DKMT_NODEMETADATA nodeMetadata = { + .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEMETADATA, + .pPrivateDriverData = &nodeMetadata, + .PrivateDriverDataSize = sizeof(nodeMetadata), + }); + if (!NT_SUCCESS(status)) { break; } + + if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; } + + D3DKMT_NODE_PERFDATA nodePerfData = { + .NodeOrdinal = nodeIdx, + .PhysicalAdapterIndex = 0, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEPERFDATA, + .pPrivateDriverData = &nodePerfData, + .PrivateDriverDataSize = sizeof(nodePerfData), + }); + if (NT_SUCCESS(status)) { + if (nodePerfData.MaxFrequency != 0) { + gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000); + FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency); + } else { + FF_DEBUG("Adapter #%u does not report max graphics frequency", i); + } + break; + } else { + FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s", + i, + nodeIdx, + ffDebugNtStatus(status)); + } + } + } + + if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) { + D3DKMT_ADAPTER_PERFDATA adapterPerfData = { + .PhysicalAdapterIndex = 0, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_ADAPTERPERFDATA, + .pPrivateDriverData = &adapterPerfData, + .PrivateDriverDataSize = sizeof(adapterPerfData), + }); + if (NT_SUCCESS(status)) { + if (adapterPerfData.Temperature != 0) { + gpu->temperature = adapterPerfData.Temperature / 10.0; + FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature); + } else { + FF_DEBUG("Adapter #%u does not report temperature data", i); + } + } else { + FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + } + + if (gpu->type == FF_GPU_TYPE_UNKNOWN) { + FF_DEBUG("Using fallback GPU type detection"); + 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; + } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_VMWARE || gpu->vendor.chars == FF_GPU_VENDOR_NAME_PARALLELS) { + // Virtualized GPUs + gpu->type = FF_GPU_TYPE_INTEGRATED; + } + + if (gpu->type != FF_GPU_TYPE_UNKNOWN) { + FF_DEBUG("Determined GPU type based on vendor (%s) and name: %u", gpu->vendor.chars, gpu->type); + } +#if _WIN32 + else if (ffIsWindows10OrGreater()) { + const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu); + FF_A_UNUSED const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu); + FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success"); + } +#endif + else { + FF_DEBUG("Unable to determine GPU type by any method for this adapter"); + } + } + + FF_DEBUG("Adapter #%u summary: name='%s', vendor='%s', type=%u, deviceId=%" PRIu64, + i, + gpu->name.length ? gpu->name.chars : "unknown", + gpu->vendor.length ? gpu->vendor.chars : "unknown", + (uint32_t) gpu->type, + (uint64_t) gpu->deviceId); + + close_adapter: + status = D3DKMTCloseAdapter(&(D3DKMT_CLOSEADAPTER) { .hAdapter = adapter->hAdapter }); + if (NT_SUCCESS(status)) { + FF_DEBUG("Closed adapter #%u successfully", i); + } else { + FF_DEBUG("Failed to close adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + + return NULL; +} diff --git a/src/detection/gpu/gpu_windows.cpp b/src/detection/gpu/gpu_windows.cpp new file mode 100644 index 0000000..ae6f00e --- /dev/null +++ b/src/detection/gpu/gpu_windows.cpp @@ -0,0 +1,83 @@ +extern "C" { +#include "gpu.h" +#include "common/library.h" +#include "common/debug.h" +} + +#if __has_include() && __has_include() + + #include + #include + #include "common/windows/util.hpp" + +static IDXCoreAdapterFactory* loadDxCoreFactory() { + static bool initialized = false; + static IDXCoreAdapterFactory* factory = nullptr; + if (initialized) { + return factory; // Already loaded + } + + initialized = true; + FF_LIBRARY_LOAD(dxcore, NULL, "dxcore" FF_LIBRARY_EXTENSION, 1) + + // 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(dxcore, "DXCoreCreateAdapterFactory"); + if (ffDXCoreCreateAdapterFactory == nullptr) { + return NULL; + } + #else + auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) DXCoreCreateAdapterFactory; + #endif + + HRESULT hr = ffDXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) { + FF_DEBUG("DXCoreCreateAdapterFactory failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr)); + return NULL; + } + + dxcore = NULL; // Don't unload + return factory; +} + +extern "C" const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult* gpu) { + auto* factory = loadDxCoreFactory(); + if (!factory) { + return "Failed to load DXCore library or create adapter factory"; + } + + IDXCoreAdapter* adapter = nullptr; + HRESULT hr = factory->GetAdapterByLuid(adapterLuid, IID_PPV_ARGS(&adapter)); + if (FAILED(hr)) { + FF_DEBUG("GetAdapterByLuid failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr)); + return "Failed to get adapter by LUID"; + } + + on_scope_exit releaseAdapter{ [adapter] { adapter->Release(); } }; + + bool isIntegrated = false; + hr = adapter->GetProperty(DXCoreAdapterProperty::IsIntegrated, sizeof(isIntegrated), &isIntegrated); + if (FAILED(hr)) { + FF_DEBUG("GetProperty(IsIntegrated) failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr)); + return "Failed to get adapter properties"; + } + + gpu->type = isIntegrated ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; + FF_DEBUG("GPU type determined using DXCore: %s", isIntegrated ? "Integrated" : "Discrete"); + + return nullptr; +} + +#else + + #warning "DXCore headers not available, GPU type detection may be less accurate" + +extern "C" const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult* gpu) { + FF_UNUSED(adapterLuid, gpu); + FF_DEBUG("DXCore not available, skipping GPU type detection with DXCore"); + return "DXCore not available"; +} + +#endif diff --git a/src/detection/gpu/igcl.h b/src/detection/gpu/igcl.h new file mode 100644 index 0000000..6bf4ffe --- /dev/null +++ b/src/detection/gpu/igcl.h @@ -0,0 +1,216 @@ +#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); diff --git a/src/detection/gpu/intel_drm.h b/src/detection/gpu/intel_drm.h new file mode 100644 index 0000000..3e25748 --- /dev/null +++ b/src/detection/gpu/intel_drm.h @@ -0,0 +1,125 @@ +#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[]; +}; diff --git a/src/detection/gpu/mtml.h b/src/detection/gpu/mtml.h new file mode 100644 index 0000000..9f62fc3 --- /dev/null +++ b/src/detection/gpu/mtml.h @@ -0,0 +1,110 @@ +#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); diff --git a/src/detection/gpu/nvapi.h b/src/detection/gpu/nvapi.h new file mode 100644 index 0000000..102d57a --- /dev/null +++ b/src/detection/gpu/nvapi.h @@ -0,0 +1,50 @@ +// 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); diff --git a/src/detection/gpu/nvml.h b/src/detection/gpu/nvml.h new file mode 100644 index 0000000..74b1840 --- /dev/null +++ b/src/detection/gpu/nvml.h @@ -0,0 +1,152 @@ +#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); diff --git a/src/detection/gtk_qt/gtk.c b/src/detection/gtk_qt/gtk.c new file mode 100644 index 0000000..05a8db1 --- /dev/null +++ b/src/detection/gtk_qt/gtk.c @@ -0,0 +1,206 @@ +#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_A_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) || + ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) { + themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", NULL, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue; + iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", NULL, "icon-theme", FF_VARIANT_TYPE_STRING).strValue; + fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue; + cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", NULL, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue; + cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", NULL, "cursor-size", FF_VARIANT_TYPE_INT).intValue; + wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", NULL, "picture-uri", FF_VARIANT_TYPE_STRING).strValue; + } else if ( + ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) { + ffEnlightenmentSettings settings = {}; + if (ffSettingsGetEnlightenmentProperty(&settings)) { + themeName = settings.theme; + iconsName = settings.icon_theme; + fontName = settings.font; + cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application"; + cursorSize = settings.cursor_size; + wallpaper = settings.desktop_default_background; + } + } + + applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper); +} + +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 diff --git a/src/detection/gtk_qt/gtk_qt.h b/src/detection/gtk_qt/gtk_qt.h new file mode 100644 index 0000000..b831bb6 --- /dev/null +++ b/src/detection/gtk_qt/gtk_qt.h @@ -0,0 +1,25 @@ +#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); diff --git a/src/detection/gtk_qt/qt.c b/src/detection/gtk_qt/qt.c new file mode 100644 index 0000000..26c8b58 --- /dev/null +++ b/src/detection/gtk_qt/qt.c @@ -0,0 +1,209 @@ +#include "fastfetch.h" +#include "common/properties.h" +#include "common/thread.h" +#include "common/strutil.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 FF_A_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; +} diff --git a/src/detection/host/host.h b/src/detection/host/host.h new file mode 100644 index 0000000..a6fe3fb --- /dev/null +++ b/src/detection/host/host.h @@ -0,0 +1,20 @@ +#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); diff --git a/src/detection/host/host_android.c b/src/detection/host/host_android.c new file mode 100644 index 0000000..1157a92 --- /dev/null +++ b/src/detection/host/host_android.c @@ -0,0 +1,27 @@ +#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; +} diff --git a/src/detection/host/host_apple.c b/src/detection/host/host_apple.c new file mode 100644 index 0000000..a177bf4 --- /dev/null +++ b/src/detection/host/host_apple.c @@ -0,0 +1,68 @@ +#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; +} diff --git a/src/detection/host/host_bsd.c b/src/detection/host/host_bsd.c new file mode 100644 index 0000000..3eb547d --- /dev/null +++ b/src/detection/host/host_bsd.c @@ -0,0 +1,26 @@ +#include "host.h" +#include "common/settings.h" +#include "common/smbios.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; +} diff --git a/src/detection/host/host_linux.c b/src/detection/host/host_linux.c new file mode 100644 index 0000000..81418a8 --- /dev/null +++ b/src/detection/host/host_linux.c @@ -0,0 +1,117 @@ +#include "host.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/smbios.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 + bool productName = ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_name", "/sys/class/dmi/id/product_name", &host->name); + bool productFamily = ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_family", "/sys/class/dmi/id/product_family", &host->family); + if (productName || productFamily) { + 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); + +#if __x86_64__ + ffHostDetectMac(host); +#endif + + // KVM/Qemu virtual machine + if (ffStrbufStartsWithS(&host->name, "Standard PC")) { + ffStrbufPrependS(&host->name, "KVM/QEMU "); + } +#if __aarch64__ + else if (host->family.length == 0 && ffStrbufEqualS(&host->vendor, "Apple Inc.") && ffStrbufStartsWithS(&host->name, "Mac")) { + // Hack for Asahi Linux + ffStrbufDestroy(&host->family); + ffStrbufInitMove(&host->family, &host->name); + getHostProductName(&host->name); + getHostSerialNumber(&host->serial); + } +#endif + } else { + getHostProductFamily(&host->family); + getHostProductName(&host->name); + getHostSerialNumber(&host->serial); + getHostVendor(&host->vendor); + } + + 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; +} diff --git a/src/detection/host/host_mac.c b/src/detection/host/host_mac.c new file mode 100644 index 0000000..d680332 --- /dev/null +++ b/src/detection/host/host_mac.c @@ -0,0 +1,444 @@ +#include "host.h" +#include "common/strutil.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,9")) { + return "MacBook Pro (14-inch, M5 Pro, 2026)"; + } + if (ffStrEquals(version, "17,8")) { + return "MacBook Pro (16-inch, M5 Pro, 2026)"; + } + if (ffStrEquals(version, "17,7")) { + return "MacBook Pro (14-inch, M5 Max, 2026)"; + } + if (ffStrEquals(version, "17,6")) { + return "MacBook Pro (16-inch, M5 Max, 2026)"; + } + if (ffStrEquals(version, "17,5")) { + return "MacBook Neo (13-inch, A18 Pro, 2026)"; + } + if (ffStrEquals(version, "17,4")) { + return "MacBook Air (15-inch, M5, 2026)"; + } + if (ffStrEquals(version, "17,3")) { + return "MacBook Air (13-inch, M5, 2026)"; + } + if (ffStrEquals(version, "17,2")) { + return "MacBook Pro (14-inch, M5, 2025)"; + } + 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 diff --git a/src/detection/host/host_nbsd.c b/src/detection/host/host_nbsd.c new file mode 100644 index 0000000..a5a29d2 --- /dev/null +++ b/src/detection/host/host_nbsd.c @@ -0,0 +1,25 @@ +#include "host.h" +#include "common/sysctl.h" +#include "common/smbios.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; +} diff --git a/src/detection/host/host_nosupport.c b/src/detection/host/host_nosupport.c new file mode 100644 index 0000000..61a7254 --- /dev/null +++ b/src/detection/host/host_nosupport.c @@ -0,0 +1,5 @@ +#include "host.h" + +const char* ffDetectHost(FF_A_UNUSED FFHostResult* host) { + return "Not supported on this platform"; +} diff --git a/src/detection/host/host_obsd.c b/src/detection/host/host_obsd.c new file mode 100644 index 0000000..cb16ecf --- /dev/null +++ b/src/detection/host/host_obsd.c @@ -0,0 +1,22 @@ +#include "host.h" +#include "common/sysctl.h" +#include "common/smbios.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; +} diff --git a/src/detection/host/host_windows.c b/src/detection/host/host_windows.c new file mode 100644 index 0000000..9f25829 --- /dev/null +++ b/src/detection/host/host_windows.c @@ -0,0 +1,74 @@ +#include "host.h" +#include "common/smbios.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]; + } FF_A_PACKED UUID; // varies + uint8_t WakeUpType; // enum + + // 2.4+ + uint8_t SKUNumber; // string + uint8_t Family; // string +} FF_A_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 __x86_64__ + ffHostDetectMac(host); +#endif + + return NULL; +} diff --git a/src/detection/icons/icons.h b/src/detection/icons/icons.h new file mode 100644 index 0000000..7f3f20e --- /dev/null +++ b/src/detection/icons/icons.h @@ -0,0 +1,11 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/icons/option.h" + +typedef struct FFIconsResult { + FFstrbuf icons1; + FFstrbuf icons2; +} FFIconsResult; + +const char* ffDetectIcons(FFIconsResult* result); diff --git a/src/detection/icons/icons_linux.c b/src/detection/icons/icons_linux.c new file mode 100644 index 0000000..0ecaa07 --- /dev/null +++ b/src/detection/icons/icons_linux.c @@ -0,0 +1,30 @@ +#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; +} diff --git a/src/detection/icons/icons_nosupport.c b/src/detection/icons/icons_nosupport.c new file mode 100644 index 0000000..ab8cebb --- /dev/null +++ b/src/detection/icons/icons_nosupport.c @@ -0,0 +1,5 @@ +#include "icons.h" + +const char* ffDetectIcons(FF_A_UNUSED FFIconsResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/icons/icons_windows.c b/src/detection/icons/icons_windows.c new file mode 100644 index 0000000..e199432 --- /dev/null +++ b/src/detection/icons/icons_windows.c @@ -0,0 +1,47 @@ +#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)) { + // If the key doesn't exist, it means that the user never changed the default settings. + ffStrbufSetStatic(&result->icons2, "Recycle Bin"); + return NULL; + } + + // 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; +} diff --git a/src/detection/initsystem/initsystem.h b/src/detection/initsystem/initsystem.h new file mode 100644 index 0000000..3c59cf0 --- /dev/null +++ b/src/detection/initsystem/initsystem.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/initsystem/initsystem_haiku.c b/src/detection/initsystem/initsystem_haiku.c new file mode 100644 index 0000000..04857bc --- /dev/null +++ b/src/detection/initsystem/initsystem_haiku.c @@ -0,0 +1,35 @@ +#include "initsystem.h" +#include "common/strutil.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; +} diff --git a/src/detection/initsystem/initsystem_linux.c b/src/detection/initsystem/initsystem_linux.c new file mode 100644 index 0000000..445657b --- /dev/null +++ b/src/detection/initsystem/initsystem_linux.c @@ -0,0 +1,116 @@ +#include "initsystem.h" +#include "common/processing.h" +#include "common/binary.h" +#include "common/strutil.h" + +#include +#include + +FF_A_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; +} diff --git a/src/detection/initsystem/initsystem_nosupport.c b/src/detection/initsystem/initsystem_nosupport.c new file mode 100644 index 0000000..e8ccb8e --- /dev/null +++ b/src/detection/initsystem/initsystem_nosupport.c @@ -0,0 +1,5 @@ +#include "initsystem.h" + +const char* ffDetectInitSystem(FF_A_UNUSED FFInitSystemResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/keyboard/keyboard.h b/src/detection/keyboard/keyboard.h new file mode 100644 index 0000000..6838a9c --- /dev/null +++ b/src/detection/keyboard/keyboard.h @@ -0,0 +1,8 @@ +#include "fastfetch.h" + +typedef struct FFKeyboardDevice { + FFstrbuf serial; + FFstrbuf name; +} FFKeyboardDevice; + +const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */); diff --git a/src/detection/keyboard/keyboard_apple.c b/src/detection/keyboard/keyboard_apple.c new file mode 100644 index 0000000..e2ecc37 --- /dev/null +++ b/src/detection/keyboard/keyboard_apple.c @@ -0,0 +1,36 @@ +#include "keyboard.h" +#include "common/apple/cf_helpers.h" +#include "common/mallocHelper.h" + +#include +#include + +static void enumSet(IOHIDDeviceRef value, FFlist* results) { + FFKeyboardDevice* device = FF_LIST_ADD(FFKeyboardDevice, *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; +} diff --git a/src/detection/keyboard/keyboard_bsd.c b/src/detection/keyboard/keyboard_bsd.c new file mode 100644 index 0000000..4b0ee57 --- /dev/null +++ b/src/detection/keyboard/keyboard_bsd.c @@ -0,0 +1,92 @@ +#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 = FF_LIST_ADD(FFKeyboardDevice, *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 = FF_LIST_ADD(FFKeyboardDevice, *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); +} diff --git a/src/detection/keyboard/keyboard_haiku.cpp b/src/detection/keyboard/keyboard_haiku.cpp new file mode 100644 index 0000000..dc1d28a --- /dev/null +++ b/src/detection/keyboard/keyboard_haiku.cpp @@ -0,0 +1,27 @@ +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 = FF_LIST_ADD(FFKeyboardDevice, *devices); + ffStrbufInit(&item->serial); + ffStrbufInitS(&item->name, device->Name()); + } + + return NULL; +} diff --git a/src/detection/keyboard/keyboard_linux.c b/src/detection/keyboard/keyboard_linux.c new file mode 100644 index 0000000..1a29abb --- /dev/null +++ b/src/detection/keyboard/keyboard_linux.c @@ -0,0 +1,104 @@ +#include "keyboard.h" +#include "common/io.h" +#include "common/strutil.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 = FF_LIST_ADD(FFKeyboardDevice, *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; +} diff --git a/src/detection/keyboard/keyboard_nosupport.c b/src/detection/keyboard/keyboard_nosupport.c new file mode 100644 index 0000000..6523ed4 --- /dev/null +++ b/src/detection/keyboard/keyboard_nosupport.c @@ -0,0 +1,5 @@ +#include "keyboard.h" + +const char* ffDetectKeyboard(FF_A_UNUSED FFlist* devices /* List of FFKeyboardDevice */) { + return "No mouse support on this platform"; +} diff --git a/src/detection/keyboard/keyboard_windows.c b/src/detection/keyboard/keyboard_windows.c new file mode 100644 index 0000000..272f632 --- /dev/null +++ b/src/detection/keyboard/keyboard_windows.c @@ -0,0 +1,85 @@ +#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 = FF_LIST_ADD(FFKeyboardDevice, *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; +} diff --git a/src/detection/libc/libc.h b/src/detection/libc/libc.h new file mode 100644 index 0000000..f2718e8 --- /dev/null +++ b/src/detection/libc/libc.h @@ -0,0 +1,10 @@ +#pragma once + +#include "fastfetch.h" + +typedef struct FFLibcResult { + const char* name; + const char* version; +} FFLibcResult; + +const char* ffDetectLibc(FFLibcResult* result); diff --git a/src/detection/libc/libc_android.c b/src/detection/libc/libc_android.c new file mode 100644 index 0000000..2aec4ed --- /dev/null +++ b/src/detection/libc/libc_android.c @@ -0,0 +1,24 @@ +#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 +} diff --git a/src/detection/libc/libc_apple.c b/src/detection/libc/libc_apple.c new file mode 100644 index 0000000..07ca2b5 --- /dev/null +++ b/src/detection/libc/libc_apple.c @@ -0,0 +1,12 @@ +#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; +} diff --git a/src/detection/libc/libc_bsd.c b/src/detection/libc/libc_bsd.c new file mode 100644 index 0000000..bd73165 --- /dev/null +++ b/src/detection/libc/libc_bsd.c @@ -0,0 +1,20 @@ +#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; +} diff --git a/src/detection/libc/libc_linux.c b/src/detection/libc/libc_linux.c new file mode 100644 index 0000000..53186a3 --- /dev/null +++ b/src/detection/libc/libc_linux.c @@ -0,0 +1,25 @@ +#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; +} diff --git a/src/detection/libc/libc_nosupport.c b/src/detection/libc/libc_nosupport.c new file mode 100644 index 0000000..e4687b0 --- /dev/null +++ b/src/detection/libc/libc_nosupport.c @@ -0,0 +1,7 @@ +#include "libc.h" + +const char* ffDetectLibc(FFLibcResult* result) { + result->name = "Unknown"; + result->version = NULL; + return NULL; +} diff --git a/src/detection/libc/libc_windows.cpp b/src/detection/libc/libc_windows.cpp new file mode 100644 index 0000000..c94062a --- /dev/null +++ b/src/detection/libc/libc_windows.cpp @@ -0,0 +1,68 @@ +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; +} diff --git a/src/detection/lm/lm.h b/src/detection/lm/lm.h new file mode 100644 index 0000000..d30229a --- /dev/null +++ b/src/detection/lm/lm.h @@ -0,0 +1,12 @@ +#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); diff --git a/src/detection/lm/lm_linux.c b/src/detection/lm/lm_linux.c new file mode 100644 index 0000000..140205d --- /dev/null +++ b/src/detection/lm/lm_linux.c @@ -0,0 +1,168 @@ +#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_A_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; +} diff --git a/src/detection/lm/lm_nosupport.c b/src/detection/lm/lm_nosupport.c new file mode 100644 index 0000000..22c1803 --- /dev/null +++ b/src/detection/lm/lm_nosupport.c @@ -0,0 +1,5 @@ +#include "lm.h" + +const char* ffDetectLM(FF_A_UNUSED FFLMResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/loadavg/loadavg.h b/src/detection/loadavg/loadavg.h new file mode 100644 index 0000000..4cfa696 --- /dev/null +++ b/src/detection/loadavg/loadavg.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +const char* ffDetectLoadavg(double result[3]); diff --git a/src/detection/loadavg/loadavg_bsd.c b/src/detection/loadavg/loadavg_bsd.c new file mode 100644 index 0000000..83896b2 --- /dev/null +++ b/src/detection/loadavg/loadavg_bsd.c @@ -0,0 +1,23 @@ +#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; +} diff --git a/src/detection/loadavg/loadavg_linux.c b/src/detection/loadavg/loadavg_linux.c new file mode 100644 index 0000000..a169312 --- /dev/null +++ b/src/detection/loadavg/loadavg_linux.c @@ -0,0 +1,33 @@ +#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; +} diff --git a/src/detection/loadavg/loadavg_nosupport.c b/src/detection/loadavg/loadavg_nosupport.c new file mode 100644 index 0000000..f14c717 --- /dev/null +++ b/src/detection/loadavg/loadavg_nosupport.c @@ -0,0 +1,5 @@ +#include "detection/loadavg/loadavg.h" + +const char* ffDetectLoadavg(FF_A_UNUSED double result[3]) { + return "Not supported on this platform"; +} diff --git a/src/detection/loadavg/loadavg_sunos.c b/src/detection/loadavg/loadavg_sunos.c new file mode 100644 index 0000000..91e3edd --- /dev/null +++ b/src/detection/loadavg/loadavg_sunos.c @@ -0,0 +1,7 @@ +#include "detection/loadavg/loadavg.h" + +#include + +const char* ffDetectLoadavg(double result[3]) { + return getloadavg(result, 3) == 3 ? NULL : "getloadavg() failed"; +} diff --git a/src/detection/locale/locale.h b/src/detection/locale/locale.h new file mode 100644 index 0000000..50f0bad --- /dev/null +++ b/src/detection/locale/locale.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +const char* ffDetectLocale(FFstrbuf* result); diff --git a/src/detection/locale/locale_linux.c b/src/detection/locale/locale_linux.c new file mode 100644 index 0000000..4c04f26 --- /dev/null +++ b/src/detection/locale/locale_linux.c @@ -0,0 +1,22 @@ +#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"; +} diff --git a/src/detection/locale/locale_windows.c b/src/detection/locale/locale_windows.c new file mode 100644 index 0000000..b247365 --- /dev/null +++ b/src/detection/locale/locale_windows.c @@ -0,0 +1,16 @@ +#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; +} diff --git a/src/detection/localip/localip.h b/src/detection/localip/localip.h new file mode 100644 index 0000000..d3b9015 --- /dev/null +++ b/src/detection/localip/localip.h @@ -0,0 +1,47 @@ +#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); diff --git a/src/detection/localip/localip_linux.c b/src/detection/localip/localip_linux.c new file mode 100644 index 0000000..d07ec76 --- /dev/null +++ b/src/detection/localip/localip_linux.c @@ -0,0 +1,1011 @@ +#include "localip.h" +#include "common/io.h" +#include "common/netif.h" +#include "common/strutil.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 = {}; + static bool initialized = false; + if (!initialized) { + initialized = true; + ffListInit(&addresses); + 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 = FF_LIST_ADD(struct in6_addr, 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(); + + 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 = FF_LIST_ADD(FFAdapter, adapters); + *adapter = (FFAdapter) { + .mac = ifa, + .ipv4 = ffListCreate(), + .ipv6 = ffListCreate(), + }; + 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, (unsigned) 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) { + FF_A_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; +} diff --git a/src/detection/localip/localip_windows.c b/src/detection/localip/localip_windows.c new file mode 100644 index 0000000..6a7e0e7 --- /dev/null +++ b/src/detection/localip/localip_windows.c @@ -0,0 +1,270 @@ +#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_A_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 = FF_LIST_ADD(FFLocalIpResult, *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_A_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; +} diff --git a/src/detection/media/media.c b/src/detection/media/media.c new file mode 100644 index 0000000..7adfb99 --- /dev/null +++ b/src/detection/media/media.c @@ -0,0 +1,45 @@ +#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.length = 0; + result.position = 0; + 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; +} diff --git a/src/detection/media/media.h b/src/detection/media/media.h new file mode 100644 index 0000000..ea241e6 --- /dev/null +++ b/src/detection/media/media.h @@ -0,0 +1,21 @@ +#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; + uint32_t length; // In milliseconds + uint32_t position; // In milliseconds + bool removeCoverAfterUse; +} FFMediaResult; + +const FFMediaResult* ffDetectMedia(bool saveCover); diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m new file mode 100644 index 0000000..d9501d1 --- /dev/null +++ b/src/detection/media/media_apple.m @@ -0,0 +1,209 @@ +#include "fastfetch.h" +#include "common/processing.h" +#include "common/apple/cf_helpers.h" +#include "common/time.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)) FF_A_WEAK_IMPORT; +extern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing)) FF_A_WEAK_IMPORT; +extern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) FF_A_WEAK_IMPORT; +extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) FF_A_WEAK_IMPORT; + +static uint32_t getTrueElapsedTime(CFDictionaryRef info) { + double elapsedTime; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoElapsedTime"), &elapsedTime) != NULL) { + return 0; + } + + elapsedTime *= 1000; + + double playbackRate; + uint64_t timestampEpoch; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoPlaybackRate"), &playbackRate) == NULL && + ffCfDictGetDateAsEpoch(info, CFSTR("kMRMediaRemoteNowPlayingInfoTimestamp"), ×tampEpoch) == NULL) { + uint64_t timeDiff = ffTimeGetNow() - timestampEpoch; + elapsedTime += (double) timeDiff * playbackRate; + } + + return (uint32_t) elapsedTime; +} + +static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) { +#define FF_TEST_FN_EXISTENCE(fn) \ + if (!fn) return "MediaRemote function " #fn " is not available" + FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingInfo); + FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying); +#undef FF_TEST_FN_EXISTENCE + + dispatch_group_t group = dispatch_group_create(); + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + + 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); + double value; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoDuration"), &value) == NULL) { + result->length = (uint32_t) (value * 1000); + result->position = getTrueElapsedTime(info); + } + + 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; +} + +#if !FF_MODULE_DISABLE_MEDIA +__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; + } + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.song); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.artist); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.album); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.playerId); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.player); + ffStrbufAppendC(&media.status, '\n'); + if (saveCover) { + ffStrbufAppend(&media.status, &media.cover); + } + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppendUInt(&media.status, media.position); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppendUInt(&media.status, media.length); + write(STDOUT_FILENO, media.status.chars, media.status.length); + ffStrbufDestroy(&media.status); + ffStrbufDestroy(&media.song); + ffStrbufDestroy(&media.artist); + ffStrbufDestroy(&media.album); + ffStrbufDestroy(&media.playerId); + ffStrbufDestroy(&media.player); + ffStrbufDestroy(&media.cover); + return 0; +} +#endif + +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\ncoverPath\nelapsedTimeMS\ndurationMS + FFstrbuf* const strList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover }; + char* line = NULL; + size_t len = 0; + for (uint32_t i = 0; i < ARRAY_SIZE(strList) && ffStrbufGetline(&line, &len, &buffer); ++i) { + ffStrbufSetS(strList[i], line); + } + uint32_t* const numList[] = { &result->position, &result->length }; + for (uint32_t i = 0; i < ARRAY_SIZE(numList) && ffStrbufGetline(&line, &len, &buffer); ++i) { + *numList[i] = (uint32_t) strtoul(line, NULL, 10); + } + return NULL; +} + +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { + 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; + } + } +} diff --git a/src/detection/media/media_linux.c b/src/detection/media/media_linux.c new file mode 100644 index 0000000..724e834 --- /dev/null +++ b/src/detection/media/media_linux.c @@ -0,0 +1,288 @@ +#include "fastfetch.h" +#include "common/io.h" +#include "common/strutil.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* mpris = key + strlen("mpris:"); + if (ffStrEquals(mpris, "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]); + } + } + } + } else if (ffStrEquals(mpris, "length")) { + int64_t length = 0; // microseconds + if (ffDBusGetInt(data, &dictIterator, &length) && length > 0) { + result->length = (uint32_t) (length / 1000); + } + } + } + + 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); + } else if (ffStrEquals(key, "Position")) { + int64_t position = 0; // microseconds + if (ffDBusGetInt(data, &dictIterator, &position) && position > 0) { + result->position = (uint32_t) (position / 1000); + } + } + + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) + } + + 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) || + ffStrEquals(busName + strlen(FF_DBUS_MPRIS_PREFIX), "playerctld")) { + 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 +} diff --git a/src/detection/media/media_nosupport.c b/src/detection/media/media_nosupport.c new file mode 100644 index 0000000..e7d1c0f --- /dev/null +++ b/src/detection/media/media_nosupport.c @@ -0,0 +1,5 @@ +#include "media.h" + +void ffDetectMediaImpl(FFMediaResult* media) { + ffStrbufAppendS(&media->error, "Not supported on this platform"); +} diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp new file mode 100644 index 0000000..f0ebd35 --- /dev/null +++ b/src/detection/media/media_windows.cpp @@ -0,0 +1,421 @@ +extern "C" { +#include "media.h" +#include "common/time.h" +#include "common/windows/unicode.h" +#include "common/windows/com.h" +} + +#if FF_HAVE_WINRT + #include + #include + #include + #include + + #include + #include + #include + #include + +using winrt::impl::abi_t; +using winrt::Windows::Foundation::IAsyncOperation; +using winrt::Windows::Foundation::IAsyncOperationWithProgress; + +static inline void deleteHstring(HSTRING* pstr) { + if (*pstr) { + WindowsDeleteString(*pstr); + } +} + +static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { + uint32_t length; + const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); + ffStrbufSetNWS(destination, length, raw); +} + +template +static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID iid, Interface** factory) { + HSTRING_HEADER header; + HSTRING runtimeClass; + HRESULT hr = WindowsCreateStringReference(className, (UINT32)::wcslen(className), &header, &runtimeClass); + if (FAILED(hr)) { + return hr; + } + + return RoGetActivationFactory(runtimeClass, iid, reinterpret_cast(factory)); +} + +template +static inline HRESULT ffQueryInterface(SourceAbi* source, abi_t** target) { + return source->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); +} + +template +static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { + IAsyncInfo* FF_AUTO_RELEASE_COM_OBJECT asyncInfo = NULL; + HRESULT hr = ffQueryInterface(operation, &asyncInfo); + if (FAILED(hr)) { + return hr; + } + + AsyncStatus status = AsyncStatus::Started; + + for (;;) { + hr = asyncInfo->get_Status(&status); + if (FAILED(hr)) { + return hr; + } + if (status == AsyncStatus::Started) { + ffTimeSleep(0); + } else { + break; + } + } + + if (status != AsyncStatus::Completed) { + HRESULT errorCode = E_FAIL; + asyncInfo->get_ErrorCode(&errorCode); + return FAILED(errorCode) ? errorCode : E_FAIL; + } + + return operation->GetResults((void**) result); +} + +template +static HRESULT ffRunAndWait(TOperation&& operation, abi_t** result) { + abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; + HRESULT hr = operation(reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { + return hr; + } + + return ffWaitForAsyncOperation(opResult, result); +} + +template +static HRESULT ffRunAndWait2(TOperation&& operation, abi_t** result) { + *result = NULL; + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; + HRESULT hr = operation(reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { + return hr; + } + + return ffWaitForAsyncOperation(opResult, result); +} + +static HRESULT ffSaveThumbnailToTempPath( + abi_t* thumbnail, + FFstrbuf* destination) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT contentStream = NULL; + HRESULT hr = ffRunAndWait([=](void** result) { + return thumbnail->OpenReadAsync(result); + }, + &contentStream); + if (FAILED(hr) || !contentStream) { + return FAILED(hr) ? hr : E_FAIL; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT randomAccessStream = NULL; + hr = ffQueryInterface(contentStream, &randomAccessStream); + if (FAILED(hr)) { + return hr; + } + + UINT64 size = 0; + hr = randomAccessStream->get_Size(&size); + if (FAILED(hr) || size == 0) { + return FAILED(hr) ? hr : S_FALSE; + } + + if (size > 0xFFFFFFFFu) { + return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE); + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT bufferFactory = NULL; + hr = ffGetActivationFactory(L"Windows.Storage.Streams.Buffer", winrt::guid_of(), &bufferFactory); + if (FAILED(hr)) { + return hr; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT buffer = NULL; + hr = bufferFactory->Create((UINT32) size, reinterpret_cast(&buffer)); + if (FAILED(hr) || !buffer) { + return FAILED(hr) ? hr : E_FAIL; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT inputStream = NULL; + hr = ffQueryInterface(contentStream, &inputStream); + if (FAILED(hr)) { + return hr; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT readBuffer = NULL; + hr = ffRunAndWait2([=](void** result) { + return inputStream->ReadAsync(buffer, (uint32_t) size, (uint32_t) winrt::Windows::Storage::Streams::InputStreamOptions::None, result); + }, + &readBuffer); + if (FAILED(hr) || !readBuffer) { + return FAILED(hr) ? hr : E_FAIL; + } + + UINT32 length = 0; + hr = readBuffer->get_Length(&length); + if (FAILED(hr) || length == 0) { + return FAILED(hr) ? hr : S_FALSE; + } + + Windows::Storage::Streams::IBufferByteAccess* FF_AUTO_RELEASE_COM_OBJECT byteAccess = NULL; + hr = readBuffer->QueryInterface(IID_PPV_ARGS(&byteAccess)); + if (FAILED(hr)) { + return hr; + } + + byte* bytes = NULL; + hr = byteAccess->Buffer(&bytes); + if (FAILED(hr) || !bytes) { + return FAILED(hr) ? hr : E_FAIL; + } + + wchar_t tempDirectory[MAX_PATH]; + DWORD tempLength = GetTempPathW(MAX_PATH, tempDirectory); + if (tempLength == 0 || tempLength >= MAX_PATH) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + wchar_t tempFilePath[MAX_PATH]; + if (!GetTempFileNameW(tempDirectory, L"fft", 0, tempFilePath)) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + HANDLE file = CreateFileW(tempFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + DWORD writeError = GetLastError(); + DeleteFileW(tempFilePath); + return HRESULT_FROM_WIN32(writeError); + } + + DWORD written = 0; + BOOL writtenOk = WriteFile(file, bytes, length, &written, NULL); + NtClose(file); + file = NULL; + + if (!writtenOk || written != length) { + DWORD writeError = GetLastError(); + DeleteFileW(tempFilePath); + return HRESULT_FROM_WIN32(writtenOk ? ERROR_WRITE_FAULT : writeError); + } + + ffStrbufSetWS(destination, tempFilePath); + return S_OK; +} + +static const char* getMedia(FFMediaResult* result, bool saveCover) { + const char* error = ffInitCom(); + if (error) { + return error; + } + + do { + abi_t* FF_AUTO_RELEASE_COM_OBJECT managerStatics = NULL; + HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", winrt::guid_of(), &managerStatics); + if (FAILED(hr) || !managerStatics) { + error = "winrt: RoGetActivationFactory(GlobalSystemMediaTransportControlsSessionManager) failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT manager = NULL; + hr = ffRunAndWait([=](void** result) { + return managerStatics->RequestAsync(result); + }, + &manager); + if (FAILED(hr) || !manager) { + error = "winrt: RequestAsync().GetResults() failed"; + break; + } + + FF_A_CLEANUP(deleteHstring) HSTRING playerId = NULL; + + abi_t* FF_AUTO_RELEASE_COM_OBJECT session = NULL; + if (instance.config.general.playerName.length) { + abi_t>* FF_AUTO_RELEASE_COM_OBJECT sessions = NULL; + hr = manager->GetSessions(reinterpret_cast(&sessions)); + if (FAILED(hr) || !sessions) { + error = "winrt: GetSessions() failed"; + break; + } + uint32_t sessionCount = 0; + hr = sessions->get_Size(&sessionCount); + if (FAILED(hr)) { + error = "winrt: GetSessions().get_Size() failed"; + break; + } + for (uint32_t i = 0; i < sessionCount; i++) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT currentSession = NULL; + hr = sessions->GetAt(i, reinterpret_cast(¤tSession)); + if (FAILED(hr) || !currentSession) { + continue; + } + + hr = currentSession->get_SourceAppUserModelId(reinterpret_cast(&playerId)); + if (FAILED(hr) || !playerId) { + continue; + } + + ffStrbufSetHstring(&result->playerId, playerId); + + if (ffStrbufContainIgnCase(&result->playerId, &instance.config.general.playerName)) { + session = currentSession; + currentSession = NULL; // Don't release the session object + break; + } + deleteHstring(&playerId); + ffStrbufClear(&result->playerId); + } + + if (!session) { + error = "winrt: No media session found with the specified player name"; + break; + } + } else { + hr = manager->GetCurrentSession(reinterpret_cast(&session)); + + if (FAILED(hr) || !session) { + error = "winrt: GetCurrentSession() failed"; + break; + } + + hr = session->get_SourceAppUserModelId(reinterpret_cast(&playerId)); + if (FAILED(hr)) { + error = "winrt: get_SourceAppUserModelId() failed"; + break; + } + + ffStrbufSetHstring(&result->playerId, playerId); + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT mediaProps = NULL; + hr = ffRunAndWait([=](void** result) { + return session->TryGetMediaPropertiesAsync(result); + }, + &mediaProps); + if (FAILED(hr) || !mediaProps) { + error = "winrt: TryGetMediaPropertiesAsync().GetResults() failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT playbackInfo = NULL; + hr = session->GetPlaybackInfo(reinterpret_cast(&playbackInfo)); + bool isPlaying = false; + double playbackRate = 1.0; + if (SUCCEEDED(hr) && playbackInfo) { + int32_t playbackStatusValue = 0; + if (SUCCEEDED(playbackInfo->get_PlaybackStatus(&playbackStatusValue))) { + isPlaying = playbackStatusValue == static_cast(winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing); + switch (static_cast(playbackStatusValue)) { + #define FF_MEDIA_SET_STATUS(status_code) \ + case winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus::status_code: \ + ffStrbufSetStatic(&result->status, #status_code); \ + break; + FF_MEDIA_SET_STATUS(Closed) + FF_MEDIA_SET_STATUS(Opened) + FF_MEDIA_SET_STATUS(Changing) + FF_MEDIA_SET_STATUS(Stopped) + FF_MEDIA_SET_STATUS(Playing) + FF_MEDIA_SET_STATUS(Paused) + #undef FF_MEDIA_SET_STATUS + } + } + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT playbackRateRef = NULL; + if (SUCCEEDED(playbackInfo->get_PlaybackRate(reinterpret_cast(&playbackRateRef))) && playbackRateRef) { + if (SUCCEEDED(playbackRateRef->get_Value(&playbackRate)) && playbackRate < 0.0) { + playbackRate = 0.0; + } + } + } + + FF_A_CLEANUP(deleteHstring) HSTRING title = NULL; + if (SUCCEEDED(mediaProps->get_Title(reinterpret_cast(&title)))) { + ffStrbufSetHstring(&result->song, title); + } + + FF_A_CLEANUP(deleteHstring) HSTRING artist = NULL; + if (SUCCEEDED(mediaProps->get_Artist(reinterpret_cast(&artist)))) { + ffStrbufSetHstring(&result->artist, artist); + } + + FF_A_CLEANUP(deleteHstring) HSTRING album = NULL; + if (SUCCEEDED(mediaProps->get_AlbumTitle(reinterpret_cast(&album)))) { + ffStrbufSetHstring(&result->album, album); + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT timelineProps = NULL; + hr = session->GetTimelineProperties(reinterpret_cast(&timelineProps)); + if (SUCCEEDED(hr) && timelineProps) { + int64_t duration = 0; + if (SUCCEEDED(timelineProps->get_EndTime(&duration)) && duration > 0) { + result->length = (uint32_t) (duration / 10000); // Convert from 100-nanosecond units to milliseconds + + int64_t position = 0; + if (SUCCEEDED(timelineProps->get_Position(&position))) { + result->position = (uint32_t) (position / 10000); // Convert from 100-nanosecond units to milliseconds + + int64_t lastUpdatedTime = 0; + if (isPlaying && SUCCEEDED(timelineProps->get_LastUpdatedTime(&lastUpdatedTime)) && lastUpdatedTime > 0) { + uint64_t lastUpdatedTimeMs = ffFileTimeToUnixMs((uint64_t) lastUpdatedTime); + uint64_t nowMs = ffTimeGetNow(); + if (nowMs > lastUpdatedTimeMs) { + result->position += (uint32_t) (((double) (nowMs - lastUpdatedTimeMs)) * playbackRate); + } + } + } + } + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfoStatics = NULL; + hr = ffGetActivationFactory(L"Windows.ApplicationModel.AppInfo", winrt::guid_of(), &appInfoStatics); + if (SUCCEEDED(hr) && appInfoStatics) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfo = NULL; + if (SUCCEEDED(appInfoStatics->GetFromAppUserModelId(reinterpret_cast(playerId), reinterpret_cast(&appInfo))) && appInfo) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT displayInfo = NULL; + if (SUCCEEDED(appInfo->get_DisplayInfo(reinterpret_cast(&displayInfo))) && displayInfo) { + FF_A_CLEANUP(deleteHstring) HSTRING displayName = NULL; + if (SUCCEEDED(displayInfo->get_DisplayName(reinterpret_cast(&displayName)))) { + ffStrbufSetHstring(&result->player, displayName); + } + } + } + } + + if (result->player.length == 0) { + ffStrbufSet(&result->player, &result->playerId); + if (ffStrbufEndsWithIgnCaseS(&result->player, ".exe")) { + ffStrbufSubstrBefore(&result->player, result->player.length - 4); + } + } + + if (saveCover) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT thumbnail = NULL; + hr = mediaProps->get_Thumbnail(reinterpret_cast(&thumbnail)); + if (SUCCEEDED(hr) && thumbnail) { + if (SUCCEEDED(ffSaveThumbnailToTempPath(thumbnail, &result->cover)) && result->cover.length > 0) { + result->removeCoverAfterUse = true; + } + } + } + } while (false); + + return error; +} +#else +static const char* getMedia(FFMediaResult* media, bool saveCover) { + FF_UNUSED(media, saveCover); + return "Fastfetch is not compiled with WinRT support"; +} +#endif // FF_HAVE_WINRT + +extern "C" void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { + const char* error = getMedia(media, saveCover); + ffStrbufAppendS(&media->error, error); +} diff --git a/src/detection/memory/memory.h b/src/detection/memory/memory.h new file mode 100644 index 0000000..1c23828 --- /dev/null +++ b/src/detection/memory/memory.h @@ -0,0 +1,11 @@ +#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); diff --git a/src/detection/memory/memory_apple.c b/src/detection/memory/memory_apple.c new file mode 100644 index 0000000..6203949 --- /dev/null +++ b/src/detection/memory/memory_apple.c @@ -0,0 +1,35 @@ +#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; +} diff --git a/src/detection/memory/memory_bsd.c b/src/detection/memory/memory_bsd.c new file mode 100644 index 0000000..f76f6b8 --- /dev/null +++ b/src/detection/memory/memory_bsd.c @@ -0,0 +1,16 @@ +#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; +} diff --git a/src/detection/memory/memory_haiku.c b/src/detection/memory/memory_haiku.c new file mode 100644 index 0000000..7888801 --- /dev/null +++ b/src/detection/memory/memory_haiku.c @@ -0,0 +1,16 @@ +#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; +} diff --git a/src/detection/memory/memory_linux.c b/src/detection/memory/memory_linux.c new file mode 100644 index 0000000..74d20a0 --- /dev/null +++ b/src/detection/memory/memory_linux.c @@ -0,0 +1,62 @@ +#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; +} diff --git a/src/detection/memory/memory_nbsd.c b/src/detection/memory/memory_nbsd.c new file mode 100644 index 0000000..ca09475 --- /dev/null +++ b/src/detection/memory/memory_nbsd.c @@ -0,0 +1,18 @@ +#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; +} diff --git a/src/detection/memory/memory_nosupport.c b/src/detection/memory/memory_nosupport.c new file mode 100644 index 0000000..b44aaad --- /dev/null +++ b/src/detection/memory/memory_nosupport.c @@ -0,0 +1,5 @@ +#include "memory.h" + +const char* ffDetectMemory(FF_A_UNUSED FFMemoryResult* ram) { + return "Not supported on this platform"; +} diff --git a/src/detection/memory/memory_obsd.c b/src/detection/memory/memory_obsd.c new file mode 100644 index 0000000..c6da087 --- /dev/null +++ b/src/detection/memory/memory_obsd.c @@ -0,0 +1,18 @@ +#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; +} diff --git a/src/detection/memory/memory_sunos.c b/src/detection/memory/memory_sunos.c new file mode 100644 index 0000000..7dfdeab --- /dev/null +++ b/src/detection/memory/memory_sunos.c @@ -0,0 +1,9 @@ +#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; +} diff --git a/src/detection/memory/memory_windows.c b/src/detection/memory/memory_windows.c new file mode 100644 index 0000000..779425d --- /dev/null +++ b/src/detection/memory/memory_windows.c @@ -0,0 +1,19 @@ +#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; +} diff --git a/src/detection/mouse/mouse.h b/src/detection/mouse/mouse.h new file mode 100644 index 0000000..3247eeb --- /dev/null +++ b/src/detection/mouse/mouse.h @@ -0,0 +1,8 @@ +#include "fastfetch.h" + +typedef struct FFMouseDevice { + FFstrbuf serial; + FFstrbuf name; +} FFMouseDevice; + +const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */); diff --git a/src/detection/mouse/mouse_apple.c b/src/detection/mouse/mouse_apple.c new file mode 100644 index 0000000..2c5b2d7 --- /dev/null +++ b/src/detection/mouse/mouse_apple.c @@ -0,0 +1,36 @@ +#include "mouse.h" +#include "common/apple/cf_helpers.h" +#include "common/mallocHelper.h" + +#include +#include + +static void enumSet(IOHIDDeviceRef value, FFlist* results) { + FFMouseDevice* device = FF_LIST_ADD(FFMouseDevice, *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; +} diff --git a/src/detection/mouse/mouse_bsd.c b/src/detection/mouse/mouse_bsd.c new file mode 100644 index 0000000..da7783b --- /dev/null +++ b/src/detection/mouse/mouse_bsd.c @@ -0,0 +1,56 @@ +#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 = FF_LIST_ADD(FFMouseDevice, *devices); + ffStrbufInitS(&device->serial, di.udi_serial); + ffStrbufInitS(&device->name, di.udi_product); + } + } + hid_end_parse(hData); + } + + hid_dispose_report_desc(repDesc); + } + + return NULL; +} diff --git a/src/detection/mouse/mouse_haiku.cpp b/src/detection/mouse/mouse_haiku.cpp new file mode 100644 index 0000000..5835c8e --- /dev/null +++ b/src/detection/mouse/mouse_haiku.cpp @@ -0,0 +1,27 @@ +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 = FF_LIST_ADD(FFMouseDevice, *devices); + ffStrbufInit(&item->serial); + ffStrbufInitS(&item->name, device->Name()); + } + + return NULL; +} diff --git a/src/detection/mouse/mouse_linux.c b/src/detection/mouse/mouse_linux.c new file mode 100644 index 0000000..939c332 --- /dev/null +++ b/src/detection/mouse/mouse_linux.c @@ -0,0 +1,45 @@ +#include "mouse.h" +#include "common/io.h" +#include "common/strutil.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 = FF_LIST_ADD(FFMouseDevice, *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; +} diff --git a/src/detection/mouse/mouse_nosupport.c b/src/detection/mouse/mouse_nosupport.c new file mode 100644 index 0000000..aa1332d --- /dev/null +++ b/src/detection/mouse/mouse_nosupport.c @@ -0,0 +1,5 @@ +#include "mouse.h" + +const char* ffDetectMouse(FF_A_UNUSED FFlist* devices /* List of FFMouseDevice */) { + return "No mouse support on this platform"; +} diff --git a/src/detection/mouse/mouse_windows.c b/src/detection/mouse/mouse_windows.c new file mode 100644 index 0000000..3f2370a --- /dev/null +++ b/src/detection/mouse/mouse_windows.c @@ -0,0 +1,84 @@ +#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 = FF_LIST_ADD(FFMouseDevice, *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; +} diff --git a/src/detection/netio/netio.c b/src/detection/netio/netio.c new file mode 100644 index 0000000..c6b9da6 --- /dev/null +++ b/src/detection/netio/netio.c @@ -0,0 +1,81 @@ +#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); + 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); + 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; +} diff --git a/src/detection/netio/netio.h b/src/detection/netio/netio.h new file mode 100644 index 0000000..1e48b53 --- /dev/null +++ b/src/detection/netio/netio.h @@ -0,0 +1,20 @@ +#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); diff --git a/src/detection/netio/netio_apple.c b/src/detection/netio/netio_apple.c new file mode 100644 index 0000000..125cfd9 --- /dev/null +++ b/src/detection/netio/netio_apple.c @@ -0,0 +1,55 @@ +#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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/netio/netio_bsd.c b/src/detection/netio/netio_bsd.c new file mode 100644 index 0000000..83c1b6b --- /dev/null +++ b/src/detection/netio/netio_bsd.c @@ -0,0 +1,62 @@ +#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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/netio/netio_haiku.cpp b/src/detection/netio/netio_haiku.cpp new file mode 100644 index 0000000..88a123d --- /dev/null +++ b/src/detection/netio/netio_haiku.cpp @@ -0,0 +1,52 @@ +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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/netio/netio_linux.c b/src/detection/netio/netio_linux.c new file mode 100644 index 0000000..5870d3f --- /dev/null +++ b/src/detection/netio/netio_linux.c @@ -0,0 +1,91 @@ +#include "netio.h" + +#include "common/io.h" +#include "common/netif.h" +#include "common/strutil.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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/netio/netio_nosupport.c b/src/detection/netio/netio_nosupport.c new file mode 100644 index 0000000..30f73a7 --- /dev/null +++ b/src/detection/netio/netio_nosupport.c @@ -0,0 +1,5 @@ +#include "netio.h" + +const char* ffNetIOGetIoCounters(FF_A_UNUSED FFlist* result, FF_A_UNUSED FFNetIOOptions* options) { + return "Not supported on this platform"; +} diff --git a/src/detection/netio/netio_sunos.c b/src/detection/netio/netio_sunos.c new file mode 100644 index 0000000..3789e45 --- /dev/null +++ b/src/detection/netio/netio_sunos.c @@ -0,0 +1,68 @@ +#include "netio.h" +#include "common/netif.h" +#include "common/strutil.h" + +#include + +static inline void kstatFreeWrap(kstat_ctl_t** pkc) { + assert(pkc); + if (*pkc) { + kstat_close(*pkc); + } +} + +const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { + FF_A_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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/netio/netio_windows.c b/src/detection/netio/netio_windows.c new file mode 100644 index 0000000..3c64334 --- /dev/null +++ b/src/detection/netio/netio_windows.c @@ -0,0 +1,71 @@ +#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 = FF_LIST_ADD(FFNetIOResult, *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; +} diff --git a/src/detection/opencl/opencl.c b/src/detection/opencl/opencl.c new file mode 100644 index 0000000..14216ac --- /dev/null +++ b/src/detection/opencl/opencl.c @@ -0,0 +1,218 @@ +#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/strutil.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 = FF_LIST_ADD(FFGPUResult, 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; + static bool initialized; + + if (!initialized) { + initialized = true; + ffStrbufInit(&result.version); + ffStrbufInit(&result.name); + ffStrbufInit(&result.vendor); + ffListInit(&result.gpus); + +#ifdef FF_HAVE_OPENCL + result.error = detectOpenCL(&result); +#else + result.error = "fastfetch was compiled without OpenCL support"; +#endif + } + + return &result; +} diff --git a/src/detection/opencl/opencl.h b/src/detection/opencl/opencl.h new file mode 100644 index 0000000..d96eb50 --- /dev/null +++ b/src/detection/opencl/opencl.h @@ -0,0 +1,14 @@ +#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(); diff --git a/src/detection/opengl/opengl.h b/src/detection/opengl/opengl.h new file mode 100644 index 0000000..f7ff9d5 --- /dev/null +++ b/src/detection/opengl/opengl.h @@ -0,0 +1,17 @@ +#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); diff --git a/src/detection/opengl/opengl_apple.c b/src/detection/opengl/opengl_apple.c new file mode 100644 index 0000000..9128593 --- /dev/null +++ b/src/detection/opengl/opengl_apple.c @@ -0,0 +1,66 @@ + +#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"; + } +} diff --git a/src/detection/opengl/opengl_haiku.cpp b/src/detection/opengl/opengl_haiku.cpp new file mode 100644 index 0000000..64a21a0 --- /dev/null +++ b/src/detection/opengl/opengl_haiku.cpp @@ -0,0 +1,38 @@ +#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"; + } +} diff --git a/src/detection/opengl/opengl_linux.c b/src/detection/opengl/opengl_linux.c new file mode 100644 index 0000000..5f88b7e --- /dev/null +++ b/src/detection/opengl/opengl_linux.c @@ -0,0 +1,200 @@ +#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 +} diff --git a/src/detection/opengl/opengl_shared.c b/src/detection/opengl/opengl_shared.c new file mode 100644 index 0000000..307e71d --- /dev/null +++ b/src/detection/opengl/opengl_shared.c @@ -0,0 +1,214 @@ +#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 diff --git a/src/detection/opengl/opengl_windows.c b/src/detection/opengl/opengl_windows.c new file mode 100644 index 0000000..01bd986 --- /dev/null +++ b/src/detection/opengl/opengl_windows.c @@ -0,0 +1,120 @@ +#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"; + } +} diff --git a/src/detection/os/os.c b/src/detection/os/os.c new file mode 100644 index 0000000..7f66733 --- /dev/null +++ b/src/detection/os/os.c @@ -0,0 +1,21 @@ +#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; +} diff --git a/src/detection/os/os.h b/src/detection/os/os.h new file mode 100644 index 0000000..5b62aab --- /dev/null +++ b/src/detection/os/os.h @@ -0,0 +1,19 @@ +#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(); diff --git a/src/detection/os/os_android.c b/src/detection/os/os_android.c new file mode 100644 index 0000000..47ea40b --- /dev/null +++ b/src/detection/os/os_android.c @@ -0,0 +1,16 @@ +#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); +} diff --git a/src/detection/os/os_apple.m b/src/detection/os/os_apple.m new file mode 100644 index 0000000..2890fc3 --- /dev/null +++ b/src/detection/os/os_apple.m @@ -0,0 +1,105 @@ +#include "os.h" +#include "common/io.h" +#include "common/sysctl.h" +#include "common/strutil.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); +} diff --git a/src/detection/os/os_haiku.c b/src/detection/os/os_haiku.c new file mode 100644 index 0000000..e8e9f4a --- /dev/null +++ b/src/detection/os/os_haiku.c @@ -0,0 +1,66 @@ +#include "os.h" +#include +#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%" PRIi32, 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%" PRId32 "A%" PRIi32 "-", relVer, alphaVer + 1); + } else { + ffStrbufSetF(&os->version, "R%" PRIi32 "A%" PRIi32, 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%" PRIi32 "B%" PRIi32 "-", relVer, betaVer + 1); + } else { + ffStrbufSetF(&os->version, "R%" PRIi32 "B%" PRIi32, relVer, betaVer); + } + } + } + } + } + + if (!os->version.length) { + system_info sys; + if (get_system_info(&sys) == B_OK) { + ffStrbufAppendF(&os->version, "R%" PRIx64, sys.kernel_version); + } + } +} diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c new file mode 100644 index 0000000..fc653a3 --- /dev/null +++ b/src/detection/os/os_linux.c @@ -0,0 +1,397 @@ +#include "os.h" +#include "common/properties.h" +#include "common/parsing.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/strutil.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_A_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_A_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; + } + + // xdgConfigDirs contains plasma only + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { + ffStrbufSetStatic(&result->name, "Ubuntu Studio"); + ffStrbufSetStatic(&result->id, "ubuntu-studio"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + 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, "ukui")) { + ffStrbufSetStatic(&result->name, "Ubuntu Kylin"); + ffStrbufSetStatic(&result->id, "ubuntu-kylin"); + 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; + } + + if (ffStrContains(xdgConfigDirs, "unity")) { + ffStrbufSetStatic(&result->name, "Ubuntu Unity"); + ffStrbufSetStatic(&result->id, "ubuntu-unity"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + return false; +} + +FF_A_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_A_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_A_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; +} + +FF_A_UNUSED 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); +} + +FF_A_UNUSED static void detectDeepinEnhancement(FFOSResult* result) { + if (ffStrbufContainC(&result->prettyName, '(')) { + return; + } + + FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY edition = ffStrbufCreate(); + + if (!ffParsePropFileValues( + FASTFETCH_TARGET_DIR_ETC "/os-version", + 2, + (FFpropquery[]) { + { "MinorVersion=", &minor }, + { "EditionName=", &edition }, + }) || + minor.length == 0) { + return; + } + + ffStrbufSet(&result->versionID, &minor); + + if (edition.length > 0) { + ffStrbufSetF(&result->prettyName, "%s %s (%s)", result->name.chars, minor.chars, edition.chars); + } else { + ffStrbufSetF(&result->prettyName, "%s %s", result->name.chars, minor.chars); + } +} + +static void detectOS(FFOSResult* os) { +#ifdef FF_CUSTOM_OS_RELEASE_PATH + parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os); + #ifdef FF_CUSTOM_LSB_RELEASE_PATH + parseLsbRelease(FF_STR(FF_CUSTOM_LSB_RELEASE_PATH), os); + #endif + return; +#endif + +#ifdef __linux__ + if (detectBedrock(os)) { + return; + } +#endif + + // 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"); + } + } else if (ffStrbufEqualS(&os->id, "deepin")) { + detectDeepinEnhancement(os); + } +#endif +} diff --git a/src/detection/os/os_nbsd.c b/src/detection/os/os_nbsd.c new file mode 100644 index 0000000..200d0f2 --- /dev/null +++ b/src/detection/os/os_nbsd.c @@ -0,0 +1,6 @@ +#include "os.h" + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "NetBSD"); + ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); +} diff --git a/src/detection/os/os_obsd.c b/src/detection/os/os_obsd.c new file mode 100644 index 0000000..4baf185 --- /dev/null +++ b/src/detection/os/os_obsd.c @@ -0,0 +1,6 @@ +#include "os.h" + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "OpenBSD"); + ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); +} diff --git a/src/detection/os/os_sunos.c b/src/detection/os/os_sunos.c new file mode 100644 index 0000000..72f9946 --- /dev/null +++ b/src/detection/os/os_sunos.c @@ -0,0 +1,17 @@ +#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"); +} diff --git a/src/detection/os/os_windows.c b/src/detection/os/os_windows.c new file mode 100644 index 0000000..2f280c8 --- /dev/null +++ b/src/detection/os/os_windows.c @@ -0,0 +1,92 @@ +#include "os.h" +#include "common/strutil.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); + } else if (ffStrbufStartsWithS(&os->variant, "Embedded ")) { + ffStrbufAppendS(&os->name, " Embedded"); + ffStrbufSubstrAfter(&os->variant, strlen(" Embedded") - 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); + } +} diff --git a/src/detection/packages/packages.c b/src/detection/packages/packages.c new file mode 100644 index 0000000..25f59b3 --- /dev/null +++ b/src/detection/packages/packages.c @@ -0,0 +1,119 @@ +#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]; + } + + 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 diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h new file mode 100644 index 0000000..f0d4200 --- /dev/null +++ b/src/detection/packages/packages.h @@ -0,0 +1,72 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/packages/option.h" + +typedef struct FFPackagesResult { + uint32_t amSystem; + uint32_t amUser; + uint32_t apk; + uint32_t appimage; + uint32_t brew; + uint32_t brewCask; + uint32_t cards; + 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 moss; + uint32_t mport; + 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 scoopGlobal; + uint32_t scoopUser; + 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; + +#if FF_PACKAGES_REMOVE_DISABLED + #define FF_PACKAGES_IS_ENABLED(options, pkgName) ({ (void) options; !((FF_PACKAGES_DISABLE_LIST) & (FF_PACKAGES_FLAG_ ## pkgName ## _BIT)); }) +#else + #define FF_PACKAGES_IS_ENABLED(options, pkgName) (!((options)->disabled & (FF_PACKAGES_FLAG_ ## pkgName ## _BIT))) +#endif + +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 diff --git a/src/detection/packages/packages_apple.c b/src/detection/packages/packages_apple.c new file mode 100644 index 0000000..43de97d --- /dev/null +++ b/src/detection/packages/packages_apple.c @@ -0,0 +1,56 @@ +#include "packages.h" +#include "common/io.h" +#include "common/parsing.h" +#include "common/processing.h" +#include "common/strutil.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 (FF_PACKAGES_IS_ENABLED(options, BREW)) { + 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 (FF_PACKAGES_IS_ENABLED(options, MACPORTS)) { + 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 (FF_PACKAGES_IS_ENABLED(options, NIX)) { + 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"); + } +} diff --git a/src/detection/packages/packages_bsd.c b/src/detection/packages/packages_bsd.c new file mode 100644 index 0000000..73772ac --- /dev/null +++ b/src/detection/packages/packages_bsd.c @@ -0,0 +1,28 @@ +#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 (FF_PACKAGES_IS_ENABLED(options, PKG)) { + result->pkg = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT "/var/db/pkg/local.sqlite", "SELECT count(*) FROM packages", "pkg"); + } + if (FF_PACKAGES_IS_ENABLED(options, MPORT)) { + result->mport = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT "/var/db/mport/master.db", "SELECT count(*) FROM packages", "mport"); + } +} diff --git a/src/detection/packages/packages_haiku.c b/src/detection/packages/packages_haiku.c new file mode 100644 index 0000000..eee14cf --- /dev/null +++ b/src/detection/packages/packages_haiku.c @@ -0,0 +1,12 @@ +#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 (FF_PACKAGES_IS_ENABLED(options, HPKG)) { + result->hpkgSystem = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/system/packages", false); + result->hpkgUser = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/boot/home/config/packages", false); + } +} diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c new file mode 100644 index 0000000..79de8cb --- /dev/null +++ b/src/detection/packages/packages_linux.c @@ -0,0 +1,651 @@ +#include "packages.h" +#include "common/io.h" +#include "common/parsing.h" +#include "common/properties.h" +#include "common/settings.h" +#include "common/strutil.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 getNumElementsBySuffix(FFstrbuf* baseDir, const char* dirname, const char* suffix) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + ffStrbufSubstrBefore(baseDir, baseDirLength); + if (dirp == NULL) { + return 0; + } + + uint32_t count = 0; + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] != '.' && ffStrEndsWithIgnCase(entry->d_name, suffix)) { + ++count; + } + } + return count; +} + +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 (FF_PACKAGES_IS_ENABLED(options, APK)) { + packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk"); + } + if (FF_PACKAGES_IS_ENABLED(options, DPKG)) { + packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg"); + } + if (FF_PACKAGES_IS_ENABLED(options, LPKG)) { + packageCounts->lpkg += getNumStrings(baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", "\n", "lpkg"); + } + if (FF_PACKAGES_IS_ENABLED(options, EMERGE)) { + packageCounts->emerge += countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); + } + if (FF_PACKAGES_IS_ENABLED(options, EOPKG)) { + packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true); + } + if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) { + packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); + } + if (FF_PACKAGES_IS_ENABLED(options, KISS)) { + packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true); + } + if (FF_PACKAGES_IS_ENABLED(options, NIX)) { + packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); + packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system"); + } + if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) { + packageCounts->pacman += getPacmanPackages(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, LPKGBUILD)) { + packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); + } + if (FF_PACKAGES_IS_ENABLED(options, PKGTOOL)) { + packageCounts->pkgtool += getNumElements(baseDir, "/var/log/packages", false); + } + if (FF_PACKAGES_IS_ENABLED(options, RPM)) { + // `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 (FF_PACKAGES_IS_ENABLED(options, SNAP)) { + packageCounts->snap += getSnap(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, XBPS)) { + packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps"); + } + if (FF_PACKAGES_IS_ENABLED(options, BREW)) { + packageCounts->brewCask += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true); + packageCounts->brew += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PALUDIS)) { + packageCounts->paludis += countFilesRecursive(baseDir, "/var/db/paludis/repositories", "environment.bz2"); + } + if (FF_PACKAGES_IS_ENABLED(options, OPKG)) { + packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", "Package:", "opkg"); // openwrt + } + if (FF_PACKAGES_IS_ENABLED(options, AM)) { + packageCounts->amSystem = getAMSystem(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, SORCERY)) { + packageCounts->sorcery += getNumStrings(baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery"); + } + if (FF_PACKAGES_IS_ENABLED(options, GUIX)) { + packageCounts->guixSystem += getGuixPackages(baseDir, "/run/current-system/profile"); + } + if (FF_PACKAGES_IS_ENABLED(options, LINGLONG)) { + packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/layers", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PACSTALL)) { + packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); + } + if (FF_PACKAGES_IS_ENABLED(options, PISI)) { + packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PKGSRC)) { + packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); + } + if (FF_PACKAGES_IS_ENABLED(options, MOSS)) { + packageCounts->moss += getSQLite3Int(baseDir, "/.moss/db/state", "SELECT COUNT(*) FROM state_selections WHERE state_id = (SELECT MAX(id) FROM state)", "moss"); + } + if (FF_PACKAGES_IS_ENABLED(options, CARDS)) { + packageCounts->cards += getNumElements(baseDir, "/var/lib/pkg/DB", true); + } +} + +static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { + getPackageCounts(baseDir, packageCounts, options); + + if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) { + 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 (FF_PACKAGES_IS_ENABLED(options, RPM) && result->rpm == 0) { + result->rpm = getRpmFromLibrpm(); + } +#endif + + ffStrbufSet(&baseDir, &instance.state.platform.homeDir); + if (FF_PACKAGES_IS_ENABLED(options, NIX)) { + // 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 (FF_PACKAGES_IS_ENABLED(options, GUIX)) { + result->guixUser += getGuixPackages(&baseDir, ".guix-profile"); + result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile"); + } + + if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) { + result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share"); + } + + if (FF_PACKAGES_IS_ENABLED(options, AM)) { + result->amUser = getAMUser(); + } + + if (FF_PACKAGES_IS_ENABLED(options, SOAR)) { + result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", "SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true", "soar"); + } + + if (FF_PACKAGES_IS_ENABLED(options, APPIMAGE)) { + result->appimage += getNumElementsBySuffix(&baseDir, "/AppImages", ".appimage"); + result->appimage += getNumElementsBySuffix(&baseDir, "/Applications", ".appimage"); + } +} diff --git a/src/detection/packages/packages_nbsd.c b/src/detection/packages/packages_nbsd.c new file mode 100644 index 0000000..2f79126 --- /dev/null +++ b/src/detection/packages/packages_nbsd.c @@ -0,0 +1,9 @@ +#include "packages.h" + +#include "common/io.h" + +void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { + if (FF_PACKAGES_IS_ENABLED(options, PKGSRC)) { + result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/usr/pkg/pkgdb", true); + } +} diff --git a/src/detection/packages/packages_nix.c b/src/detection/packages/packages_nix.c new file mode 100644 index 0000000..d1cc264 --- /dev/null +++ b/src/detection/packages/packages_nix.c @@ -0,0 +1,149 @@ +#include "packages.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/strutil.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; +} diff --git a/src/detection/packages/packages_nosupport.c b/src/detection/packages/packages_nosupport.c new file mode 100644 index 0000000..40300fa --- /dev/null +++ b/src/detection/packages/packages_nosupport.c @@ -0,0 +1,4 @@ +#include "packages.h" + +void ffDetectPackagesImpl(FF_A_UNUSED FFPackagesResult* result, FF_A_UNUSED FFPackagesOptions* options) { +} diff --git a/src/detection/packages/packages_obsd.c b/src/detection/packages/packages_obsd.c new file mode 100644 index 0000000..c7b098e --- /dev/null +++ b/src/detection/packages/packages_obsd.c @@ -0,0 +1,9 @@ +#include "packages.h" + +#include "common/io.h" + +void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { + if (FF_PACKAGES_IS_ENABLED(options, PKG)) { + result->pkg = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/var/db/pkg", true); + } +} diff --git a/src/detection/packages/packages_sunos.c b/src/detection/packages/packages_sunos.c new file mode 100644 index 0000000..16b8cd7 --- /dev/null +++ b/src/detection/packages/packages_sunos.c @@ -0,0 +1,17 @@ +#include "packages.h" +#include + +void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { + if (FF_PACKAGES_IS_ENABLED(options, PKG)) { + 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 (FF_PACKAGES_IS_ENABLED(options, PKGSRC)) { + result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/usr/pkg/pkgdb", true); + } +} diff --git a/src/detection/packages/packages_windows.c b/src/detection/packages/packages_windows.c new file mode 100644 index 0000000..a3faee5 --- /dev/null +++ b/src/detection/packages/packages_windows.c @@ -0,0 +1,203 @@ +#include "packages.h" +#include "common/processing.h" +#include "common/strutil.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* FF_A_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_A_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 (FF_PACKAGES_IS_ENABLED(options, SCOOP)) { + detectScoop(result); + } + if (FF_PACKAGES_IS_ENABLED(options, CHOCO)) { + detectChoco(result); + } + if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) { + detectPacman(result); + } + if (FF_PACKAGES_IS_ENABLED(options, WINGET)) { + detectWinget(result); + } +} diff --git a/src/detection/physicaldisk/physicaldisk.h b/src/detection/physicaldisk/physicaldisk.h new file mode 100644 index 0000000..3481063 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk.h @@ -0,0 +1,17 @@ +#include "fastfetch.h" +#include "modules/physicaldisk/option.h" + +#define FF_PHYSICALDISK_TEMP_UNSET (-DBL_MAX) + +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); diff --git a/src/detection/physicaldisk/physicaldisk_apple.c b/src/detection/physicaldisk/physicaldisk_apple.c new file mode 100644 index 0000000..f4c5cd6 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_apple.c @@ -0,0 +1,191 @@ +#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 + FF_A_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(kIOBlockStorageDriverClass), &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 entryDriver = registryEntry; + + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryMedia = IO_OBJECT_NULL; + IORegistryEntryGetChildEntry(entryDriver, kIOServicePlane, &entryMedia); + + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPhysical = 0; + if (IORegistryEntryGetParentEntry(entryDriver, kIOServicePlane, &entryPhysical) != KERN_SUCCESS) { + continue; + } + + FF_STRBUF_AUTO_DESTROY deviceName = ffStrbufCreate(); + + if (entryMedia != IO_OBJECT_NULL) { + io_name_t name; + if (IORegistryEntryGetName(entryMedia, name) == KERN_SUCCESS) { + ffStrbufSetS(&deviceName, name); + } + } + + if (deviceName.length == 0) { + FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), NULL, kNilOptions); + if (deviceCharacteristics) { + if (ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyVendorNameKey), &deviceName) == NULL && deviceName.length > 0) { + ffStrbufAppendC(&deviceName, ' '); + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + if (ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductNameKey), &name) == NULL) { + ffStrbufAppend(&deviceName, &name); + } + } + } + + if (options->namePrefix.length && ffStrbufStartsWith(&deviceName, &options->namePrefix) != 0) { + continue; + } + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + + FF_STRBUF_AUTO_DESTROY interconnect = ffStrbufCreate(); + FF_CFTYPE_AUTO_RELEASE CFDictionaryRef protocolCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyProtocolCharacteristicsKey), NULL, kNilOptions); + if (protocolCharacteristics) { + if (ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectTypeKey), &interconnect) == NULL) { + if (ffStrbufEqualS(&interconnect, kIOPropertyPhysicalInterconnectTypeVirtual)) { + if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + continue; + } + + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + FF_STRBUF_AUTO_DESTROY location = ffStrbufCreate(); + if (ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectLocationKey), &location) == NULL) { + ffStrbufAppendS(&interconnect, " - "); + ffStrbufAppend(&interconnect, &location); + } + } + } + } + + uint64_t size = 0; + if (entryMedia != IO_OBJECT_NULL) { + FF_CFTYPE_AUTO_RELEASE CFNumberRef mediaSize = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaSizeKey), NULL, kNilOptions); + if (mediaSize) { + ffCfNumGetInt64(mediaSize, (int64_t*) &size); + } + } + + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + continue; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInitMove(&device->name, &deviceName); + ffStrbufInit(&device->devPath); + ffStrbufInitMove(&device->interconnect, &interconnect); + device->type = type; + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + + if (entryMedia != IO_OBJECT_NULL) { + FF_CFTYPE_AUTO_RELEASE CFBooleanRef removable = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaRemovableKey), NULL, kNilOptions); + if (removable) { + device->type |= CFBooleanGetValue(removable) ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; + } + + FF_CFTYPE_AUTO_RELEASE CFBooleanRef writable = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaWritableKey), NULL, kNilOptions); + if (writable) { + device->type |= CFBooleanGetValue(writable) ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY; + } + + FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOBSDNameKey), NULL, kNilOptions); + if (bsdName) { + ffCfStrGetString(bsdName, &device->devPath); + ffStrbufPrependS(&device->devPath, "/dev/"); + } + } + + FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), NULL, kNilOptions); + if (deviceCharacteristics) { + ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductSerialNumberKey), &device->serial); + ffStrbufTrimSpace(&device->serial); + ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductRevisionLevelKey), &device->revision); + ffStrbufTrimRightSpace(&device->revision); + + if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL)) { + 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 (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL) && options->temp) { + FF_CFTYPE_AUTO_RELEASE CFBooleanRef nvmeSMARTCapable = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyNVMeSMARTCapableKey), NULL, kNilOptions); + if (nvmeSMARTCapable && CFBooleanGetValue(nvmeSMARTCapable)) { + detectSsdTemp(entryPhysical, &device->temperature); + } + } +#endif + } + + return NULL; +} diff --git a/src/detection/physicaldisk/physicaldisk_bsd.c b/src/detection/physicaldisk/physicaldisk_bsd.c new file mode 100644 index 0000000..5a87768 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_bsd.c @@ -0,0 +1,127 @@ +#include "physicaldisk.h" + +#if __has_include() + + #include "common/strutil.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; + } + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + if (!ffStrEquals(provider->lg_geom->lg_class->lg_name, "DISK")) { + if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + continue; + } + + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + } + uint64_t size = (uint64_t) provider->lg_mediasize; + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + continue; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(provider->lg_name); + FF_STRBUF_AUTO_DESTROY identifier = 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); + } 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 = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInitF(&device->devPath, "/dev/%s", provider->lg_name); + ffStrbufInitMove(&device->serial, &identifier); + ffStrbufTrimSpace(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInit(&device->interconnect); + ffStrbufInitMove(&device->name, &name); + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + + switch (snapIter->device_type & DEVSTAT_TYPE_IF_MASK) { + case DEVSTAT_TYPE_IF_SCSI: + ffStrbufSetStatic(&device->interconnect, "SCSI"); + break; + case DEVSTAT_TYPE_IF_IDE: + ffStrbufSetStatic(&device->interconnect, "IDE"); + break; + case DEVSTAT_TYPE_IF_OTHER: + ffStrbufSetStatic(&device->interconnect, "OTHER"); + break; + + // https://github.com/freebsd/freebsd-src/commit/d282baddb0b029ca8466d23ac51e95c918442535 + case 0x040 /*DEVSTAT_TYPE_IF_NVME*/: + ffStrbufSetStatic(&device->interconnect, "NVMe"); + break; + } + + 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; + } + + 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 diff --git a/src/detection/physicaldisk/physicaldisk_haiku.c b/src/detection/physicaldisk/physicaldisk_haiku.c new file mode 100644 index 0000000..e44ddce --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_haiku.c @@ -0,0 +1,104 @@ +#include "physicaldisk.h" + +#include "common/io.h" +#include "common/strutil.h" + +#include +#include +#include +#include + +static const char* searchRawDeviceFile(FFstrbuf* path, const char* diskType, FFlist* result, FFPhysicalDiskOptions* options) { + 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, options); + } else if (ffStrEquals(entry->d_name, "raw")) { + FF_AUTO_CLOSE_FD int rawfd = open(path->chars, O_RDONLY | O_CLOEXEC); + if (rawfd < 0) { + continue; + } + + device_geometry geometry; + if (ioctl(rawfd, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0) { + continue; + } + + 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); + } + + if (options->namePrefix.length && strncmp(name, options->namePrefix.chars, options->namePrefix.length) != 0) { + continue; + } + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + uint64_t size = (uint64_t) geometry.cylinder_count * geometry.head_count * geometry.sectors_per_track * geometry.bytes_per_sector; + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + continue; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *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 = type | + (geometry.read_only ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE) | + (geometry.removable ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED); + device->size = size; + } + + ffStrbufSubstrBefore(path, baseLen); + } + return NULL; +} + +const char* ffDetectPhysicalDisk(FFlist* result, 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, options); + ffStrbufSubstrBefore(&path, baseLen); + } + + return NULL; +} diff --git a/src/detection/physicaldisk/physicaldisk_linux.c b/src/detection/physicaldisk/physicaldisk_linux.c new file mode 100644 index 0000000..f8126b9 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_linux.c @@ -0,0 +1,202 @@ +#include "physicaldisk.h" +#include "common/io.h" +#include "common/properties.h" +#include "common/strutil.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) { + uint64_t size = 0; + + { + char blkSize[32]; + ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize); + if (fileSize > 0) { + blkSize[fileSize] = 0; + size = (uint64_t) strtoul(blkSize, NULL, 10) * 512; + } + } + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + return; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + int devfd = openat(dfd, "device", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); + + if (devfd < 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + return; + } + + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + + if (devfd > 0) { + 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; + } + } else { + ffStrbufSetS(&name, devName); + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInitMove(&device->name, &name); + ffStrbufInitF(&device->devPath, "/dev/%s", devName); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInit(&device->interconnect); + device->type = type; + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + + bool isVirtio = false; + if (devfd > 0) { + if (ffStrStartsWith(devName, "nvme")) { + ffStrbufSetStatic(&device->interconnect, "NVMe"); + } else if (ffStrStartsWith(devName, "mmcblk")) { + ffStrbufSetStatic(&device->interconnect, "MMC"); + } 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, "VirtIO"); + isVirtio = true; // VirtIO devices are virtual, but we still want to report it + } else { + if (ffAppendFileBufferRelative(devfd, "transport", &device->interconnect)) { + ffStrbufTrimRightSpace(&device->interconnect); + } + } + } + } + } else { + ffStrbufSetStatic(&device->interconnect, "Virtual"); + } + + if (devfd > 0 && !isVirtio) { + char isRotationalChar = '1'; + if (ffReadFileDataRelative(dfd, "queue/rotational", 1, &isRotationalChar) > 0) { + device->type |= isRotationalChar == '1' ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD; + } + + if (ffReadFileBufferRelative(devfd, "serial", &device->serial)) { + ffStrbufTrimSpace(&device->serial); + } + + if (ffReadFileBufferRelative(devfd, "firmware_rev", &device->revision) || + ffReadFileBufferRelative(devfd, "rev", &device->revision)) { + ffStrbufTrimRightSpace(&device->revision); + } + + if (options->temp) { + device->temperature = detectNvmeTemp(devfd); + } + } + + { + 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; + } + } +} + +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; +} diff --git a/src/detection/physicaldisk/physicaldisk_nbsd.c b/src/detection/physicaldisk/physicaldisk_nbsd.c new file mode 100644 index 0000000..0733f3b --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_nbsd.c @@ -0,0 +1,324 @@ +#define DKTYPENAMES +#include "physicaldisk.h" +#include "common/io.h" +#include "common/sysctl.h" +#include "common/debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline const char* retstsToStr(uint8_t retsts) { + switch (retsts) { + case SCCMD_OK: + return "OK"; + case SCCMD_TIMEOUT: + return "TIMEOUT"; + case SCCMD_BUSY: + return "BUSY"; + case SCCMD_SENSE: + return "SENSE"; + case SCCMD_UNKNOWN: + return "UNKNOWN"; + default: + return "?"; + } +} + +#ifndef NDEBUG +static inline const char* senseKeyToStr(uint8_t key) { + switch (key) { + case 0x0: + return "No Sense"; + case 0x1: + return "Recovered Error"; + case 0x2: + return "Not Ready"; + case 0x3: + return "Medium Error"; + case 0x4: + return "Hardware Error"; + case 0x5: + return "Illegal Request"; + case 0x6: + return "Unit Attention"; + case 0x7: + return "Data Protect"; + case 0x8: + return "Blank Check"; + case 0x9: + return "Vendor Specific"; + case 0xA: + return "Copy Aborted"; + case 0xB: + return "Aborted Command"; + case 0xC: + return "Equal"; + case 0xD: + return "Volume Overflow"; + case 0xE: + return "Miscompare"; + case 0xF: + return "Completed"; + default: + return "Unknown"; + } +} + +static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) { + uint8_t responseCode = req->sense[0] & 0x7F; + uint8_t senseKey; + uint8_t asc; + uint8_t ascq; + + if (responseCode == 0x72 || responseCode == 0x73) { + senseKey = req->sense[1] & 0x0F; + asc = req->sense[2]; + ascq = req->sense[3]; + } else { + senseKey = req->sense[2] & 0x0F; + asc = req->sense[12]; + ascq = req->sense[13]; + } + + FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate(); + for (size_t i = 0; i < req->senselen_used; ++i) { + if (i) { + ffStrbufAppendC(&rawSense, ' '); + } + ffStrbufAppendF(&rawSense, "%02X", req->sense[i]); + } + + FF_DEBUG( + "%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]", + operation, + diskName, + responseCode, + senseKey, + senseKeyToStr(senseKey), + asc, + ascq, + rawSense.length ? rawSense.chars : "empty"); +} +#else + #define logScsiSense(...) ((void) 0) +#endif + +const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { + FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate(); + FF_DEBUG("Querying disk names via sysctl hw.disknames"); + const char* error = ffSysctlGetString("hw.disknames", &diskNames); + if (error) { + FF_DEBUG("ffSysctlGetString(hw.disknames) failed: %s", error); + return error; + } + + FF_DEBUG("Disk names: %s", diskNames.chars); + + char* diskName = NULL; + size_t len = 0; + while (ffStrbufGetdelim(&diskName, &len, ' ', &diskNames)) { + FF_DEBUG("Probing disk: %s", diskName); + + if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) { + .chars = diskName, + .length = (uint32_t) len, + }, + &options->namePrefix)) { + FF_DEBUG("Skipping %s due to namePrefix filter", diskName); + continue; + } + + char devPath[256]; + FF_AUTO_CLOSE_FD int f = opendisk(diskName, O_RDONLY, devPath, ARRAY_SIZE(devPath), 0); + if (f < 0) { + if (errno == EACCES) { + FF_DEBUG("opendisk(%s) failed: permission denied", diskName); + return "Permission denied; root required"; + } else { + FF_DEBUG("opendisk(%s) failed: %s", diskName, strerror(errno)); + continue; + } + } + + FF_DEBUG("Opened %s as %s", diskName, devPath); + + struct disklabel dl; + if (ioctl(f, DIOCGDINFO, &dl) < 0) { + FF_DEBUG("ioctl(DIOCGDINFO) failed for %s: %s", diskName, strerror(errno)); + continue; + } + + unsigned long sectorsPerUnit, sectorSize; + + const char* devType = NULL; + + prop_dictionary_t dict = NULL; + if (prop_dictionary_recv_ioctl(f, DIOCGDISKINFO, &dict) == 0) { + FF_DEBUG("DIOCGDISKINFO succeeded for %s", diskName); + prop_dictionary_get_string(dict, "type", &devType); + + prop_dictionary_t geometry; + if (prop_dictionary_get_dict(dict, "geometry", &geometry)) { + prop_dictionary_get_ulong(geometry, "sectors-per-unit", §orsPerUnit); + prop_dictionary_get_ulong(geometry, "sector-size", §orSize); + } else { + FF_DEBUG("No geometry in diskinfo for %s, falling back to disklabel", diskName); + sectorsPerUnit = dl.d_secperunit; + sectorSize = dl.d_secsize; + } + } else { + FF_DEBUG("DIOCGDISKINFO failed for %s, falling back to disklabel", diskName); + sectorsPerUnit = dl.d_secperunit; + sectorSize = dl.d_secsize; + } + + FFPhysicalDiskType type = dl.d_flags & D_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; + + switch (dl.d_type) { + case DKTYPE_VND: + case DKTYPE_LD: + case DKTYPE_RAID: + case DKTYPE_CGD: + case DKTYPE_VINUM: + case DKTYPE_DM: + case DKTYPE_RUMPD: + case DKTYPE_MD: { + if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + FF_DEBUG("Skipping virtual disk %s due to hideType", diskName); + continue; + } + + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + break; + } + } + + uint64_t size = sectorsPerUnit * sectorSize; + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + FF_DEBUG("Skipping unused disk %s due to hideType", diskName); + continue; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInitS(&device->name, devType ?: dl.d_packname); + ffStrbufInitS(&device->devPath, devPath); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInitS(&device->interconnect, dktypenames[dl.d_type]); + device->type = type; + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu", + device->name.chars, + device->devPath.chars, + device->interconnect.chars, + (unsigned long long) device->size); + + if (dict) { + prop_object_release(dict); + dict = NULL; + } + + struct scsipi_inquiry_data inquiry = {}; + scsireq_t req = { + .cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) }, + .cmdlen = 6, + .databuf = (caddr_t) &inquiry, + .datalen = sizeof(inquiry), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK) { + ffStrbufClear(&device->name); + ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor); + ffStrbufTrimRight(&device->name, '\0'); + ffStrbufTrimRight(&device->name, ' '); + ffStrbufTrimRight(&device->name, ','); + ffStrbufAppendC(&device->name, ' '); + ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product); + ffStrbufTrimRight(&device->name, '\0'); + ffStrbufTrimRight(&device->name, ' '); + + ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision); + ffStrbufTrimRight(&device->name, '\0'); + ffStrbufTrimRight(&device->revision, ' '); + FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars); + } else { + FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts); + continue; + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno)); + continue; + } + +#ifdef SINQ_EVPD // Available since NetBSD 11.0 + struct { + struct scsipi_inquiry_evpd_header header; + struct scsipi_inquiry_evpd_serial body; + } evpd = {}; + req = (scsireq_t) { + .cmd = { [0] = INQUIRY, [1] = SINQ_EVPD, [2] = SINQ_VPD_UNIT_SERIAL, [4] = sizeof(evpd) }, + .cmdlen = 6, + .databuf = (caddr_t) &evpd, + .datalen = sizeof(evpd), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK && evpd.header.pagecode == SINQ_VPD_UNIT_SERIAL) { + for (uint8_t i = 0; i < evpd.header.length[1]; ++i) { + ffStrbufAppendF(&device->serial, "%02X", evpd.body.serial_number[i]); + } + ffStrbufTrimRight(&device->serial, ' '); + FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars); + } else if (req.retsts == SCCMD_SENSE) { + logScsiSense(diskName, "SCSI serial page", &req); + } else { + FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.header.pagecode); + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno)); + } +#endif + + struct scsi_mode_parameter_header_6 mode = {}; + req = (scsireq_t) { + .cmd = { [0] = SCSI_MODE_SENSE_6, [2] = SMS_PAGE_MASK, [4] = sizeof(mode) }, + .cmdlen = 6, + .databuf = (caddr_t) &mode, + .datalen = sizeof(mode), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK && mode.data_length > 0) { + device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE; + FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite"); + } else if (req.retsts == SCCMD_SENSE) { + logScsiSense(diskName, "SCSI mode sense", &req); + } else { + FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length); + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno)); + } + + FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type); + } + + return NULL; +} diff --git a/src/detection/physicaldisk/physicaldisk_nosupport.c b/src/detection/physicaldisk/physicaldisk_nosupport.c new file mode 100644 index 0000000..26e0ce8 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_nosupport.c @@ -0,0 +1,5 @@ +#include "physicaldisk.h" + +const char* ffDetectPhysicalDisk(FF_A_UNUSED FFlist* result, FF_A_UNUSED FFPhysicalDiskOptions* options) { + return "Not supported on this platform"; +} diff --git a/src/detection/physicaldisk/physicaldisk_obsd.c b/src/detection/physicaldisk/physicaldisk_obsd.c new file mode 100644 index 0000000..6e89a99 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_obsd.c @@ -0,0 +1,279 @@ +#include "physicaldisk.h" +#include "common/io.h" +#include "common/sysctl.h" +#include "common/debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static inline const char* retstsToStr(uint8_t retsts) { + switch (retsts) { + case SCCMD_OK: + return "OK"; + case SCCMD_TIMEOUT: + return "TIMEOUT"; + case SCCMD_BUSY: + return "BUSY"; + case SCCMD_SENSE: + return "SENSE"; + case SCCMD_UNKNOWN: + return "UNKNOWN"; + default: + return "?"; + } +} + +#ifndef NDEBUG +static inline const char* senseKeyToStr(uint8_t key) { + switch (key) { + case 0x0: + return "No Sense"; + case 0x1: + return "Recovered Error"; + case 0x2: + return "Not Ready"; + case 0x3: + return "Medium Error"; + case 0x4: + return "Hardware Error"; + case 0x5: + return "Illegal Request"; + case 0x6: + return "Unit Attention"; + case 0x7: + return "Data Protect"; + case 0x8: + return "Blank Check"; + case 0x9: + return "Vendor Specific"; + case 0xA: + return "Copy Aborted"; + case 0xB: + return "Aborted Command"; + case 0xC: + return "Equal"; + case 0xD: + return "Volume Overflow"; + case 0xE: + return "Miscompare"; + case 0xF: + return "Completed"; + default: + return "Unknown"; + } +} + +static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) { + uint8_t responseCode = req->sense[0] & 0x7F; + uint8_t senseKey; + uint8_t asc; + uint8_t ascq; + + if (responseCode == 0x72 || responseCode == 0x73) { + senseKey = req->sense[1] & 0x0F; + asc = req->sense[2]; + ascq = req->sense[3]; + } else { + senseKey = req->sense[2] & 0x0F; + asc = req->sense[12]; + ascq = req->sense[13]; + } + + FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate(); + for (size_t i = 0; i < req->senselen_used; ++i) { + if (i) { + ffStrbufAppendC(&rawSense, ' '); + } + ffStrbufAppendF(&rawSense, "%02X", req->sense[i]); + } + + FF_DEBUG( + "%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]", + operation, + diskName, + responseCode, + senseKey, + senseKeyToStr(senseKey), + asc, + ascq, + rawSense.length ? rawSense.chars : "empty"); +} +#else + #define logScsiSense(...) ((void) 0) +#endif + +const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { + FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate(); + FF_DEBUG("Querying disk names via sysctl hw.disknames"); + const char* error = ffSysctlGetString(CTL_HW, HW_DISKNAMES, &diskNames); + if (error) { + FF_DEBUG("ffSysctlGetString(HW_DISKNAMES) failed: %s", error); + return error; + } + + FF_DEBUG("Disk names: %s", diskNames.chars); + + char* diskName = NULL; + size_t len = 0; + while (ffStrbufGetdelim(&diskName, &len, ',', &diskNames)) { + char* colon = strchr(diskName, ':'); + if (colon) { + *colon = '\0'; + } + + FF_DEBUG("Probing disk: %s", diskName); + + if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) { + .chars = diskName, + .length = (uint32_t) len, + }, + &options->namePrefix)) { + FF_DEBUG("Skipping %s due to namePrefix filter", diskName); + continue; + } + + char* devPath = NULL; + FF_AUTO_CLOSE_FD int f = opendev(diskName, O_RDONLY, OPENDEV_PART, &devPath); + if (f < 0) { + if (errno == EACCES) { + FF_DEBUG("opendev(%s) failed: permission denied", diskName); + return "Permission denied; root required"; + } else { + FF_DEBUG("opendev(%s) failed: %s", diskName, strerror(errno)); + continue; // Unknown error + } + } + + FF_DEBUG("Opened %s as %s", diskName, devPath); + + struct disklabel dl; + if (ioctl(f, DIOCGPDINFO, &dl) < 0) { + FF_DEBUG("ioctl(DIOCGPDINFO) failed for %s: %s", diskName, strerror(errno)); + continue; + } + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + if (dl.d_type == DTYPE_VND || dl.d_type == DTYPE_RDROOT) { + if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + FF_DEBUG("Skipping virtual disk %s due to hideType", diskName); + continue; + } + + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + } + + uint64_t size = DL_GETDSIZE(&dl) * dl.d_secsize; + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + FF_DEBUG("Skipping unused disk %s due to hideType", diskName); + continue; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInitS(&device->name, dl.d_packname); + ffStrbufInitS(&device->devPath, devPath); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInitS(&device->interconnect, dl.d_typename); + device->type = type; + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu", + device->name.chars, + device->devPath.chars, + device->interconnect.chars, + (unsigned long long) device->size); + + struct scsi_inquiry_data inquiry = {}; + struct scsireq req = { + .cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) }, + .cmdlen = 6, + .databuf = (caddr_t) &inquiry, + .datalen = sizeof(inquiry), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK) { + ffStrbufClear(&device->name); + ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor); + ffStrbufTrimRight(&device->name, '\0'); + ffStrbufTrimRight(&device->name, ' '); + ffStrbufAppendC(&device->name, ' '); + ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product); + ffStrbufTrimRight(&device->name, '\0'); + ffStrbufTrimRight(&device->name, ' '); + + ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision); + ffStrbufTrimRight(&device->revision, '\0'); + ffStrbufTrimRight(&device->revision, ' '); + device->type |= inquiry.dev_qual2 & SID_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; + FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars); + } else { + FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts); + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno)); + } + + struct scsi_vpd_serial evpd = {}; + req = (struct scsireq) { + .cmd = { [0] = INQUIRY, [1] = SI_EVPD, [2] = SI_PG_SERIAL, [4] = sizeof(evpd) }, + .cmdlen = 6, + .databuf = (caddr_t) &evpd, + .datalen = sizeof(evpd), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK && evpd.hdr.page_code == SI_PG_SERIAL) { + for (uint8_t i = 0; i < evpd.hdr.page_length[1]; ++i) { + ffStrbufAppendF(&device->serial, "%02X", evpd.serial[i]); + } + ffStrbufTrimSpace(&device->serial); + FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars); + } else if (req.retsts == SCCMD_SENSE) { + logScsiSense(diskName, "SCSI serial page", &req); + } else { + FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.hdr.page_code); + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno)); + } + + struct scsi_mode_header mode = {}; + req = (scsireq_t) { + .cmd = { [0] = MODE_SENSE, [2] = SMS_PAGE_CODE, [4] = sizeof(mode) }, + .cmdlen = 6, + .databuf = (caddr_t) &mode, + .datalen = sizeof(mode), + .timeout = 1000, + .flags = SCCMD_READ, + }; + if (ioctl(f, SCIOCCOMMAND, &req) == 0) { + if (req.retsts == SCCMD_OK) { + device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE; + FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite"); + } else if (req.retsts == SCCMD_SENSE) { + logScsiSense(diskName, "SCSI mode sense", &req); + } else { + FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length); + } + } else { + FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno)); + } + + FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type); + } + + return NULL; +} diff --git a/src/detection/physicaldisk/physicaldisk_sunos.c b/src/detection/physicaldisk/physicaldisk_sunos.c new file mode 100644 index 0000000..d929d5a --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_sunos.c @@ -0,0 +1,107 @@ +#include "physicaldisk.h" +#include "common/strutil.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) { + FFPhysicalDiskOptions* options = bundle->options; + FFlist* result = bundle->disks; + + 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 (options->namePrefix.length && !ffStrbufStartsWithIgnCase(&name, &options->namePrefix)) { + return DI_WALK_CONTINUE; + } + + int* value; + + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + uint64_t size = 0; + int64_t* nblocks; + if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, "device-nblocks", &nblocks) > 0) { + if (*nblocks == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + return DI_WALK_CONTINUE; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-blksize", &value) > 0) { + size = (uint64_t) ((uint64_t) *nblocks * (uint64_t) *value); + } + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + 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 = type; + device->size = size; + + char* buf; + bool usb = false; + if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-serial-no", &buf) > 0) { + ffStrbufSetS(&device->serial, buf); + ffStrbufTrimSpace(&device->serial); + } else { + di_node_t parent = di_parent_node(node); + if (parent != DI_NODE_NIL && di_prop_lookup_strings(DDI_DEV_T_ANY, parent, "usb-serialno", &buf) > 0) { + ffStrbufSetS(&device->serial, buf); + usb = true; + } + } + if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-revision-id", &buf) > 0) { + ffStrbufSetS(&device->revision, buf); + ffStrbufTrimRightSpace(&device->revision); + } + + if (usb) { + ffStrbufSetStatic(&device->interconnect, "USB"); + } else if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "class", &buf) > 0) { + ffStrbufSetS(&device->interconnect, buf); + } else { + di_node_t parent = di_parent_node(node); + if (parent != DI_NODE_NIL && di_prop_lookup_strings(DDI_DEV_T_ANY, parent, "model", &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; + + 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; + } + } + + 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; +} diff --git a/src/detection/physicaldisk/physicaldisk_windows.c b/src/detection/physicaldisk/physicaldisk_windows.c new file mode 100644 index 0000000..5d4f7f7 --- /dev/null +++ b/src/detection/physicaldisk/physicaldisk_windows.c @@ -0,0 +1,349 @@ +#include "physicaldisk.h" +#include "common/io.h" +#include "common/windows/unicode.h" +#include "common/mallocHelper.h" +#include "common/debug.h" + +#include +#include +#include +#include + +static const char* detectPhysicalDisk(const char* physicalType, 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 "CreateFileW() failed"; + } + + DWORD retSize; + FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + + const char* interconnect = NULL; + uint64_t size = 0; + { + alignas(DISK_GEOMETRY_EX) uint8_t dgeBuffer[4096]; + if (DeviceIoControl( + hDevice, + IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, + NULL, + 0, + dgeBuffer, + sizeof(dgeBuffer), + &retSize, + NULL)) { + const DISK_GEOMETRY_EX* dge = (const DISK_GEOMETRY_EX*) dgeBuffer; + size = (uint64_t) dge->DiskSize.QuadPart; + } else if (DeviceIoControl( + hDevice, + IOCTL_DISK_GET_DRIVE_GEOMETRY, + NULL, + 0, + dgeBuffer, + sizeof(dgeBuffer), + &retSize, + NULL) && + retSize >= sizeof(DISK_GEOMETRY)) { + const DISK_GEOMETRY* dg = (const DISK_GEOMETRY*) dgeBuffer; + size = (uint64_t) dg->BytesPerSector * dg->SectorsPerTrack * dg->TracksPerCylinder * (uint64_t) dg->Cylinders.QuadPart; + switch (dg->MediaType) { + case F3_1Pt44_512: + case F3_2Pt88_512: + case F3_20Pt8_512: + case F3_720_512: + case F3_120M_512: + case F3_640_512: + case F3_1Pt2_512: + case F3_1Pt23_1024: + case F3_128Mb_512: + case F3_230Mb_512: + case F3_200Mb_512: + case F3_240M_512: + case F3_32M_512: + ffStrbufSetStatic(&name, "3.5-inch Floppy Disk"); + break; + case F5_1Pt2_512: + case F5_360_512: + case F5_320_512: + case F5_320_1024: + case F5_180_512: + case F5_160_512: + case F5_640_512: + case F5_720_512: + case F5_1Pt23_1024: + ffStrbufSetStatic(&name, "5.25-inch Floppy Disk"); + break; + case F8_256_128: + ffStrbufSetStatic(&name, "8-inch Floppy Disk"); + break; + default: + return "Unsupported media type"; + } + interconnect = "Floppy Controller"; + type |= FF_PHYSICALDISK_TYPE_HDD | FF_PHYSICALDISK_TYPE_REMOVABLE; + } + } + if (size == 0) { + if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) { + return "Skipping unknown disk with size 0"; + } + + type |= FF_PHYSICALDISK_TYPE_UNUSED; + } + + const STORAGE_DEVICE_DESCRIPTOR* sdd = NULL; + alignas(STORAGE_DEVICE_DESCRIPTOR) uint8_t sddBuffer[4096]; + if (!interconnect) { + 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) { + sdd = (const STORAGE_DEVICE_DESCRIPTOR*) sddBuffer; + + switch (sdd->BusType) { + case BusTypeScsi: + interconnect = "SCSI"; + break; + case BusTypeAtapi: + interconnect = "ATAPI"; + break; + case BusTypeAta: + interconnect = "ATA"; + break; + case BusType1394: + interconnect = "IEEE 1394"; + break; + case BusTypeSsa: + interconnect = "SSA"; + break; + case BusTypeFibre: + interconnect = "Fibre"; + break; + case BusTypeUsb: + interconnect = "USB"; + break; + case BusTypeRAID: + interconnect = "RAID"; + break; + case BusTypeiScsi: + interconnect = "iSCSI"; + break; + case BusTypeSas: + interconnect = "SAS"; + break; + case BusTypeSata: + interconnect = "SATA"; + break; + case BusTypeSd: + interconnect = "SD"; + break; + case BusTypeMmc: + interconnect = "MMC"; + break; + case BusTypeVirtual: + interconnect = "Virtual"; + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + break; + case BusTypeFileBackedVirtual: + interconnect = "File Backed Virtual"; + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + break; + case BusTypeSpaces: + interconnect = "Storage Spaces"; + type |= FF_PHYSICALDISK_TYPE_VIRTUAL; + break; + case BusTypeNvme: + interconnect = "NVMe"; + break; + case BusTypeSCM: + interconnect = "SCM"; + break; + case BusTypeUfs: + interconnect = "UFS"; + break; + case 0x14 /*BusTypeNvmeof*/: + interconnect = "NVMe-oF"; + break; + default: + interconnect = "Unknown"; + break; + } + + if (type & FF_PHYSICALDISK_TYPE_VIRTUAL && options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) { + return "Skipping virtual disk"; + } + + if (sdd->VendorIdOffset != 0) { + ffStrbufSetS(&name, (const char*) sdd + sdd->VendorIdOffset); + ffStrbufTrim(&name, ' '); + } + if (sdd->ProductIdOffset != 0) { + if (name.length) { + ffStrbufAppendC(&name, ' '); + } + + ffStrbufAppendS(&name, (const char*) sdd + sdd->ProductIdOffset); + ffStrbufTrimRight(&name, ' '); + } + } + if (!name.length) { + ffStrbufSetStatic(&name, physicalType); + } + } + + if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) { + return "Name prefix mismatch"; + } + + FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result); + ffStrbufInit(&device->serial); + ffStrbufInit(&device->revision); + ffStrbufInitMove(&device->name, &name); + ffStrbufInit(&device->devPath); + ffStrbufInitStatic(&device->interconnect, interconnect); + device->type = type; + device->size = size; + device->temperature = FF_PHYSICALDISK_TEMP_UNSET; + + ffStrbufSetWS(&device->devPath, szDevice); + + if (sdd) { + if (sdd->SerialNumberOffset != 0) { + ffStrbufSetS(&device->serial, (const char*) sdd + sdd->SerialNumberOffset); + ffStrbufTrimSpace(&device->serial); + } + + if (sdd->ProductRevisionOffset != 0) { + ffStrbufSetS(&device->revision, (const char*) sdd + sdd->ProductRevisionOffset); + ffStrbufTrimRightSpace(&device->revision); + } + + device->type |= sdd->RemovableMedia ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; + } + + { + alignas(GET_MEDIA_TYPES) uint8_t buffer[4096]; + 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; + } + } 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; + } + } + } + } + + if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL) && !(device->type & FF_PHYSICALDISK_TYPE_HDD)) { + 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; + } + + 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 NULL; +} + +static void detectPhysicalDisksByInterfaceClass(const char* type, const GUID* interfaceClassGuid, FFlist* result, FFPhysicalDiskOptions* options) { + ULONG cchDeviceInterfaces = 0; + if (CM_Get_Device_Interface_List_SizeW( + &cchDeviceInterfaces, + (LPGUID) interfaceClassGuid, + NULL, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS || + cchDeviceInterfaces <= 1) { + return; + } + + wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*) malloc(cchDeviceInterfaces * sizeof(wchar_t)); + if (!mszDeviceInterfaces) { + return; + } + + if (CM_Get_Device_Interface_ListW( + (LPGUID) interfaceClassGuid, + NULL, + mszDeviceInterfaces, + cchDeviceInterfaces, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) { + return; + } + + // MULTI_SZ: "str1\0str2\0...\0\0" + for (const wchar_t* p = mszDeviceInterfaces; *p; p += wcslen(p) + 1) { + FF_DEBUG("Probing %s: %ls", type, p); + FF_A_UNUSED const char* error = detectPhysicalDisk(type, p, result, options); + if (error == NULL) { + FF_DEBUG("Detected device \"%s\"", FF_LIST_LAST(FFPhysicalDiskResult, *result)->name.chars); + } else { + FF_DEBUG("Failed to detect device %s: %s", type, error); + } + } +} + +const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { + detectPhysicalDisksByInterfaceClass("Floppy", &GUID_DEVINTERFACE_FLOPPY, result, options); + detectPhysicalDisksByInterfaceClass("Disk", &GUID_DEVINTERFACE_DISK, result, options); + detectPhysicalDisksByInterfaceClass("CD-ROM", &GUID_DEVINTERFACE_CDROM, result, options); + detectPhysicalDisksByInterfaceClass("Tape", &GUID_DEVINTERFACE_TAPE, result, options); + return NULL; +} diff --git a/src/detection/physicalmemory/physicalmemory.c b/src/detection/physicalmemory/physicalmemory.c new file mode 100644 index 0000000..0188e05 --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory.c @@ -0,0 +1,73 @@ +#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); + } +} diff --git a/src/detection/physicalmemory/physicalmemory.h b/src/detection/physicalmemory/physicalmemory.h new file mode 100644 index 0000000..d4c8511 --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory.h @@ -0,0 +1,22 @@ +#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(FFPhysicalMemoryOptions* options, FFlist* result); // list of FFPhysicalMemoryResult + +void FFPhysicalMemoryUpdateVendorString(FFPhysicalMemoryResult* device); diff --git a/src/detection/physicalmemory/physicalmemory_apple.m b/src/detection/physicalmemory/physicalmemory_apple.m new file mode 100644 index 0000000..e35f538 --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory_apple.m @@ -0,0 +1,163 @@ +#include "physicalmemory.h" +#include "common/processing.h" +#include "common/smbios.h" +#include "common/strutil.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 = FF_LIST_ADD(FFPhysicalMemoryResult, *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; + } + } +} + +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_A_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 = FF_LIST_ADD(FFPhysicalMemoryResult, *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(FF_A_UNUSED FFPhysicalMemoryOptions* options, FFlist* result) +{ + #if __aarch64__ + if (detectFromIokit(result) == NULL) + return NULL; + #endif + + return detectFromSystemProfiler(result); +} diff --git a/src/detection/physicalmemory/physicalmemory_linux.c b/src/detection/physicalmemory/physicalmemory_linux.c new file mode 100644 index 0000000..02df789 --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory_linux.c @@ -0,0 +1,235 @@ +#include "physicalmemory.h" +#include "common/smbios.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 +} FF_A_PACKED FFSmbiosMemoryDevice; + +static_assert(offsetof(FFSmbiosMemoryDevice, RcdRevisionNumber) == 0x62, + "FFSmbiosMemoryDevice: Wrong struct alignment"); + +const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, 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"; + } + + const FFSmbiosMemoryDevice* endOfTable = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_END_OF_TABLE]; + for (; data != endOfTable; 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; + + if (!installed && !options->showEmptySlots) { + continue; + } + + FFPhysicalMemoryResult* device = FF_LIST_ADD(FFPhysicalMemoryResult, *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 + "CAMM", // 0x11 + "CUDIMM", // 0x12 + "CSODIMM", // 0x13 + }; + 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 + "MRDIMM", // 0x25 + }; + 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; +} diff --git a/src/detection/physicalmemory/physicalmemory_nosupport.c b/src/detection/physicalmemory/physicalmemory_nosupport.c new file mode 100644 index 0000000..2edb3a7 --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory_nosupport.c @@ -0,0 +1,6 @@ +#include "physicalmemory.h" + +const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* result) { + FF_UNUSED(options, result); + return "Not supported on this platform"; +} diff --git a/src/detection/poweradapter/poweradapter.h b/src/detection/poweradapter/poweradapter.h new file mode 100644 index 0000000..2d889b7 --- /dev/null +++ b/src/detection/poweradapter/poweradapter.h @@ -0,0 +1,15 @@ +#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); diff --git a/src/detection/poweradapter/poweradapter_apple.c b/src/detection/poweradapter/poweradapter_apple.c new file mode 100644 index 0000000..9a5008a --- /dev/null +++ b/src/detection/poweradapter/poweradapter_apple.c @@ -0,0 +1,39 @@ +#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 = FF_LIST_ADD(FFPowerAdapterResult, *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; +} diff --git a/src/detection/poweradapter/poweradapter_linux.c b/src/detection/poweradapter/poweradapter_linux.c new file mode 100644 index 0000000..fb555d5 --- /dev/null +++ b/src/detection/poweradapter/poweradapter_linux.c @@ -0,0 +1,91 @@ +#include "poweradapter.h" +#include "common/io.h" +#include "common/strutil.h" + +#include +#include +#include + +static void parsePowerAdapter(int dfd, const char* id, FFlist* results) { + FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate(); + + { + char present = '\0'; + if (ffReadFileDataRelative(dfd, "present", 1, &present) && present == '0') { + return; + } + } + + // type must exist and be "Mains" + if (ffReadFileBufferRelative(dfd, "type", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + } + + if (!ffStrbufEqualS(&tmpBuffer, "Mains")) { + return; + } + + // scope may not exist or must not be "Device" (?) + if (ffReadFileBufferRelative(dfd, "scope", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + + if (ffStrbufEqualS(&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 = FF_LIST_ADD(FFPowerAdapterResult, *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; +} diff --git a/src/detection/poweradapter/poweradapter_nosupport.c b/src/detection/poweradapter/poweradapter_nosupport.c new file mode 100644 index 0000000..d5e725b --- /dev/null +++ b/src/detection/poweradapter/poweradapter_nosupport.c @@ -0,0 +1,5 @@ +#include "poweradapter.h" + +const char* ffDetectPowerAdapter(FF_A_UNUSED FFlist* results) { + return "Not supported on this platform"; +} diff --git a/src/detection/processes/processes.h b/src/detection/processes/processes.h new file mode 100644 index 0000000..69cf463 --- /dev/null +++ b/src/detection/processes/processes.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +const char* ffDetectProcesses(uint32_t* result); diff --git a/src/detection/processes/processes_bsd.c b/src/detection/processes/processes_bsd.c new file mode 100644 index 0000000..e3f02b8 --- /dev/null +++ b/src/detection/processes/processes_bsd.c @@ -0,0 +1,23 @@ +#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; +} diff --git a/src/detection/processes/processes_haiku.c b/src/detection/processes/processes_haiku.c new file mode 100644 index 0000000..c50e62d --- /dev/null +++ b/src/detection/processes/processes_haiku.c @@ -0,0 +1,14 @@ +#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; +} diff --git a/src/detection/processes/processes_linux.c b/src/detection/processes/processes_linux.c new file mode 100644 index 0000000..dee8c09 --- /dev/null +++ b/src/detection/processes/processes_linux.c @@ -0,0 +1,27 @@ +#include "processes.h" + +#include "common/io.h" +#include "common/strutil.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; +} diff --git a/src/detection/processes/processes_nbsd.c b/src/detection/processes/processes_nbsd.c new file mode 100644 index 0000000..efd3d6f --- /dev/null +++ b/src/detection/processes/processes_nbsd.c @@ -0,0 +1,15 @@ +#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; +} diff --git a/src/detection/processes/processes_nosupport.c b/src/detection/processes/processes_nosupport.c new file mode 100644 index 0000000..7da208e --- /dev/null +++ b/src/detection/processes/processes_nosupport.c @@ -0,0 +1,5 @@ +#include "processes.h" + +const char* ffDetectProcesses(uint32_t* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/processes/processes_obsd.c b/src/detection/processes/processes_obsd.c new file mode 100644 index 0000000..6246d8f --- /dev/null +++ b/src/detection/processes/processes_obsd.c @@ -0,0 +1,15 @@ +#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; +} diff --git a/src/detection/processes/processes_windows.c b/src/detection/processes/processes_windows.c new file mode 100644 index 0000000..03b34db --- /dev/null +++ b/src/detection/processes/processes_windows.c @@ -0,0 +1,34 @@ +#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; +} diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c new file mode 100644 index 0000000..abca57c --- /dev/null +++ b/src/detection/publicip/publicip.c @@ -0,0 +1,95 @@ +#include "publicip.h" +#include "common/networking.h" + +#define FF_UNINITIALIZED ((const char*) (uintptr_t) -1) +static FFNetworkingState states[2]; +static const char* statuses[2] = { FF_UNINITIALIZED, FF_UNINITIALIZED }; + +void ffPreparePublicIp(FFPublicIPOptions* options) { + FFNetworkingState* state = &states[options->ipv6]; + const char** status = &statuses[options->ipv6]; + if (*status != FF_UNINITIALIZED) { + 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_UNINITIALIZED) { + ffPreparePublicIp(options); + } + + if (*status != NULL) { + return *status; + } + + FF_STRBUF_AUTO_DESTROY response = ffStrbufCreateA(4096); + const char* error = ffNetworkingRecvHttpResponse(state, &response); + + *state = (FFNetworkingState) {}; + *status = FF_UNINITIALIZED; + + 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* FF_A_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; +} diff --git a/src/detection/publicip/publicip.h b/src/detection/publicip/publicip.h new file mode 100644 index 0000000..e8f9702 --- /dev/null +++ b/src/detection/publicip/publicip.h @@ -0,0 +1,12 @@ +#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); diff --git a/src/detection/sound/audio_oss_sunos.h b/src/detection/sound/audio_oss_sunos.h new file mode 100644 index 0000000..4e0e760 --- /dev/null +++ b/src/detection/sound/audio_oss_sunos.h @@ -0,0 +1,898 @@ +/* + * 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 */ diff --git a/src/detection/sound/sound.h b/src/detection/sound/sound.h new file mode 100644 index 0000000..041e6a1 --- /dev/null +++ b/src/detection/sound/sound.h @@ -0,0 +1,16 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/sound/option.h" + +#define FF_SOUND_VOLUME_UNKNOWN 255 + +typedef struct FFSoundDevice { + FFstrbuf identifier; + FFstrbuf name; + FFstrbuf platformApi; + uint8_t volume; // 0-100% + FFSoundType type; +} FFSoundDevice; + +const char* ffDetectSound(FFSoundOptions* options, FFlist* devices /* List of FFSoundDevice */); diff --git a/src/detection/sound/sound_apple.c b/src/detection/sound/sound_apple.c new file mode 100644 index 0000000..aaacf78 --- /dev/null +++ b/src/detection/sound/sound_apple.c @@ -0,0 +1,112 @@ +#include "sound.h" +#include "common/apple/cf_helpers.h" + +#include +#include + +#ifndef MAC_OS_VERSION_12_0 + #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster +#endif + +const char* ffDetectSound(FFSoundOptions* options, 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] = {}; + if (options->soundType & FF_SOUND_TYPE_MAIN) { + deviceIds[0] = mainDeviceId; + dataSize = sizeof(mainDeviceId); + } else { + 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; + } + } + } + + uint32_t active = true; + dataSize = sizeof(active); + if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &active) == kAudioHardwareNoError) { + if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !active) { + continue; + } + } + + FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices); + device->type = (deviceId == mainDeviceId ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | + (active ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE); + 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 + } + + 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; +} diff --git a/src/detection/sound/sound_bsd.c b/src/detection/sound/sound_bsd.c new file mode 100644 index 0000000..cbac868 --- /dev/null +++ b/src/detection/sound/sound_bsd.c @@ -0,0 +1,90 @@ +#include "sound.h" +#include "common/io.h" +#include "common/sysctl.h" + +#include +#include +#include + +const char* ffDetectSound(FFSoundOptions* options, 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) { + bool isMain = idev == defaultDev; + if ((options->soundType & FF_SOUND_TYPE_MAIN) && !isMain) { + continue; + } + + 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 = FF_LIST_ADD(FFSoundDevice, *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->type = FF_SOUND_TYPE_ACTIVE | (isMain ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE); + } + + return NULL; +} diff --git a/src/detection/sound/sound_haiku.cpp b/src/detection/sound/sound_haiku.cpp new file mode 100644 index 0000000..d69602e --- /dev/null +++ b/src/detection/sound/sound_haiku.cpp @@ -0,0 +1,88 @@ +extern "C" { +#include "sound.h" +#include "common/strutil.h" +} +#include +#include +#include +#include + +const char* ffDetectSound(FF_A_UNUSED FFSoundOptions* options, 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 = FF_LIST_ADD(FFSoundDevice, *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->type = (FFSoundType) (FF_SOUND_TYPE_ACTIVE | FF_SOUND_TYPE_MAIN); + + 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; +} diff --git a/src/detection/sound/sound_linux.c b/src/detection/sound/sound_linux.c new file mode 100644 index 0000000..8af7e93 --- /dev/null +++ b/src/detection/sound/sound_linux.c @@ -0,0 +1,164 @@ +#include "sound.h" + +#ifdef FF_HAVE_PULSE + #include "common/library.h" + #include + +struct DetectionInfoBundle { + FFstrbuf serverName; + FFstrbuf defaultDeviceId; + FFlist* result; + FFSoundOptions* options; +}; + +static void paSinkInfoCallback(FF_A_UNUSED pa_context* c, const pa_sink_info* i, int eol, void* userdata) { + if (eol > 0 || !i) { + return; + } + + struct DetectionInfoBundle* bundle = userdata; + + bool isMain = ffStrbufEqualS(&bundle->defaultDeviceId, i->name); + if ((bundle->options->soundType & FF_SOUND_TYPE_MAIN) && !isMain) { + return; + } + + bool isActive = i->active_port && i->active_port->available != PA_PORT_AVAILABLE_NO; + if ((bundle->options->soundType & FF_SOUND_TYPE_ACTIVE) && !isActive) { + return; + } + + FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *bundle->result); + ffStrbufInitS(&device->identifier, i->name); + ffStrbufInitCopy(&device->platformApi, &bundle->serverName); + 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->type = (isMain ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | (isActive ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE); +} + +static void paServerInfoCallback(FF_A_UNUSED pa_context* c, const pa_server_info* i, void* userdata) { + if (!i) { + return; + } + + struct DetectionInfoBundle* bundle = userdata; + + const char* realServer = strstr(i->server_name, "(on "); + if (realServer) { + ffStrbufSetS(&bundle->serverName, realServer + strlen("(on ")); + ffStrbufTrimRight(&bundle->serverName, ')'); + } else { + ffStrbufSetF(&bundle->serverName, "%s %s", i->server_name, i->server_version); + } + + ffStrbufSetS(&bundle->defaultDeviceId, i->default_sink_name); +} + +static const char* detectSound(FFSoundOptions* options, 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_cancel) + 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"; + } + + struct DetectionInfoBundle bundle = { + .serverName = ffStrbufCreate(), + .defaultDeviceId = ffStrbufCreate(), + .result = devices, + .options = options, + }; + const char* error = NULL; + + if (ffpa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + error = "Failed to connect to pulseaudio context"; + goto exit; + } + + pa_context_state_t state; + while ((state = ffpa_context_get_state(context)) != PA_CONTEXT_READY) { + if (!PA_CONTEXT_IS_GOOD(state)) { + error = "Failed to get pulseaudio context state"; + goto exit; + } + + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + { + pa_operation* operation = ffpa_context_get_server_info(context, paServerInfoCallback, &bundle); + if (!operation) { + error = "Failed to get pulseaudio server info"; + goto exit; + } + while (ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) { + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + ffpa_operation_unref(operation); + } + + { + pa_operation* operation = ffpa_context_get_sink_info_list(context, paSinkInfoCallback, &bundle); + if (!operation) { + error = "Failed to get pulseaudio sink info list"; + goto exit; + } + + while (ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) { + if (options->soundType & FF_SOUND_TYPE_MAIN && devices->length > 0) { + ffpa_operation_cancel(operation); + } + + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + ffpa_operation_unref(operation); + } + +exit: + ffStrbufDestroy(&bundle.serverName); + ffStrbufDestroy(&bundle.defaultDeviceId); + ffpa_context_unref(context); + ffpa_mainloop_free(mainloop); + return error; +} + +#endif // FF_HAVE_PULSE + +const char* ffDetectSound(FFSoundOptions* options, FFlist* devices) { +#ifdef FF_HAVE_PULSE + return detectSound(options, devices); +#else + FF_UNUSED(options, devices); + return "Fastfetch was built without libpulse support"; +#endif +} diff --git a/src/detection/sound/sound_nbsd.c b/src/detection/sound/sound_nbsd.c new file mode 100644 index 0000000..efd276e --- /dev/null +++ b/src/detection/sound/sound_nbsd.c @@ -0,0 +1,58 @@ +#include "sound.h" +#include "common/io.h" + +#include +#include +#include +#include +#include + +const char* ffDetectSound(FFSoundOptions* options, 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) { + bool isMain = idev == defaultDev; + if ((options->soundType & FF_SOUND_TYPE_MAIN) && !isMain) { + continue; + } + + 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 = FF_LIST_ADD(FFSoundDevice, *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->type = FF_SOUND_TYPE_ACTIVE | (isMain ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE); + } + + return NULL; +} diff --git a/src/detection/sound/sound_nosupport.c b/src/detection/sound/sound_nosupport.c new file mode 100644 index 0000000..b672360 --- /dev/null +++ b/src/detection/sound/sound_nosupport.c @@ -0,0 +1,5 @@ +#include "sound.h" + +const char* ffDetectSound(FF_A_UNUSED FFlist* devices /* List of FFSoundDevice */) { + return "Not supported on this platform"; +} diff --git a/src/detection/sound/sound_obsd.c b/src/detection/sound/sound_obsd.c new file mode 100644 index 0000000..a6dd6df --- /dev/null +++ b/src/detection/sound/sound_obsd.c @@ -0,0 +1,90 @@ +#include "sound.h" +#include "common/strutil.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(FFSoundOptions* options, FFlist* devices) { + FF_A_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 = FF_LIST_ADD(FFSoundDevice, *devices); + ffStrbufInitS(&device->name, bundle.name); + ffStrbufInitS(&device->identifier, SIO_DEVANY); + ffStrbufInitStatic(&device->platformApi, "sndio"); + device->volume = 0; + device->type = FF_SOUND_TYPE_ACTIVE | FF_SOUND_TYPE_MAIN; + + 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; +} diff --git a/src/detection/sound/sound_sunos.c b/src/detection/sound/sound_sunos.c new file mode 100644 index 0000000..7c84e55 --- /dev/null +++ b/src/detection/sound/sound_sunos.c @@ -0,0 +1,109 @@ +#include "sound.h" +#include "common/io.h" +#include "common/strutil.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(FFSoundOptions* options, 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) { + bool isMain = idev == defaultDev; + if ((options->soundType & FF_SOUND_TYPE_MAIN) && !isMain) { + continue; + } + + 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; + } + + if (options->soundType == FF_SOUND_TYPE_ACTIVE && !mi.enabled) { + 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 = FF_LIST_ADD(FFSoundDevice, *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->type = (mi.enabled ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE) | + (isMain ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE); + } + + return NULL; +} diff --git a/src/detection/sound/sound_windows.cpp b/src/detection/sound/sound_windows.cpp new file mode 100644 index 0000000..6d6307a --- /dev/null +++ b/src/detection/sound/sound_windows.cpp @@ -0,0 +1,122 @@ +extern "C" { +#include "sound.h" +#include "common/windows/com.h" +} +#include "common/windows/unicode.hpp" +#include "common/windows/variant.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 FF_A_CLEANUP(ffCoTaskMemFreeWrapper) + +static const char* detectSoundDevice(FFlist* devices /* List of FFSoundDevice */, IMMDevice* immDevice, LPWSTR mainDeviceId) { + LPWSTR FF_COTASK_AUTO_FREE immDeviceId = NULL; + if (FAILED(immDevice->GetId(&immDeviceId))) { + return "immDevice->GetId() failed"; + } + + IPropertyStore* FF_AUTO_RELEASE_COM_OBJECT immPropStore = NULL; + if (FAILED(immDevice->OpenPropertyStore(STGM_READ, &immPropStore))) { + return "immDevice->OpenPropertyStore() failed"; + } + + DWORD immState; + if (FAILED(immDevice->GetState(&immState))) { + return "immDevice->GetState() failed"; + } + + FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices); + device->type = (FFSoundType) ((!mainDeviceId || wcscmp(immDeviceId, mainDeviceId) == 0 ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | + ((immState & DEVICE_STATE_ACTIVE) ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE)); + device->volume = FF_SOUND_VOLUME_UNKNOWN; + ffStrbufInitWS(&device->identifier, immDeviceId); + ffStrbufInit(&device->name); + ffStrbufInitStatic(&device->platformApi, "Core Audio APIs"); + + { + FFPropVariant friendlyName; + if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_FriendlyName, &friendlyName))) { + ffStrbufSetWSV(&device->name, friendlyName.get()); + } else if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_DeviceDesc, &friendlyName))) { + ffStrbufSetWSV(&device->name, friendlyName.get()); + } else { + ffStrbufSetStatic(&device->name, "Unknown Device"); + } + } + + IAudioEndpointVolume* FF_AUTO_RELEASE_COM_OBJECT immEndpointVolume = NULL; + if (SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**) &immEndpointVolume))) { + BOOL muted; + if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted) { + FLOAT volume; + if (SUCCEEDED(immEndpointVolume->GetMasterVolumeLevelScalar(&volume))) { + device->volume = (uint8_t) (volume * 100 + 0.5); + } + } + } + + return NULL; +} + +const char* ffDetectSound(FFSoundOptions* options, 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_PPV_ARGS(&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 (options->soundType & FF_SOUND_TYPE_MAIN) { + return detectSoundDevice(devices, pDefaultDevice, NULL); + } + + 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 | (options->soundType & FF_SOUND_TYPE_ACTIVE ? 0 : 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; + } + + detectSoundDevice(devices, immDevice, mainDeviceId); + } + + return NULL; +} diff --git a/src/detection/swap/swap.h b/src/detection/swap/swap.h new file mode 100644 index 0000000..eb32f75 --- /dev/null +++ b/src/detection/swap/swap.h @@ -0,0 +1,12 @@ +#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 */); diff --git a/src/detection/swap/swap_apple.c b/src/detection/swap/swap_apple.c new file mode 100644 index 0000000..40dda49 --- /dev/null +++ b/src/detection/swap/swap_apple.c @@ -0,0 +1,28 @@ +#include "swap.h" + +#include "common/sysctl.h" +#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"; + } + + if (xsw.xsu_total == 0) { + if (__builtin_available(macOS 26.0, *)) { + // "vm.compressor_mode" no longer exists in macOS 26.0 + } else { + if (ffSysctlGetInt("vm.compressor_mode", 4) <= 2) { + return NULL; // Swap is disabled + } + } + } + + FFSwapResult* swap = FF_LIST_ADD(FFSwapResult, *result); + ffStrbufInitStatic(&swap->name, xsw.xsu_encrypted ? "Encrypted" : "Normal"); + swap->bytesTotal = xsw.xsu_total; + swap->bytesUsed = xsw.xsu_used; + return NULL; +} diff --git a/src/detection/swap/swap_bsd.c b/src/detection/swap/swap_bsd.c new file mode 100644 index 0000000..a54cd52 --- /dev/null +++ b/src/detection/swap/swap_bsd.c @@ -0,0 +1,78 @@ +#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 = FF_LIST_ADD(FFSwapResult, *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 diff --git a/src/detection/swap/swap_haiku.c b/src/detection/swap/swap_haiku.c new file mode 100644 index 0000000..8a3b08c --- /dev/null +++ b/src/detection/swap/swap_haiku.c @@ -0,0 +1,27 @@ +#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 = FF_LIST_ADD(FFSwapResult, *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; +} diff --git a/src/detection/swap/swap_linux.c b/src/detection/swap/swap_linux.c new file mode 100644 index 0000000..b31f268 --- /dev/null +++ b/src/detection/swap/swap_linux.c @@ -0,0 +1,82 @@ +#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 = FF_LIST_ADD(FFSwapResult, *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 = FF_LIST_ADD(FFSwapResult, *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); +} diff --git a/src/detection/swap/swap_nosupport.c b/src/detection/swap/swap_nosupport.c new file mode 100644 index 0000000..ebc29b4 --- /dev/null +++ b/src/detection/swap/swap_nosupport.c @@ -0,0 +1,5 @@ +#include "swap.h" + +const char* ffDetectSwap(FFSwapResult* swap) { + return "Not supported on this platform"; +} diff --git a/src/detection/swap/swap_obsd.c b/src/detection/swap/swap_obsd.c new file mode 100644 index 0000000..1296aeb --- /dev/null +++ b/src/detection/swap/swap_obsd.c @@ -0,0 +1,35 @@ +#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 = FF_LIST_ADD(FFSwapResult, *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; +} diff --git a/src/detection/swap/swap_sunos.c b/src/detection/swap/swap_sunos.c new file mode 100644 index 0000000..fa3260d --- /dev/null +++ b/src/detection/swap/swap_sunos.c @@ -0,0 +1,32 @@ +#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 = FF_LIST_ADD(FFSwapResult, *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; +} diff --git a/src/detection/swap/swap_windows.c b/src/detection/swap/swap_windows.c new file mode 100644 index 0000000..e96298b --- /dev/null +++ b/src/detection/swap/swap_windows.c @@ -0,0 +1,35 @@ +#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"; + } + + if (size == 0) { + return NULL; + } + + 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 = FF_LIST_ADD(FFSwapResult, *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; +} diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c new file mode 100644 index 0000000..7ef7120 --- /dev/null +++ b/src/detection/terminalfont/terminalfont.c @@ -0,0 +1,407 @@ +#include "terminalfont.h" +#include "common/io.h" +#include "common/properties.h" +#include "common/processing.h" +#include "common/debug.h" +#include "common/strutil.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 bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize) { + FF_DEBUG("parsing config: %s", path->chars); + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); + if (!ffAppendFileBuffer(path->chars, &buffer)) { + FF_DEBUG("cannot read config: %s", path->chars); + return false; + } + + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, &buffer)) { + if (ffParsePropLine(line, "font-family =", &temp)) { + FF_DEBUG("found font-family='%s' in %s", temp.chars, path->chars); + if (fontName->length > 0) { + ffStrbufDestroy(fontNameFallback); + ffStrbufInitMove(fontNameFallback, fontName); + } + ffStrbufDestroy(fontName); + ffStrbufInitMove(fontName, &temp); + } else if (ffParsePropLine(line, "font-size =", fontSize)) { + FF_DEBUG("found font-size='%s' in %s", temp.chars, path->chars); + // Latter overrides former + ffStrbufDestroy(fontSize); + ffStrbufInitMove(fontSize, &temp); + } + } + return true; +} + +static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont, const char* configPathMac, const char* configPathUnix) { + 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(); + + if (configPathMac && configPathUnix) { +#if __APPLE__ + ffStrbufSet(&configPath, &instance.state.platform.homeDir); + ffStrbufAppendS(&configPath, "Library/Application Support/"); + ffStrbufAppendS(&configPath, configPathMac); // com.mitchellh.ghostty/config + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize); +#endif + + if (instance.state.platform.configDirs.length > 0) { + ffStrbufSet(&configPath, FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs)); + ffStrbufAppendS(&configPath, configPathUnix); // ghostty/config + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize); + } + } else { + // 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) { + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, &buffer)) { + if (ffStrStartsWith(line, "font-family = ")) { + FF_DEBUG("found %s", line); + if (fontName.length > 0) { + ffStrbufDestroy(&fontNameFallback); + ffStrbufInitMove(&fontNameFallback, &fontName); + } + ffStrbufSetNS(&fontName, (uint32_t) (len - strlen("font-family = ")), line + strlen("font-family = ")); + } else if (ffStrStartsWith(line, "font-size = ")) { + FF_DEBUG("found %s", line); + // `ghostty +show-config` reports only one font size even if the config has multiple font sizes + ffStrbufSetNS(&fontSize, (uint32_t) (len - strlen("font-size = ")), line + strlen("font-size = ")); + } + } + } else { + FF_DEBUG("`ghostty +show-config` failed: %s", error); + } + } + + 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_A_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_A_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, NULL, NULL); + } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "Muxy")) { + detectGhostty(&terminal->exe, terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf"); + } 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; +} diff --git a/src/detection/terminalfont/terminalfont.h b/src/detection/terminalfont/terminalfont.h new file mode 100644 index 0000000..bdc416c --- /dev/null +++ b/src/detection/terminalfont/terminalfont.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/terminalfont/terminalfont_android.c b/src/detection/terminalfont/terminalfont_android.c new file mode 100644 index 0000000..a721078 --- /dev/null +++ b/src/detection/terminalfont/terminalfont_android.c @@ -0,0 +1,71 @@ +#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; +} diff --git a/src/detection/terminalfont/terminalfont_apple.m b/src/detection/terminalfont/terminalfont_apple.m new file mode 100644 index 0000000..126235f --- /dev/null +++ b/src/detection/terminalfont/terminalfont_apple.m @@ -0,0 +1,106 @@ +#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; +} diff --git a/src/detection/terminalfont/terminalfont_linux.c b/src/detection/terminalfont/terminalfont_linux.c new file mode 100644 index 0000000..bfc8959 --- /dev/null +++ b/src/detection/terminalfont/terminalfont_linux.c @@ -0,0 +1,545 @@ +#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/strutil.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_A_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; +} diff --git a/src/detection/terminalfont/terminalfont_windows.c b/src/detection/terminalfont/terminalfont_windows.c new file mode 100644 index 0000000..87566af --- /dev/null +++ b/src/detection/terminalfont/terminalfont_windows.c @@ -0,0 +1,287 @@ +#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/strutil.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* FF_A_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 = FF_LIST_ADD(FFstrbuf, 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; +} diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c new file mode 100644 index 0000000..2222216 --- /dev/null +++ b/src/detection/terminalshell/terminalshell.c @@ -0,0 +1,949 @@ +#include "fastfetch.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/properties.h" +#include "common/path.h" +#include "common/strutil.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" +#elif __APPLE__ + #include "common/apple/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_A_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 extractBusyboxVersion(const char* line, uint32_t len, void* userdata) { + if (!ffStrStartsWith(line, "BusyBox v")) { + return true; + } + + line += strlen("BusyBox v"); + len -= (uint32_t) strlen("BusyBox v"); + const char* space = memchr(line, ' ', len); + if (space) { + len = (uint32_t) (space - line); + } + + ffStrbufSetNS((FFstrbuf*) userdata, len, line); + return false; +} + +static bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version) { + ffBinaryExtractStrings(exe->chars, extractBusyboxVersion, version, (uint32_t) strlen("BusyBox v0.0.0")); + + const char* error = ffStrbufEndsWithS(exe, "busybox") + ? ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "ash", "--help", NULL }) + : ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--help", NULL }); + if (error != 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_A_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_A_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 (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") || ffStrEqualsIgnCase(exeName, "busybox")) { + 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_A_UNUSED static bool getTerminalVersionTermux(FFstrbuf* version) { + ffStrbufSetS(version, getenv("TERMUX_VERSION")); + return version->length > 0; +} + +static bool extractGeneralVersion(const char* str, FF_A_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_A_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_A_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_A_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_A_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; + } + } + + // Likely TDE konsole. See #2319 + if (!getExeVersionRaw(exe, version)) { + return false; + } + return ffStrbufSubstrAfterLastC(version, ' '); +} + +FF_A_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_A_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_A_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_A_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_A_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_A_UNUSED static bool getTerminalVersionUrxvt(FF_A_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_A_UNUSED static bool getTerminalVersionSt(FF_A_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_A_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_A_UNUSED static bool getTerminalVersionWeston(FF_A_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_A_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 (ffGetAppNameAndVersion(exe->chars, NULL, version)) { + return true; + } + #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_A_UNUSED static bool getTerminalVersionPtyxis(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) { + if (ffProcessAppendStdOut(version, (char* const[]) { "ptyxis", "--version", NULL }) != NULL) { + return false; + } + + ffStrbufSubstrBeforeFirstC(version, '\n'); + ffStrbufSubstrAfterFirstC(version, ' '); + return true; +} + +FF_A_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_A_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_A_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_A_UNUSED FFstrbuf* exe, FFstrbuf* version) { +#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); + +#elif __APPLE__ + + return ffGetAppNameAndVersion(exe->chars, NULL, version); + +#else + + return false; + +#endif +} diff --git a/src/detection/terminalshell/terminalshell.h b/src/detection/terminalshell/terminalshell.h new file mode 100644 index 0000000..008e30c --- /dev/null +++ b/src/detection/terminalshell/terminalshell.h @@ -0,0 +1,35 @@ +#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(); + +bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); +bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version); diff --git a/src/detection/terminalshell/terminalshell_linux.c b/src/detection/terminalshell/terminalshell_linux.c new file mode 100644 index 0000000..1eccf26 --- /dev/null +++ b/src/detection/terminalshell/terminalshell_linux.c @@ -0,0 +1,449 @@ +#include "terminalshell.h" +#include "common/io.h" +#include "common/parsing.h" +#include "common/processing.h" +#include "common/thread.h" +#include "common/strutil.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); + } +} + +static void setShellInfoDetails(FFShellResult* result) { + 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 if (ffStrbufEqualS(&result->processName, "busybox")) { + ffStrbufInitStatic(&result->prettyName, "ash"); + } else { + // https://github.com/fastfetch-cli/fastfetch/discussions/280#discussioncomment-3831734 + ffStrbufInitS(&result->prettyName, result->exeName); + } +} + +static void setTerminalInfoDetails(FFTerminalResult* result) { + // For Nixpkgs. Ref: #510 and https://github.com/NixOS/nixpkgs/pull/249428 + // We use processName when detecting version and font, overriding it for simplification + if (ffStrbufStartsWithC(&result->processName, '.') && ffStrbufStartsWithS(&result->exePath,"/nix/store")) { + ffStrbufSubstrAfter(&result->processName, 0); + if (strlen(result->exeName) < 15) { + ffStrbufSubstrBeforeLastC(&result->processName, '-'); + } + } + + 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 || (ffStrbufStartsWithS(&result->exePath,"/nix/store") && strlen(result->exeName) > 15)) { // if exeName starts with processName, print it. Otherwise print processName. For nixpkgs, use exeName if processName can't be unwrapped + ffStrbufInitS(&result->prettyName, result->exeName); + } else { + ffStrbufInitCopy(&result->prettyName, &result->processName); + } +} + +#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); + + if (result.processName.length > 0) { + setShellInfoDetails(&result); + if (instance.config.general.detectVersion) { + fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, result.exeName, &result.version); + } + } + + 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); + + if (result.processName.length > 0) { + setTerminalInfoDetails(&result); + if (instance.config.general.detectVersion) { + fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version); + } + } + + return &result; +} diff --git a/src/detection/terminalshell/terminalshell_windows.c b/src/detection/terminalshell/terminalshell_windows.c new file mode 100644 index 0000000..d037663 --- /dev/null +++ b/src/detection/terminalshell/terminalshell_windows.c @@ -0,0 +1,379 @@ +#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/strutil.h" + +#include +#include +#include +#include +#include +#include +#include + +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"); + } else if (ffStrbufIgnCaseEqualS(&result->prettyName, "busybox")) { + ffStrbufInitStatic(&result->prettyName, "ash"); + } +} + +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\\"); + FF_AUTO_CLOSE_FD HANDLE hkcu = NULL; + if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Console\\%%Startup", &hkcu, NULL) && + ffRegReadData(hkcu, L"DelegationTerminal", &(FFArgBuffer) { + .data = uuid, + .length = (uint32_t) (sizeof(regPath) - (size_t) (uuid - regPath) * sizeof(wchar_t)), + }, + NULL)) { + 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 hklm = NULL; + if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hklm, NULL)) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); + if (ffRegGetSubKey(hklm, 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); + } + } +} + +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'; + } + if (instance.config.general.detectVersion) { + 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); + if (instance.config.general.detectVersion) { + fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version); + } + } + + return &result; +} diff --git a/src/detection/terminalsize/terminalsize.h b/src/detection/terminalsize/terminalsize.h new file mode 100644 index 0000000..1ad97e1 --- /dev/null +++ b/src/detection/terminalsize/terminalsize.h @@ -0,0 +1,13 @@ +#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); diff --git a/src/detection/terminalsize/terminalsize_linux.c b/src/detection/terminalsize/terminalsize_linux.c new file mode 100644 index 0000000..e06f958 --- /dev/null +++ b/src/detection/terminalsize/terminalsize_linux.c @@ -0,0 +1,38 @@ +#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; +} diff --git a/src/detection/terminalsize/terminalsize_windows.c b/src/detection/terminalsize/terminalsize_windows.c new file mode 100644 index 0000000..05888e7 --- /dev/null +++ b/src/detection/terminalsize/terminalsize_windows.c @@ -0,0 +1,45 @@ +#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 = { .cbSize = sizeof(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; +} diff --git a/src/detection/terminaltheme/terminaltheme.c b/src/detection/terminaltheme/terminaltheme.c new file mode 100644 index 0000000..e609754 --- /dev/null +++ b/src/detection/terminaltheme/terminaltheme.c @@ -0,0 +1,105 @@ +#include "terminaltheme.h" +#include "common/io.h" +#include "common/strutil.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; +} diff --git a/src/detection/terminaltheme/terminaltheme.h b/src/detection/terminaltheme/terminaltheme.h new file mode 100644 index 0000000..a2808dc --- /dev/null +++ b/src/detection/terminaltheme/terminaltheme.h @@ -0,0 +1,18 @@ +#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); diff --git a/src/detection/theme/theme.h b/src/detection/theme/theme.h new file mode 100644 index 0000000..3498b1d --- /dev/null +++ b/src/detection/theme/theme.h @@ -0,0 +1,11 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/theme/option.h" + +typedef struct FFThemeResult { + FFstrbuf theme1; + FFstrbuf theme2; +} FFThemeResult; + +const char* ffDetectTheme(FFThemeResult* result); diff --git a/src/detection/theme/theme_apple.c b/src/detection/theme/theme_apple.c new file mode 100644 index 0000000..a60d39c --- /dev/null +++ b/src/detection/theme/theme_apple.c @@ -0,0 +1,22 @@ +#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; +} diff --git a/src/detection/theme/theme_linux.c b/src/detection/theme/theme_linux.c new file mode 100644 index 0000000..1de7db8 --- /dev/null +++ b/src/detection/theme/theme_linux.c @@ -0,0 +1,60 @@ +#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; +} diff --git a/src/detection/theme/theme_nosupport.c b/src/detection/theme/theme_nosupport.c new file mode 100644 index 0000000..6f42b96 --- /dev/null +++ b/src/detection/theme/theme_nosupport.c @@ -0,0 +1,5 @@ +#include "theme.h" + +const char* ffDetectTheme(FF_A_UNUSED FFThemeResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/theme/theme_windows.c b/src/detection/theme/theme_windows.c new file mode 100644 index 0000000..339b79b --- /dev/null +++ b/src/detection/theme/theme_windows.c @@ -0,0 +1,27 @@ +#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; +} diff --git a/src/detection/tpm/tpm.h b/src/detection/tpm/tpm.h new file mode 100644 index 0000000..da842ec --- /dev/null +++ b/src/detection/tpm/tpm.h @@ -0,0 +1,11 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/tpm/option.h" + +typedef struct FFTPMResult { + FFstrbuf version; + FFstrbuf description; +} FFTPMResult; + +const char* ffDetectTPM(FFTPMResult* result); diff --git a/src/detection/tpm/tpm_apple.c b/src/detection/tpm/tpm_apple.c new file mode 100644 index 0000000..250dd01 --- /dev/null +++ b/src/detection/tpm/tpm_apple.c @@ -0,0 +1,30 @@ +#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"; +} diff --git a/src/detection/tpm/tpm_bsd.c b/src/detection/tpm/tpm_bsd.c new file mode 100644 index 0000000..4b73413 --- /dev/null +++ b/src/detection/tpm/tpm_bsd.c @@ -0,0 +1,22 @@ +#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; +} diff --git a/src/detection/tpm/tpm_linux.c b/src/detection/tpm/tpm_linux.c new file mode 100644 index 0000000..040da38 --- /dev/null +++ b/src/detection/tpm/tpm_linux.c @@ -0,0 +1,24 @@ +#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; +} diff --git a/src/detection/tpm/tpm_nosupport.c b/src/detection/tpm/tpm_nosupport.c new file mode 100644 index 0000000..492f37f --- /dev/null +++ b/src/detection/tpm/tpm_nosupport.c @@ -0,0 +1,5 @@ +#include "tpm.h" + +const char* ffDetectTPM(FF_A_UNUSED FFTPMResult* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/tpm/tpm_windows.c b/src/detection/tpm/tpm_windows.c new file mode 100644 index 0000000..14f6e8a --- /dev/null +++ b/src/detection/tpm/tpm_windows.c @@ -0,0 +1,51 @@ +#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; +} diff --git a/src/detection/uptime/uptime.h b/src/detection/uptime/uptime.h new file mode 100644 index 0000000..3f8631c --- /dev/null +++ b/src/detection/uptime/uptime.h @@ -0,0 +1,11 @@ +#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); diff --git a/src/detection/uptime/uptime_bsd.c b/src/detection/uptime/uptime_bsd.c new file mode 100644 index 0000000..46943b0 --- /dev/null +++ b/src/detection/uptime/uptime_bsd.c @@ -0,0 +1,27 @@ +#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; +} diff --git a/src/detection/uptime/uptime_haiku.c b/src/detection/uptime/uptime_haiku.c new file mode 100644 index 0000000..bf5dd25 --- /dev/null +++ b/src/detection/uptime/uptime_haiku.c @@ -0,0 +1,8 @@ +#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; +} diff --git a/src/detection/uptime/uptime_linux.c b/src/detection/uptime/uptime_linux.c new file mode 100644 index 0000000..75c7768 --- /dev/null +++ b/src/detection/uptime/uptime_linux.c @@ -0,0 +1,38 @@ +#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 +} diff --git a/src/detection/uptime/uptime_sunos.c b/src/detection/uptime/uptime_sunos.c new file mode 100644 index 0000000..2a37299 --- /dev/null +++ b/src/detection/uptime/uptime_sunos.c @@ -0,0 +1,19 @@ +#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; +} diff --git a/src/detection/uptime/uptime_windows.c b/src/detection/uptime/uptime_windows.c new file mode 100644 index 0000000..695edad --- /dev/null +++ b/src/detection/uptime/uptime_windows.c @@ -0,0 +1,17 @@ +#include "uptime.h" +#include "common/time.h" +#include "common/windows/nt.h" + +const char* ffDetectUptime(FFUptimeResult* result) { + // QueryInterruptTime 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; +} diff --git a/src/detection/users/users.h b/src/detection/users/users.h new file mode 100644 index 0000000..8f24cc0 --- /dev/null +++ b/src/detection/users/users.h @@ -0,0 +1,14 @@ +#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); diff --git a/src/detection/users/users_linux.c b/src/detection/users/users_linux.c new file mode 100644 index 0000000..bc84eed --- /dev/null +++ b/src/detection/users/users_linux.c @@ -0,0 +1,204 @@ +#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_A_UNUSED FFUserResult* user, FF_A_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; +} diff --git a/src/detection/users/users_nosupport.c b/src/detection/users/users_nosupport.c new file mode 100644 index 0000000..0a7e30a --- /dev/null +++ b/src/detection/users/users_nosupport.c @@ -0,0 +1,6 @@ +#include "fastfetch.h" +#include "users.h" + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { + return "Not supported on this platform"; +} diff --git a/src/detection/users/users_obsd.c b/src/detection/users/users_obsd.c new file mode 100644 index 0000000..97a22f0 --- /dev/null +++ b/src/detection/users/users_obsd.c @@ -0,0 +1,39 @@ +#include "fastfetch.h" +#include "users.h" +#include "common/io.h" + +#include + +const char* ffDetectUsers(FF_A_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 = FF_LIST_ADD(FFUserResult, *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; +} diff --git a/src/detection/users/users_windows.c b/src/detection/users/users_windows.c new file mode 100644 index 0000000..4cde44e --- /dev/null +++ b/src/detection/users/users_windows.c @@ -0,0 +1,61 @@ +#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 = FF_LIST_ADD(FFUserResult, *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; +} diff --git a/src/detection/version/version.c b/src/detection/version/version.c new file mode 100644 index 0000000..8bd066e --- /dev/null +++ b/src/detection/version/version.c @@ -0,0 +1,110 @@ +#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 +} +; diff --git a/src/detection/version/version.h b/src/detection/version/version.h new file mode 100644 index 0000000..1330535 --- /dev/null +++ b/src/detection/version/version.h @@ -0,0 +1,19 @@ +#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; diff --git a/src/detection/vulkan/vulkan.c b/src/detection/vulkan/vulkan.c new file mode 100644 index 0000000..013c457 --- /dev/null +++ b/src/detection/vulkan/vulkan.c @@ -0,0 +1,332 @@ +#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/strutil.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 = FF_LIST_ADD(FFGPUResult, 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; + static bool initialized; + + if (!initialized) { + FF_DEBUG("Initializing Vulkan detection cache"); + initialized = true; + ffStrbufInit(&result.driver); + ffStrbufInit(&result.apiVersion); + ffStrbufInit(&result.conformanceVersion); + ffStrbufInit(&result.instanceVersion); + ffListInit(&result.gpus); + +#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; +} diff --git a/src/detection/vulkan/vulkan.h b/src/detection/vulkan/vulkan.h new file mode 100644 index 0000000..6a49722 --- /dev/null +++ b/src/detection/vulkan/vulkan.h @@ -0,0 +1,15 @@ +#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(); diff --git a/src/detection/wallpaper/wallpaper.h b/src/detection/wallpaper/wallpaper.h new file mode 100644 index 0000000..96fead9 --- /dev/null +++ b/src/detection/wallpaper/wallpaper.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +const char* ffDetectWallpaper(FFstrbuf* result); diff --git a/src/detection/wallpaper/wallpaper_apple.m b/src/detection/wallpaper/wallpaper_apple.m new file mode 100644 index 0000000..800aee9 --- /dev/null +++ b/src/detection/wallpaper/wallpaper_apple.m @@ -0,0 +1,72 @@ +#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"; +} diff --git a/src/detection/wallpaper/wallpaper_haiku.cpp b/src/detection/wallpaper/wallpaper_haiku.cpp new file mode 100644 index 0000000..0bfad62 --- /dev/null +++ b/src/detection/wallpaper/wallpaper_haiku.cpp @@ -0,0 +1,54 @@ +extern "C" { +#include "wallpaper.h" +#include "common/mallocHelper.h" +} + +#include +#include +#include +#include +#include +#include +#include +#include + +const char* ffDetectWallpaper(FFstrbuf* result) { + BMessage backgrounds; + BPath pDesktop; + BString path; + + if (find_directory(B_DESKTOP_DIRECTORY, &pDesktop) < B_OK) { + return "find_directory(B_DESKTOP_DIRECTORY) failed"; + } + + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + + BNode nDesktop(pDesktop.Path()); + if (nDesktop.InitCheck() == B_OK) { + struct attr_info ai; + if (nDesktop.GetAttrInfo(B_BACKGROUND_INFO, &ai) == B_OK && ai.size > 0) { + FF_AUTO_FREE char* pAttr = (char*) malloc(ai.size); + if (nDesktop.ReadAttr(B_BACKGROUND_INFO, ai.type, 0LL, pAttr, (size_t) ai.size) >= B_OK) { + if (backgrounds.Unflatten(pAttr) == B_OK) { + for (int i = 0; backgrounds.FindString(B_BACKGROUND_IMAGE, i, &path) == B_OK; i++) { + int32 ws; + if (backgrounds.FindInt32(B_BACKGROUND_WORKSPACES, i, &ws) == B_OK) { + if (ws & (1 << current_workspace())) { + // We try to match the one for the current workspace + break; + } + } + } + } + } + } + } + + if (path.Length() < 1) { + return "Failed to detect the current wallpaper path"; + } + + ffStrbufAppendS(result, path.String()); + return NULL; +} diff --git a/src/detection/wallpaper/wallpaper_linux.c b/src/detection/wallpaper/wallpaper_linux.c new file mode 100644 index 0000000..f5ad4ec --- /dev/null +++ b/src/detection/wallpaper/wallpaper_linux.c @@ -0,0 +1,102 @@ +#include "wallpaper.h" +#include "common/io.h" +#include "common/settings.h" +#include "detection/displayserver/displayserver.h" +#include "detection/gtk_qt/gtk_qt.h" + +static const char* detectCosmicComp(FFstrbuf* result) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs)); + ffStrbufAppendS(&path, "cosmic/com.system76.CosmicBackground/v1/"); + uint32_t basePathLength = path.length; + + FF_STRBUF_AUTO_DESTROY backgrounds = ffStrbufCreate(); + ffStrbufAppendS(&path, "backgrounds"); + bool sharedWallpaper = true; + FF_STRBUF_AUTO_DESTROY monitorName = ffStrbufCreate(); + if (ffReadFileBuffer(path.chars, &backgrounds)) { + FF_STRBUF_AUTO_DESTROY cleaned = ffStrbufCreateCopy(&backgrounds); + ffStrbufRemoveStrings(&cleaned, 4, (const char*[]){ " ", "\n", "\r", "\t" }); + + if (!ffStrbufEqualS(&cleaned, "[]")) { + const char* firstQuote = strchr(backgrounds.chars, '"'); + if (firstQuote != NULL) { + const char* secondQuote = strchr(firstQuote + 1, '"'); + if (secondQuote != NULL && secondQuote > firstQuote + 1) { + ffStrbufAppendNS(&monitorName, (uint32_t) (secondQuote - firstQuote - 1), firstQuote + 1); + sharedWallpaper = false; + } + } + } + } + ffStrbufSubstrBefore(&path, basePathLength); + + if (sharedWallpaper) { + ffStrbufAppendS(&path, "all"); + } else { + ffStrbufAppendS(&path, "output."); + ffStrbufAppend(&path, &monitorName); + } + + FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); + if (!ffReadFileBuffer(path.chars, &output)) { + return "Failed to read COSMIC wallpaper config"; + } + + const char* sourceStart = strstr(output.chars, "source:"); + if (sourceStart == NULL) { + return "COSMIC wallpaper config doesn't contain source"; + } + + const char* pathStart = strstr(sourceStart, "Path("); + if (pathStart == NULL) { + return "COSMIC wallpaper source is not a Path value"; + } + + pathStart += strlen("Path("); + while (*pathStart == ' ' || *pathStart == '\t') { + ++pathStart; + } + + if (*pathStart != '\'' && *pathStart != '"') { + return "COSMIC wallpaper Path format is invalid"; + } + + char quote = *pathStart; + ++pathStart; + const char* pathEnd = strchr(pathStart, quote); + if (pathEnd == NULL || pathEnd == pathStart) { + return "COSMIC wallpaper path is empty"; + } + + ffStrbufAppendNS(result, (uint32_t) (pathEnd - pathStart), pathStart); + return NULL; +} + +const char* ffDetectWallpaper(FFstrbuf* result) { + const FFDisplayServerResult* wm = ffConnectDisplayServer(); + if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_COSMIC_COMP)) { + return detectCosmicComp(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; +} diff --git a/src/detection/wallpaper/wallpaper_nosupport.c b/src/detection/wallpaper/wallpaper_nosupport.c new file mode 100644 index 0000000..9be8e91 --- /dev/null +++ b/src/detection/wallpaper/wallpaper_nosupport.c @@ -0,0 +1,5 @@ +#include "wallpaper.h" + +const char* ffDetectWallpaper(FF_A_UNUSED FFstrbuf* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/wallpaper/wallpaper_windows.c b/src/detection/wallpaper/wallpaper_windows.c new file mode 100644 index 0000000..2adf083 --- /dev/null +++ b/src/detection/wallpaper/wallpaper_windows.c @@ -0,0 +1,15 @@ +#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; +} diff --git a/src/detection/weather/weather.c b/src/detection/weather/weather.c new file mode 100644 index 0000000..69df642 --- /dev/null +++ b/src/detection/weather/weather.c @@ -0,0 +1,62 @@ +#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; +} diff --git a/src/detection/weather/weather.h b/src/detection/weather/weather.h new file mode 100644 index 0000000..bd3d1bc --- /dev/null +++ b/src/detection/weather/weather.h @@ -0,0 +1,7 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/weather/option.h" + +void ffPrepareWeather(FFWeatherOptions* options); +const char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result); diff --git a/src/detection/wifi/wifi.h b/src/detection/wifi/wifi.h new file mode 100644 index 0000000..e457295 --- /dev/null +++ b/src/detection/wifi/wifi.h @@ -0,0 +1,62 @@ +#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) { + // Return 0 for unknown / non-standard frequencies. + if (frequency == 2484) { + return 14; + } + + // 2.4 GHz channels 1-13 + if (frequency >= 2412 && frequency <= 2472 && ((frequency - 2407) % 5) == 0) { + return (uint16_t) ((frequency - 2407) / 5); + } + + // 4.9 GHz public safety band (e.g. channels 182-196) + if (frequency >= 4910 && frequency <= 4980 && ((frequency - 4000) % 5) == 0) { + return (uint16_t) ((frequency - 4000) / 5); + } + + // 5 GHz channels + if (frequency >= 5000 && frequency <= 5895 && ((frequency - 5000) % 5) == 0) { + return (uint16_t) ((frequency - 5000) / 5); + } + + // 6 GHz channels (Wi-Fi 6E/7) + // 5935 MHz is a special case mapped to channel 2. + if (frequency == 5935) { + return 2; + } + if (frequency >= 5955 && frequency <= 7115 && ((frequency - 5950) % 5) == 0) { + return (uint16_t) ((frequency - 5950) / 5); + } + + return 0; +} diff --git a/src/detection/wifi/wifi_android.c b/src/detection/wifi/wifi_android.c new file mode 100644 index 0000000..c4537da --- /dev/null +++ b/src/detection/wifi/wifi_android.c @@ -0,0 +1,69 @@ +#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* FF_A_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 = FF_LIST_ADD(FFWifiResult, *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; +} diff --git a/src/detection/wifi/wifi_apple.m b/src/detection/wifi/wifi_apple.m new file mode 100644 index 0000000..21808e0 --- /dev/null +++ b/src/detection/wifi/wifi_apple.m @@ -0,0 +1,165 @@ +#include "wifi.h" +#include "common/processing.h" +#include "common/strutil.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 = FF_LIST_ADD(FFWifiResult, *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 Personal 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; +} diff --git a/src/detection/wifi/wifi_bsd.c b/src/detection/wifi/wifi_bsd.c new file mode 100644 index 0000000..fab1293 --- /dev/null +++ b/src/detection/wifi/wifi_bsd.c @@ -0,0 +1,153 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.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 = FF_LIST_ADD(FFWifiResult, *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; +} diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c new file mode 100644 index 0000000..6f76159 --- /dev/null +++ b/src/detection/wifi/wifi_linux.c @@ -0,0 +1,976 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/debug.h" +#include "common/strutil.h" + +#include +#include +#include +#include +#include +#include +#include + +#if !__BIG_ENDIAN__ + #include + #include + + // Silence warning of `NLA_HDRLEN` and `NLA_ALIGN` + #pragma GCC diagnostic ignored "-Wsign-conversion" + +typedef struct FFWifiNlContext { + int sockFd; + uint16_t nl80211FamilyId; + uint32_t portId; + uint32_t seq; +} FFWifiNlContext; + +typedef struct FFWifiSecurityFlags { + bool privacy : 1; + bool wep : 1; + bool wpa : 1; + bool wpa2 : 1; + bool wpa3 : 1; + bool owe : 1; + bool eap : 1; +} FFWifiSecurityFlags; + +static inline double rssiToSignalQuality(int rssi) { + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 + : (rssi + 100) * 2); +} + +static inline uint32_t ffWifiGetNetlinkPortId(int sockFd) { + struct sockaddr_nl addr = {}; + socklen_t addrLen = sizeof(addr); + if (getsockname(sockFd, (struct sockaddr*) &addr, &addrLen) < 0) { + FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno)); + return instance.state.platform.pid; + } + + return addr.nl_pid; +} + +static inline bool ffWifiNlAttrOk(const struct nlattr* attr, size_t remaining) { + return remaining >= sizeof(*attr) && + attr->nla_len >= sizeof(*attr) && + attr->nla_len <= remaining; +} + +static const struct nlattr* ffWifiNlAttrNext(const struct nlattr* attr, size_t* remaining) { + size_t alignedLen = NLA_ALIGN(attr->nla_len); + if (alignedLen > *remaining) { + *remaining = 0; + return NULL; + } + + *remaining -= alignedLen; + return (const struct nlattr*) ((const char*) attr + alignedLen); +} + +static inline size_t ffWifiNlAttrPayload(const struct nlattr* attr) { + return attr->nla_len > NLA_HDRLEN ? attr->nla_len - NLA_HDRLEN : 0; +} + +static inline const void* ffWifiNlAttrData(const struct nlattr* attr) { + // Big endian? + return (const uint8_t*) attr + NLA_HDRLEN; +} + +static bool ffWifiNlAppendAttr(struct nlmsghdr* nlh, size_t maxLen, uint16_t type, const void* data, uint16_t dataLen) { + size_t offset = NLMSG_ALIGN(nlh->nlmsg_len); + size_t attrLen = NLA_HDRLEN + dataLen; + size_t alignedLen = NLA_ALIGN(attrLen); + size_t newLen = offset + alignedLen; + if (newLen > maxLen || attrLen > UINT16_MAX || newLen > UINT32_MAX) { + return false; + } + + struct nlattr* attr = (struct nlattr*) ((char*) nlh + offset); + attr->nla_type = type; + attr->nla_len = (uint16_t) attrLen; + memcpy((char*) attr + NLA_HDRLEN, data, dataLen); + memset((char*) attr + attrLen, 0, alignedLen - attrLen); + nlh->nlmsg_len = (uint32_t) newLen; + return true; +} + +static bool ffWifiNlGetFamilyId(FFWifiNlContext* ctx) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[64]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = GENL_ID_CTRL, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = CTRL_CMD_GETFAMILY, + .version = 1, // generic netlink control protocol version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, "nl80211", sizeof("nl80211"))) { + FF_DEBUG("Failed to append CTRL_ATTR_FAMILY_NAME attribute"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 family request: sent=%zd expected=%u", sent, req.nlh.nlmsg_len); + return false; + } + + uint8_t buffer[8192]; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 family reply: %s", strerror(errno)); + return false; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 family query failed: %s", strerror(-err->error)); + return false; + } + continue; + } + + if (nlh->nlmsg_type != GENL_ID_CTRL) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + if (genl->cmd != CTRL_CMD_NEWFAMILY) { + continue; + } + + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != CTRL_ATTR_FAMILY_ID || ffWifiNlAttrPayload(attr) < sizeof(uint16_t)) { + continue; + } + + ctx->nl80211FamilyId = *(const uint16_t*) ffWifiNlAttrData(attr); + return true; + } + } + } +} + +static bool ffWifiNlInit(FFWifiNlContext* ctx) { + FF_AUTO_CLOSE_FD int _ = ctx->sockFd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC); + if (ctx->sockFd < 0) { + FF_DEBUG("Failed to create generic netlink socket: %s", strerror(errno)); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + if (bind(ctx->sockFd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { + FF_DEBUG("Failed to bind generic netlink socket: %s", strerror(errno)); + return false; + } + + if (setsockopt( + ctx->sockFd, + SOL_SOCKET, + SO_RCVTIMEO, + &(struct timeval){ .tv_sec = 0, .tv_usec = 250000 }, // 250 ms recv timeout + sizeof(struct timeval)) < 0) { + FF_DEBUG("Failed to set netlink receive timeout: %s", strerror(errno)); + return false; + } + + ctx->portId = ffWifiGetNetlinkPortId(ctx->sockFd); + if (!ffWifiNlGetFamilyId(ctx)) { + return false; + } + + _ = -1; // We are ok now + return true; +} + +static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol) { + double rate = -DBL_MAX; + size_t remaining = ffWifiNlAttrPayload(rateAttr); + + for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr); + ffWifiNlAttrOk(info, remaining); + info = ffWifiNlAttrNext(info, &remaining)) { + uint16_t type = (uint16_t) (info->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(info); + + switch (type) { + case 30 /* NL80211_RATE_INFO_UHR_MCS */: + ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)"); + break; + case 23 /* NL80211_RATE_INFO_S1G_MCS */: + ffStrbufSetStatic(protocol, "802.11ah (Wi-Fi HaLow)"); + break; + case 19 /* NL80211_RATE_INFO_EHT_MCS */: + ffStrbufSetStatic(protocol, "802.11be (Wi-Fi 7)"); + break; + case 13 /* NL80211_RATE_INFO_HE_MCS */: + ffStrbufSetStatic(protocol, "802.11ax (Wi-Fi 6)"); + break; + case NL80211_RATE_INFO_VHT_MCS: + ffStrbufSetStatic(protocol, "802.11ac (Wi-Fi 5)"); + break; + case NL80211_RATE_INFO_MCS: + ffStrbufSetStatic(protocol, "802.11n (Wi-Fi 4)"); + break; + case NL80211_RATE_INFO_BITRATE32: + if (payload >= sizeof(uint32_t)) { + rate = *(uint32_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps + } + break; + case NL80211_RATE_INFO_BITRATE: + if (payload >= sizeof(uint16_t) && rate == -DBL_MAX) { + rate = *(uint16_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps + } + break; + } + } + + return rate; +} + +static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) { + ffStrbufClear(&item->conn.security); + + if (sec->wep) { + ffStrbufAppendS(&item->conn.security, "WEP/"); + } + if (sec->wpa) { + ffStrbufAppendS(&item->conn.security, "WPA/"); + } + if (sec->wpa2) { + ffStrbufAppendS(&item->conn.security, "WPA2/"); + } + if (sec->wpa3) { + ffStrbufAppendS(&item->conn.security, "WPA3/"); + } + if (sec->owe) { + ffStrbufAppendS(&item->conn.security, "OWE/"); + } + if (sec->eap) { + ffStrbufAppendS(&item->conn.security, "802.1X/"); + } + + if (!item->conn.security.length) { + if (sec->privacy) { + ffStrbufSetStatic(&item->conn.security, "WEP"); + } else { + ffStrbufSetStatic(&item->conn.security, "Insecure"); + } + } else { + ffStrbufTrimRight(&item->conn.security, '/'); + } +} + +static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { // version(2) + group cipher suite(4) + pairwise count(2) + return; + } + + sec->wpa2 = true; + size_t pos = 0; + + pos += 2; // RSN version field length + if (pos + 4 > len) { // group cipher suite selector length + return; + } + pos += 4; // skip group cipher suite selector + + if (pos + 2 > len) { // pairwise cipher suite count field length + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2; // skip pairwise cipher suite count field + + size_t pairwiseLen = (size_t) pairwiseCount * 4; // each suite selector is 4 bytes + if (pos + pairwiseLen > len) { + return; + } + pos += pairwiseLen; + + if (pos + 2 > len) { // AKM suite count field length + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; // skip AKM suite count field + + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes + const uint8_t* akm = ie + pos; + if (akm[0] != 0x00 || akm[1] != 0x0f || akm[2] != 0xac) { // RSN OUI 00:0f:ac + continue; + } + + switch (akm[3]) { + case 1: // 802.1X + case 5: // FT/802.1X + case 11: // 802.1X-SHA256 + case 12: // FT/802.1X-SHA384 (suite selector value) + sec->eap = true; + break; + case 8: // SAE (WPA3-Personal) + sec->wpa3 = true; + break; + case 18: // OWE + sec->owe = true; + break; + default: + break; + } + } + + if (sec->owe) { + sec->wpa2 = false; + } +} + +static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { // OUI+type(4) + version(2) + multicast cipher suite(4) starts here + return; + } + + if (!(ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2 && ie[3] == 0x01)) { // Microsoft WPA OUI/type + return; + } + + sec->wpa = true; + + size_t pos = 4; // WPA vendor OUI/type selector length + if (pos + 2 > len) { // WPA version field length + return; + } + pos += 2; // skip WPA version + + if (pos + 4 > len) { // multicast cipher suite selector length + return; + } + pos += 4; // skip multicast cipher suite selector + + if (pos + 2 > len) { // unicast cipher suite count field length + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2 + (size_t) pairwiseCount * 4; // count field(2) + N unicast suite selectors(4 each) + + if (pos + 2 > len) { // AKM suite count field length + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; // skip AKM suite count field + + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes + const uint8_t* akm = ie + pos; + if (!(akm[0] == 0x00 && akm[1] == 0x50 && akm[2] == 0xf2)) { // WPA vendor OUI 00:50:f2 + continue; + } + if (akm[3] == 1) { // WPA Enterprise (802.1X) + sec->eap = true; + } + } +} + +static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FFWifiResult* item, FFWifiSecurityFlags* sec) { + size_t pos = 0; + while (pos + 2 <= length) { + uint8_t id = ies[pos]; + uint8_t len = ies[pos + 1]; + pos += 2; + if (pos + len > length) { + break; + } + + const uint8_t* ie = ies + pos; + if (id == 0) { // SSID element ID + ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie); + } else if (id == 48) { // RSN element ID + ffWifiParseRsnIe(ie, len, sec); + } else if (id == 221) { // vendor-specific element ID (WPA IE lives here) + ffWifiParseWpaVendorIe(ie, len, sec); + } + + pos += len; + } +} + +static bool ffWifiIsBssAssociated(const struct nlattr* bssAttr) { + size_t remaining = ffWifiNlAttrPayload(bssAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) { + return *(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED; + } + } + + return false; +} + +static void ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item) { + FFWifiSecurityFlags sec = {}; + size_t remaining = ffWifiNlAttrPayload(bssAttr); + + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_BSS_BSSID && payload >= 6) { + const uint8_t* mac = (const uint8_t*) ffWifiNlAttrData(attr); + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else if (type == NL80211_BSS_FREQUENCY && payload >= sizeof(uint32_t)) { + item->conn.frequency = (uint16_t) *(uint32_t*) ffWifiNlAttrData(attr); + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + } else if (type == NL80211_BSS_SIGNAL_MBM && payload >= sizeof(int32_t)) { + int rssi = *(int32_t*) ffWifiNlAttrData(attr) / 100; // mBm (100 * dBm) => dBm + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_BSS_CAPABILITY && payload >= sizeof(uint16_t)) { + uint16_t capability = *(uint16_t*) ffWifiNlAttrData(attr); + sec.privacy = (capability & (1u << 4u)) != 0; // IEEE 802.11 capability bit 4: privacy + } else if (type == NL80211_BSS_INFORMATION_ELEMENTS || type == NL80211_BSS_BEACON_IES) { + ffWifiParseInformationElements((const uint8_t*) ffWifiNlAttrData(attr), payload, item, &sec); + } + } + + ffWifiApplySecurityFlags(item, &sec); + return; +} + +static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_SCAN, + .version = 0, // nl80211 command version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 scan request"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 scan request"); + return false; + } + + uint8_t buffer[1024 * 16]; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 scan reply: %s", strerror(errno)); + return false; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + return false; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error == 0) { + continue; + } + FF_DEBUG("nl80211 scan request failed: %s", strerror(-err->error)); + return false; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_BSS) { + continue; + } + + if (!ffWifiIsBssAssociated(attr)) { + continue; + } + + ffWifiParseBssAttr(attr, item); + ffStrbufSetStatic(&item->conn.status, "connected"); + return true; + } + } + } + + return false; +} + +static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResult* item) { + size_t remaining = ffWifiNlAttrPayload(staInfoAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(staInfoAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) { + int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr); + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_STA_INFO_TX_BITRATE && item->conn.txRate == -DBL_MAX) { + double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (tx != -DBL_MAX) { + item->conn.txRate = tx; + } + } else if (type == NL80211_STA_INFO_RX_BITRATE && item->conn.rxRate == -DBL_MAX) { + double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (rx != -DBL_MAX) { + item->conn.rxRate = rx; + } + } + } +} + +static bool ffWifiFetchStationInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_STATION, + .version = 0, // nl80211 command version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 station request"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 station request"); + return false; + } + + uint8_t buffer[8192]; + bool gotStation = false; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 station reply: %s", strerror(errno)); + return gotStation; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + return gotStation; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 station request failed: %s", strerror(-err->error)); + } + return gotStation; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_STA_INFO) { + continue; + } + + ffWifiParseStationInfo(attr, item); + gotStation = true; + } + } + } +} + +static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + if (!ffWifiNlInit(ctx)) { + FF_DEBUG("Failed to initialize netlink context, skipping"); + ctx->sockFd = -2; // sentinel: permanent netlink failure, don't retry + return "Netlink initialization failed"; + } + } else { + FF_DEBUG("Netlink socket is not available, skipping"); + return "Netlink socket unavailable"; + } + } + + FF_DEBUG("Starting netlink wifi detection for interface %s", item->inf.description.chars); + if (ffWifiFetchScanInfo(ctx, item, ifIndex)) { + FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars); + ffStrbufSetStatic(&item->conn.status, "connected"); + ffWifiFetchStationInfo(ctx, item, ifIndex); + if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) { + FF_DEBUG("nl80211 station info did not include MCS family fields"); + } + } else { + FF_DEBUG("No associated BSS found"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); + } + + FF_DEBUG("Netlink wifi detection completed"); + return NULL; +} +#endif + +typedef struct FFWifiIcContext { + int sockFd; +} FFWifiIcContext; + +static const char* detectWithIoctl(FFWifiIcContext* ctx, FFWifiResult* item, char ifName[static IFNAMSIZ]) { + int sock = -1; + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sock < 0) { + FF_DEBUG("Failed to initialize ioctl context, skipping: %s", strerror(errno)); + ctx->sockFd = -2; // sentinel: permanent ioctl failure, don't retry + return "socket() failed"; + } + ctx->sockFd = sock; + } else { + FF_DEBUG("Ioctl socket is not available, skipping"); + return "ioctl socket unavailable"; + } + } else { + sock = ctx->sockFd; + } + + FF_DEBUG("Starting ioctl wifi detection for interface %s", ifName); + struct iwreq iwr = {}; + strcpy(iwr.ifr_name, ifName); + + if (!item->conn.ssid.length) { + FF_DEBUG("Getting SSID via ioctl"); + ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE); + iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars; + iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1; + iwr.u.essid.flags = 0; + if (ioctl(sock, SIOCGIWESSID, &iwr) >= 0) { + ffStrbufSetStatic(&item->conn.status, "connected"); + ffStrbufRecalculateLength(&item->conn.ssid); + FF_DEBUG("SSID: %s", item->conn.ssid.chars); + } else { + FF_DEBUG("Failed to get SSID via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.protocol.length) { + FF_DEBUG("Getting protocol name via ioctl"); + if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0) { + char* token = iwr.u.name; + if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) { + token += strlen("IEEE "); + } + if (ffStrStartsWith(token, "802.11")) { + token += strlen("802.11"); + if (*token) { + if (*token == ' ') { + token++; + } + for (char* c = token; *c; ++c) { + if (*c >= 'A' && *c <= 'Z') { + *c += 'a' - 'A'; + } + } + if (ffStrEquals(token, "n")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (ffStrEquals(token, "ac")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + } else if (ffStrEquals(token, "ax")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + } else if (ffStrEquals(token, "be")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + } else if (ffStrEquals(token, "bn")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11bn (Wi-Fi 8)"); + } else { + ffStrbufSetStatic(&item->conn.protocol, "802.11"); + ffStrbufAppendS(&item->conn.protocol, token); + } + } + } + FF_DEBUG("Protocol: %s", item->conn.protocol.length ? item->conn.protocol.chars : "(unknown)"); + } else { + FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.bssid.length) { + FF_DEBUG("Getting BSSID via ioctl"); + if (ioctl(sock, SIOCGIWAP, &iwr) >= 0) { + for (int i = 0; i < 6; ++i) { + ffStrbufAppendF(&item->conn.bssid, "%.2X:", (uint8_t) iwr.u.ap_addr.sa_data[i]); + } + ffStrbufTrimRight(&item->conn.bssid, ':'); + FF_DEBUG("BSSID: %s", item->conn.bssid.chars); + } else { + FF_DEBUG("Failed to get BSSID via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.txRate == -DBL_MAX) { + FF_DEBUG("Getting bitrate via ioctl"); + if (ioctl(sock, SIOCGIWRATE, &iwr) >= 0) { + if (iwr.u.bitrate.value > 0) { + item->conn.txRate = iwr.u.bitrate.value / 1000000.; // bps => Mbps + FF_DEBUG("TX bitrate: %.2f Mbps", item->conn.txRate); + } else { + FF_DEBUG("Bitrate value is zero or negative, ignoring"); + } + } else { + FF_DEBUG("Failed to get bitrate via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.frequency == 0 && item->conn.channel == 0) { + FF_DEBUG("Getting frequency via ioctl"); + if (ioctl(sock, SIOCGIWFREQ, &iwr) >= 0) { + if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { // kernel may return direct channel number + 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) { // normalize exponent to 10^6 (MHz) + iwr.u.freq.m /= 10; + iwr.u.freq.e++; + } + while (iwr.u.freq.e > 6) { // normalize exponent to 10^6 (MHz) + iwr.u.freq.m *= 10; + iwr.u.freq.e--; + } + item->conn.frequency = (uint16_t) iwr.u.freq.m; + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel); + } + } else { + FF_DEBUG("Failed to get frequency via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.signalQuality == -DBL_MAX) { + FF_DEBUG("Getting signal stats via ioctl"); + struct iw_statistics stats; + iwr.u.data.pointer = &stats; + iwr.u.data.length = sizeof(stats); + iwr.u.data.flags = 0; + + if (ioctl(sock, SIOCGIWSTATS, &iwr) >= 0) { + int8_t level = (int8_t) stats.qual.level; + item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 + : (level + 100) * 2; + FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality); + } else { + FF_DEBUG("Failed to get signal stats via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.security.length) { + FF_DEBUG("Getting security info via ioctl"); + struct iw_encode_ext iwe; + iwr.u.data.pointer = &iwe; + iwr.u.data.length = sizeof(iwe); + iwr.u.data.flags = 0; + if (ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0) { + switch (iwe.alg) { + case IW_ENCODE_ALG_WEP: + ffStrbufAppendS(&item->conn.security, "WEP"); + FF_DEBUG("Security: WEP"); + break; + case IW_ENCODE_ALG_TKIP: + ffStrbufAppendS(&item->conn.security, "TKIP"); + FF_DEBUG("Security: TKIP"); + break; + case IW_ENCODE_ALG_CCMP: + ffStrbufAppendS(&item->conn.security, "CCMP"); + FF_DEBUG("Security: CCMP"); + break; + case IW_ENCODE_ALG_PMK: + ffStrbufAppendS(&item->conn.security, "PMK"); + FF_DEBUG("Security: PMK"); + break; + case IW_ENCODE_ALG_AES_CMAC: + ffStrbufAppendS(&item->conn.security, "CMAC"); + FF_DEBUG("Security: CMAC"); + break; + default: + ffStrbufAppendF(&item->conn.security, "Unknown (%d)", (int) iwe.alg); + FF_DEBUG("Security: Unknown (%d)", (int) iwe.alg); + break; + } + } else { + FF_DEBUG("Failed to get security info via ioctl: %s", strerror(errno)); + } + } + + FF_DEBUG("Ioctl wifi detection completed"); + return NULL; +} + +const char* ffDetectWifi(FFlist* result) { + FF_DEBUG("Starting wifi detection"); + + struct if_nameindex* infs = if_nameindex(); + if (!infs) { + FF_DEBUG("if_nameindex failed: %s", strerror(errno)); + return "if_nameindex() failed"; + } + +#if !__BIG_ENDIAN__ + FFWifiNlContext nl = { .sockFd = -1 }; +#endif + FFWifiIcContext ic = { .sockFd = -1 }; + + 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; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *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)) { + ffStrbufSetStatic(&item->inf.status, "unknown"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); + continue; + } + + if (operstate == 'u') { + ffStrbufSetStatic(&item->inf.status, "up"); + +#if !__BIG_ENDIAN__ + detectWithNetlink(&nl, item, i->if_index); +#endif + detectWithIoctl(&ic, item, i->if_name); + } else { + ffStrbufSetStatic(&item->conn.status, "disconnected"); + + ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name); + char flags[16]; + ssize_t len = ffReadFileData(buffer.chars, sizeof(flags) - 1, flags); + if (len <= 0) { + ffStrbufSetStatic(&item->inf.status, "unknown"); + continue; + } + flags[len] = '\0'; + + unsigned flagsVal = (unsigned) strtoul(flags, NULL, 16); // parse /sys flags as hexadecimal + if (flagsVal & IFF_UP) { + ffStrbufSetStatic(&item->inf.status, "up"); + } else { + ffStrbufSetStatic(&item->inf.status, "down"); + } + } + } + + if_freenameindex(infs); +#if !__BIG_ENDIAN__ + if (nl.sockFd >= 0) { + close(nl.sockFd); + } +#endif + if (ic.sockFd >= 0) { + close(ic.sockFd); + } + + FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length); + return NULL; +} diff --git a/src/detection/wifi/wifi_nbsd.c b/src/detection/wifi/wifi_nbsd.c new file mode 100644 index 0000000..52897c4 --- /dev/null +++ b/src/detection/wifi/wifi_nbsd.c @@ -0,0 +1,168 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.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 = FF_LIST_ADD(FFWifiResult, *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; +} diff --git a/src/detection/wifi/wifi_nosupport.c b/src/detection/wifi/wifi_nosupport.c new file mode 100644 index 0000000..bb4a806 --- /dev/null +++ b/src/detection/wifi/wifi_nosupport.c @@ -0,0 +1,5 @@ +#include "wifi.h" + +const char* ffDetectWifi(FF_A_UNUSED FFlist* result) { + return "Not support on this platform"; +} diff --git a/src/detection/wifi/wifi_obsd.c b/src/detection/wifi/wifi_obsd.c new file mode 100644 index 0000000..f686231 --- /dev/null +++ b/src/detection/wifi/wifi_obsd.c @@ -0,0 +1,109 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.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 = FF_LIST_ADD(FFWifiResult, *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; +} diff --git a/src/detection/wifi/wifi_windows.c b/src/detection/wifi/wifi_windows.c new file mode 100644 index 0000000..125c314 --- /dev/null +++ b/src/detection/wifi/wifi_windows.c @@ -0,0 +1,242 @@ +#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 = FF_LIST_ADD(FFWifiResult, *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 diff --git a/src/detection/wm/wm.h b/src/detection/wm/wm.h new file mode 100644 index 0000000..115a8cd --- /dev/null +++ b/src/detection/wm/wm.h @@ -0,0 +1,7 @@ +#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); diff --git a/src/detection/wm/wm_apple.m b/src/detection/wm/wm_apple.m new file mode 100644 index 0000000..89273e9 --- /dev/null +++ b/src/detection/wm/wm_apple.m @@ -0,0 +1,94 @@ +#include "wm.h" + +#include "common/sysctl.h" +#include "common/mallocHelper.h" +#include "common/strutil.h" +#include "common/apple/version.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) { + buf[length] = '\0'; + FF_STRBUF_AUTO_DESTROY pluginVersion = ffStrbufCreate(); + if (ffGetAppNameAndVersion(buf, pluginName, &pluginVersion)) { + if (pluginName->length == 0) { + ffStrbufSetS(pluginName, comm); + } + if (pluginVersion.length > 0) { + ffStrbufAppendC(pluginName, ' '); + ffStrbufAppend(pluginName, &pluginVersion); + } + break; + } + } + } + + ffStrbufAppendS(pluginName, comm); + pluginName->chars[0] = (char) toupper(pluginName->chars[0]); + break; + } + + return NULL; +} + +const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_A_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; +} diff --git a/src/detection/wm/wm_linux.c b/src/detection/wm/wm_linux.c new file mode 100644 index 0000000..a62a4af --- /dev/null +++ b/src/detection/wm/wm_linux.c @@ -0,0 +1,335 @@ +#include "wm.h" + +#include "common/processing.h" +#include "common/io.h" +#include "common/binary.h" +#include "common/path.h" +#include "common/strutil.h" +#include "common/debug.h" + +const char* ffDetectWMPlugin(FF_A_UNUSED FFstrbuf* pluginName) { + return "Not supported on this platform"; +} + +static bool extractCommonWmVersion(const char* line, FF_A_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_A_UNUSED uint32_t len, void* userdata) { + FFstrbuf* result = (FFstrbuf*) userdata; + if (!ffStrStartsWith(line, "sway")) { + return true; + } + if (ffStrStartsWith(line + 4, " version ")) { + ffStrbufSetNS(result, len - (uint32_t) strlen("sway version "), line + strlen("sway version ")); + ffStrbufTrimRightSpace(result); + return false; + } else { + char swayfxVer[32], swayVer[32]; + if (sscanf(line + 4, "fx version %31[^ ] (based on sway %31[^)])", swayfxVer, swayVer) == 2) { + ffStrbufSetF(result, "%s [swayfx %s]", swayVer, swayfxVer); + return false; + } + } + + return true; +} + +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("sway version 0.0.0")); + if (result->length > 0) { + return NULL; + } + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + if (ffProcessAppendStdOut(&buffer, (char* const[]) { path.chars, "--version", NULL }) == NULL) { // sway version 1.10 + return extractSwayVersion(buffer.chars, buffer.length, result) ? "Failed to parse sway version output" : 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_A_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_A_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"; +} diff --git a/src/detection/wm/wm_nosupport.c b/src/detection/wm/wm_nosupport.c new file mode 100644 index 0000000..65c5348 --- /dev/null +++ b/src/detection/wm/wm_nosupport.c @@ -0,0 +1,9 @@ +#include "wm.h" + +const char* ffDetectWMPlugin(FF_A_UNUSED FFstrbuf* pluginName) { + return "Not supported on this platform"; +} + +const char* ffDetectWMVersion(FF_A_UNUSED const FFstrbuf* wmName, FF_A_UNUSED FFstrbuf* result, FF_A_UNUSED FFWMOptions* options) { + return "Not supported on this platform"; +} diff --git a/src/detection/wm/wm_windows.c b/src/detection/wm/wm_windows.c new file mode 100644 index 0000000..ee36399 --- /dev/null +++ b/src/detection/wm/wm_windows.c @@ -0,0 +1,204 @@ +#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_A_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"; +} diff --git a/src/detection/wmtheme/wmtheme.h b/src/detection/wmtheme/wmtheme.h new file mode 100644 index 0000000..a737a7a --- /dev/null +++ b/src/detection/wmtheme/wmtheme.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fastfetch.h" + +bool ffDetectWmTheme(FFstrbuf* themeOrError); diff --git a/src/detection/wmtheme/wmtheme_apple.m b/src/detection/wmtheme/wmtheme_apple.m new file mode 100644 index 0000000..8c43877 --- /dev/null +++ b/src/detection/wmtheme/wmtheme_apple.m @@ -0,0 +1,40 @@ +#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; +} diff --git a/src/detection/wmtheme/wmtheme_haiku.cpp b/src/detection/wmtheme/wmtheme_haiku.cpp new file mode 100644 index 0000000..243eaa4 --- /dev/null +++ b/src/detection/wmtheme/wmtheme_haiku.cpp @@ -0,0 +1,30 @@ +extern "C" { +#include "fastfetch.h" +#include "wmtheme.h" +} + +#include +#include + +bool ffDetectWmTheme(FFstrbuf* themeOrError) +{ + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + + DecorInfoUtility *util = new DecorInfoUtility(); + DecorInfo* decor = NULL; + + if (util) { + decor = util->CurrentDecorator(); + if (decor) { + ffStrbufAppendS(themeOrError, decor->Name().String()); + } + delete util; + if (decor) { + return true; + } + } + + ffStrbufAppendS(themeOrError, "Failed to get DecorInfo"); + return false; +} diff --git a/src/detection/wmtheme/wmtheme_linux.c b/src/detection/wmtheme/wmtheme_linux.c new file mode 100644 index 0000000..e912302 --- /dev/null +++ b/src/detection/wmtheme/wmtheme_linux.c @@ -0,0 +1,340 @@ +#include "wmtheme.h" +#include "common/io.h" +#include "common/properties.h" +#include "common/parsing.h" +#include "common/settings.h" +#include "common/strutil.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; +} + +static bool detectCosmicComp(FFstrbuf* themeOrError) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs)); + ffStrbufAppendS(&path, "cosmic/"); + uint32_t basePathLength = path.length; + + const char* variant; + { + char isDarkC; + ffStrbufAppendS(&path, "com.system76.CosmicTheme.Mode/v1/is_dark"); + if (ffReadFileData(path.chars, 1, &isDarkC) == 1) { + variant = isDarkC == 't' || isDarkC == '1' ? "Dark" : "Light"; + } else { + ffStrbufAppendF(themeOrError, "Couldn't read cosmic theme mode from %s", path.chars); + return false; + } + ffStrbufSubstrBefore(&path, basePathLength); + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + ffStrbufAppendF(&path, "com.system76.CosmicTheme.%s/v1/name", variant); + if (ffReadFileBuffer(path.chars, &name)) { + ffStrbufTrimSpace(&name); + ffStrbufTrim(&name, '"'); + } + ffStrbufSubstrBefore(&path, basePathLength); + + if (name.length > 0) { + ffStrbufAppend(themeOrError, &name); + } else { + ffStrbufAppendS(themeOrError, variant); + } + + FF_STRBUF_AUTO_DESTROY accent = ffStrbufCreate(); + ffStrbufAppendF(&path, "com.system76.CosmicTheme.%s/v1/accent", variant); + if (ffReadFileBuffer(path.chars, &accent)) { + const char* baseStart = strstr(accent.chars, "base:"); + if (baseStart != NULL) { + const char* contentStart = strchr(baseStart, '('); + if (contentStart != NULL) { + int depth = 1; + const char* contentEnd = contentStart + 1; + while (*contentEnd != '\0' && depth > 0) { + if (*contentEnd == '(') { + ++depth; + } else if (*contentEnd == ')') { + --depth; + } + ++contentEnd; + } + + if (depth == 0) { + double red = 0.0, green = 0.0, blue = 0.0, alpha = 1.0; + bool ok = true; + for (uint32_t i = 0; i < 4; ++i) { + const char* key = + i == 0 ? "red:" : (i == 1 ? "green:" : (i == 2 ? "blue:" : "alpha:")); + const char* componentStart = strstr(contentStart, key); + if (componentStart == NULL || componentStart >= contentEnd) { + ok = false; + break; + } + + componentStart += strlen(key); + char* componentEnd = NULL; + double value = strtod(componentStart, &componentEnd); + if (componentEnd == componentStart) { + ok = false; + break; + } + + if (i == 0) { + red = value; + } else if (i == 1) { + green = value; + } else if (i == 2) { + blue = value; + } else { + alpha = value; + } + } + + if (ok) { + uint32_t r = red <= 0.0 ? 0 : (red >= 1.0 ? 255 : (uint32_t) (red * 255.0 + 0.5)); + uint32_t g = green <= 0.0 ? 0 : (green >= 1.0 ? 255 : (uint32_t) (green * 255.0 + 0.5)); + uint32_t b = blue <= 0.0 ? 0 : (blue >= 1.0 ? 255 : (uint32_t) (blue * 255.0 + 0.5)); + uint32_t a = alpha <= 0.0 ? 0 : (alpha >= 1.0 ? 255 : (uint32_t) (alpha * 255.0 + 0.5)); + uint32_t rgb = (r << 16) | (g << 8) | b; + if (a == 255) { + ffStrbufAppendF(themeOrError, " - #%06X", rgb); + } else { + ffStrbufAppendF(themeOrError, " - #%06X%02X", rgb, a); + } + } + } + } + } + } + ffStrbufSubstrBefore(&path, basePathLength); + + if (name.length > 0) { + ffStrbufAppendF(themeOrError, " (%s)", variant); + } + + return true; +} + +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_COSMIC_COMP)) { + return detectCosmicComp(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); + } + + if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_ENLIGHTENMENT)) { + return detectGTKThemeAsWMTheme(themeOrError); + } + + ffStrbufAppendS(themeOrError, "Unknown WM: "); + ffStrbufAppend(themeOrError, &wm->wmPrettyName); + return false; +} diff --git a/src/detection/wmtheme/wmtheme_nosupport.c b/src/detection/wmtheme/wmtheme_nosupport.c new file mode 100644 index 0000000..8cba51a --- /dev/null +++ b/src/detection/wmtheme/wmtheme_nosupport.c @@ -0,0 +1,7 @@ +#include "fastfetch.h" +#include "wmtheme.h" + +bool ffDetectWmTheme(FFstrbuf* themeOrError) { + ffStrbufAppendS(themeOrError, "Not supported on this platform"); + return false; +} diff --git a/src/detection/wmtheme/wmtheme_windows.c b/src/detection/wmtheme/wmtheme_windows.c new file mode 100644 index 0000000..5d16a84 --- /dev/null +++ b/src/detection/wmtheme/wmtheme_windows.c @@ -0,0 +1,182 @@ +#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; +} diff --git a/src/detection/zpool/libzfs_simplified.h b/src/detection/zpool/libzfs_simplified.h new file mode 100644 index 0000000..5faf4ec --- /dev/null +++ b/src/detection/zpool/libzfs_simplified.h @@ -0,0 +1,48 @@ +#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*); diff --git a/src/detection/zpool/zpool.c b/src/detection/zpool/zpool.c new file mode 100644 index 0000000..ce7abb3 --- /dev/null +++ b/src/detection/zpool/zpool.c @@ -0,0 +1,130 @@ +#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 = FF_LIST_ADD(FFZpoolResult, *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"; + } + + FF_A_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_A_UNUSED FFlist* result) { + return "fastfetch was compiled without libzfs support"; +} + +#endif diff --git a/src/detection/zpool/zpool.h b/src/detection/zpool/zpool.h new file mode 100644 index 0000000..2e6523f --- /dev/null +++ b/src/detection/zpool/zpool.h @@ -0,0 +1,17 @@ +#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 */); diff --git a/src/fastfetch.c b/src/fastfetch.c new file mode 100644 index 0000000..dc5c179 --- /dev/null +++ b/src/fastfetch.c @@ -0,0 +1,864 @@ +#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/strutil.h" +#include "common/mallocHelper.h" +#include "fastfetch_datatext.h" + +#include +#include +#include + +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 for the `%s` module. 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:"); + + uint32_t maxWidth = 20; + for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { + uint32_t len = (uint32_t) strlen(baseInfo->formatArgs.args[i].name) + 2; + if (len > maxWidth) { + maxWidth = len; + } + } + + for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { + const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i]; + ffStrbufSetF(&variable, "{%s}", arg->name); + printf("%*s: %s\n", (int) maxWidth, 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) { + ffLogoPrintRemaining(); // `logoLineCacheClear` inside so that ffLogoPrintLine will use `\e[nC` to move the cursor to the right position instead of reprinting the logo + fputs("\e[J", stdout); // Clear from cursor to the end of the screen to prevent artifacts when the new output is shorter than the previous one + fflush(stdout); + ffTimeSleep(instance.state.dynamicInterval); + fputs("\e[H", stdout); // Move cursor to the top left corner to overwrite the previous output + instance.state.keysHeight = 0; // Reset keysHeight so `ffLogoPrintRemaining` will recalculate it + } 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); +} diff --git a/src/fastfetch.h b/src/fastfetch.h new file mode 100644 index 0000000..ac9fa6c --- /dev/null +++ b/src/fastfetch.h @@ -0,0 +1,46 @@ +#pragma once + +#include "fastfetch_config.h" + +#include +#include + +#include "common/arrutil.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 FFLogoLineCacheState { + FFlist lines; + uint32_t nextLine; + uint32_t rightOffset; +} FFLogoLineCacheState; + +typedef struct FFstate { + uint32_t logoWidth; + uint32_t logoHeight; + uint32_t keysHeight; + bool terminalLightTheme; + bool titleFqdn; + uint32_t dynamicInterval; + FFPlatform platform; + FFLogoLineCacheState logoLineCache; +} FFstate; + +typedef struct FFinstance { + FFconfig config; + FFstate state; +} FFinstance; +extern FFinstance instance; // Defined in `common/init.c` +extern FFModuleBaseInfo** ffModuleInfos[]; diff --git a/src/fastfetch_config.h.in b/src/fastfetch_config.h.in new file mode 100644 index 0000000..03b8f57 --- /dev/null +++ b/src/fastfetch_config.h.in @@ -0,0 +1,21 @@ +#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@" diff --git a/src/fastfetch_datatext.h.in b/src/fastfetch_datatext.h.in new file mode 100644 index 0000000..127db62 --- /dev/null +++ b/src/fastfetch_datatext.h.in @@ -0,0 +1,4 @@ +#pragma once + +#define FASTFETCH_DATATEXT_JSON_HELP @DATATEXT_JSON_HELP@ +#define FASTFETCH_DATATEXT_STRUCTURE "@DEFAULT_STRUCTURE@" diff --git a/src/flashfetch.c b/src/flashfetch.c new file mode 100644 index 0000000..e056ca2 --- /dev/null +++ b/src/flashfetch.c @@ -0,0 +1,129 @@ +#include "fastfetch.h" + +#include "common/init.h" +#include "logo/logo.h" +#include "modules/modules.h" + +#define MODULE_OPTION(name) \ + FF_A_CLEANUP(ffDestroy##name##Options) FF##name##Options options; \ + ffInit##name##Options(&options); + +// A dirty replicate of neofetch; demonstration only. +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 + ffLogoPrintDetected(FF_LOGO_SIZE_NORMAL); + + // Print all modules + { + MODULE_OPTION(Title) + ffPrintTitle(&options); + } + { + MODULE_OPTION(Separator) + ffPrintSeparator(&options); + } + { + MODULE_OPTION(OS) + ffPrintOS(&options); + } + { + MODULE_OPTION(Host) + ffPrintHost(&options); + } + { + MODULE_OPTION(Kernel) + ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{release}"); + ffPrintKernel(&options); + } + { + MODULE_OPTION(Uptime) + ffPrintUptime(&options); + } + { + MODULE_OPTION(Packages) + options.combined = true; + ffPrintPackages(&options); + } + { + MODULE_OPTION(Shell) + ffPrintShell(&options); + } + { + MODULE_OPTION(Display) + options.compactType = FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT; + ffStrbufSetStatic(&options.moduleArgs.key, "Resolution"); + ffPrintDisplay(&options); + } + { + MODULE_OPTION(DE) + ffPrintDE(&options); + } + { + instance.config.general.detectVersion = false; + MODULE_OPTION(WM) + options.detectPlugin = true; + ffPrintWM(&options); + instance.config.general.detectVersion = true; + } + { + MODULE_OPTION(WMTheme) + ffPrintWMTheme(&options); + } + { + MODULE_OPTION(Theme) + ffPrintTheme(&options); + } + { + MODULE_OPTION(Icons) + ffPrintIcons(&options); + } + { + MODULE_OPTION(Terminal) + ffPrintTerminal(&options); + } + { + MODULE_OPTION(TerminalFont) + ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{/name}{-}{/}{name}{?size} {size}{?}"); + ffPrintTerminalFont(&options); + } + { + MODULE_OPTION(CPU) + ffPrintCPU(&options); + } + { + MODULE_OPTION(GPU) + ffStrbufSetStatic(&options.moduleArgs.key, "GPU"); + ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{name}"); + ffPrintGPU(&options); + } + { + MODULE_OPTION(Memory) + ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{} / {}"); + ffPrintMemory(&options); + } + { + MODULE_OPTION(Break) + ffPrintBreak(&options); + } + { + MODULE_OPTION(Colors) + ffPrintColors(&options); + } + + ffLogoPrintRemaining(); + ffFinish(); + ffDestroyInstance(); + return 0; +} diff --git a/src/logo/ascii/_/unknown.txt b/src/logo/ascii/_/unknown.txt new file mode 100644 index 0000000..ed7418e --- /dev/null +++ b/src/logo/ascii/_/unknown.txt @@ -0,0 +1,18 @@ + ________ + _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 + "" \ No newline at end of file diff --git a/src/logo/ascii/a.inc b/src/logo/ascii/a.inc new file mode 100644 index 0000000..848cad1 --- /dev/null +++ b/src/logo/ascii/a.inc @@ -0,0 +1,881 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo A[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_ADELIE + // Adélie + { + .names = { "Adélie", "Adelie" }, + .lines = FASTFETCH_DATATEXT_LOGO_ADELIE, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AEROS + // AerOS + { + .names = { "aerOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_AEROS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AEON + // Aeon + { + .names = { "Aeon" }, + .lines = FASTFETCH_DATATEXT_LOGO_AEON, + .colors = { + FF_COLOR_FG_256 "36", + FF_COLOR_FG_256 "36", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AERYNOS + // AerynOS + { + .names = { "AerynOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_AERYNOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AERYNOS_OLD + // AerynOS_old + { + .names = { "AerynOS_old" }, + .lines = FASTFETCH_DATATEXT_LOGO_AERYNOS_OLD, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .colors = { + FF_COLOR_FG_DEFAULT, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AFTERGLOW + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AIX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALMALINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALPINE + // Alpine + { + .names = { "Alpine" }, + .lines = FASTFETCH_DATATEXT_LOGO_ALPINE, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALPINE2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALPINE_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALPINE2_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALPINE3_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALTER + // Alter + { + .names = { "Alter" }, + .lines = FASTFETCH_DATATEXT_LOGO_ALTER, + .colors = { + FF_COLOR_FG_CYAN, + }, + .colorKeys = FF_COLOR_FG_CYAN, + .colorTitle = FF_COLOR_FG_CYAN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ALTLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AMAZON + // Amazon + { + .names = { "Amazon" }, + .lines = FASTFETCH_DATATEXT_LOGO_AMAZON, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AMAZON_LINUX + // AmazonLinux + { + .names = { "Amazon Linux", "amzn" }, + .lines = FASTFETCH_DATATEXT_LOGO_AMAZON_LINUX, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "178", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AMIGA + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AMOGOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANARCHY + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANDROID + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANDROID_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANDUINOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANTERGOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANTIX + // Antix + { + .names = { "antiX" }, + .lines = FASTFETCH_DATATEXT_LOGO_ANTIX, + .colors = { + FF_COLOR_FG_RED, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ANUSHOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AOSCOS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AOSCOS_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_APERTURE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_APRICITY + // Apricity + { + .names = { "Apricity" }, + .lines = FASTFETCH_DATATEXT_LOGO_APRICITY, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCHBOX + // ArchBox + { + .names = { "ArchBox" }, + .lines = FASTFETCH_DATATEXT_LOGO_ARCHBOX, + .colors = { + FF_COLOR_FG_GREEN, + }, + .colorKeys = FF_COLOR_FG_GREEN, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCHCRAFT + // Archcraft + { + .names = { "Archcraft" }, + .lines = FASTFETCH_DATATEXT_LOGO_ARCHCRAFT, + .colors = { + FF_COLOR_FG_CYAN, + }, + .colorKeys = FF_COLOR_FG_CYAN, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCHCRAFT2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCH + // Arch + { + .names = { "arch", "archmerge" }, + .lines = FASTFETCH_DATATEXT_LOGO_ARCH, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCH2 + // Arch2 + { + .names = { "arch2" }, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_ARCH2, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCH3 + // Arch3 + { + .names = { "arch3" }, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_ARCH3, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCH_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCH_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCHLABS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCHSTRIKE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARKANE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARMBIAN + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARMBIAN2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARTIX + // Artix + { + .names = { "artix" }, + .lines = FASTFETCH_DATATEXT_LOGO_ARTIX, + .colors = { + FF_COLOR_FG_CYAN, + }, + .colorKeys = FF_COLOR_FG_CYAN, + .colorTitle = FF_COLOR_FG_CYAN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARTIX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARTIX2_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARCO_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARSELINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ARYA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASAHI + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASAHI2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASTER + // Aster + { + .names = { "aster" }, + .lines = FASTFETCH_DATATEXT_LOGO_ASTER, + .colors = { + FF_COLOR_FG_CYAN, + }, + .colorKeys = FF_COLOR_FG_CYAN, + .colorTitle = FF_COLOR_FG_CYAN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASTEROIDOS + // 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", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASTOS + // AstOS + { + .names = { "astOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_ASTOS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASTRA_LINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_JANUSLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ATHENAOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ATHENAOS_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AURORA + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AXOS + // AxOS + { + .names = { "AxOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_AXOS, + .colors = { + FF_COLOR_FG_RGB "222;6;255", + FF_COLOR_FG_RGB "222;6;255", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_AZOS + // Azos + { + .names = { "Azos" }, + .lines = FASTFETCH_DATATEXT_LOGO_AZOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_RED, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/a/adelie.txt b/src/logo/ascii/a/adelie.txt new file mode 100644 index 0000000..923072f --- /dev/null +++ b/src/logo/ascii/a/adelie.txt @@ -0,0 +1,16 @@ + $3 ,-^-___ +$3 /\\\/// +$2refined.$1 /\\\\// +$2reliable.$1 /\\\/// +$2ready.$1 /\\/////\ + __///\\\\/////\ +$3 _//////\\\\\\\//// +$1 ///////$3\\\\\\\\\\// + //////$1\\\\\/ + /////\\\\\/ + /////$3\\\\/ + /\\///\\\/ + /\\\/$1\\/ + /\\\\// + ////// + /// $3\\\\\ \ No newline at end of file diff --git a/src/logo/ascii/a/aeon.txt b/src/logo/ascii/a/aeon.txt new file mode 100644 index 0000000..902af06 --- /dev/null +++ b/src/logo/ascii/a/aeon.txt @@ -0,0 +1,14 @@ +⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ +⣿⡇ ⢸⣿ +⣿⡇ ⢀⣀ ⣀⡀ ⢸⣿ +⣿⣇ ⠸⣿⣄ ⣠⣿⠇ ⣸⣿ +⢹⣿⡄ ⠙⠻⠿⠿⠟⠋ ⢠⣿⡏ + ⠹⣿⣦⡀ ⢀⣴⣿⠏ + ⠈⠛⢿⣶⣤⣄ ⣠⣤⣶⡿⠛⠁ +$2 ⣠⣴⡿⠿⠛ ⠛⠿⢿⣦⣄ + ⣠⣾⠟⠉ ⠉⠻⣷⣄ +⢰⣿⠏ ⢀⣤⣶⣶⣤⡀ ⠹⣿⡆ +⣿⡟ ⢰⣿⠏⠁⠈⠹⣿⡆ ⢿⣿ +⣿⡇ ⠈⠋ ⠙⠁ ⢸⣿ +⣿⡇ ⢸⣿ +⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿ \ No newline at end of file diff --git a/src/logo/ascii/a/aeros.txt b/src/logo/ascii/a/aeros.txt new file mode 100644 index 0000000..9448c51 --- /dev/null +++ b/src/logo/ascii/a/aeros.txt @@ -0,0 +1,21 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/a/aerynos.txt b/src/logo/ascii/a/aerynos.txt new file mode 100644 index 0000000..fe8b3a7 --- /dev/null +++ b/src/logo/ascii/a/aerynos.txt @@ -0,0 +1,18 @@ + $1:: + $1-++- + $1-++++- + $1-++++++- + $1=+++==+++= + $1=+++= =+++= + $1=+++= =+++= + $1:+++= =+++: + $1-+++- -+++- + $1:+++=. .=+++: + $2.. $1-++++=--=+++++++=: + $2.:::. $1:=++++++++===++++=. + $2.::::. $1.:----:. $1.-++++- + $2.::::. .::. $1.++++- + $2.:::. .:::. $1.=+++- + $2.::::. ..:::: $1.=-. .++++= + $2.::::::::::::::::. $1.+++++++++++++++= +$2'''''''''''''''''' $1''''''''''''''''' \ No newline at end of file diff --git a/src/logo/ascii/a/aerynos_old.txt b/src/logo/ascii/a/aerynos_old.txt new file mode 100644 index 0000000..3078dac --- /dev/null +++ b/src/logo/ascii/a/aerynos_old.txt @@ -0,0 +1,16 @@ + ;llll. + 0MMMMMM: + NMMMMMMMMd + .@ .cccccccccoWMMMMMMMMMM0 + @@ .MMMMMMMMMMMMMMMMMMMMMMMN + OMMMMMMMMMMMMMMMW. +.@ .MMMMMMMMMMMMMMMMMMM. +.@ dMMMMMMMMMMMMMMMMMMl OMMMMMMMMk + .OWMMMMM; dMMMMMMMMMk + .MMMMMMMMMk + @@ ooooooooooooooooooo .MMMMMMMMMN + .@ oooooMMMMMMMMMMMMP NMMMMMMMMW. + KMMMMMMMMMM. 0MMMMMMMMM: + NMMMMMMMMM. dMMMMMMMMMd + .WMMMMMMMW. XMMMMMMMMO + .MMMMMMMk. xMMMMMMMMX \ No newline at end of file diff --git a/src/logo/ascii/a/afterglow.txt b/src/logo/ascii/a/afterglow.txt new file mode 100644 index 0000000..6b8bbed --- /dev/null +++ b/src/logo/ascii/a/afterglow.txt @@ -0,0 +1,15 @@ + $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@@" \ No newline at end of file diff --git a/src/logo/ascii/a/aix.txt b/src/logo/ascii/a/aix.txt new file mode 100644 index 0000000..42b3f43 --- /dev/null +++ b/src/logo/ascii/a/aix.txt @@ -0,0 +1,20 @@ + `:+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/- \ No newline at end of file diff --git a/src/logo/ascii/a/almalinux.txt b/src/logo/ascii/a/almalinux.txt new file mode 100644 index 0000000..6a1000f --- /dev/null +++ b/src/logo/ascii/a/almalinux.txt @@ -0,0 +1,20 @@ +$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; \ No newline at end of file diff --git a/src/logo/ascii/a/alpine.txt b/src/logo/ascii/a/alpine.txt new file mode 100644 index 0000000..3379ff4 --- /dev/null +++ b/src/logo/ascii/a/alpine.txt @@ -0,0 +1,20 @@ + .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. \ No newline at end of file diff --git a/src/logo/ascii/a/alpine2.txt b/src/logo/ascii/a/alpine2.txt new file mode 100644 index 0000000..4d79ae3 --- /dev/null +++ b/src/logo/ascii/a/alpine2.txt @@ -0,0 +1,16 @@ + .:::::::::::::::::::::. + .:::::::::::::::::::::::. + .:::::::::::::::::::::::::. + .:::::::::::::::::::::::::::. + .:::::::::$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::' + ':::::::::::::::::::::::::::::::' + ':::::::::::::::::::::::::::::' + ':::::::::::::::::::::::::::' + ':::::::::::::::::::::::::' + ':::::::::::::::::::::::' + ':::::::::::::::::::::' diff --git a/src/logo/ascii/a/alpine2_small.txt b/src/logo/ascii/a/alpine2_small.txt new file mode 100644 index 0000000..65be837 --- /dev/null +++ b/src/logo/ascii/a/alpine2_small.txt @@ -0,0 +1,6 @@ +$1 /\ /\ + /$2/ $1\ \ + /$2// $1\ \ +/$2// $1\ \ +$2// $1\ \ + \ \ No newline at end of file diff --git a/src/logo/ascii/a/alpine3_small.txt b/src/logo/ascii/a/alpine3_small.txt new file mode 100644 index 0000000..38a4476 --- /dev/null +++ b/src/logo/ascii/a/alpine3_small.txt @@ -0,0 +1,6 @@ + ,db, + ,d%%%%b, ,db, + ,%%%%P'%%%b,d%%%b, + ,%%%%P, `%%%b'^q%%b, + ,%%%%P,d| `%%%b '^%%b, +`%%%' 'q$| 'q%%b'`q%%b diff --git a/src/logo/ascii/a/alpine_small.txt b/src/logo/ascii/a/alpine_small.txt new file mode 100644 index 0000000..a0cb082 --- /dev/null +++ b/src/logo/ascii/a/alpine_small.txt @@ -0,0 +1,6 @@ + /\ /\ + // \ \ + // \ \ +/// \ \ +// \ \ + \ \ No newline at end of file diff --git a/src/logo/ascii/a/alter.txt b/src/logo/ascii/a/alter.txt new file mode 100644 index 0000000..c5727e9 --- /dev/null +++ b/src/logo/ascii/a/alter.txt @@ -0,0 +1,20 @@ + %, + ^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, \ No newline at end of file diff --git a/src/logo/ascii/a/altlinux.txt b/src/logo/ascii/a/altlinux.txt new file mode 100644 index 0000000..1ad2ba6 --- /dev/null +++ b/src/logo/ascii/a/altlinux.txt @@ -0,0 +1,22 @@ + ############## + ###################### + ########################## + ##+$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###### \ No newline at end of file diff --git a/src/logo/ascii/a/amazon.txt b/src/logo/ascii/a/amazon.txt new file mode 100644 index 0000000..27ff31a --- /dev/null +++ b/src/logo/ascii/a/amazon.txt @@ -0,0 +1,19 @@ + `-/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+:` \ No newline at end of file diff --git a/src/logo/ascii/a/amazon_linux.txt b/src/logo/ascii/a/amazon_linux.txt new file mode 100644 index 0000000..04394bc --- /dev/null +++ b/src/logo/ascii/a/amazon_linux.txt @@ -0,0 +1,10 @@ + , $2#_$1 + ~\_ $2####_$1 + ~~ \_$2#####\$1 + ~~ \$2###|$1 + ~~ \$2#/$1 ___ + ~~ V~' '-> + ~~~ / + ~~._. _/ + _/ _/ + _/m/' \ No newline at end of file diff --git a/src/logo/ascii/a/amiga.txt b/src/logo/ascii/a/amiga.txt new file mode 100644 index 0000000..ba4350d --- /dev/null +++ b/src/logo/ascii/a/amiga.txt @@ -0,0 +1,13 @@ + ----.--- + :-==.-==- +$2 -=== ===: + ===-:===. +$3 -+++:=++= + =+++ +++. +$6 .+++-=++= +$4.::. ::: $6 :**+ +**- +$4 :--:.---. $6 .+**=-***. +$5 :--- ---::$6***:***+ +$5 .---::-$6=##*.*##- +$7 ----+$6##=:##*. +$7 :-$6###-*##+ diff --git a/src/logo/ascii/a/amogos.txt b/src/logo/ascii/a/amogos.txt new file mode 100644 index 0000000..8f79c3a --- /dev/null +++ b/src/logo/ascii/a/amogos.txt @@ -0,0 +1,19 @@ + ___________ + / \ + / $2______$1 \ + / $2/ \$1 \ + | $2( )$1 \ + / $2\______/$1 | + | | + / \ + | | + | | + / | + | | + | _______ | + ____/ / \ | +/ | | | +| / ____/ | +\_________/ / | + \ __/ + \_______/ \ No newline at end of file diff --git a/src/logo/ascii/a/anarchy.txt b/src/logo/ascii/a/anarchy.txt new file mode 100644 index 0000000..75456bd --- /dev/null +++ b/src/logo/ascii/a/anarchy.txt @@ -0,0 +1,28 @@ + $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 \ No newline at end of file diff --git a/src/logo/ascii/a/android.txt b/src/logo/ascii/a/android.txt new file mode 100644 index 0000000..dba9163 --- /dev/null +++ b/src/logo/ascii/a/android.txt @@ -0,0 +1,18 @@ + -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 \ No newline at end of file diff --git a/src/logo/ascii/a/android_small.txt b/src/logo/ascii/a/android_small.txt new file mode 100644 index 0000000..0d7df96 --- /dev/null +++ b/src/logo/ascii/a/android_small.txt @@ -0,0 +1,6 @@ + ;, ,; + ';,.-----.,;' + ,' ', + / O O \ +| | +'-----------------' \ No newline at end of file diff --git a/src/logo/ascii/a/anduinos.txt b/src/logo/ascii/a/anduinos.txt new file mode 100644 index 0000000..d57e6f9 --- /dev/null +++ b/src/logo/ascii/a/anduinos.txt @@ -0,0 +1,19 @@ + $1+++++++++++ + +++++++++++++++++++++ + +++++++++++++++++++++++++++++++ +=+++++++++++++++++++++++++++++++++++++++= ++++++++++++++++++++++++++++++++++++++++++++++ +=++++++++++++++++++++++++++++++++++++++++++++ + ==+++++++++++++++++++++++++++++++++== + +++++++++++++++++++++++++++ +$2**** $1++=+++++++++++=++ $2**** +********** $1+++++++ $2********** +************** ************** + **************** **************** + *********************** +****** ************* ****** +*********** *** *********** +*************** *************** + *************** *************** + ********************* + *********** diff --git a/src/logo/ascii/a/antergos.txt b/src/logo/ascii/a/antergos.txt new file mode 100644 index 0000000..f7b0eba --- /dev/null +++ b/src/logo/ascii/a/antergos.txt @@ -0,0 +1,19 @@ +$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+:.` \ No newline at end of file diff --git a/src/logo/ascii/a/antix.txt b/src/logo/ascii/a/antix.txt new file mode 100644 index 0000000..85b56cc --- /dev/null +++ b/src/logo/ascii/a/antix.txt @@ -0,0 +1,12 @@ + \ + , - ~ ^ ~ - \ / + , ' \ ' , / + , \ '/ + , \ / , + ,___, \/ , + / | _ _ _|_ o /\ , +|, | / |/ | | | / \ , + \,_/\_/ | |_/|_/|_/_/ \, + , / ,\ + , / , ' \ + ' - , _ _ _ , ' \ No newline at end of file diff --git a/src/logo/ascii/a/anushos.txt b/src/logo/ascii/a/anushos.txt new file mode 100644 index 0000000..bf831b7 --- /dev/null +++ b/src/logo/ascii/a/anushos.txt @@ -0,0 +1,20 @@ + $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####################### diff --git a/src/logo/ascii/a/aoscos.txt b/src/logo/ascii/a/aoscos.txt new file mode 100644 index 0000000..3df0782 --- /dev/null +++ b/src/logo/ascii/a/aoscos.txt @@ -0,0 +1,17 @@ + $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`""""""" \ No newline at end of file diff --git a/src/logo/ascii/a/aoscos_old.txt b/src/logo/ascii/a/aoscos_old.txt new file mode 100644 index 0000000..16f31c2 --- /dev/null +++ b/src/logo/ascii/a/aoscos_old.txt @@ -0,0 +1,20 @@ + .:+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+:. \ No newline at end of file diff --git a/src/logo/ascii/a/aoscosretro.txt b/src/logo/ascii/a/aoscosretro.txt new file mode 100644 index 0000000..88e0ea8 --- /dev/null +++ b/src/logo/ascii/a/aoscosretro.txt @@ -0,0 +1,18 @@ +$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################ \ No newline at end of file diff --git a/src/logo/ascii/a/aoscosretro_small.txt b/src/logo/ascii/a/aoscosretro_small.txt new file mode 100644 index 0000000..cf8ec45 --- /dev/null +++ b/src/logo/ascii/a/aoscosretro_small.txt @@ -0,0 +1,9 @@ +$2 _____ $1_____$2 + -' '-$1| |$2 + / ___ $1| |$2 +| / _ \$1|_____|$2 +' / /_\ \ + \ / _____ \$4___ + $3|$2/_/ $3| $4| | + $3| | $4|___| + $3|_____| \ No newline at end of file diff --git a/src/logo/ascii/a/aperture.txt b/src/logo/ascii/a/aperture.txt new file mode 100644 index 0000000..59a9a77 --- /dev/null +++ b/src/logo/ascii/a/aperture.txt @@ -0,0 +1,20 @@ + .,-:;//;:=, + $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=++%%%%+/:-. \ No newline at end of file diff --git a/src/logo/ascii/a/apricity.txt b/src/logo/ascii/a/apricity.txt new file mode 100644 index 0000000..ec991e9 --- /dev/null +++ b/src/logo/ascii/a/apricity.txt @@ -0,0 +1,18 @@ + ./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+/-` \ No newline at end of file diff --git a/src/logo/ascii/a/arch.txt b/src/logo/ascii/a/arch.txt new file mode 100644 index 0000000..75338f0 --- /dev/null +++ b/src/logo/ascii/a/arch.txt @@ -0,0 +1,19 @@ + -` + .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: +`++:. `-/+/ +.` `/ \ No newline at end of file diff --git a/src/logo/ascii/a/arch2.txt b/src/logo/ascii/a/arch2.txt new file mode 100644 index 0000000..138883b --- /dev/null +++ b/src/logo/ascii/a/arch2.txt @@ -0,0 +1,19 @@ + ▄ + ▟█▙ + ▟███▙ + ▟█████▙ + ▟███████▙ + ▂▔▀▜██████▙ + ▟██▅▂▝▜█████▙ + ▟█████████████▙ + ▟███████████████▙ + ▟█████████████████▙ + ▟███████████████████▙ +$2 ▟█████████▛▀▀▜████████▙ + ▟████████▛ ▜███████▙ + ▟█████████ ████████▙ + ▟██████████ █████▆▅▄▃▂ + ▟██████████▛ ▜█████████▙ + ▟██████▀▀▀ ▀▀██████▙ + ▟███▀▘ ▝▀███▙ +▟▛▀ ▀▜▙ \ No newline at end of file diff --git a/src/logo/ascii/a/arch3.txt b/src/logo/ascii/a/arch3.txt new file mode 100644 index 0000000..bf76557 --- /dev/null +++ b/src/logo/ascii/a/arch3.txt @@ -0,0 +1,19 @@ + . + / \ + / \ + / \ + / \ + />, \ + / `*. \ + / ` \ + / \ + / \ +$2 / ,.-+-.. \ + / ,/' `\. \ + / .|' `|. _ \ + / :|. ,|; `+.\ + / .\: ;/, "<\ + / __,--+" "+--.__ \ + / _,+'" "'+._ \ + /,-' `-.\ +' ' \ No newline at end of file diff --git a/src/logo/ascii/a/arch_old.txt b/src/logo/ascii/a/arch_old.txt new file mode 100644 index 0000000..a0391b7 --- /dev/null +++ b/src/logo/ascii/a/arch_old.txt @@ -0,0 +1,16 @@ + __ + _=(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' '! \ No newline at end of file diff --git a/src/logo/ascii/a/arch_small.txt b/src/logo/ascii/a/arch_small.txt new file mode 100644 index 0000000..e1957f7 --- /dev/null +++ b/src/logo/ascii/a/arch_small.txt @@ -0,0 +1,7 @@ + /\ + / \ + / \ + / \ +$2 / ,, \ + / | | \ +/_-'' ''-_\ \ No newline at end of file diff --git a/src/logo/ascii/a/archbox.txt b/src/logo/ascii/a/archbox.txt new file mode 100644 index 0000000..e295ec9 --- /dev/null +++ b/src/logo/ascii/a/archbox.txt @@ -0,0 +1,19 @@ + ...:+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/-` \ No newline at end of file diff --git a/src/logo/ascii/a/archcraft.txt b/src/logo/ascii/a/archcraft.txt new file mode 100644 index 0000000..1cebab9 --- /dev/null +++ b/src/logo/ascii/a/archcraft.txt @@ -0,0 +1,20 @@ +$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⠉⠙⠛⠿⢆ \ No newline at end of file diff --git a/src/logo/ascii/a/archcraft2.txt b/src/logo/ascii/a/archcraft2.txt new file mode 100644 index 0000000..28ec4cd --- /dev/null +++ b/src/logo/ascii/a/archcraft2.txt @@ -0,0 +1,20 @@ + -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: +:=-:` `-=+: \ No newline at end of file diff --git a/src/logo/ascii/a/archlabs.txt b/src/logo/ascii/a/archlabs.txt new file mode 100644 index 0000000..cedb10d --- /dev/null +++ b/src/logo/ascii/a/archlabs.txt @@ -0,0 +1,21 @@ + '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; + .,'. .',. \ No newline at end of file diff --git a/src/logo/ascii/a/archstrike.txt b/src/logo/ascii/a/archstrike.txt new file mode 100644 index 0000000..7fc46d2 --- /dev/null +++ b/src/logo/ascii/a/archstrike.txt @@ -0,0 +1,17 @@ +$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******, \ No newline at end of file diff --git a/src/logo/ascii/a/arco.txt b/src/logo/ascii/a/arco.txt new file mode 100644 index 0000000..d24c3ca --- /dev/null +++ b/src/logo/ascii/a/arco.txt @@ -0,0 +1,20 @@ + /- + 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 \ No newline at end of file diff --git a/src/logo/ascii/a/arco_small.txt b/src/logo/ascii/a/arco_small.txt new file mode 100644 index 0000000..66fdc5a --- /dev/null +++ b/src/logo/ascii/a/arco_small.txt @@ -0,0 +1,11 @@ + A + ooo + ooooo + ooooooo + ooooooooo + ooooo ooooo + ooooo ooooo + ooooo ooooo + ooooo $2$1 + ooooo $2$1 +ooooo $2 \ No newline at end of file diff --git a/src/logo/ascii/a/arkane.txt b/src/logo/ascii/a/arkane.txt new file mode 100644 index 0000000..57f50de --- /dev/null +++ b/src/logo/ascii/a/arkane.txt @@ -0,0 +1,23 @@ + .:.. + ..:::...... + $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: \ No newline at end of file diff --git a/src/logo/ascii/a/armbian.txt b/src/logo/ascii/a/armbian.txt new file mode 100644 index 0000000..d5e1374 --- /dev/null +++ b/src/logo/ascii/a/armbian.txt @@ -0,0 +1,22 @@ + .. + `:]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",,," ",,,^ \ No newline at end of file diff --git a/src/logo/ascii/a/armbian2.txt b/src/logo/ascii/a/armbian2.txt new file mode 100644 index 0000000..7307cf2 --- /dev/null +++ b/src/logo/ascii/a/armbian2.txt @@ -0,0 +1,14 @@ + █ █ █ █ █ █ █ █ █ █ █ + ███████████████████████ +▄▄██ ██▄▄ +▄▄██ ███████████ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ █████████████ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ ██ ██ ██▄▄ +▄▄██ ██▄▄ + ███████████████████████ + █ █ █ █ █ █ █ █ █ █ █ \ No newline at end of file diff --git a/src/logo/ascii/a/arselinux.txt b/src/logo/ascii/a/arselinux.txt new file mode 100644 index 0000000..303f9b8 --- /dev/null +++ b/src/logo/ascii/a/arselinux.txt @@ -0,0 +1,15 @@ + ⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⣴⣶⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣄⠀⠀⠀⠀⣼⠟⠁⠀⠀⢀⣀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⢀⣤⡀⠀⠀⠀⠉⢻⣷⡄⠀⠀⠁⠀⢀⣤⣾⡿⠟⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣷⣿⠏⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠙⠉⠁⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠠⣶⣶⣶⡶ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠂⠀⠀⠀⠀⠀⢀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠇⠀⠀⠀⠀⠀⣠⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣇⣀⣀⣀⣠⣴⣾⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢸⣿⠀⠀⠀⠀⠀⣤⣤⣴⣶⣾⠿⠟⣿⡏⠙⠛⠛⠛⠋⠉⢀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⣿⡄⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⢸⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠇⠀⠀⠀⠀⠀⠀⠘⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ \ No newline at end of file diff --git a/src/logo/ascii/a/artix.txt b/src/logo/ascii/a/artix.txt new file mode 100644 index 0000000..a1e26a8 --- /dev/null +++ b/src/logo/ascii/a/artix.txt @@ -0,0 +1,20 @@ + ' + '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' +'` `' \ No newline at end of file diff --git a/src/logo/ascii/a/artix2.txt b/src/logo/ascii/a/artix2.txt new file mode 100644 index 0000000..0e87beb --- /dev/null +++ b/src/logo/ascii/a/artix2.txt @@ -0,0 +1,20 @@ + .: + .==: + .====: + .======: + .========: + .==========: + .============: + .-===========: + .:-========: + .-:. .:======: + .======:.. :-===. + .============:.. .:=. + .==================:.. + .=======================: + .====================::. . + ================::. :-=== + ============-:. .:======== + ========-:. :-========== + =====:. ..:-===== + =::. ..:: diff --git a/src/logo/ascii/a/artix2_small.txt b/src/logo/ascii/a/artix2_small.txt new file mode 100644 index 0000000..9f9052f --- /dev/null +++ b/src/logo/ascii/a/artix2_small.txt @@ -0,0 +1,7 @@ + /\ + / \ + /`'.,\ + / ', + / ,`\ + / ,.'`. \ +/.,'` `'.\ diff --git a/src/logo/ascii/a/artix_small.txt b/src/logo/ascii/a/artix_small.txt new file mode 100644 index 0000000..14e7083 --- /dev/null +++ b/src/logo/ascii/a/artix_small.txt @@ -0,0 +1,13 @@ + ' + 'A' + 'ooo' + 'ookxo' + `ookxxo' + '. `ooko' + 'ooo`. `oo' + 'ooxxxoo`. `' + 'ookxxxkooo.` . + 'ookxxkoo'` .'oo' + 'ooxoo'` .:ooxxo' + 'io'` `'oo' +'` `' diff --git a/src/logo/ascii/a/arya.txt b/src/logo/ascii/a/arya.txt new file mode 100644 index 0000000..a6758e6 --- /dev/null +++ b/src/logo/ascii/a/arya.txt @@ -0,0 +1,15 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/a/asahi.txt b/src/logo/ascii/a/asahi.txt new file mode 100644 index 0000000..90de319 --- /dev/null +++ b/src/logo/ascii/a/asahi.txt @@ -0,0 +1,18 @@ + ## $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&, \ No newline at end of file diff --git a/src/logo/ascii/a/asahi2.txt b/src/logo/ascii/a/asahi2.txt new file mode 100644 index 0000000..9cec063 --- /dev/null +++ b/src/logo/ascii/a/asahi2.txt @@ -0,0 +1,18 @@ + $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 \ No newline at end of file diff --git a/src/logo/ascii/a/aster.txt b/src/logo/ascii/a/aster.txt new file mode 100644 index 0000000..f5c2a1a --- /dev/null +++ b/src/logo/ascii/a/aster.txt @@ -0,0 +1,18 @@ + ...''... + .;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: + ,. \ No newline at end of file diff --git a/src/logo/ascii/a/asteroidos.txt b/src/logo/ascii/a/asteroidos.txt new file mode 100644 index 0000000..d7dfca6 --- /dev/null +++ b/src/logo/ascii/a/asteroidos.txt @@ -0,0 +1,17 @@ +$1 *** +$1 ***** +$1 ********** +$1 *************** +$1 *///****////****////. +$2 (/////// /////// ///////( +$2 /(((((//* //, //((((((. +$2 ((((((((((( ((( (((((((( +$2 *((((((((((((((((((((((( (((((((( +$3 (((((#(((((((#((((( ((#((((( +$3 (#(#(#####(#(#, ####(#(# +$3 ######### ######## +$3 /######## ######## +$4 #######%####### +$4 (#%%%%%%%# +$4 %%%%% +$4 %%% \ No newline at end of file diff --git a/src/logo/ascii/a/astos.txt b/src/logo/ascii/a/astos.txt new file mode 100644 index 0000000..f37157b --- /dev/null +++ b/src/logo/ascii/a/astos.txt @@ -0,0 +1,23 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/a/astra_linux.txt b/src/logo/ascii/a/astra_linux.txt new file mode 100644 index 0000000..f35a66d --- /dev/null +++ b/src/logo/ascii/a/astra_linux.txt @@ -0,0 +1,22 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/a/athenaos.txt b/src/logo/ascii/a/athenaos.txt new file mode 100644 index 0000000..383b5ab --- /dev/null +++ b/src/logo/ascii/a/athenaos.txt @@ -0,0 +1,29 @@ + 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:'' + :' \ No newline at end of file diff --git a/src/logo/ascii/a/athenaos_old.txt b/src/logo/ascii/a/athenaos_old.txt new file mode 100644 index 0000000..d26fa04 --- /dev/null +++ b/src/logo/ascii/a/athenaos_old.txt @@ -0,0 +1,24 @@ + . .. +$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 \ No newline at end of file diff --git a/src/logo/ascii/a/aurora.txt b/src/logo/ascii/a/aurora.txt new file mode 100644 index 0000000..fe04fdc --- /dev/null +++ b/src/logo/ascii/a/aurora.txt @@ -0,0 +1,28 @@ + +++++++++ + +++++$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++ + +++++ \ No newline at end of file diff --git a/src/logo/ascii/a/axos.txt b/src/logo/ascii/a/axos.txt new file mode 100644 index 0000000..4cc4f83 --- /dev/null +++ b/src/logo/ascii/a/axos.txt @@ -0,0 +1,16 @@ + ▂🬭𜴧⎻𜷗𜴗𜴦𜵎⎻𜴧🬭▂ + ▁▂𜴧━𜵼𜵶𜶮▂🬭🬭𜴐🭗 🬁𜴜🬭🬭▂𜶮▁𜵁━𜴧▂▁ + 𜷓𜵐🬂🬂🮂𜶮𜴸𜴪━𜴆🮂▔ ▔🮂𜴆━𜴩𜵳𜴸🮂🬂🬂𜶚🬿 + 🭄🬨▍ 🬜🮂▔ ▁▂▂🬭🬭▂▂▁ ▔🮂🬪 🮈🬕𜶿 + 🭄🭙▐▎𜵫 🭃██████████🭎 🭢𜶡🮇▌🭤𜶿 + 🭄🭙 ▐🭄🭙 🭋█████🭜𜴦█████🭀 🭤🭏▌ 🭤🭏 + 𜵫🭗 🮉🭛 🭃███🭝🭙 🭥🭒███🭎 🭦▋ 🭢𜶡 +▟🬮𜴧━🭷🭘 🭋████▄▂ ▂▄████🭀 🭣𜴇━𜴧🬯🭏 +🭕𜴸𜴆╾🭺🬽 🭃██🭡 🭖██🭐 🭈𜵡╼𜴆🬡🭠 + 𜴤🬼 🮉🭀 🭋███΄ 🭤███🭀 🭋▊ 🭇𜵐 + 𜴠🬾 ▐𜴢🬾▁▁ 🭃██𜴍 𜶘██🭐 ▁▁🭉🭫▌ 🭉🭠 + 𜴠🬾🮈▎𜶚 ▔🮂🬂▀🬎🬎 🮅🬎▀🬂🮂▔ 𜵐🮇▌🭉𜴏 + 𜴢🭿▍ 𜴬▂▁ ▁▂🬜΄🮈🬲𜴏 + 🭥𜶡🬭🬭▂𜶭▁𜵁━𜴧▂▁ ▁▂𜴧━𜶷𜶭𜶮▂🬭🬭𜵫🭚 + ▔⎺𜴆━𜴩▔𜶮🮂🬂🬂𜴜🬼 🭇𜴐🬂🬂🮂𜶮▔𜴨━𜴆⎺▔ + ⠈🮂🭷𜴆╾𜶦𜷞🭄𜴬╼𜴆🬂🮂΄ \ No newline at end of file diff --git a/src/logo/ascii/a/azos.txt b/src/logo/ascii/a/azos.txt new file mode 100644 index 0000000..e341d3f --- /dev/null +++ b/src/logo/ascii/a/azos.txt @@ -0,0 +1,17 @@ +$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 ((((( \ No newline at end of file diff --git a/src/logo/ascii/b.inc b/src/logo/ascii/b.inc new file mode 100644 index 0000000..2844655 --- /dev/null +++ b/src/logo/ascii/b.inc @@ -0,0 +1,218 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo B[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_BEDROCK + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BEDROCK_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BERSERKARCH + // BerserkArch + { + .names = { "BerserkArch" }, + .lines = FASTFETCH_DATATEXT_LOGO_BERSERKARCH, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BIGLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BITRIG + // Bitrig + { + .names = { "Bitrig" }, + .lines = FASTFETCH_DATATEXT_LOGO_BITRIG, + .colors = { + FF_COLOR_FG_GREEN, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_GREEN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLACKARCH + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLACKMESA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLACKPANTHER + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLAG + // BLAG + { + .names = { "BLAG" }, + .lines = FASTFETCH_DATATEXT_LOGO_BLAG, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLANKON + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BLUELIGHT + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BODHI + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BONSAI + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BREDOS + // 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", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BSD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_BUNSENLABS + // BunsenLabs + { + .names = { "BunsenLabs" }, + .lines = FASTFETCH_DATATEXT_LOGO_BUNSENLABS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/b/bedrock.txt b/src/logo/ascii/b/bedrock.txt new file mode 100644 index 0000000..6bbd44a --- /dev/null +++ b/src/logo/ascii/b/bedrock.txt @@ -0,0 +1,17 @@ +-------------------------------------- +-------------------------------------- +-------------------------------------- +---$2\\\\\\\\\\\\$1----------------------- +----$2\\\ \\\$1---------------------- +-----$2\\\ \\\$1--------------------- +------$2\\\ \\\\\\\\\\\\\\\\\$1------ +-------$2\\\ \\\$1----- +--------$2\\\ \\\$1---- +---------$2\\\ ______ \\\$1--- +----------$2\\\ ///$1--- +-----------$2\\\ ///$1---- +------------$2\\\ ///$1----- +-------------$2\\\////////////////$1------ +-------------------------------------- +-------------------------------------- +-------------------------------------- \ No newline at end of file diff --git a/src/logo/ascii/b/bedrock_small.txt b/src/logo/ascii/b/bedrock_small.txt new file mode 100644 index 0000000..59d7de1 --- /dev/null +++ b/src/logo/ascii/b/bedrock_small.txt @@ -0,0 +1,6 @@ + _________ +| $2__ $1 | +| $2\ \___ $1 | +| $2 \ _ \$1 | +| $2 \___/$1 | +|_________| diff --git a/src/logo/ascii/b/berserkarch.txt b/src/logo/ascii/b/berserkarch.txt new file mode 100644 index 0000000..90128af --- /dev/null +++ b/src/logo/ascii/b/berserkarch.txt @@ -0,0 +1,21 @@ + .~?5P^ + :&@#Y~..!^ :^ + 7@&: ^JPP!!55#!~YB#7. + ^: !&@&.?&GJ5G##G&@@@@@@J:. + JP. @@#?#@&#@&5&@@@@@@@@@@G!: + ^PP: :#@&@@@@@@&#G&B#&@@@@@@@@&5^ + ~J Y@@@@&&@@@@@@#~Y@@@@@@@@@@@@@&Y. +:G .@@@PJ&@&&@@@&BP#@@@@@@@@@@@@@@??~ +5: .&@BG&@7J@@@!. .:!J&@@@@@@@@@@~ .. +? . :B@@@&BG@@@@!. .&@@@@@@@@@@~ +~ ?&&@&@#Y~^^: !#@@@@@@@@@@B. +. J&7:&@@^ ^@@@@@@@@@@&!G. + .&G~. J@@@@@@@@@@@# ! + . . ^YG@@@@@@@@@@@@!? + P. : ~#B&@@@@@@@@@@@B#@@. + Y@: ?&B@@@@@@@@@@@@&G?~P@@B + .?#5. .Y&@@@@@@@@@@&####BGB@@@@Y + !#G^ Y@@@@@@@@@@BP#&@@@@@@@@&!B^ + B@@#Y5##&&&@@@@@#&@@@@@@@@@#! ^ + ^YG#@@@@@@@@@@@@@@@@@@@@#J: + .~#@@@@@@@@@&&BP?^ \ No newline at end of file diff --git a/src/logo/ascii/b/biglinux.txt b/src/logo/ascii/b/biglinux.txt new file mode 100644 index 0000000..b61a3a6 --- /dev/null +++ b/src/logo/ascii/b/biglinux.txt @@ -0,0 +1,19 @@ + ... + :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 \ No newline at end of file diff --git a/src/logo/ascii/b/bitrig.txt b/src/logo/ascii/b/bitrig.txt new file mode 100644 index 0000000..ef26184 --- /dev/null +++ b/src/logo/ascii/b/bitrig.txt @@ -0,0 +1,17 @@ + `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 \ No newline at end of file diff --git a/src/logo/ascii/b/blackarch.txt b/src/logo/ascii/b/blackarch.txt new file mode 100644 index 0000000..440d5f8 --- /dev/null +++ b/src/logo/ascii/b/blackarch.txt @@ -0,0 +1,21 @@ +$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 `/ \ No newline at end of file diff --git a/src/logo/ascii/b/blackmesa.txt b/src/logo/ascii/b/blackmesa.txt new file mode 100644 index 0000000..c25a44d --- /dev/null +++ b/src/logo/ascii/b/blackmesa.txt @@ -0,0 +1,20 @@ + .-;+$$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$$+;-. \ No newline at end of file diff --git a/src/logo/ascii/b/blackpanther.txt b/src/logo/ascii/b/blackpanther.txt new file mode 100644 index 0000000..a96f7e6 --- /dev/null +++ b/src/logo/ascii/b/blackpanther.txt @@ -0,0 +1,23 @@ +$3 ........ + .,»╔╗╗╬▄▄╫█▀▓▄▄╬╗╗g≈,. + ,j╗╬╣▓▓███████▌;»╙▀▀▀▀█▄▄╗j, + .≈╗╬▓██▀▀▀▀▀╠╙░░»»;:`$2``>$1▄ $3▐ ▓╫╗⌂, + .j╬▓█▀▒░░░░░░░░░»»»;:```` ╙▀█▌╬░, + ;╗▓█▄▄███████▀░░»»»»;```` ╓▄▄█▄▄φ ██▌Ñ>. + .j╣█████▀▀░░░░░░░░»»╓▄▄¿``▄███████/▄████▓╬U. + .j╣▓██▀ÜÑ╦╦░░░░░░▐█@▄████⌐▐███████████████▓╬H. + «╫▓█▀░ÑÑ╩╦░░░░░░░░▀██████M"▀███████████████▓╫░ + :]╣█▌ÑÑÑÑ▄▄██▀░░░░»»██████████████████████████Ñ~ + »╫▓█╫ÑÑ▄███▀░░░░░»»▐██████████████████████████▌░ + `j╣█▌Ñ╬████░░░░░░░»»▐████████████████████████▌▐█U` + `/╫█▌▄███▌░░░░░░░»»»;▀██████████████▀████████w▐█░` + ;╟█▌███▌░░░░░░░▄▄»»;:`▀▀████████▀Ü▄████████▌ ▐▌>` + `]▓████░░░░░░░░██⌂;:````╓▄▄µp╓▄▄██████████▀ ,█M` + "╠╣██▌░░░░░░░»██▌;```` ╙▀██████████████M █▀" + "╟╣█░░░░░░░░»███⌂``` ▐▀████████▀░ █▌░` + "╩█▄░░░░░░»»▀███ `` └└` ,█▀"` + `░▀█▄░░░»»»»████@ .▄█Ü` + `╙▀█▄@»»»;`▀███▌¿ ,▄▀Ñ"` + `"╨▀█▄▄▄░`▐█████▄, ,▄▄▀▀░` + `"╙╩▀▀▀▀████████▓▌▌▌▀▀▀╨"`` + ``""░╚╨╝╝╝╝╨╨░""`` \ No newline at end of file diff --git a/src/logo/ascii/b/blag.txt b/src/logo/ascii/b/blag.txt new file mode 100644 index 0000000..f8eafb1 --- /dev/null +++ b/src/logo/ascii/b/blag.txt @@ -0,0 +1,17 @@ + d + ,MK: + xMMMX: + .NMMMMMX; + lMMMMMMMM0clodkO0KXWW: + KMMMMMMMMMMMMMMMMMMX' + .;d0NMMMMMMMMMMMMMMMMMMK. + .;dONMMMMMMMMMMMMMMMMMMMMMMx +'dKMMMMMMMMMMMMMMMMMMMMMMMMl + .:xKWMMMMMMMMMMMMMMMMMMM0. + .:xNMMMMMMMMMMMMMMMMMK. + lMMMMMMMMMMMMMMMMMMK. + ,MMMMMMMMWkOXWMMMMMM0 + .NMMMMMNd. `':ldko + OMMMK: + oWk, + ;: \ No newline at end of file diff --git a/src/logo/ascii/b/blankon.txt b/src/logo/ascii/b/blankon.txt new file mode 100644 index 0000000..a7862e2 --- /dev/null +++ b/src/logo/ascii/b/blankon.txt @@ -0,0 +1,17 @@ +$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+. \ No newline at end of file diff --git a/src/logo/ascii/b/bluelight.txt b/src/logo/ascii/b/bluelight.txt new file mode 100644 index 0000000..946bd12 --- /dev/null +++ b/src/logo/ascii/b/bluelight.txt @@ -0,0 +1,19 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/b/bodhi.txt b/src/logo/ascii/b/bodhi.txt new file mode 100644 index 0000000..77b6c9d --- /dev/null +++ b/src/logo/ascii/b/bodhi.txt @@ -0,0 +1,18 @@ +$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, \ No newline at end of file diff --git a/src/logo/ascii/b/bonsai.txt b/src/logo/ascii/b/bonsai.txt new file mode 100644 index 0000000..994b200 --- /dev/null +++ b/src/logo/ascii/b/bonsai.txt @@ -0,0 +1,16 @@ +$2 ,####, + $2#######, $2,#####, + $2#####',# $2'###### + $2''###'$3';,,,'$2###' + $3 ,; '''' + $3 ;;; $2,#####, + $3 ;;;' ,,;$2;;### + $3 ';;;;''$2'####' + $3 ;;; + $3 ,.;;';'',,, + $3 ' ' +$1 # + # O + ##, ,##,',##, ,## ,#, , + # # # # #''# #,, # # # + '#' '##' # # ,,# '##;, # \ No newline at end of file diff --git a/src/logo/ascii/b/bredos.txt b/src/logo/ascii/b/bredos.txt new file mode 100644 index 0000000..d8542fc --- /dev/null +++ b/src/logo/ascii/b/bredos.txt @@ -0,0 +1,9 @@ + ████████████████ + ██████░░░░░░████░░░░██ ████ + ████░░░░████░░░░░░██░░░░░░██░░░░██ + ██░░░░░░░░░░██░░░░░░██░░░░░░░░██░░░░██ + ██░░░░░░░░░░██░░░░░░░░░░░░░░░░██░░░░░░██ +██▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓██ +██▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓██ +██▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓██ + ██████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/b/bsd.txt b/src/logo/ascii/b/bsd.txt new file mode 100644 index 0000000..f6c9ea9 --- /dev/null +++ b/src/logo/ascii/b/bsd.txt @@ -0,0 +1,19 @@ +$1 , , + /( )` + \ \___ / | + /- _ `-/ ' + ($2/\/ \ $1\ /\ + $2/ / | ` $1\ + $3O O $2) $1/ | + $2`-^--'$1`< ' + (_.) _ ) / + `.___/` / + `-----' / +$4<----. __ / __ \ +$4<----|====$1O)))$4==$1) \) /$4====| +<----' $1`--' `.__,' \ + | | + \ / /\ + $5______$1( (_ / \______/ + $5,' ,-----' | + `--{__________) \ No newline at end of file diff --git a/src/logo/ascii/b/bunsenlabs.txt b/src/logo/ascii/b/bunsenlabs.txt new file mode 100644 index 0000000..8d31988 --- /dev/null +++ b/src/logo/ascii/b/bunsenlabs.txt @@ -0,0 +1,20 @@ + `++ + -yMMs + `yMMMMN` + -NMMMMMMm. + :MMMMMMMMMN- + .NMMMMMMMMMMM/ + yMMMMMMMMMMMMM/ +`MMMMMMNMMMMMMMN. +-MMMMN+ /mMMMMMMy +-MMMm` `dMMMMMM +`MMN. .NMMMMM. + hMy yMMMMM` + -Mo +MMMMN + /o +MMMMs + +MMMN` + hMMM: + `NMM/ + +MN: + mh. + -/ \ No newline at end of file diff --git a/src/logo/ascii/c.inc b/src/logo/ascii/c.inc new file mode 100644 index 0000000..2cf1a4a --- /dev/null +++ b/src/logo/ascii/c.inc @@ -0,0 +1,515 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo C[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_CACHYOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CACHYOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CALCULATE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CALINIXOS + // CalinixOS + { + .names = { "Calinix", "calinixos" }, + .lines = FASTFETCH_DATATEXT_LOGO_CALINIXOS, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CALINIXOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CARBS + // Carbs + { + .names = { "Carbs" }, + .lines = FASTFETCH_DATATEXT_LOGO_CARBS, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CBL_MARINER + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CELOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CENTER + // Center + { + .names = { "Center" }, + .lines = FASTFETCH_DATATEXT_LOGO_CENTER, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CENTOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CENTOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CEREUS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHAKRA + // Chakra + { + .names = { "Chakra" }, + .lines = FASTFETCH_DATATEXT_LOGO_CHAKRA, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHALETOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHAPEAU + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHIMERA_LINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHONKYSEALOS + // ChonkySealOS + { + .names = { "ChonkySealOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_CHONKYSEALOS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CHROM + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CLEANJARO + // Cleanjaro + { + .names = { "Cleanjaro" }, + .lines = FASTFETCH_DATATEXT_LOGO_CLEANJARO, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CLEANJARO_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CLEAR_LINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CLEAROS + // ClearOS + { + .names = { "ClearOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_CLEAROS, + .colors = { + FF_COLOR_FG_GREEN, + }, + .colorKeys = FF_COLOR_FG_GREEN, + .colorTitle = FF_COLOR_FG_GREEN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CLOVER + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_COBALT + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CODEX + // Codex Linux (reMarkable OS) + { + .names = { "Codex Linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_CODEX, + .colors = { + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CONDRES + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TORIZONCORE + // Common Torizon + { + .names = { "common-torizon" }, + .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_COSMIC + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CRUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CRUX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CRYSTAL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CUCUMBER + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CUERDOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CUTEFISHOS + // CutefishOS + { + .names = { "CutefishOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_CUTEFISHOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CUTEOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CYBEROS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_CYCLEDREAM + // cycledream + { + .names = { "cycledream" }, + .lines = FASTFETCH_DATATEXT_LOGO_CYCLEDREAM, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/c/cachyos.txt b/src/logo/ascii/c/cachyos.txt new file mode 100644 index 0000000..10c1e42 --- /dev/null +++ b/src/logo/ascii/c/cachyos.txt @@ -0,0 +1,22 @@ + $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.......................... \ No newline at end of file diff --git a/src/logo/ascii/c/cachyos_small.txt b/src/logo/ascii/c/cachyos_small.txt new file mode 100644 index 0000000..6497db4 --- /dev/null +++ b/src/logo/ascii/c/cachyos_small.txt @@ -0,0 +1,8 @@ + /''''''''''''/ + /''''''''''''/ + /''''''/ +/''''''/ +\......\ + \......\ + \.............../ + \............./ \ No newline at end of file diff --git a/src/logo/ascii/c/calculate.txt b/src/logo/ascii/c/calculate.txt new file mode 100644 index 0000000..7ac7bc8 --- /dev/null +++ b/src/logo/ascii/c/calculate.txt @@ -0,0 +1,20 @@ + ...... + ,,+++++++,. + .,,,....,,,$2+**+,,.$1 + ............,$2++++,,,$1 + ............... + ......,,,........ + .....+*#####+,,,*+. + .....,*###############,..,,,,,,.. + ......,*#################*..,,,,,..,,,.. + .,,....*####################+***+,,,,...,++, + .,,..,..*#####################*, + ,+,.+*..*#######################. + ,+,,+*+..,########################* +.,++++++. ..+##**###################+ +..... ..+##***#################*. + .,.*#*****##############*. + ..,,*********#####****+. + $2.,++*****+++$1*****************$2+++++,.$1 + $2,++++++**+++++$1***********$2+++++++++,$1 + $2.,,,,++++,.. .,,,,,.....,+++,.,,$1 \ No newline at end of file diff --git a/src/logo/ascii/c/calinixos.txt b/src/logo/ascii/c/calinixos.txt new file mode 100644 index 0000000..d8cbade --- /dev/null +++ b/src/logo/ascii/c/calinixos.txt @@ -0,0 +1,25 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⠔⠒⠒⠋⠉⠉⠉⠉⠓⠒⠒⠦⠤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠉⣁⣠⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣤⣄⣈⠙⠲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠴⠋⢁⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡈⠑⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣠⠞⢁⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⠞⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠋⠉⠁⠀⠀⠀⠀⠈⠉⠙⠛⠿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠈⢢⡀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⡰⠃⣠⣾⣿⣿⣿⣿⣿⣿⡿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⡿⠁⠀⠀⠀⠀⠀⠀⠙⣄⠀⠀⠀⠀ +⠀⠀⠀⡼⠁⣴⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀ +⠀⠀⡼⠀⣼⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣆⠀⠀ +⠀⣰⠁⣸⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀ +⢀⡇⢠⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠀ +⢸⠀⣸⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄ +⣼⠀⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ +⡇⠀⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢛⣿⣿⣿⣿⣿⣿⣿⡦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ +⢻⠀⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ +⢸⡀⢹⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃ +⠀⣇⠘⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀ +⠀⠸⡄⢹⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃⠀ +⠀⠀⢳⡀⢻⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⠀ +⠀⠀⠀⠳⡀⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⣄⡀⠀⠀⠀⠀⢠⠏⠀⠀⠀ +⠀⠀⠀⠀⠙⣄⠙⢿⣿⣿⣿⣿⣿⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣦⡀⠀⡰⠃⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠈⠢⡈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣀⡀⠀⠀⠀⠀⢀⣀⣠⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⣠⠞⠁⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⡈⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⣁⠴⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⣉⠙⠛⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠋⣉⡤⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠒⠢⠤⠤⠤⠤⠤⠤⠤⠤⠖⠒⠋⠉⠀ \ No newline at end of file diff --git a/src/logo/ascii/c/calinixos_small.txt b/src/logo/ascii/c/calinixos_small.txt new file mode 100644 index 0000000..fa6e7c7 --- /dev/null +++ b/src/logo/ascii/c/calinixos_small.txt @@ -0,0 +1,15 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠐⣂⣈⣩⣭⣭⣍⣀⣐⠀⠄⡀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⡀⠔⣨⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣅⠢⡀⠀⠀⠀⠀⠀ +⠀⠀⠀⠠⢊⣴⣾⣿⣿⣿⣿⠿⠟⠛⠛⠛⠛⠻⠿⣿⣿⣿⣿⠃⠀⠠⡀⠀⠀⠀ +⠀⠀⡐⢡⣾⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠁⠀⠀⠀⠈⢆⠀⠀ +⠀⡘⢰⣿⣿⣿⡟⠁⠀⠀⢀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⠀ +⢠⢠⣿⣿⣿⡟⠀⠀⠀⠀⠀⠙⠿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡀ +⡄⢸⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁ +⡇⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣹⣿⣿⣿⣷⠄⠀⠀⠀⠀⠀⠀⠀⠀ +⠃⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀ +⠘⡘⣿⣿⣿⣧⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠁ +⠀⠡⠸⣿⣿⣿⣧⡀⠀⠀⠀⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⠆⠀ +⠀⠀⠡⡘⢿⣿⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣷⣦⡀⢀⠊⠀⠀ +⠀⠀⠀⠈⠊⡻⢿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣶⣿⣿⣿⣿⡿⢟⠕⠁⠀⠀⠀ +⠀⠀⠀⠀⠀⠈⠢⢙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⡩⠐⠁⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠈⠐⠂⠭⠉⠙⣛⣛⠋⠉⠭⠐⠂⠁⠀⠀⠀⠀ \ No newline at end of file diff --git a/src/logo/ascii/c/carbs.txt b/src/logo/ascii/c/carbs.txt new file mode 100644 index 0000000..f1f19a2 --- /dev/null +++ b/src/logo/ascii/c/carbs.txt @@ -0,0 +1,18 @@ + .......... + ..,;:ccccccc:;'.. + ..,clllc:;;;;;:cllc,. + .,cllc,... ..';;'. + .;lol;.. .. + .,lol;. + .coo:. + .'lol,. + .,lol,. + .,lol,. + 'col;. + .:ooc'. + .'col:. + .'cllc'.. .''. + ..:lolc,'.......',cll,. + ..;cllllccccclllc;'. + ...',;;;;;;,,... + ..... \ No newline at end of file diff --git a/src/logo/ascii/c/cbl_mariner.txt b/src/logo/ascii/c/cbl_mariner.txt new file mode 100644 index 0000000..c4dc9ee --- /dev/null +++ b/src/logo/ascii/c/cbl_mariner.txt @@ -0,0 +1,18 @@ + . + :- . + :==. .=: + :===: -==: + :-===: .====: + :-====- -=====: + -====== :=======: + -======. .=========: + -======: -==========. + -======- -===========. + :======- :===========. + :=======. .-==========. + :=======: -==========. + :=======- :==========. + :=======- .-========- +:--------. :========- + ..:::--=========- + ..::---================-=- \ No newline at end of file diff --git a/src/logo/ascii/c/celos.txt b/src/logo/ascii/c/celos.txt new file mode 100644 index 0000000..b5b7661 --- /dev/null +++ b/src/logo/ascii/c/celos.txt @@ -0,0 +1,15 @@ + `-:/++++/:-` + -/syyyyyyyyyyyyy+- + :ssssyyyyyyyyyyyyyyyy/ + .osy$2mmmmmmmmmmmmmmmNNNNNmmhy+ + $1.sssshhhhhhhddddddddddddddds- + $1`osssssssyyyyyyyyyyyyyyyyyyhy` + $1:ssssssyyyyyyyyyyyyyyyyyyyyhh/ +$2sMMMMMMMMMMMMMMMMMMMMMMMh$1yyyyyyhho + :sssssssyyyyyyyyyyyyyyyyyyyhh/ + `ssssssssyyyyyyyyyyyyyyyyyyhy. + -sssssyddddddddddddddddddddy + -ssss$2hmmmmmmmmmmmmmmmmmmmyssss- + $1`/ssssyyyyyyyyyyyyyyyy+` + $1`:osyyyyyyyyyyyyys/` + $1`.:/+ooooo+:-` \ No newline at end of file diff --git a/src/logo/ascii/c/center.txt b/src/logo/ascii/c/center.txt new file mode 100644 index 0000000..58d64f7 --- /dev/null +++ b/src/logo/ascii/c/center.txt @@ -0,0 +1,13 @@ + . + o, + . d, . + ';' ..d;.. .cl' + .:; 'oldO,.oo. + ..,:,xKXxoo;'. +,;;;;;ldxkONMMMXxkxc;;;;;. +.....':oddXWMNOxlcl:...... + .:dlxk0c;:. . + :d:.,xcld,.,:. + ;l, .l; ';' + .o; + l, \ No newline at end of file diff --git a/src/logo/ascii/c/centos.txt b/src/logo/ascii/c/centos.txt new file mode 100644 index 0000000..be42a9d --- /dev/null +++ b/src/logo/ascii/c/centos.txt @@ -0,0 +1,19 @@ + .. + .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'' \ No newline at end of file diff --git a/src/logo/ascii/c/centos_small.txt b/src/logo/ascii/c/centos_small.txt new file mode 100644 index 0000000..52d8027 --- /dev/null +++ b/src/logo/ascii/c/centos_small.txt @@ -0,0 +1,7 @@ + $2____$1^$4____ + $2|\ $1|$4 /| + $2| \ $1|$4 / | +$4<---- $3----> + $3| / $2|$1 \ | + $3|/__$2|$1__\| + $2v \ No newline at end of file diff --git a/src/logo/ascii/c/cereus.txt b/src/logo/ascii/c/cereus.txt new file mode 100644 index 0000000..bae0738 --- /dev/null +++ b/src/logo/ascii/c/cereus.txt @@ -0,0 +1,27 @@ + .. + '::,..... + .,;: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 ..',;'. \ No newline at end of file diff --git a/src/logo/ascii/c/chakra.txt b/src/logo/ascii/c/chakra.txt new file mode 100644 index 0000000..9a6a82a --- /dev/null +++ b/src/logo/ascii/c/chakra.txt @@ -0,0 +1,18 @@ + _ _ _ "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' + "''" \ No newline at end of file diff --git a/src/logo/ascii/c/chaletos.txt b/src/logo/ascii/c/chaletos.txt new file mode 100644 index 0000000..e51c652 --- /dev/null +++ b/src/logo/ascii/c/chaletos.txt @@ -0,0 +1,20 @@ + `.//+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+:`` \ No newline at end of file diff --git a/src/logo/ascii/c/chapeau.txt b/src/logo/ascii/c/chapeau.txt new file mode 100644 index 0000000..f96d531 --- /dev/null +++ b/src/logo/ascii/c/chapeau.txt @@ -0,0 +1,18 @@ + .-/-. + ////////. + ////////$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////////// + `////////////////. + -////////- \ No newline at end of file diff --git a/src/logo/ascii/c/chimera_linux.txt b/src/logo/ascii/c/chimera_linux.txt new file mode 100644 index 0000000..d0bc304 --- /dev/null +++ b/src/logo/ascii/c/chimera_linux.txt @@ -0,0 +1,18 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/c/chonkysealos.txt b/src/logo/ascii/c/chonkysealos.txt new file mode 100644 index 0000000..a0ededb --- /dev/null +++ b/src/logo/ascii/c/chonkysealos.txt @@ -0,0 +1,21 @@ + .-/-. + .:-=++****++=-:. + .:=+*##%%%%%%%%%%##*+=:. + :=*#%%%%%%%%%%%%%%%%%%%%#*=: + :=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=. + -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+- + =+#%%%%@@@@@@@%%%%%%%@@@@@@@%%%%%#+= + =+#@%%%%*+=-==*%%%%%%%#+====*%%%%%@#+= +:+*%%%%@* +@%%%@# -@%%%%%*+: +=+#%%%%%%#+====*###%%##*=--=+*%%%%%%%#+= ++*%%%%%%%@@##%%%%*=::=#%%%##%@%%%%%%%%*+ ++*%%%%%%%@**@%%%%%@==@%%%%%@+#%%%%%%%%*+ +=+#%%%%%%@#*@%%%%%%**%%%%%@%+%%%%%%%%#+= +:+*%%%%%%%@#*####**###*####*%@%%%%%%%*+: + =+#@%%%%%%@%%%%%%%@@%%%%%%%%%%%%%%@#+= + =+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+= + -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%*+- + .=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=. + :=*##%%%%%%%%%%%%%%%%%%##*=: + .:=+*##%%%%%%%%%%##*+=:. + .:-=++****++=-:. \ No newline at end of file diff --git a/src/logo/ascii/c/chrom.txt b/src/logo/ascii/c/chrom.txt new file mode 100644 index 0000000..d72922a --- /dev/null +++ b/src/logo/ascii/c/chrom.txt @@ -0,0 +1,18 @@ +$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:. \ No newline at end of file diff --git a/src/logo/ascii/c/cleanjaro.txt b/src/logo/ascii/c/cleanjaro.txt new file mode 100644 index 0000000..ce33aeb --- /dev/null +++ b/src/logo/ascii/c/cleanjaro.txt @@ -0,0 +1,12 @@ +███████▌ ████████████████ +███████▌ ████████████████ +███████▌ ████████████████ +███████▌ +███████▌ +███████▌ +███████▌ +███████▌ +█████████████████████████ +█████████████████████████ +█████████████████████████ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/src/logo/ascii/c/cleanjaro_small.txt b/src/logo/ascii/c/cleanjaro_small.txt new file mode 100644 index 0000000..b069f25 --- /dev/null +++ b/src/logo/ascii/c/cleanjaro_small.txt @@ -0,0 +1,7 @@ +█████ ██████████ +█████ ██████████ +█████ +█████ +█████ +████████████████ +████████████████ \ No newline at end of file diff --git a/src/logo/ascii/c/clear_linux.txt b/src/logo/ascii/c/clear_linux.txt new file mode 100644 index 0000000..e3201f4 --- /dev/null +++ b/src/logo/ascii/c/clear_linux.txt @@ -0,0 +1,20 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/c/clearos.txt b/src/logo/ascii/c/clearos.txt new file mode 100644 index 0000000..3b6fe97 --- /dev/null +++ b/src/logo/ascii/c/clearos.txt @@ -0,0 +1,20 @@ + `.--::::::--.` + .-:////////////////:-. + `-////////////////////////-` + -////////////////////////////- + `//////////////-..-//////////////` + ./////////////: ://///////////. + `//////:..-////: :////-..-//////` + ://////` -///:.``.:///-` ://///: +`///////:. -////////-` `:///////` +.//:--////:. -////-` `:////--://. +./: .////:. --` `:////- :/. +`//-` .////:. `:////- `-//` + :///-` .////:. `:////- `-///: + `/////-` -///: :///- `-/////` + `//////- `///: :///` .//////` + `:////: `///: :///` -////:` + .://: `///: :///` -//:. + .:: `///: :///` -:. + `///: :///` + `... ...` \ No newline at end of file diff --git a/src/logo/ascii/c/clover.txt b/src/logo/ascii/c/clover.txt new file mode 100644 index 0000000..bc21d89 --- /dev/null +++ b/src/logo/ascii/c/clover.txt @@ -0,0 +1,20 @@ + `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` \ No newline at end of file diff --git a/src/logo/ascii/c/cobalt.txt b/src/logo/ascii/c/cobalt.txt new file mode 100644 index 0000000..4ed0219 --- /dev/null +++ b/src/logo/ascii/c/cobalt.txt @@ -0,0 +1,16 @@ +$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*********************** + **************************** + ***** \ No newline at end of file diff --git a/src/logo/ascii/c/codex.txt b/src/logo/ascii/c/codex.txt new file mode 100644 index 0000000..6ae684f --- /dev/null +++ b/src/logo/ascii/c/codex.txt @@ -0,0 +1,12 @@ +#: :+#@@@@%# +@@#: .*@@@@@@@@@ +@@@@#- .@@@@@@@@@@@ +@@@@@@%- %@@@@@@@@@@@ +@@@@@@@@%= :@@@@%%%%@@@@ +@@@@@@@@@@%==*-: .:=# +:*@@@@@@@@@@= + :*@@@@@@@@= + .*@@@@@@= + .+@@@@= + .+%@= + += diff --git a/src/logo/ascii/c/condres.txt b/src/logo/ascii/c/condres.txt new file mode 100644 index 0000000..1024184 --- /dev/null +++ b/src/logo/ascii/c/condres.txt @@ -0,0 +1,17 @@ +$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`:/:--------.```````` \ No newline at end of file diff --git a/src/logo/ascii/c/cosmic.txt b/src/logo/ascii/c/cosmic.txt new file mode 100644 index 0000000..10fd4e1 --- /dev/null +++ b/src/logo/ascii/c/cosmic.txt @@ -0,0 +1,11 @@ + .xMMMMMMMMMMMMMMMMMMMMMMx. +JDMMMMMMMMMMMMMMMMMMMMMMMMOL +IMMMY' 'YMMMI +MMMM MMMM +MMMM MMMM +IMMMb dMMMI +'YMMMMMMMMb. .dMMMMMMMMK' + 'OMMMMMMMP' 'YMMMMMMMP' + + $2.x76767$36767676$476767$5676x. + $2'*76767$36767676$476767$5676*' diff --git a/src/logo/ascii/c/crux.txt b/src/logo/ascii/c/crux.txt new file mode 100644 index 0000000..7b62147 --- /dev/null +++ b/src/logo/ascii/c/crux.txt @@ -0,0 +1,18 @@ + $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 \ No newline at end of file diff --git a/src/logo/ascii/c/crux_small.txt b/src/logo/ascii/c/crux_small.txt new file mode 100644 index 0000000..5f597b2 --- /dev/null +++ b/src/logo/ascii/c/crux_small.txt @@ -0,0 +1,7 @@ + ___ + ($3.· $1| + ($2<> $1| + / $3__ $1\ + ( $3/ \ $1/| +$2_$1/\ $3__)$1/$2_$1) +$2\/$1-____$2\/ \ No newline at end of file diff --git a/src/logo/ascii/c/crystal.txt b/src/logo/ascii/c/crystal.txt new file mode 100644 index 0000000..85cfd25 --- /dev/null +++ b/src/logo/ascii/c/crystal.txt @@ -0,0 +1,20 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/c/cucumber.txt b/src/logo/ascii/c/cucumber.txt new file mode 100644 index 0000000..e899fb8 --- /dev/null +++ b/src/logo/ascii/c/cucumber.txt @@ -0,0 +1,20 @@ + `.-://++++++//:-.` + `:/+//$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/+/:` + `.-://++++++//:-.` \ No newline at end of file diff --git a/src/logo/ascii/c/cuerdos.txt b/src/logo/ascii/c/cuerdos.txt new file mode 100644 index 0000000..1161084 --- /dev/null +++ b/src/logo/ascii/c/cuerdos.txt @@ -0,0 +1,13 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀ +⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣶⣾⣿⣿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣶⣿⣿⣿⡇ +⠀⠀⡄⢀⣤⣤⣤⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣄⠀⣿⣿⣿⣿⣿⡇ +⠀⠀⡇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⠁ +⠀⠀⣧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃ +⠀⠀⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁ +⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁ +⠀⠀⠇⠘⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠛⠛⠋⠉ +⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇ +⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉ +____________________________________ +| Optimizado hasta el último pixel | +************************************ \ No newline at end of file diff --git a/src/logo/ascii/c/cutefishos.txt b/src/logo/ascii/c/cutefishos.txt new file mode 100644 index 0000000..08b9920 --- /dev/null +++ b/src/logo/ascii/c/cutefishos.txt @@ -0,0 +1,9 @@ + ___ww___ +_ _wwMMM@M^^^^MMMMww_ +M0w_ _wMMM~~ ~~MMm_ + ~MMy _ww0M~ ~MMy + ~MMMM~ o "MM +$3 jw0M~~MMMw_ _wMM' +wMM~ ~~MMmw__ __w0M~ +~ ~~MM0MmwwwwwwwwwMMM~ + ~~~~^^~~~ \ No newline at end of file diff --git a/src/logo/ascii/c/cuteos.txt b/src/logo/ascii/c/cuteos.txt new file mode 100644 index 0000000..1829a7b --- /dev/null +++ b/src/logo/ascii/c/cuteos.txt @@ -0,0 +1,14 @@ + $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 \ No newline at end of file diff --git a/src/logo/ascii/c/cyberos.txt b/src/logo/ascii/c/cyberos.txt new file mode 100644 index 0000000..3a5fd10 --- /dev/null +++ b/src/logo/ascii/c/cyberos.txt @@ -0,0 +1,15 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/c/cycledream.txt b/src/logo/ascii/c/cycledream.txt new file mode 100644 index 0000000..fd0df05 --- /dev/null +++ b/src/logo/ascii/c/cycledream.txt @@ -0,0 +1,21 @@ + .: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, \ No newline at end of file diff --git a/src/logo/ascii/d.inc b/src/logo/ascii/d.inc new file mode 100644 index 0000000..f9acc5b --- /dev/null +++ b/src/logo/ascii/d.inc @@ -0,0 +1,206 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo D[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_DAHLIA + // DahliaOS + { + .names = { "dahliaOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_DAHLIA, + .colors = { + FF_COLOR_FG_RED, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DARKOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DEBIAN + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DEBIAN_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DEEPIN + // Deepin + { + .names = { "Deepin" }, + .lines = FASTFETCH_DATATEXT_LOGO_DEEPIN, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DESAOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DEVUAN + // Devuan + { + .names = { "Devuan" }, + .lines = FASTFETCH_DATATEXT_LOGO_DEVUAN, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DEVUAN_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DIETPI + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DRACOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DRAGONFLY + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DRAGONFLY_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DRAGONFLY_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DRAUGER + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_DROIDIAN + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/d/dahlia.txt b/src/logo/ascii/d/dahlia.txt new file mode 100644 index 0000000..df886d9 --- /dev/null +++ b/src/logo/ascii/d/dahlia.txt @@ -0,0 +1,17 @@ + .#. + *%@@@%* + .,,,,,(&@@@@@@@&/,,,,,. + ,#@@@@@@@@@@@@@@@@@@@@@#. + ,#@@@@@@@&#///#&@@@@@@@#. + ,/%&@@@@@%/, .,(%@@@@@&#/. + *#&@@@@@@#,. .*#@@@@@@&#, + .&@@@@@@@@@( .(@@@@@@@@@&&. +#@@@@@@@@@@( )@@@@@@@@@@@# + °@@@@@@@@@@( .(@@@@@@@@@@@° + *%@@@@@@@(. ,#@@@@@@@%* + ,(&@@@@@@%*. ./%@@@@@@%(, + ,#@@@@@@@&(***(&@@@@@@@#. + ,#@@@@@@@@@@@@@@@@@@@@@#. + ,*****#&@@@@@@@&(*****, + ,/%@@@%/. + ,#, \ No newline at end of file diff --git a/src/logo/ascii/d/darkos.txt b/src/logo/ascii/d/darkos.txt new file mode 100644 index 0000000..aaf1546 --- /dev/null +++ b/src/logo/ascii/d/darkos.txt @@ -0,0 +1,20 @@ +$3⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⠋⡆⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡆⢀⣤⢛⠛⣠⣿⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⣿⠟⣡⠊⣠⣾⣿⠃⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣯⣿⠀⠊⣤⣿⣿⣿⠃⣴⣧⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⡟⣠⣶⣿⣿⣿⢋⣤⠿⠛⠉⢁⣭⣽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$4 ⠀⠀⠀⠀⠀⠀ ⠀⣠⠖⡭⢉⣿⣯⣿⣯⣿⣿⣿⣟⣧⠛⢉⣤⣶⣾⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$5⠀⠀⠀⠀⠀⠀⠀⠀⣴⣫⠓⢱⣯⣿⢿⠋⠛⢛⠟⠯⠶⢟⣿⣯⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$2⠀⠀⠀⠀⠀⠀⢀⡮⢁⣴⣿⣿⣿⠖⣠⠐⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠛⢿⣶⣄⠀⠀⠀⠀⠀⠀⠀ +$3⠀⠀⠀⠀⢀⣤⣷⣿⣿⠿⢛⣭⠒⠉⠀⠀⠀⣀⣀⣄⣤⣤⣴⣶⣶⣶⣿⣿⣿⣿⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀ +$1⠀⢀⣶⠏⠟⠝⠉⢀⣤⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$6⢴⣯⣤⣶⣿⣿⣿⣿⣿⡿⣿⣯⠉⠉⠉⠉⠀⠀⠀⠈⣿⡀⣟⣿⣿⢿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$5⠀⠀⠀⠉⠛⣿⣧⠀⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠃⣿⣿⣯⣿⣦⡀⠀⠉⠻⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$3⠀⠀⠀⠀⠀⠀⠉⢿⣮⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠀⣯⠉⠉⠛⢿⣿⣷⣄⠀⠈⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀ +$2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⠀⠀⠀⠀⠀⠀⠀⢀⢡⠃⣾⣿⣿⣦⠀⠀⠀⠙⢿⣿⣤⠀⠙⣄⠀⠀⠀⠀⠀⠀⠀ +$6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢋⡟⢠⣿⣿⣿⠋⢿⣄⠀⠀⠀⠈⡄⠙⣶⣈⡄⠀⠀⠀⠀⠀⠀ +$1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠚⢲⣿⠀⣾⣿⣿⠁⠀⠀⠉⢷⡀⠀⠀⣇⠀⠀⠈⠻⡀⠀⠀⠀⠀⠀ +$4⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢢⣀⣿⡏⠀⣿⡿⠀⠀⠀⠀⠀⠀⠙⣦⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$3⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠿⣧⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣮⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +$5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠀⠀⠀⠀⠀⠀ \ No newline at end of file diff --git a/src/logo/ascii/d/debian.txt b/src/logo/ascii/d/debian.txt new file mode 100644 index 0000000..514bc5a --- /dev/null +++ b/src/logo/ascii/d/debian.txt @@ -0,0 +1,17 @@ + $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._ + `"""" \ No newline at end of file diff --git a/src/logo/ascii/d/debian_small.txt b/src/logo/ascii/d/debian_small.txt new file mode 100644 index 0000000..7db04d8 --- /dev/null +++ b/src/logo/ascii/d/debian_small.txt @@ -0,0 +1,6 @@ + _____ + / __ \ +| / | +| \___- +-_ + --_ \ No newline at end of file diff --git a/src/logo/ascii/d/deepin.txt b/src/logo/ascii/d/deepin.txt new file mode 100644 index 0000000..f28458c --- /dev/null +++ b/src/logo/ascii/d/deepin.txt @@ -0,0 +1,18 @@ + ............ + .';;;;;. .,;,. + .,;;;;;;;. ';;;;;;;. + .;::::::::' .,::;;,''''',. + ,'.:::::::: .;;'. '; + ;' '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:,. \ No newline at end of file diff --git a/src/logo/ascii/d/desaos.txt b/src/logo/ascii/d/desaos.txt new file mode 100644 index 0000000..f8e9fc3 --- /dev/null +++ b/src/logo/ascii/d/desaos.txt @@ -0,0 +1,16 @@ +███████████████████████ +███████████████████████ +███████████████████████ +███████████████████████ +████████ ███████ +████████ ███████ +████████ ███████ +████████ ███████ +████████ ███████ +████████ ███████ +████████ ███████ +██████████████████████████████ +██████████████████████████████ +████████████████████████ +████████████████████████ +████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/d/devuan.txt b/src/logo/ascii/d/devuan.txt new file mode 100644 index 0000000..b9a7dd8 --- /dev/null +++ b/src/logo/ascii/d/devuan.txt @@ -0,0 +1,15 @@ + ..,,;;;::;,.. + `':ddd;:,. + `'dPPd:,. + `:b$$$$b`. + 'P$$$$$d` + .$$$$$$$$$` + ;$$$$$$$$$P + .:P$$$$$$$$$$$$` + .,:b$$$$$$$$$$$$$;' + .,:dP$$$$$$$$$$$$$$$$b:' + .,:;db$$$$$$$$$$$$$$$$$$$$Pd'` + ,db$$$$$$$$$$$$$$$$$$$$$$$$$$$$b:'` +:$$$$$$$$$$$$$$$$$$$$$$$$b:'` + `$$$$$$$$$bd:''` + `'''` \ No newline at end of file diff --git a/src/logo/ascii/d/devuan_small.txt b/src/logo/ascii/d/devuan_small.txt new file mode 100644 index 0000000..a85add0 --- /dev/null +++ b/src/logo/ascii/d/devuan_small.txt @@ -0,0 +1,7 @@ + ..:::. + ..-==- + .+#: + =@@ + :+%@#: +.:=+#@@%*: +#@@@#=: \ No newline at end of file diff --git a/src/logo/ascii/d/dietpi.txt b/src/logo/ascii/d/dietpi.txt new file mode 100644 index 0000000..505c7dc --- /dev/null +++ b/src/logo/ascii/d/dietpi.txt @@ -0,0 +1,19 @@ + :=+******+- -+******+=: + =#-::-::::-=#:-#=-::::-::-#= + :%-::--==-::-%%-::-==--::-%: + +#-:::::=+++$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@%@@@@@@@@+ + #@@@@@@@@@@@@@@@@@@@@@@# + -#@@@@@@@@@@@@@@@@@@#- + -*%@@@@@@@@@@%*- + .+%@@@@%+. \ No newline at end of file diff --git a/src/logo/ascii/d/dracos.txt b/src/logo/ascii/d/dracos.txt new file mode 100644 index 0000000..6cfa5c7 --- /dev/null +++ b/src/logo/ascii/d/dracos.txt @@ -0,0 +1,13 @@ + `-:/- + -os: + -os/` + :sy+-` + `/yyyy+. + `+yyyyo- + `/yyyys: +`:osssoooo++- +yyyyyy/` + ./yyyyyyo yo`:syyyy+. + -oyyy+ +- :yyyyyo- + `:sy: `. `/yyyyys: + ./o/.` .oyyso+oo:` + :+oo+//::::///:-.` `.` \ No newline at end of file diff --git a/src/logo/ascii/d/dragonfly.txt b/src/logo/ascii/d/dragonfly.txt new file mode 100644 index 0000000..95357ad --- /dev/null +++ b/src/logo/ascii/d/dragonfly.txt @@ -0,0 +1,15 @@ +$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 | | + | | + | | + | | + | | + | | + `|' \ No newline at end of file diff --git a/src/logo/ascii/d/dragonfly_old.txt b/src/logo/ascii/d/dragonfly_old.txt new file mode 100644 index 0000000..81dacd2 --- /dev/null +++ b/src/logo/ascii/d/dragonfly_old.txt @@ -0,0 +1,17 @@ + .-. + $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 /=\ + \=/ + ^ \ No newline at end of file diff --git a/src/logo/ascii/d/dragonfly_small.txt b/src/logo/ascii/d/dragonfly_small.txt new file mode 100644 index 0000000..6612742 --- /dev/null +++ b/src/logo/ascii/d/dragonfly_small.txt @@ -0,0 +1,7 @@ +$2 ,$1_$2, +('-_$1|$2_-') + >--$1|$2--< +(_-'$1|$2'-_) + $1| + | + | \ No newline at end of file diff --git a/src/logo/ascii/d/drauger.txt b/src/logo/ascii/d/drauger.txt new file mode 100644 index 0000000..6dc569c --- /dev/null +++ b/src/logo/ascii/d/drauger.txt @@ -0,0 +1,16 @@ + -``- + `:+``+:` + `/++``++/. + .++/. ./++. + :++/` `/++: + `/++: :++/` + ./+/- -/+/. + -++/. ./++- + :++:` `:++: + `/++- -++/` + ./++. ./+/. + -++/` `/++- + :++:` `:++: + `/++- -++/` +.:-.`..............................`.-:. +`.-/++++++++++++++++++++++++++++++++/-.` \ No newline at end of file diff --git a/src/logo/ascii/d/droidian.txt b/src/logo/ascii/d/droidian.txt new file mode 100644 index 0000000..adf1706 --- /dev/null +++ b/src/logo/ascii/d/droidian.txt @@ -0,0 +1,15 @@ +$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._ \ No newline at end of file diff --git a/src/logo/ascii/e.inc b/src/logo/ascii/e.inc new file mode 100644 index 0000000..3e5fd91 --- /dev/null +++ b/src/logo/ascii/e.inc @@ -0,0 +1,256 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo E[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_ELBRUS + // Elbrus + { + .names = { "elbrus" }, + .lines = FASTFETCH_DATATEXT_LOGO_ELBRUS, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ELEMENTARY + // Elementary + { + .names = { "Elementary" }, + .lines = FASTFETCH_DATATEXT_LOGO_ELEMENTARY, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ELEMENTARY_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ELIVE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EMMABUNTUS + // Emmabuntüs + { + .names = { "Emmabuntus" }, + .lines = FASTFETCH_DATATEXT_LOGO_EMMABUNTUS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EMPEROROS + // EmperorOS + { + .names = { "Emperor" }, + .lines = FASTFETCH_DATATEXT_LOGO_EMPEROROS, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENOS + // EN-OS + { + .names = { "ENOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_ENOS, + .colors = { + FF_COLOR_FG_LIGHT_BLUE, + }, + .colorKeys = FF_COLOR_FG_LIGHT_BLUE, + .colorTitle = FF_COLOR_FG_LIGHT_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENCRYPTOS + // EncryptOS + { + .names = { "EncryptOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_ENCRYPTOS, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_GREEN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENDLESS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ENSO + // Enso + { + .names = { "Enso" }, + .lines = FASTFETCH_DATATEXT_LOGO_ENSO, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ESHANIZEDOS + // EshanizedOS + { + .names = { "EshanizedOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_ESHANIZEDOS, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EUROLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS + // EvolutionOS + { + .names = { "EvolutionOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_OLD + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EWEOS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EXHERBO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_EXODIA_PREDATOR + // ExodiaOS + { + .names = { "Exodia" }, + .lines = FASTFETCH_DATATEXT_LOGO_EXODIA_PREDATOR, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/e/elbrus.txt b/src/logo/ascii/e/elbrus.txt new file mode 100644 index 0000000..9e30e83 --- /dev/null +++ b/src/logo/ascii/e/elbrus.txt @@ -0,0 +1,14 @@ +▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ +██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ +██ ██ +██ ███████ ███████ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ███████ ███████ +██ ██ ██ +██ ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██ +██ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ +██ ██ +███████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/e/elementary.txt b/src/logo/ascii/e/elementary.txt new file mode 100644 index 0000000..6fc0b9e --- /dev/null +++ b/src/logo/ascii/e/elementary.txt @@ -0,0 +1,17 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/e/elementary_small.txt b/src/logo/ascii/e/elementary_small.txt new file mode 100644 index 0000000..5cb0103 --- /dev/null +++ b/src/logo/ascii/e/elementary_small.txt @@ -0,0 +1,6 @@ + _______ + / ____ \ +/ | / /\ +|__\ / / | +\ /__/ / + \_______/ \ No newline at end of file diff --git a/src/logo/ascii/e/elive.txt b/src/logo/ascii/e/elive.txt new file mode 100644 index 0000000..e9ce457 --- /dev/null +++ b/src/logo/ascii/e/elive.txt @@ -0,0 +1,17 @@ + *@$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 \ No newline at end of file diff --git a/src/logo/ascii/e/emmabuntus.txt b/src/logo/ascii/e/emmabuntus.txt new file mode 100644 index 0000000..8b5aa21 --- /dev/null +++ b/src/logo/ascii/e/emmabuntus.txt @@ -0,0 +1,15 @@ + _~~_ + 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/ + *~~* diff --git a/src/logo/ascii/e/emperoros.txt b/src/logo/ascii/e/emperoros.txt new file mode 100644 index 0000000..55dcaa9 --- /dev/null +++ b/src/logo/ascii/e/emperoros.txt @@ -0,0 +1,13 @@ + !! + !!!! + llllll + llllll + IIIIIIIIII + IIIIIIIIIIIIIIIIIIII +;;;;;;;;;;;;;;;;;;;;;;;; + ;;;;;;;;;;;;;;;;;;;; + :;;::;:;:: + :::::: + ,,,,,, + ,,,, + "" \ No newline at end of file diff --git a/src/logo/ascii/e/encryptos.txt b/src/logo/ascii/e/encryptos.txt new file mode 100644 index 0000000..2783b1b --- /dev/null +++ b/src/logo/ascii/e/encryptos.txt @@ -0,0 +1,13 @@ + ******* + *** **. + ** ** + ** ** + + ***************** +,,,,,,,,,,,,,,,,*** +,,,,,,, ,,,,,,, +,,,,,,, ,,,,,,, +,,,,,,, ,,,,,,, +,,,,,,, ,,,,,,, +,,,,,,,,,,,,,,,,,,, + ,,,,,,,,,,,,. \ No newline at end of file diff --git a/src/logo/ascii/e/endeavouros.txt b/src/logo/ascii/e/endeavouros.txt new file mode 100644 index 0000000..2bdf27d --- /dev/null +++ b/src/logo/ascii/e/endeavouros.txt @@ -0,0 +1,15 @@ + $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./++++++++++++++++++++++++++++++/:. + `:::::::::::::::::::::::::------`` \ No newline at end of file diff --git a/src/logo/ascii/e/endeavouros_small.txt b/src/logo/ascii/e/endeavouros_small.txt new file mode 100644 index 0000000..dbe011a --- /dev/null +++ b/src/logo/ascii/e/endeavouros_small.txt @@ -0,0 +1,7 @@ + /$2o$3. +$1 /$2sssso$3- +$1 /$2ossssssso$3: +$1 /$2ssssssssssso$3+ +$1 /$2ssssssssssssssso$3+ +$1//$2osssssssssssssso$3+- + `+++++++++++++++-` \ No newline at end of file diff --git a/src/logo/ascii/e/endless.txt b/src/logo/ascii/e/endless.txt new file mode 100644 index 0000000..8fc8b65 --- /dev/null +++ b/src/logo/ascii/e/endless.txt @@ -0,0 +1,20 @@ + `:+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+:` \ No newline at end of file diff --git a/src/logo/ascii/e/enos.txt b/src/logo/ascii/e/enos.txt new file mode 100644 index 0000000..4a13b87 --- /dev/null +++ b/src/logo/ascii/e/enos.txt @@ -0,0 +1,6 @@ + ______ _ _ ____ _____ +| ____| \ | | / __ \ / ____| +| |__ | \| |______| | | | (___ +| __| | . ` |______| | | |\___ \ +| |____| |\ | | |__| |____) | +|______|_| \_| \____/|_____/ diff --git a/src/logo/ascii/e/enso.txt b/src/logo/ascii/e/enso.txt new file mode 100644 index 0000000..5f93412 --- /dev/null +++ b/src/logo/ascii/e/enso.txt @@ -0,0 +1,19 @@ + .:--==--:. + :=*#############*+-. + .+##################*##*: + .*##########+==-==++*####*##- + =########=: .-+**#***. + *#######- ++*#**. + +######+ -*+#** + :######* .*+**= + *######: --#*# + ####### +++#. + #######. ++=*. + *######+ .-+*+ + :#######- -:*+: + =#######*. :.*+- + +########*- :*=- + =###########+=: =+=: + .+#############. .-==: + .=###########= ..:--:. + .-+######+ \ No newline at end of file diff --git a/src/logo/ascii/e/eshanizedos.txt b/src/logo/ascii/e/eshanizedos.txt new file mode 100644 index 0000000..3d6e348 --- /dev/null +++ b/src/logo/ascii/e/eshanizedos.txt @@ -0,0 +1,27 @@ + .:-==++++++++++++++++++++++-. + .:=+##############################+ + .=*###################################- + :+######################################: + :+######################################*- + .+#############*+--:::::::::::::::::::::. + :*##########*=: + -###########= + :##########+. + *#########= + =*********+ .::::::::::::::::. + **********. -*###################+: +:********** =#######################= +-*********+ +************************ +:*********+ :***********************: +.********** .=******************+- + +*********- + .**********. + =**********: + +**********=. + =***********=: + -*************+-. + :+****************+++++++++++++++++++++==: + :+**************************************+. + -+************************************- + .-+*********************************. + :-==+*************************=. \ No newline at end of file diff --git a/src/logo/ascii/e/eurolinux.txt b/src/logo/ascii/e/eurolinux.txt new file mode 100644 index 0000000..18c808d --- /dev/null +++ b/src/logo/ascii/e/eurolinux.txt @@ -0,0 +1,18 @@ + __ + -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 \ No newline at end of file diff --git a/src/logo/ascii/e/evolutionos.txt b/src/logo/ascii/e/evolutionos.txt new file mode 100644 index 0000000..16dfd8b --- /dev/null +++ b/src/logo/ascii/e/evolutionos.txt @@ -0,0 +1,21 @@ + $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' + .. \ No newline at end of file diff --git a/src/logo/ascii/e/evolutionos_old.txt b/src/logo/ascii/e/evolutionos_old.txt new file mode 100644 index 0000000..5144631 --- /dev/null +++ b/src/logo/ascii/e/evolutionos_old.txt @@ -0,0 +1,18 @@ + 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;;' + ''''''''''''''''''''''''' \ No newline at end of file diff --git a/src/logo/ascii/e/evolutionos_small.txt b/src/logo/ascii/e/evolutionos_small.txt new file mode 100644 index 0000000..ddc911a --- /dev/null +++ b/src/logo/ascii/e/evolutionos_small.txt @@ -0,0 +1,9 @@ + $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, \ No newline at end of file diff --git a/src/logo/ascii/e/eweos.txt b/src/logo/ascii/e/eweos.txt new file mode 100644 index 0000000..6a5b6bd --- /dev/null +++ b/src/logo/ascii/e/eweos.txt @@ -0,0 +1,15 @@ +$2 #####%%% +$2 ##%%$3////$2%%%%%$3/// +$2 #%%%%$3////((((////$2% +$1 *@@@@@@@$3/$5,,,$3/////$5,,,$2%$1@@@@@@@ + .@@@@@@@@@@@$3////////$2%%%$1@@@@@@@@@@@@ +@@@$4...$1@@@@@@$3////$2%%$3////$1@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@ @@@@@@ + @@@ @@@ diff --git a/src/logo/ascii/e/exherbo.txt b/src/logo/ascii/e/exherbo.txt new file mode 100644 index 0000000..e8d0846 --- /dev/null +++ b/src/logo/ascii/e/exherbo.txt @@ -0,0 +1,22 @@ +$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' \ No newline at end of file diff --git a/src/logo/ascii/e/exodia_predator.txt b/src/logo/ascii/e/exodia_predator.txt new file mode 100644 index 0000000..4b75012 --- /dev/null +++ b/src/logo/ascii/e/exodia_predator.txt @@ -0,0 +1,24 @@ +- : ++: :+ +++. .++ ++++ : . +++ ++++= .+ + =+++ +++++- ++ += -++++ +++++++- -++ ++- -++++++ +++++++++: .+++ +++. :++++++++ +++++++++++: ++++ ++++ :++++++++++ ++++++++++++==++++ ++++=++++++=+++++ ++++++.:++++++++++ ++++++++++:.+++++ ++++++. .+++++++++ +++++++++. .+++++ ++++++: ++++++++ ++++++++ :+++++ +++++++- =+++++++ +++++++= -++++++ + :+++++= =+++++++ +++++++= =+++++: + :+++= =+++++++ +++++++= =+++: + -+= =+++++++ +++++++= ++- + : =++++++- -++++++= : + =++++- -++++= + =++= =++= + =++ ++= + =+. .+= + =- -= + : : \ No newline at end of file diff --git a/src/logo/ascii/f.inc b/src/logo/ascii/f.inc new file mode 100644 index 0000000..8be44fa --- /dev/null +++ b/src/logo/ascii/f.inc @@ -0,0 +1,288 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo F[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_FASTFETCH + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ASAHI + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA2_SMALL + { + .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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_SILVERBLUE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_KINOITE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_SERICEA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEMBOYOS + // FemboyOS + { + .names = { "FemboyOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_FEMBOYOS, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FEREN + // Feren + { + .names = { "Feren" }, + .lines = FASTFETCH_DATATEXT_LOGO_FEREN, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FILOTIMO + // Filotimo + { + .names = { "filotimo" }, + .lines = FASTFETCH_DATATEXT_LOGO_FILOTIMO, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FINNIX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FLOFLIS + // Floflis + { + .names = { "Floflis" }, + .lines = FASTFETCH_DATATEXT_LOGO_FLOFLIS, + .colors = { + FF_COLOR_FG_LIGHT_CYAN, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FREEBSD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FREEBSD_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FREEMINT + // FreeMiNT + { + .names = { "FreeMiNT" }, + .lines = FASTFETCH_DATATEXT_LOGO_FREEMINT, + .colors = { + FF_COLOR_FG_WHITE, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FRUGALWARE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FUNTOO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FURRETO + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/f/fastfetch.txt b/src/logo/ascii/f/fastfetch.txt new file mode 100644 index 0000000..099baf8 --- /dev/null +++ b/src/logo/ascii/f/fastfetch.txt @@ -0,0 +1,11 @@ +╭───────────────────────╮ +│ $2● $3● $4● $5FASTFETCH $1│ +├───────────────────────┤ +│ │ +│ $2 /\ $7►►►►►►► $1│ +│ $2 /--\ $7►►►►►► $1│ +│ $2/----\ $7►►►►► $1│ +│ $6|xx| $7►►►► $1│ +│ $6|xx| $7►►► $1│ +│ $3^^^^ $1│ +╰───────────────────────╯ \ No newline at end of file diff --git a/src/logo/ascii/f/fedora.txt b/src/logo/ascii/f/fedora.txt new file mode 100644 index 0000000..f0cfe76 --- /dev/null +++ b/src/logo/ascii/f/fedora.txt @@ -0,0 +1,19 @@ + .',;::::;,'. + .';: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::;,. \ No newline at end of file diff --git a/src/logo/ascii/f/fedora2_small.txt b/src/logo/ascii/f/fedora2_small.txt new file mode 100644 index 0000000..4768040 --- /dev/null +++ b/src/logo/ascii/f/fedora2_small.txt @@ -0,0 +1,5 @@ + __ + / \ + __ |_ +/ | +\__/ \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_coreos.txt b/src/logo/ascii/f/fedora_coreos.txt new file mode 100644 index 0000000..0eae596 --- /dev/null +++ b/src/logo/ascii/f/fedora_coreos.txt @@ -0,0 +1,20 @@ + ..... + .';: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:;.. + ..... \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_kinoite.txt b/src/logo/ascii/f/fedora_kinoite.txt new file mode 100644 index 0000000..64bc7d5 --- /dev/null +++ b/src/logo/ascii/f/fedora_kinoite.txt @@ -0,0 +1,18 @@ + ,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 ::, ,:: + '''' \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_old.txt b/src/logo/ascii/f/fedora_old.txt new file mode 100644 index 0000000..b943ce9 --- /dev/null +++ b/src/logo/ascii/f/fedora_old.txt @@ -0,0 +1,17 @@ + /:-------------:\ + :-------------------:: + :-----------$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-------------:: +:---------------------:// \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_sericea.txt b/src/logo/ascii/f/fedora_sericea.txt new file mode 100644 index 0000000..cd7dfb2 --- /dev/null +++ b/src/logo/ascii/f/fedora_sericea.txt @@ -0,0 +1,17 @@ + :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: \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_silverblue.txt b/src/logo/ascii/f/fedora_silverblue.txt new file mode 100644 index 0000000..993a99f --- /dev/null +++ b/src/logo/ascii/f/fedora_silverblue.txt @@ -0,0 +1,17 @@ + .;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;' \ No newline at end of file diff --git a/src/logo/ascii/f/fedora_small.txt b/src/logo/ascii/f/fedora_small.txt new file mode 100644 index 0000000..0220cc5 --- /dev/null +++ b/src/logo/ascii/f/fedora_small.txt @@ -0,0 +1,9 @@ + ,'''''. + | ,. | + | | '_' + ,....| |.. +.' ,_;| ..' +| | | | +| ',_,' | + '. ,' + ''''' \ No newline at end of file diff --git a/src/logo/ascii/f/femboyos.txt b/src/logo/ascii/f/femboyos.txt new file mode 100644 index 0000000..c447e7a --- /dev/null +++ b/src/logo/ascii/f/femboyos.txt @@ -0,0 +1,20 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/f/feren.txt b/src/logo/ascii/f/feren.txt new file mode 100644 index 0000000..f1d3a5c --- /dev/null +++ b/src/logo/ascii/f/feren.txt @@ -0,0 +1,16 @@ + `----------` + :+ooooooooo+. +-o+oooooooooo+- +..`/+++++++++++/...````````````````` + .++++++++++++++++++++++++++/////- + ++++++++++++++++++++++++++++++++//:` + -++++++++++++++++++++++++++++++/-` + ++++++++++++++++++++++++++++:. + -++++++++++++++++++++++++/. + +++++++++++++++++++++/-` + -++++++++++++++++++//-` + .:+++++++++++++//////- + .:++++++++//////////- + `-++++++---:::://///. + `.:///+++. ` + `......... \ No newline at end of file diff --git a/src/logo/ascii/f/filotimo.txt b/src/logo/ascii/f/filotimo.txt new file mode 100644 index 0000000..48ebb26 --- /dev/null +++ b/src/logo/ascii/f/filotimo.txt @@ -0,0 +1,20 @@ +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 ,;;;;;::::' ';;;;;;:::: +';;::, ;;;;::: .;;;;;::' .;;;;;:: + ;;;, ;;;;: .;;;:' ;;;: + ,;, ,;: ';' + ., \ No newline at end of file diff --git a/src/logo/ascii/f/finnix.txt b/src/logo/ascii/f/finnix.txt new file mode 100644 index 0000000..5673c6e --- /dev/null +++ b/src/logo/ascii/f/finnix.txt @@ -0,0 +1,17 @@ + ,,:;;;;:,, + ,;*%S########S%*;, + ;?#################S?: + :%######################?: + +##########################; + +############################; + :#############.**,#############, + *###########+ +###########+ + ?########## $3Finnix$1 ##########* + *###########, ,###########+ + :#############%..%#############, + *############################+ + *##########################+ + ;S######################%: + ,+%##################%; + :+?S##########S?+: + ,:;++++;:, \ No newline at end of file diff --git a/src/logo/ascii/f/floflis.txt b/src/logo/ascii/f/floflis.txt new file mode 100644 index 0000000..6247b37 --- /dev/null +++ b/src/logo/ascii/f/floflis.txt @@ -0,0 +1,19 @@ + ,▄▄▄▌▓▓███▓▓▌▄▄▄, + ,▄▒▓███████████████████▓▄▄ + ▄▓███████████████████████████▌ + ▓███████████████████████████████ + , ╙▓████████████████████████████▀ ▄ + ╓█▓▄ ╙▀▓████████████████████▀▀` ,▄██▓ + ╓█████▌▄, '▀▀▀▀▓▓▓▓▓▓▀▀Å╙` ▄▄▓█████▌ + ██████████▓▌▄ , ▄▓███████████▄ +╢████████████▓ ║████▓▓███▌ ╣█████████████▓ +▓█████████████ ▐█████████▀ ▓██████████████ +▓█████████████ ▐█████████▄ ███████████████ +▀████████████▌ ║█████████▌ ▀█████████████▌ + ████████████M ▓██████████ ▐█████████████⌐ + ▀██████████▌ ▐███████████▌ ▀███████████▌ + ╙▓█████▓ ▓██████████████▄ ▀███████▀ + ╝▓██▀ ╓▓████████████████▓ ▀▓██▀ + ,▄████████████████████▌, + ╝▀████████████████████▓▀' + `╙▀▀▓▓███████▓▀▀╩' \ No newline at end of file diff --git a/src/logo/ascii/f/freebsd.txt b/src/logo/ascii/f/freebsd.txt new file mode 100644 index 0000000..8526f42 --- /dev/null +++ b/src/logo/ascii/f/freebsd.txt @@ -0,0 +1,15 @@ +``` $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`:` + .-- `--. + .---.....----. \ No newline at end of file diff --git a/src/logo/ascii/f/freebsd_small.txt b/src/logo/ascii/f/freebsd_small.txt new file mode 100644 index 0000000..ee345f8 --- /dev/null +++ b/src/logo/ascii/f/freebsd_small.txt @@ -0,0 +1,6 @@ +$1/\,-'''''-,/\ +\_) (_/ +| | +| | + ; ; + '-_____-' \ No newline at end of file diff --git a/src/logo/ascii/f/freemint.txt b/src/logo/ascii/f/freemint.txt new file mode 100644 index 0000000..34d6b73 --- /dev/null +++ b/src/logo/ascii/f/freemint.txt @@ -0,0 +1,15 @@ + ## + ## ######### + #### ## + #### #### ## +#### #### ## ## + #### #### ## ## + #### #### ## ## ## + #### ###### + ###### ## ## #### + #### ################ + #### ## #### + ## #### ###### + ## ## #### #### + ## ## ## ## ## #### + #### ## ## ## \ No newline at end of file diff --git a/src/logo/ascii/f/frugalware.txt b/src/logo/ascii/f/frugalware.txt new file mode 100644 index 0000000..91c55e5 --- /dev/null +++ b/src/logo/ascii/f/frugalware.txt @@ -0,0 +1,23 @@ + `++/::-.` + /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 + .-:+++` \ No newline at end of file diff --git a/src/logo/ascii/f/funtoo.txt b/src/logo/ascii/f/funtoo.txt new file mode 100644 index 0000000..7943633 --- /dev/null +++ b/src/logo/ascii/f/funtoo.txt @@ -0,0 +1,10 @@ + .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;. \ No newline at end of file diff --git a/src/logo/ascii/f/furreto.txt b/src/logo/ascii/f/furreto.txt new file mode 100644 index 0000000..95ee07c --- /dev/null +++ b/src/logo/ascii/f/furreto.txt @@ -0,0 +1,22 @@ + .$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. \ No newline at end of file diff --git a/src/logo/ascii/g.inc b/src/logo/ascii/g.inc new file mode 100644 index 0000000..3fa3f7a --- /dev/null +++ b/src/logo/ascii/g.inc @@ -0,0 +1,244 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo G[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_GALLIUMOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GARUDA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GARUDA_DRAGON + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GARUDA_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GENTOO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GENTOO_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GHOSTBSD + // GhostBSD + { + .names = { "GhostBSD" }, + .lines = FASTFETCH_DATATEXT_LOGO_GHOSTBSD, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GHOSTFREAK + // GhostFreak + { + .names = { "GhostFreak" }, + .lines = FASTFETCH_DATATEXT_LOGO_GHOSTFREAK, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GLAUCUS + // Glaucus + { + .names = { "Glaucus" }, + .lines = FASTFETCH_DATATEXT_LOGO_GLAUCUS, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GNEWSENSE + // GNewSense + { + .names = { "gNewSense" }, + .lines = FASTFETCH_DATATEXT_LOGO_GNEWSENSE, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GNOME + // GNOME OS + { + .names = { "GNOME OS" }, // matches "NAME" + .lines = FASTFETCH_DATATEXT_LOGO_GNOME, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GNU + // GNU + { + .names = { "GNU" }, + .lines = FASTFETCH_DATATEXT_LOGO_GNU, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GOBOLINUX + // GoboLinux + { + .names = { "GoboLinux", "Gobo" }, + .lines = FASTFETCH_DATATEXT_LOGO_GOBOLINUX, + .colors = { + FF_COLOR_FG_MAGENTA, + }, + .colorKeys = FF_COLOR_FG_MAGENTA, + .colorTitle = FF_COLOR_FG_MAGENTA, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GOLDENDOGLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GRAPHENEOS + // GrapheneOS + { + .names = { "GrapheneOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_GRAPHENEOS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GROMBYANG + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GUIX + // Guix + { + .names = { "Guix" }, + .lines = FASTFETCH_DATATEXT_LOGO_GUIX, + .colors = { + FF_COLOR_FG_YELLOW, + }, + .colorKeys = FF_COLOR_FG_YELLOW, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GUIX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_GXDE + // GXDE + { + .names = { "GXDE" }, + .lines = FASTFETCH_DATATEXT_LOGO_GXDE, + .colors = { + FF_COLOR_FG_RED, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/g/galliumos.txt b/src/logo/ascii/g/galliumos.txt new file mode 100644 index 0000000..527344c --- /dev/null +++ b/src/logo/ascii/g/galliumos.txt @@ -0,0 +1,19 @@ +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/:-...................................: \ No newline at end of file diff --git a/src/logo/ascii/g/garuda.txt b/src/logo/ascii/g/garuda.txt new file mode 100644 index 0000000..2234a61 --- /dev/null +++ b/src/logo/ascii/g/garuda.txt @@ -0,0 +1,18 @@ + .%;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. \ No newline at end of file diff --git a/src/logo/ascii/g/garuda_dragon.txt b/src/logo/ascii/g/garuda_dragon.txt new file mode 100644 index 0000000..e93a04d --- /dev/null +++ b/src/logo/ascii/g/garuda_dragon.txt @@ -0,0 +1,24 @@ + .:--=========--:.. + .:-=+++++===-----=======-:. + :=++++-:.. ..:-===-:. + .+++=:. .-=---. + . :-: :---: + := . :---: + .=-: :-. .---: + :-===--::. .::::. ---: + .::--====-===+=-----=-: ---: + :: :-++++====--=-:=====--=--: .--- ++**=:+++===++++++==- -===== -==-. ---: +*****+==++==--::::::======== . ===-. :--- +****==++-::.:::::-.::--======-.-=== .--- +***+=+-::::--:. -==:-==--=======-::. . :--- +=**++-::::== -+===-==: :::.:-=-==--- :--: +.*+*-:::-+= ...::==== .:: .=-:--. .. + -*++:::=+-: .:=- -- .: . ... + =*++:.++-+: .:. =-.: :-: :: :. + -*++-=+=-+=-=++===-. .====: .::::. + :++++++-:::--.-:=== --. + .=+++++- .= +.=:= .:: + :=+++++:. .: : . :---- + .-++++=-:.. .:--===-. + .-=+++++===-----=======-:. \ No newline at end of file diff --git a/src/logo/ascii/g/garuda_small.txt b/src/logo/ascii/g/garuda_small.txt new file mode 100644 index 0000000..7716d67 --- /dev/null +++ b/src/logo/ascii/g/garuda_small.txt @@ -0,0 +1,5 @@ + .----. + .' , '. + .' '-----| +'. -----, + '.____.' \ No newline at end of file diff --git a/src/logo/ascii/g/gentoo.txt b/src/logo/ascii/g/gentoo.txt new file mode 100644 index 0000000..a90dd33 --- /dev/null +++ b/src/logo/ascii/g/gentoo.txt @@ -0,0 +1,18 @@ + -/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.` + `-//////:--. \ No newline at end of file diff --git a/src/logo/ascii/g/gentoo_small.txt b/src/logo/ascii/g/gentoo_small.txt new file mode 100644 index 0000000..5c471a7 --- /dev/null +++ b/src/logo/ascii/g/gentoo_small.txt @@ -0,0 +1,7 @@ + _-----_ +( \ +\ 0 \ + $2\ ) + / _/ +( _- +\____- \ No newline at end of file diff --git a/src/logo/ascii/g/ghostbsd.txt b/src/logo/ascii/g/ghostbsd.txt new file mode 100644 index 0000000..97d060c --- /dev/null +++ b/src/logo/ascii/g/ghostbsd.txt @@ -0,0 +1,13 @@ + ,gggggg. + ,agg9* .g) + .agg* ._.,gg* + ,gga* (ggg*' + ,ga* ,ga* + ,ga' .ag* + ,ga' .agga' + 9g' .agg'g*,a + 'gggg*',gga' + .gg*' + .gga* + .gga* + (ga* \ No newline at end of file diff --git a/src/logo/ascii/g/ghostfreak.txt b/src/logo/ascii/g/ghostfreak.txt new file mode 100644 index 0000000..264abea --- /dev/null +++ b/src/logo/ascii/g/ghostfreak.txt @@ -0,0 +1,19 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/g/glaucus.txt b/src/logo/ascii/g/glaucus.txt new file mode 100644 index 0000000..f10d7e5 --- /dev/null +++ b/src/logo/ascii/g/glaucus.txt @@ -0,0 +1,12 @@ + ,, ,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** \ No newline at end of file diff --git a/src/logo/ascii/g/gnewsense.txt b/src/logo/ascii/g/gnewsense.txt new file mode 100644 index 0000000..5333e92 --- /dev/null +++ b/src/logo/ascii/g/gnewsense.txt @@ -0,0 +1,12 @@ + ..,,,,.. + .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+' \ No newline at end of file diff --git a/src/logo/ascii/g/gnome.txt b/src/logo/ascii/g/gnome.txt new file mode 100644 index 0000000..71e7b21 --- /dev/null +++ b/src/logo/ascii/g/gnome.txt @@ -0,0 +1,21 @@ + ,@@@@@@@@, + @@@@@@ @@@@@@@@@@@@ + ,@@. @@@@@@@ *@@@@@@@@@@@@ + @@@@@% @@@@@@( @@@@@@@@@@@& + @@@@@@ @@@@* @@@@@@@@@# +@@@@* @@@@, *@@@@@% +@@@@@. + @@@@# @@@@@@@@@@@@@@@@ + ,@@@@@@@@@@@@@@@@@@@@@@@, + ,@@@@@@@@@@@@@@@@@@@@@@@@@@& + .@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@( + @@@@@@@@@@@@@@@@@@@@% + @@@@@@@@@@@@@@@@ + @@@@@@@@@@@@* @@@@@@@@/ + &@@@@@@@@@@ @@@@@@@@@* + @@@@@@@@@@@, @@@@@@@@@* + ,@@@@@@@@@@@@@@@@@@@@& + &@@@@@@@@@@@@@@ + ... \ No newline at end of file diff --git a/src/logo/ascii/g/gnu.txt b/src/logo/ascii/g/gnu.txt new file mode 100644 index 0000000..0b9c3e2 --- /dev/null +++ b/src/logo/ascii/g/gnu.txt @@ -0,0 +1,18 @@ + _-`````-, ,- '- . + .' .- - | | - -. `. + /.' / `. \ +:/ : _... ..._ `` : +:: : /._ .`:'_.._\. || : +:: `._ ./ ,` : \ . _.'' . +`:. / | -. \-. \\_ / + \:._ _/ .' .@) \@) ` `\ ,.' + _/,--' .- .\,-.`--`. + ,'/'' (( \ ` ) + /'/' \ `-' ( + '/'' `._,-----' + ''/' .,---' + ''/' ;: + ''/'' ''/ + ''/''/'' + '/'/' + `; \ No newline at end of file diff --git a/src/logo/ascii/g/gobolinux.txt b/src/logo/ascii/g/gobolinux.txt new file mode 100644 index 0000000..062dc9c --- /dev/null +++ b/src/logo/ascii/g/gobolinux.txt @@ -0,0 +1,6 @@ + _____ _ + / ____| | | +| | __ ___ | |__ ___ +| | |_ |/ _ \| '_ \ / _ \ +| |__| | (_) | |_) | (_) | + \_____|\___/|_.__/ \___/ \ No newline at end of file diff --git a/src/logo/ascii/g/goldendoglinux.txt b/src/logo/ascii/g/goldendoglinux.txt new file mode 100644 index 0000000..d40abf3 --- /dev/null +++ b/src/logo/ascii/g/goldendoglinux.txt @@ -0,0 +1,19 @@ + .:^~!!^. .:: + ^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^^:::.... \ No newline at end of file diff --git a/src/logo/ascii/g/grapheneos.txt b/src/logo/ascii/g/grapheneos.txt new file mode 100644 index 0000000..e474bde --- /dev/null +++ b/src/logo/ascii/g/grapheneos.txt @@ -0,0 +1,23 @@ + 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! + # \ No newline at end of file diff --git a/src/logo/ascii/g/grombyang.txt b/src/logo/ascii/g/grombyang.txt new file mode 100644 index 0000000..9cb1451 --- /dev/null +++ b/src/logo/ascii/g/grombyang.txt @@ -0,0 +1,18 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/g/guix.txt b/src/logo/ascii/g/guix.txt new file mode 100644 index 0000000..a77ee77 --- /dev/null +++ b/src/logo/ascii/g/guix.txt @@ -0,0 +1,10 @@ + .. `. + `--..```..` `..```..--` + .-:///-:::. `-:::///:-. + ````.:::` `:::.```` + -//:` -::- + ://: -::- + `///- .:::` + -+++-:::. + :+/:::- + `-....` \ No newline at end of file diff --git a/src/logo/ascii/g/guix_small.txt b/src/logo/ascii/g/guix_small.txt new file mode 100644 index 0000000..19b87dd --- /dev/null +++ b/src/logo/ascii/g/guix_small.txt @@ -0,0 +1,7 @@ +|.__ __.| +|__ \ / __| + \ \ / / + \ \ / / + \ \ / / + \ \/ / + \__/ \ No newline at end of file diff --git a/src/logo/ascii/g/gxde.txt b/src/logo/ascii/g/gxde.txt new file mode 100644 index 0000000..388c377 --- /dev/null +++ b/src/logo/ascii/g/gxde.txt @@ -0,0 +1,20 @@ + ################ + ######################## + ##########-- --########## + #######* ****** *####### + ###### --*######--- ###### + ##### --- *#### ##### + ##### *-- #*#*#* * #### ##### +#####* ##- ##*-#* ### ### *##### +##### ###* -##*#* *# *# *### ##### +##### ###* -*##*-*## *#* ### ##### +##### #### - --#### - ##* ### ##### +##### *####* * - ###- ##* ##### +#####* #######- *###- ### *##### + #####* ***#####****--- ### *##### + ###### ----------- - ### ###### + ###### ----- ### ###### + ####### ####### + ##########*----*########## + ###################### + ############## diff --git a/src/logo/ascii/h.inc b/src/logo/ascii/h.inc new file mode 100644 index 0000000..9d471eb --- /dev/null +++ b/src/logo/ascii/h.inc @@ -0,0 +1,211 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo H[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_HAIKU + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HAIKU2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HAIKU_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HAMONIKR + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HARDCLANZ + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_FREEBSD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HARMONYOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HASH + // Hash + { + .names = { "Hash" }, + .lines = FASTFETCH_DATATEXT_LOGO_HASH, + .colors = { + FF_COLOR_FG_256 "123", + FF_COLOR_FG_256 "123", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HELIUMOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HCE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HUAYRA + // Huayra + { + .names = { "Huayra" }, + .lines = FASTFETCH_DATATEXT_LOGO_HUAYRA, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HYBRID + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HYDROOS + // HydroOS + { + .names = { "HydroOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_HYDROOS, + .colors = { + FF_COLOR_FG_RED, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_GREEN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HYPROS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HYPERBOLA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_HYPERBOLA_SMALL + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/h/haiku.txt b/src/logo/ascii/h/haiku.txt new file mode 100644 index 0000000..52863dd --- /dev/null +++ b/src/logo/ascii/h/haiku.txt @@ -0,0 +1,12 @@ + 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 + """" """" diff --git a/src/logo/ascii/h/haiku2.txt b/src/logo/ascii/h/haiku2.txt new file mode 100644 index 0000000..f09f596 --- /dev/null +++ b/src/logo/ascii/h/haiku2.txt @@ -0,0 +1,17 @@ +$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 diff --git a/src/logo/ascii/h/haiku_small.txt b/src/logo/ascii/h/haiku_small.txt new file mode 100644 index 0000000..4b85e07 --- /dev/null +++ b/src/logo/ascii/h/haiku_small.txt @@ -0,0 +1,8 @@ + ,^, + / \ +*--_ ; ; _--* +\ '" "' / + '. .' +.-'" "'-. + '-.__. .__.-' + |_| \ No newline at end of file diff --git a/src/logo/ascii/h/hamonikr.txt b/src/logo/ascii/h/hamonikr.txt new file mode 100644 index 0000000..ff8cd91 --- /dev/null +++ b/src/logo/ascii/h/hamonikr.txt @@ -0,0 +1,23 @@ + 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' \ No newline at end of file diff --git a/src/logo/ascii/h/hardclanz.txt b/src/logo/ascii/h/hardclanz.txt new file mode 100644 index 0000000..7c289df --- /dev/null +++ b/src/logo/ascii/h/hardclanz.txt @@ -0,0 +1,19 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/h/harmonyos.txt b/src/logo/ascii/h/harmonyos.txt new file mode 100644 index 0000000..cc3b7a4 --- /dev/null +++ b/src/logo/ascii/h/harmonyos.txt @@ -0,0 +1,19 @@ + ....-----.... + .-+##############+-.. + .+######################+.. + .########+- -++#######-. + .+######- -######+. + .######. .######.. + .+####+ +#####.. + .-####+. +####+. + .+####+ -####+.. + ..+####+ -####+... +$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... \ No newline at end of file diff --git a/src/logo/ascii/h/hash.txt b/src/logo/ascii/h/hash.txt new file mode 100644 index 0000000..5849628 --- /dev/null +++ b/src/logo/ascii/h/hash.txt @@ -0,0 +1,13 @@ + + ###### + + ### ###### ### + ##### ###### ##### + ###### ###### ###### + +####### '"###### '"######## +####### ###### ######## +####### ###### ######## + + ###### '"###### '"###### + ##### ###### ##### + ### ###### ### + ~ ###### ~ \ No newline at end of file diff --git a/src/logo/ascii/h/hce.txt b/src/logo/ascii/h/hce.txt new file mode 100644 index 0000000..8b5ea50 --- /dev/null +++ b/src/logo/ascii/h/hce.txt @@ -0,0 +1,24 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/h/heliumos.txt b/src/logo/ascii/h/heliumos.txt new file mode 100644 index 0000000..30430cd --- /dev/null +++ b/src/logo/ascii/h/heliumos.txt @@ -0,0 +1,20 @@ + ,,╥╥╥╦╦╦╥╥,, + ,╓╦Ñ╨^`_,,,,,,. `"╨╩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╩^ + __`"^╙╙╨╙╙^^`__ \ No newline at end of file diff --git a/src/logo/ascii/h/huayra.txt b/src/logo/ascii/h/huayra.txt new file mode 100644 index 0000000..90a774c --- /dev/null +++ b/src/logo/ascii/h/huayra.txt @@ -0,0 +1,17 @@ +$2 ` + . . ` + `` - . . + `.` -` `. - `` .` + ..`-`-` + - / .` ``` + .--.+--`+:- :/.` .-``.` + -+/so::h:.d-`./:`.` + :hNhyMomy:os-...-. ```` + .dhsshNmNhoo+:-``.``` + $1`ohy:-$2NMds+::-.`` + ````$1.hNN+`$2mMNho/:-....```` + ````` `../dmNhoo+/:..`` + ```` .dh++o/:....` +.+s/` `/s-.-.:.`` ```` +::` `::`..` + .` `.. + `` \ No newline at end of file diff --git a/src/logo/ascii/h/hybrid.txt b/src/logo/ascii/h/hybrid.txt new file mode 100644 index 0000000..ddfc3ba --- /dev/null +++ b/src/logo/ascii/h/hybrid.txt @@ -0,0 +1,16 @@ +$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######## \ No newline at end of file diff --git a/src/logo/ascii/h/hydroos.txt b/src/logo/ascii/h/hydroos.txt new file mode 100644 index 0000000..defd799 --- /dev/null +++ b/src/logo/ascii/h/hydroos.txt @@ -0,0 +1,8 @@ + _ _ _ ____ _____ +| | | | | | / __ \ / ____| +| |__| |_ _ __| |_ __ ___ | | | | (___ +| __ | | | |/ _` | '__/ _ \| | | |\___ \ +| | | | |_| | (_| | | | (_) | |__| |____) | +|_| |_|\__, |\__,_|_| \___/ \____/|_____/ + __/ | + |___/ \ No newline at end of file diff --git a/src/logo/ascii/h/hyperbola.txt b/src/logo/ascii/h/hyperbola.txt new file mode 100644 index 0000000..3e5cbaa --- /dev/null +++ b/src/logo/ascii/h/hyperbola.txt @@ -0,0 +1,16 @@ + WW + KX W + WO0W NX0O + NOO0NW WNXK0OOKW + W0OOOOOOOOOOOOKN + N0OOOOOOO0KXW + WNXXXNW + NXK00000KN + WNK0OOOOOOOOOO0W + NK0OOOOOOOOOOOOOO0W + X0OOOOOOO00KK00OOOOOK + X0OOOO0KNWW WX0OO0W + X0OO0XNW KOOW + N00KNW KOW + NKXN W0W +WW W \ No newline at end of file diff --git a/src/logo/ascii/h/hyperbola_small.txt b/src/logo/ascii/h/hyperbola_small.txt new file mode 100644 index 0000000..c2b7c38 --- /dev/null +++ b/src/logo/ascii/h/hyperbola_small.txt @@ -0,0 +1,7 @@ + |`__.`/ + \____/ + .--. + / \ + / ___ \ + / .` `.\ +/.` `.\ \ No newline at end of file diff --git a/src/logo/ascii/h/hypros.txt b/src/logo/ascii/h/hypros.txt new file mode 100644 index 0000000..5b7d4dd --- /dev/null +++ b/src/logo/ascii/h/hypros.txt @@ -0,0 +1,17 @@ + ___ + ,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 \ No newline at end of file diff --git a/src/logo/ascii/k/kdeneon.txt b/src/logo/ascii/k/kdeneon.txt new file mode 100644 index 0000000..716339e --- /dev/null +++ b/src/logo/ascii/k/kdeneon.txt @@ -0,0 +1,19 @@ + `..---+/---..` + `---.`` `` `.---.` + .--.` `` `-:-. + `:/: `.----//----.` :/- + .:. `---` `--.` .:` + .:` `--` .:- `:. + `/ `:. `.-::-.` -:` `/` + /. /. `:++++++++:` .: .: +`/ .: `+++++++++++/ /` `+` +/+` -- .++++++++++++` :. .+: +`/ .: `+++++++++++/ /` `+` + /` /. `:++++++++:` .: .: + ./ `:. `.:::-.` -:` `/` + .:` `--` .:- `:. + .:. `---` `--.` .:` + `:/: `.----//----.` :/- + .-:.` `` `-:-. + `---.`` `` `.---.` + `..---+/---..` \ No newline at end of file diff --git a/src/logo/ascii/k/kernelos.txt b/src/logo/ascii/k/kernelos.txt new file mode 100644 index 0000000..f76fc26 --- /dev/null +++ b/src/logo/ascii/k/kernelos.txt @@ -0,0 +1,19 @@ + .''''....''''. + .''... ...''. + ''.. ..'' + '.. ..'' + .'. .,,'.. .',,,' ..'. + .'. .,,,,' .,,,,,' .'. + .'. .,,,,' .,,,,,. .'. + '.. .,,,,'',,,,;. .'' +.'. .,,,,,,,;;. ..' +.'. .,,,,;;;;;$2, $1..'. + '.. .,;;;,$2';;;;;, $1..' + .'. .;;$2;;' .;;:::: $1.,. + '.. $2.;;;;' .::::::. $1.,' + '.. $2.;;;;' .::::::. $1.,$2'$1 + .'.. $2.',. + $1',.. $2.',' + $1.,,... $2..',,. + ..,,''........',,,. + ...... \ No newline at end of file diff --git a/src/logo/ascii/k/kibaos.txt b/src/logo/ascii/k/kibaos.txt new file mode 100644 index 0000000..b9ee09a --- /dev/null +++ b/src/logo/ascii/k/kibaos.txt @@ -0,0 +1,6 @@ + _ ___ _ ___ ____ +| |/ (_) |__ __ _ / _ \/ ___| +| ' /| | '_ \ / _` | | | \___ \ +| . \| | |_) | (_| | |_| |___) | +|_|\_\_|_.__/ \__,_|\___/|____/ + diff --git a/src/logo/ascii/k/kibojoe.txt b/src/logo/ascii/k/kibojoe.txt new file mode 100644 index 0000000..3afc7eb --- /dev/null +++ b/src/logo/ascii/k/kibojoe.txt @@ -0,0 +1,13 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/k/kiss.txt b/src/logo/ascii/k/kiss.txt new file mode 100644 index 0000000..275012a --- /dev/null +++ b/src/logo/ascii/k/kiss.txt @@ -0,0 +1,7 @@ + $3 ___ + ($2.· $3| + ($1<> $3| + / $2__$3 \ + ( $1/ \ $3/| +$1_$3/\ $2__)$3/$1_$3) +$1\/$3-____$1\/$2 \ No newline at end of file diff --git a/src/logo/ascii/k/kiss2.txt b/src/logo/ascii/k/kiss2.txt new file mode 100644 index 0000000..1fbd6ae --- /dev/null +++ b/src/logo/ascii/k/kiss2.txt @@ -0,0 +1,11 @@ + ██████ ██████ + ██$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████ + ████████████ \ No newline at end of file diff --git a/src/logo/ascii/k/kogaion.txt b/src/logo/ascii/k/kogaion.txt new file mode 100644 index 0000000..07c6226 --- /dev/null +++ b/src/logo/ascii/k/kogaion.txt @@ -0,0 +1,20 @@ + ;; ,; + ;;; ,;; + ,;;;; ;;;; + ,;;;;;;;; ;;;; + ;;;;;;;;;;; ;;;;; + ,;;;;;;;;;;;; ';;;;;, + ;;;;;;;;;;;;;;, ';;;;;;; + ;;;;;;;;;;;;;;;;;, ';;;;; +; ';;;;;;;;;;;;;;;;;;, ;;; +;;;, ';;;;;;;;;;;;;;;;;;;,;; +;;;;;, ';;;;;;;;;;;;;;;;;;, +;;;;;;;;, ';;;;;;;;;;;;;;;;, +;;;;;;;;;;;;, ';;;;;;;;;;;;;; +';;;;;;;;;;;;; ';;;;;;;;;;;;; + ';;;;;;;;;;;;;, ';;;;;;;;;;; + ';;;;;;;;;;;;; ;;;;;;;;;; + ';;;;;;;;;;;; ;;;;;;;; + ';;;;;;;; ;;;;;; + ';;;;; ;;;; + ';;; ;; \ No newline at end of file diff --git a/src/logo/ascii/k/korora.txt b/src/logo/ascii/k/korora.txt new file mode 100644 index 0000000..26135c9 --- /dev/null +++ b/src/logo/ascii/k/korora.txt @@ -0,0 +1,16 @@ +$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` + ``````````` \ No newline at end of file diff --git a/src/logo/ascii/k/krassos.txt b/src/logo/ascii/k/krassos.txt new file mode 100644 index 0000000..bfc3dea --- /dev/null +++ b/src/logo/ascii/k/krassos.txt @@ -0,0 +1,19 @@ + $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**@@@@@@@@@@@* \ No newline at end of file diff --git a/src/logo/ascii/k/kslinux.txt b/src/logo/ascii/k/kslinux.txt new file mode 100644 index 0000000..3844490 --- /dev/null +++ b/src/logo/ascii/k/kslinux.txt @@ -0,0 +1,11 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/k/kubuntu.txt b/src/logo/ascii/k/kubuntu.txt new file mode 100644 index 0000000..d19bca2 --- /dev/null +++ b/src/logo/ascii/k/kubuntu.txt @@ -0,0 +1,20 @@ +$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/:.` \ No newline at end of file diff --git a/src/logo/ascii/k/kylin.txt b/src/logo/ascii/k/kylin.txt new file mode 100644 index 0000000..f71dc7b --- /dev/null +++ b/src/logo/ascii/k/kylin.txt @@ -0,0 +1,22 @@ + $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= \ No newline at end of file diff --git a/src/logo/ascii/l.inc b/src/logo/ascii/l.inc new file mode 100644 index 0000000..ace13ca --- /dev/null +++ b/src/logo/ascii/l.inc @@ -0,0 +1,326 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo L[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_LAINOS + // LainOS + { + .names = { "LainOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_LAINOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_256 "14", + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LANGITKETUJUH + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LAXEROS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LEDE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LIBREELEC + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LILIDOG + // Lilidog + { + .names = { "Lilidog" }, + .lines = FASTFETCH_DATATEXT_LOGO_LILIDOG, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINGMO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LIMEOS + // LimeOS + { + .names = { "LimeOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_LIMEOS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINSPIRE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LFS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXLITE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXLITE_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXMINT + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXMINT_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXMINT2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LINUXMINT_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LIVE_RAIZO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LLIUREX + // LliureX + { + .names = { "LliureX" }, + .lines = FASTFETCH_DATATEXT_LOGO_LLIUREX, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LMDE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LOCOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LUBUNTU + // Lubuntu + { + .names = { "lubuntu" }, + .lines = FASTFETCH_DATATEXT_LOGO_LUBUNTU, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_LUNAR + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/l/lainos.txt b/src/logo/ascii/l/lainos.txt new file mode 100644 index 0000000..7b4b31c --- /dev/null +++ b/src/logo/ascii/l/lainos.txt @@ -0,0 +1,20 @@ +$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.-. + .===. .=== ===. .===. + .======== ========. + ''''' ''''' \ No newline at end of file diff --git a/src/logo/ascii/l/langitketujuh.txt b/src/logo/ascii/l/langitketujuh.txt new file mode 100644 index 0000000..220c72d --- /dev/null +++ b/src/logo/ascii/l/langitketujuh.txt @@ -0,0 +1,11 @@ +$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' \ No newline at end of file diff --git a/src/logo/ascii/l/laxeros.txt b/src/logo/ascii/l/laxeros.txt new file mode 100644 index 0000000..6d23bf1 --- /dev/null +++ b/src/logo/ascii/l/laxeros.txt @@ -0,0 +1,20 @@ + /. + `://:- + `//////: + .////////:` + -//////////:` + -/////////////` + :///////////////. + `://////.```-//////- + `://///:` .//////- + `//////: `//////: + .//////- `://///:` + -//////- `://///:` + -//////. ://////` + ://////` -//////. + `/////:` ./////: + .-::-` .:::-` + +.:://////////////////////////////////::. +//////////////////////////////////////// +.:////////////////////////////////////:. \ No newline at end of file diff --git a/src/logo/ascii/l/lede.txt b/src/logo/ascii/l/lede.txt new file mode 100644 index 0000000..efc7f7b --- /dev/null +++ b/src/logo/ascii/l/lede.txt @@ -0,0 +1,9 @@ + _________ + / /\ + / LE / \ + / DE / \ +/________/ LE \ +\ \ DE / + \ LE \ / + \ DE \ / + \________\/ \ No newline at end of file diff --git a/src/logo/ascii/l/lfs.txt b/src/logo/ascii/l/lfs.txt new file mode 100644 index 0000000..14d34d9 --- /dev/null +++ b/src/logo/ascii/l/lfs.txt @@ -0,0 +1,18 @@ +$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 .:...--. \ No newline at end of file diff --git a/src/logo/ascii/l/libreelec.txt b/src/logo/ascii/l/libreelec.txt new file mode 100644 index 0000000..13278c6 --- /dev/null +++ b/src/logo/ascii/l/libreelec.txt @@ -0,0 +1,20 @@ +$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.. \ No newline at end of file diff --git a/src/logo/ascii/l/lilidog.txt b/src/logo/ascii/l/lilidog.txt new file mode 100644 index 0000000..abc6c2f --- /dev/null +++ b/src/logo/ascii/l/lilidog.txt @@ -0,0 +1,16 @@ + +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+ \ No newline at end of file diff --git a/src/logo/ascii/l/limeos.txt b/src/logo/ascii/l/limeos.txt new file mode 100644 index 0000000..b819ffd --- /dev/null +++ b/src/logo/ascii/l/limeos.txt @@ -0,0 +1,23 @@ + ############ + ####### ####### + #### #### + ### ###### ###### ### + ### ######### ######### ### + ### ######## ######## ### + ### #### ###### ####### ### ### + ### ####### ##### ##### ###### ### + ## ######## ### ### ######## ## +### ########### ########### ### +## ############ ########### ## +## ## +## ############ ########### ## +### ########### ########### ### + ## ######## ### ### ######## ## + ### ###### ##### ##### ###### ### + ### ### ####### ####### ### ### + ### ######### ######### ### + ### ######### ######### ### + ### ###### ###### ### + #### #### + ####### ####### + ############## diff --git a/src/logo/ascii/l/lingmo.txt b/src/logo/ascii/l/lingmo.txt new file mode 100644 index 0000000..23651b4 --- /dev/null +++ b/src/logo/ascii/l/lingmo.txt @@ -0,0 +1,20 @@ + ..',;;;;;;,,.. + ..,;;;;;;;;;;;;;;;;,.. + .';;;;;;;;;;;;;;;;;;;;;;;;,. + .;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' + .;;;;;;;;;$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;;;;;;. + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;. + ;;;;;;;;;;;;;;;;;;;;;;. + ';;;;;;;;;;;;, + ';;'. diff --git a/src/logo/ascii/l/linspire.txt b/src/logo/ascii/l/linspire.txt new file mode 100644 index 0000000..48b2c8e --- /dev/null +++ b/src/logo/ascii/l/linspire.txt @@ -0,0 +1,12 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/l/linux.txt b/src/logo/ascii/l/linux.txt new file mode 100644 index 0000000..fc8a5b4 --- /dev/null +++ b/src/logo/ascii/l/linux.txt @@ -0,0 +1,12 @@ + $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##### \ No newline at end of file diff --git a/src/logo/ascii/l/linux_small.txt b/src/logo/ascii/l/linux_small.txt new file mode 100644 index 0000000..ff7a311 --- /dev/null +++ b/src/logo/ascii/l/linux_small.txt @@ -0,0 +1,7 @@ + $1___ + ($2.. $1\ + ($3<> $1| + /$2/ \ $1\ + ( $2| | $1/| +$3_$1/\ $2__)$1/$3_$1) +$3\/$1-____$3\/ \ No newline at end of file diff --git a/src/logo/ascii/l/linuxlite.txt b/src/logo/ascii/l/linuxlite.txt new file mode 100644 index 0000000..eb5185a --- /dev/null +++ b/src/logo/ascii/l/linuxlite.txt @@ -0,0 +1,20 @@ + ,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.. + . \ No newline at end of file diff --git a/src/logo/ascii/l/linuxlite_small.txt b/src/logo/ascii/l/linuxlite_small.txt new file mode 100644 index 0000000..2e0fc1f --- /dev/null +++ b/src/logo/ascii/l/linuxlite_small.txt @@ -0,0 +1,7 @@ + /\ + / \ + / $2/ $1/ +/ $2/ $1/ +\ $2\ $1\ + \_$2\$1_\ +$2 \ \ No newline at end of file diff --git a/src/logo/ascii/l/linuxmint.txt b/src/logo/ascii/l/linuxmint.txt new file mode 100644 index 0000000..c6be568 --- /dev/null +++ b/src/logo/ascii/l/linuxmint.txt @@ -0,0 +1,19 @@ + $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:=-´´´ diff --git a/src/logo/ascii/l/linuxmint2.txt b/src/logo/ascii/l/linuxmint2.txt new file mode 100644 index 0000000..c14451d --- /dev/null +++ b/src/logo/ascii/l/linuxmint2.txt @@ -0,0 +1,19 @@ + $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-' + ``-:::::-`` \ No newline at end of file diff --git a/src/logo/ascii/l/linuxmint_old.txt b/src/logo/ascii/l/linuxmint_old.txt new file mode 100644 index 0000000..e94eca0 --- /dev/null +++ b/src/logo/ascii/l/linuxmint_old.txt @@ -0,0 +1,16 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/l/linuxmint_small.txt b/src/logo/ascii/l/linuxmint_small.txt new file mode 100644 index 0000000..9d294e0 --- /dev/null +++ b/src/logo/ascii/l/linuxmint_small.txt @@ -0,0 +1,7 @@ + __________ +|_ \ + | $2| _____ $1| + | $2| | | | $1| + | $2| | | | $1| + | $2\__$2___/ $1| + \_________/ \ No newline at end of file diff --git a/src/logo/ascii/l/live_raizo.txt b/src/logo/ascii/l/live_raizo.txt new file mode 100644 index 0000000..926417d --- /dev/null +++ b/src/logo/ascii/l/live_raizo.txt @@ -0,0 +1,20 @@ + `......` + -+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: + `..` \ No newline at end of file diff --git a/src/logo/ascii/l/lliurex.txt b/src/logo/ascii/l/lliurex.txt new file mode 100644 index 0000000..27bad81 --- /dev/null +++ b/src/logo/ascii/l/lliurex.txt @@ -0,0 +1,16 @@ + ~. ........ + ::~~ +=: ~:========:~ + ..~:+oo===oo:.~+oooooooooooo+~ + .:+==ooooooooooo==ooooooooooooooo: + .:=oooooooooooooooooooooooooooooooo= +.. .=oooooooooooooooooooooooooooooooooo= +==. .+ooooooooooooooooooooooooooooooooooo= +~+:~:oooooooooooooooooooooooooooooooooooo: + ~ooooooooooooooooooooooooooooooooooooo=: + ~oooooooooooooooooooooooooooooooooo==:. + .+oooooooooooooooooooooooooo=~::::~.. + ~+oooooooooooooooooooooooo=: + .:=ooooooooooooooooooooo=: + .:==ooooooooooooooo==:. + ~~:===oooooo==+:~ + ...~~:~::.. diff --git a/src/logo/ascii/l/lmde.txt b/src/logo/ascii/l/lmde.txt new file mode 100644 index 0000000..9e45de8 --- /dev/null +++ b/src/logo/ascii/l/lmde.txt @@ -0,0 +1,17 @@ +$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:+-. + `--.`` \ No newline at end of file diff --git a/src/logo/ascii/l/locos.txt b/src/logo/ascii/l/locos.txt new file mode 100644 index 0000000..91bfe00 --- /dev/null +++ b/src/logo/ascii/l/locos.txt @@ -0,0 +1,18 @@ + $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. \ No newline at end of file diff --git a/src/logo/ascii/l/lubuntu.txt b/src/logo/ascii/l/lubuntu.txt new file mode 100644 index 0000000..49acd83 --- /dev/null +++ b/src/logo/ascii/l/lubuntu.txt @@ -0,0 +1,20 @@ + `.:/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/:.` diff --git a/src/logo/ascii/l/lunar.txt b/src/logo/ascii/l/lunar.txt new file mode 100644 index 0000000..de98565 --- /dev/null +++ b/src/logo/ascii/l/lunar.txt @@ -0,0 +1,13 @@ +`-. `-. + -ohys/-` `:+shy/` + -omNNdyo/` :+shmNNy/` + $3 - + /mMmo + hMMMN` + .NMMs + $1 -:+oooo+//: $3/MN$1. -///oooo+/-` + /:.` $3/$1 `.:/` +$3 __ + | | _ _ ___ ___ ___ + | |__| | | | .'| _| + |_____|___|_|_|__,|_| \ No newline at end of file diff --git a/src/logo/ascii/m.inc b/src/logo/ascii/m.inc new file mode 100644 index 0000000..b0a1456 --- /dev/null +++ b/src/logo/ascii/m.inc @@ -0,0 +1,436 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo M[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_MACARONIOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS2_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS3 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAINSAILOS + // MainsailOS + { + .names = { "MainsailOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS, + .colors = { + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAINSAILOS_SMALL + // MainsailOSSmall + { + .names = { "MainsailOS_small" }, + .type = FF_LOGO_LINE_TYPE_SMALL_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS_SMALL, + .colors = { + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAGEIA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAGEIA_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAGIX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAGPIEOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MANDRIVA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MANJARO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MANJARO_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MASSOS + // MassOS + { + .names = { "MassOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_MASSOS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MATUUSOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAUI + // MaUI + { + .names = { "MaUI" }, + .lines = FASTFETCH_DATATEXT_LOGO_MAUI, + .colors = { + FF_COLOR_FG_CYAN, + }, + .colorKeys = FF_COLOR_FG_CYAN, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MAUNA + // Mauna + { + .names = { "Mauna" }, + .lines = FASTFETCH_DATATEXT_LOGO_MAUNA, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MEOWIX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MER + // Mer + { + .names = { "Mer" }, + .lines = FASTFETCH_DATATEXT_LOGO_MER, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MIDNIGHTBSD + // MidnightBSD + { + .names = { "MidnightBSD" }, + .lines = FASTFETCH_DATATEXT_LOGO_MIDNIGHTBSD, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_LIGHT_BLACK, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MIDOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MIDOS_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MINIMAL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MINIX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MIRACLE_LINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MOS + // MOS + { + .names = { "MOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_MOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MSYS2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MX + // MX + { + .names = { "MX" }, + .lines = FASTFETCH_DATATEXT_LOGO_MX, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_CYAN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MX2 + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/m/macaronios.txt b/src/logo/ascii/m/macaronios.txt new file mode 100644 index 0000000..930a360 --- /dev/null +++ b/src/logo/ascii/m/macaronios.txt @@ -0,0 +1,11 @@ + .::-::::. :======---:. + .-=++++++++==-:. .:-++===--=**+=: +.=. ...:::::--=****+++=-:. -- +-: $2.-==-.$1 .::. :: +. $2:*%@@@@@#. .:::. + :#%%%%%@%#+. :*##%%%%#+. + *%%%%%%*. =. +*#%%%%%%%#: + *%%%%%%- +%%%%%%#@= + :%%%%%%#-. .:+%%%%%%%%+ + :*%%%%%%+. .-+*###*=: + :=+=-. \ No newline at end of file diff --git a/src/logo/ascii/m/macos.txt b/src/logo/ascii/m/macos.txt new file mode 100644 index 0000000..53afa49 --- /dev/null +++ b/src/logo/ascii/m/macos.txt @@ -0,0 +1,17 @@ + ..' + ,xNMM. + .OMMMMo + lMM" + .;loddo:. .olloddol;. + cKMMMMMMMMMMNWMMMMMMMMMM0: + $2.KMMMMMMMMMMMMMMMMMMMMMMMWd. + XMMMMMMMMMMMMMMMMMMMMMMMX. +$3;MMMMMMMMMMMMMMMMMMMMMMMM: +:MMMMMMMMMMMMMMMMMMMMMMMM: +$4.MMMMMMMMMMMMMMMMMMMMMMMMX. + kMMMMMMMMMMMMMMMMMMMMMMMMWd. + $5'XMMMMMMMMMMMMMMMMMMMMMMMMMMk + 'XMMMMMMMMMMMMMMMMMMMMMMMMK. + $6kMMMMMMMMMMMMMMMMMMMMMMd + ;KMMMMMMMWXXWMMMMMMMk. + "cooc*" "*coo'" \ No newline at end of file diff --git a/src/logo/ascii/m/macos2.txt b/src/logo/ascii/m/macos2.txt new file mode 100644 index 0000000..ea7c963 --- /dev/null +++ b/src/logo/ascii/m/macos2.txt @@ -0,0 +1,17 @@ + ..' + ,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'" \ No newline at end of file diff --git a/src/logo/ascii/m/macos2_small.txt b/src/logo/ascii/m/macos2_small.txt new file mode 100644 index 0000000..bb108fd --- /dev/null +++ b/src/logo/ascii/m/macos2_small.txt @@ -0,0 +1,7 @@ +$1 .:' + __ :'__ +$2 .'` `-' ``. +$3: .-' +$4: : + : `-; +$5 `.__.-.__.' \ No newline at end of file diff --git a/src/logo/ascii/m/macos3.txt b/src/logo/ascii/m/macos3.txt new file mode 100644 index 0000000..73d26c7 --- /dev/null +++ b/src/logo/ascii/m/macos3.txt @@ -0,0 +1,16 @@ + -:+:. + :++++. + /+++/. + .:-::- .+/:-\`\`.::- + .:/++++++/::::/++++++/::` + $2.:///////////////////////::` + /////////////////////////` +$3+++++++++++++++++++++++++` +++++++++++++++++++++++++ +$4ssssssssssssssssssssssss. +:ssssssssssssssssssssssss- + $5osssssssssssssssssssssssss` + `syyyyyyyyyyyyyyyyyyyyyyyyys` + $6`ossssssssssssssssssssssss + :ooooooooooooooooooo+. + `:++oo+/:- -:/+o+/- \ No newline at end of file diff --git a/src/logo/ascii/m/macos_small.txt b/src/logo/ascii/m/macos_small.txt new file mode 100644 index 0000000..2d90ee3 --- /dev/null +++ b/src/logo/ascii/m/macos_small.txt @@ -0,0 +1,7 @@ +$1 .:' + __ :'__ +$2 .'`__`-'__``. +$3:__________.-' +$4:_________: + :_________`-; +$5 `.__.-.__.' \ No newline at end of file diff --git a/src/logo/ascii/m/mageia.txt b/src/logo/ascii/m/mageia.txt new file mode 100644 index 0000000..cf4050c --- /dev/null +++ b/src/logo/ascii/m/mageia.txt @@ -0,0 +1,19 @@ + .°°. + °° .°°. + .°°°. °° + . . + °°° .°°°. + .°°°. '___' +$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,. \ No newline at end of file diff --git a/src/logo/ascii/m/mageia_small.txt b/src/logo/ascii/m/mageia_small.txt new file mode 100644 index 0000000..5bfffbb --- /dev/null +++ b/src/logo/ascii/m/mageia_small.txt @@ -0,0 +1,7 @@ + * + * + ** +$2 /\__/\ +/ \ +\ / + \____/ \ No newline at end of file diff --git a/src/logo/ascii/m/magix.txt b/src/logo/ascii/m/magix.txt new file mode 100644 index 0000000..3f19aa1 --- /dev/null +++ b/src/logo/ascii/m/magix.txt @@ -0,0 +1,19 @@ + $2@ + @@--=====@@ + @@--==@@ @@====+@ + @-@@ @==@ + @=@ + @=@$1 @=@ @-==== @=@$2 + @=@$1 @-===@==++@===+@$2 + @=@$1 @--====@@=====+@$2 +-=@$1 @--==========++@$2 +==$1 @--==========++@$2 @=@ +@==$1 @--=======@==++@$2 @=+@ + @==$1 @-==========++$2 @=@ + @==$1 @-=======@=%$2 @=@ + @==$1 @@@@@@$2 @=@ + @====@@@ @@===+% + @@=====@@==++++@@ + =#@=@ + @==@++@ + @@@ diff --git a/src/logo/ascii/m/magpieos.txt b/src/logo/ascii/m/magpieos.txt new file mode 100644 index 0000000..018597d --- /dev/null +++ b/src/logo/ascii/m/magpieos.txt @@ -0,0 +1,20 @@ + ;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. \ No newline at end of file diff --git a/src/logo/ascii/m/mainsailos.txt b/src/logo/ascii/m/mainsailos.txt new file mode 100644 index 0000000..7ca9c28 --- /dev/null +++ b/src/logo/ascii/m/mainsailos.txt @@ -0,0 +1,18 @@ + - + *%: + :%%%# + =%%%%%- + *%%%%%%# + :#%%%%%%%#. + -%%%%%%%%+ + *%%%%%%%%- : + .#%%%%%%%#. *%= + -%%%%%%%%+ :#%%%* + +%%%%%%%%- =%%%%%%#. + .#%%%%%%%#. *%%%%%%%%: + -%%%%%%%%* :#%%%%%%%#. + +%%%%%%%%- =%%%%%%%%+ :%*. + .#%%%%%%%#: *%%%%%%%%- +%%%%*: + :%%%%%%%%* :#%%%%%%%#. .*%%%%%%%* + +%%%%%%%%= -%%%%%%%%+ :%%%%%%%%* + .#%%%%%%%%: *%%%%%%%%- =%%%%%%%%= \ No newline at end of file diff --git a/src/logo/ascii/m/mainsailos_small.txt b/src/logo/ascii/m/mainsailos_small.txt new file mode 100644 index 0000000..c68fbbb --- /dev/null +++ b/src/logo/ascii/m/mainsailos_small.txt @@ -0,0 +1,7 @@ + -: + +%* + .#%%+ + -%%%: += + +%%#..#%%- + .#%%+ -%%%- += + -%%%- +%%#..#%%+ \ No newline at end of file diff --git a/src/logo/ascii/m/mandriva.txt b/src/logo/ascii/m/mandriva.txt new file mode 100644 index 0000000..635d1b1 --- /dev/null +++ b/src/logo/ascii/m/mandriva.txt @@ -0,0 +1,15 @@ +$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` \ No newline at end of file diff --git a/src/logo/ascii/m/manjaro.txt b/src/logo/ascii/m/manjaro.txt new file mode 100644 index 0000000..7f35123 --- /dev/null +++ b/src/logo/ascii/m/manjaro.txt @@ -0,0 +1,14 @@ +██████████████████ ████████ +██████████████████ ████████ +██████████████████ ████████ +██████████████████ ████████ +████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ +████████ ████████ ████████ \ No newline at end of file diff --git a/src/logo/ascii/m/manjaro_small.txt b/src/logo/ascii/m/manjaro_small.txt new file mode 100644 index 0000000..e6aceac --- /dev/null +++ b/src/logo/ascii/m/manjaro_small.txt @@ -0,0 +1,7 @@ +||||||||| |||| +||||||||| |||| +|||| |||| +|||| |||| |||| +|||| |||| |||| +|||| |||| |||| +|||| |||| |||| \ No newline at end of file diff --git a/src/logo/ascii/m/massos.txt b/src/logo/ascii/m/massos.txt new file mode 100644 index 0000000..1a06b69 --- /dev/null +++ b/src/logo/ascii/m/massos.txt @@ -0,0 +1,18 @@ +-+++/+++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/ \ No newline at end of file diff --git a/src/logo/ascii/m/matuusos.txt b/src/logo/ascii/m/matuusos.txt new file mode 100644 index 0000000..ea7a363 --- /dev/null +++ b/src/logo/ascii/m/matuusos.txt @@ -0,0 +1,20 @@ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░▒▓▓████▓▒▒░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░▒▓████████████▓░░░░░░░░░░░░░░ +░░░░░░░░░░░░▓████████████████▒░░░░░░░░░░░░ +░░░░░░░░░░░▓██████████████████▒░░░░░░░░░░░ +░░░░░░░░░░▓████▒▓███████▓▓█████░░░░░░░░░░░ +░░░░░░░░░░██████▓░▓████▒░██████▓░░░░░░░░░░ +░░░░░░░░░▒███████▓░▒▓▒░░████████░░░░░░░░░░ +░░░░░░░░░▒█████████▒░░░█████████░░░░░░░░░░ +░░░░░░░░░░██████████▓▒██████████░░░░░░░░░░ +░░░░░░░░░░▓████████████████████▒░░░░░░░░░░ +░░░░░░░░░░░███████████████████▓░░░░░░░░░░░ +░░░░░░░░░░░░█████████████████▓░░░░░░░░░░░░ +░░░░░░░░░░░░░▓██████████████▒░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░▒▓████████▓░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \ No newline at end of file diff --git a/src/logo/ascii/m/maui.txt b/src/logo/ascii/m/maui.txt new file mode 100644 index 0000000..8b7f9bb --- /dev/null +++ b/src/logo/ascii/m/maui.txt @@ -0,0 +1,20 @@ + `.-://////:--` + .:/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+:. + `.-://////:-.` \ No newline at end of file diff --git a/src/logo/ascii/m/mauna.txt b/src/logo/ascii/m/mauna.txt new file mode 100644 index 0000000..f5741e1 --- /dev/null +++ b/src/logo/ascii/m/mauna.txt @@ -0,0 +1,15 @@ + .. :-=++++=-: + .-+*+ -********* **= + =***= +******+ =---=+*. + +**** +****+ :-=++*++=-- + =****= **** :+***********+: + +****+ *** $2.-- $1+******- + =*****: *- $2:+=: $1=*****: +$2. $1+*****: : $2-+++ $1:****= +$2-+ $1:*****+ $2- ++++ $1:***- +$2-++=: $1=+****: $2:++ +++++ $1**+ +$2 +++++= $1-====-$2++++: +++++ $1*+ +$2 .=++++++++++++++: $2+++++= $1. +$2 .=+++++++++= :=+++++= + .:::--- -+++++++- + :-==++==-. \ No newline at end of file diff --git a/src/logo/ascii/m/meowix.txt b/src/logo/ascii/m/meowix.txt new file mode 100644 index 0000000..80022da --- /dev/null +++ b/src/logo/ascii/m/meowix.txt @@ -0,0 +1,10 @@ +$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** \ No newline at end of file diff --git a/src/logo/ascii/m/mer.txt b/src/logo/ascii/m/mer.txt new file mode 100644 index 0000000..e8aa0d9 --- /dev/null +++ b/src/logo/ascii/m/mer.txt @@ -0,0 +1,27 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/m/midnightbsd.txt b/src/logo/ascii/m/midnightbsd.txt new file mode 100644 index 0000000..39b3384 --- /dev/null +++ b/src/logo/ascii/m/midnightbsd.txt @@ -0,0 +1,16 @@ + ..:::'''':::.. + .:'''` `''':. + .:'` .::` `':: + :' .::' ':. + .:` .:::' `:. + :` :::: `: +:: ::::: :: +: ::::: :: : +: :::::: .:::' : +:: ::::::. : .:::::: :: + :. :::::::. : :::::::' .: + `:. `':::::::.'.:::::': .:' + `:. '':::::::::::::.: .:` + ':. `''::::::::''' .:' + '':.. ``'`` ..:'' + ''''::::::'''' \ No newline at end of file diff --git a/src/logo/ascii/m/midos.txt b/src/logo/ascii/m/midos.txt new file mode 100644 index 0000000..9c9b80e --- /dev/null +++ b/src/logo/ascii/m/midos.txt @@ -0,0 +1,20 @@ + ▁▂▃▄▅▆▇▇████▇▇▆▅▄▃▂▁ + ▃▅▇████████████████████▇▅▃ + ▂▅████████████████████████████▅▁ + ▗▟██████████████▛▀▀▜██████████████▙▖ + ▗▟████████████████▌ ▐████████████████▙▖ + ▗██████████████████▌ ▐██████████████████▖ + ▗███████████████████▌ ▐███████████████████▖ +▕███████████████▀▘ ██▌ ▐██ ▝▀███████████████▏ +▐██████████▛▀▀ ▂██▌ ▐██▂ ▀▀▜██████████▌ +███████▀▀ ▁▄▆████▌ ▐████▆▄▁ ▀▀███████ +█████ ▂▄▂ ▔▀▜████▌ ▐████▛▀▔ ▂▄▂ █████ +▐████▄▄▆█████▄▂ ▔▀██▌ ▐██▀▔ ▂▄█████▆▄▄████▌ +▕██████████████▇▄▂ ██▌ ▐██ ▂▄▇██████████████▏ + ▝███████████████████▌ ▐███████████████████▘ + ▝██████████████████▌ ▐██████████████████▘ + ▝▜████████████████▌ ▐████████████████▛▘ + ▝▜██████████████▙▄▄▟██████████████▛▘ + ▔▀▜██████████████████████████▛▀▔ + ▔▀▜████████████████████▛▀▔ + ▔▔▀▀▀▜██████▛▀▀▀▔▔ \ No newline at end of file diff --git a/src/logo/ascii/m/midos_old.txt b/src/logo/ascii/m/midos_old.txt new file mode 100644 index 0000000..4790708 --- /dev/null +++ b/src/logo/ascii/m/midos_old.txt @@ -0,0 +1,20 @@ + .:=+*#%%@@@@@@%%#*+=:. + .=*%@@@@@@@@@@@@@@@@@@@@@@%*=: + .=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+. + :*@@@@@@@@@@@@@@@@@$2++$1%@@@@@@@@@@@@@@@@#- + .*@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@#. + -%@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@- + -@@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@@= +:@@@@@@@@@@@@@@@@@%%@@@%$2..$1#@@@@#@@@@@@@@@@@@@@@@@: +*@@@@@@@@@@@@#$2*=:..$1*@@@%$2..$1#@@@#$2..:=+$1#@@@@@@@@@@@@* +%@@@@@@@#+$2=:...-+$1#@@@@@%$2..$1#@@@@@#$2+=:..:-$1+#@@@@@@@% +%@@@@@$2:..:=+-..-+$1#@@@@@%$2..$1#@@@@@%$2+-..-+=:..:$1@@@@@@ +*@@@@@$2*%$1@@@@@@%$2+-..$1#@@@%$2..$1#@@@%$2..-+$1#@@@@@@$2%*$1@@@@@* +:@@@@@@@@@@@@@@@@@$2#%$1@@@%$2..$1#@@@$2%#$1@@@@@@@@@@@@@@@@@: + =@@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@@= + -@@@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@@@= + .*@@@@@@@@@@@@@@@@@@%$2..$1#@@@@@@@@@@@@@@@@@@#: + :*@@@@@@@@@@@@@@@@@$2==$1%@@@@@@@@@@@@@@@@#-. + .+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+: + :=*%@@@@@@@@@@@@@@@@@@@@@@%*=:. + .-=+*#%%@@@@@@%%#*+=-. \ No newline at end of file diff --git a/src/logo/ascii/m/minimal.txt b/src/logo/ascii/m/minimal.txt new file mode 100644 index 0000000..7d74c99 --- /dev/null +++ b/src/logo/ascii/m/minimal.txt @@ -0,0 +1,18 @@ + ##### + ##### + ##### + ###### + ###### +##### +##### ##### + ###### ##### + ###### ##### + ###### ###### + ##### ###### + ##### ##### + ##### + ###### + ###### + ###### + ##### + ##### diff --git a/src/logo/ascii/m/minix.txt b/src/logo/ascii/m/minix.txt new file mode 100644 index 0000000..da32a8e --- /dev/null +++ b/src/logo/ascii/m/minix.txt @@ -0,0 +1,17 @@ +$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-` \ No newline at end of file diff --git a/src/logo/ascii/m/miracle_linux.txt b/src/logo/ascii/m/miracle_linux.txt new file mode 100644 index 0000000..e7dce8a --- /dev/null +++ b/src/logo/ascii/m/miracle_linux.txt @@ -0,0 +1,16 @@ + ,A + .### + .#' .#### .#. + r##: :#### ####. + +###; :#### ######C + :####: #### ,######".#. +.# :####. :### #####'.#####. +##: `####. ### ###'.########+. +#### `####::## ##'.#######+' + ####+.`###i## #',####:' + `+###MI`##### 'l###:' + `+####+`### ;#E' + `+###:## #' + `:### ' + '## + '; \ No newline at end of file diff --git a/src/logo/ascii/m/mos.txt b/src/logo/ascii/m/mos.txt new file mode 100644 index 0000000..cb1df1f --- /dev/null +++ b/src/logo/ascii/m/mos.txt @@ -0,0 +1,18 @@ + :--==========================--: +.-=================================. +-==================================- +==================================== +=======-....:==========:....:======= +=======: -======-. .======= +=======: :====- .======= +=======: :==: .======= +=======: .. .======= +=======: .: .: .======= +=======: .=- :=: .======= +=======: .===. .-==: .======= +=======: .==========: .======= +=======: :==========: :======= +==================================== +-=================================== +.==================================: + :--===========================-:. \ No newline at end of file diff --git a/src/logo/ascii/m/msys2.txt b/src/logo/ascii/m/msys2.txt new file mode 100644 index 0000000..32a7ed3 --- /dev/null +++ b/src/logo/ascii/m/msys2.txt @@ -0,0 +1,25 @@ +$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: \ No newline at end of file diff --git a/src/logo/ascii/m/mx.txt b/src/logo/ascii/m/mx.txt new file mode 100644 index 0000000..4e8f570 --- /dev/null +++ b/src/logo/ascii/m/mx.txt @@ -0,0 +1,17 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/m/mx2.txt b/src/logo/ascii/m/mx2.txt new file mode 100644 index 0000000..ea1fafa --- /dev/null +++ b/src/logo/ascii/m/mx2.txt @@ -0,0 +1,18 @@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@%*+--:------=+*%@@@@@@@@@@@@ +@@@@@@@@@#=. .-+#%@@@@@%#*+--=#@@@@@@@@@ +@@@@@@@+. .=%@@@@@@@@@@@@@@@@*-:+@@@@@@@ +@@@@@*. *@@@@@@@@@@@@@@@@@@@@@%-.*@@@@@ +@@@@- -@@@@@@@@@@@@@@@@@@@@@@@#: -@@@@ +@@@: -@@@@@@@=.*@@@@@@@@@@@@%- = :@@@ +@@= .@@@@@@@@%- :%@@@@@@@@@+ -%@# =@@ +@% +@@@@@@@@@@#. =@@@@@@*. .*@@@@. %@ +@+ *@@@@@@*..*@@+ *@@%- =@@@@@@- +@ +@= *@@@@%- -%@@- := -%@@@@@@@: +@ +@+ :@@@= +@@= .#@@@@@@@@% *@ +@% +*. .: *@@#: +@@: @@ +@@+ :%@- :- :: +@@ +@@@- .=@@= -@@@ +@@+. . +@@ +%=..:.................::...........:..-% +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \ No newline at end of file diff --git a/src/logo/ascii/m/mx_small.txt b/src/logo/ascii/m/mx_small.txt new file mode 100644 index 0000000..bbe3e75 --- /dev/null +++ b/src/logo/ascii/m/mx_small.txt @@ -0,0 +1,7 @@ + \\ / + \\/ + \\ + /\/ \\ + / \ /\ + / \/ \ +/__________\ \ No newline at end of file diff --git a/src/logo/ascii/n.inc b/src/logo/ascii/n.inc new file mode 100644 index 0000000..e45d33e --- /dev/null +++ b/src/logo/ascii/n.inc @@ -0,0 +1,266 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo N[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_NAMIB + // Namib + { + .names = { "Namib" }, + .lines = FASTFETCH_DATATEXT_LOGO_NAMIB, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NEBIOS + // NebiOS + { + .names = { "NebiOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_NEBIOS, + .colors = { + FF_COLOR_FG_RGB "255;95;95", + FF_COLOR_FG_RGB "252;146;84", + FF_COLOR_FG_RGB "248;196;72", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NEKOS + // Nekos + { + .names = { "Nekos" }, + .lines = FASTFETCH_DATATEXT_LOGO_NEKOS, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NEPTUNE + // Neptune + { + .names = { "Neptune" }, + .lines = FASTFETCH_DATATEXT_LOGO_NEPTUNE, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NETRUNNER + // NetRunner + { + .names = { "NetRunner" }, + .lines = FASTFETCH_DATATEXT_LOGO_NETRUNNER, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NEXALINUX + // NexaLinux + { + .names = { "nexalinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_NEXALINUX, + .colors = { + FF_COLOR_FG_LIGHT_BLUE, + FF_COLOR_FG_LIGHT_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NITRUX + // Nitrux + { + .names = { "Nitrux" }, + .lines = FASTFETCH_DATATEXT_LOGO_NITRUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NIXOS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NIXOS_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NIXOS_OLD + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NIXOS_OLD_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NIXOS_OLD_SMALL + // NixOS2 + { + .names = { "NixOS2" }, + .lines = FASTFETCH_DATATEXT_LOGO_NIXOS2, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NETBSD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NETBSD2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NETBSD_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NOBARA + // Nobara + { + .names = { "nobara" }, + .lines = FASTFETCH_DATATEXT_LOGO_NOBARA, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NOMADBSD + // NomadBSD + { + .names = { "nomadbsd" }, + .lines = FASTFETCH_DATATEXT_LOGO_NOMADBSD, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NUROS + // NurOS + { + .names = { "NurOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_NUROS, + .colors = { + FF_COLOR_FG_RGB "41;19;182", + FF_COLOR_FG_RGB "31;59;205", + FF_COLOR_FG_RGB "18;95;225", + FF_COLOR_FG_RGB "13;125;250", + }, + .colorKeys = FF_COLOR_FG_RGB "13;125;250", + .colorTitle = FF_COLOR_FG_RGB "41;19;182", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NURUNNER + // Nurunner + { + .names = { "Nurunner" }, + .lines = FASTFETCH_DATATEXT_LOGO_NURUNNER, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NUTYX + // NuTyX + { + .names = { "NuTyX" }, + .lines = FASTFETCH_DATATEXT_LOGO_NUTYX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_NETHYDRA + // NetHydra + { + .names = { "NetHydra" }, + .lines = FASTFETCH_DATATEXT_LOGO_NETHYDRA, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/n/namib.txt b/src/logo/ascii/n/namib.txt new file mode 100644 index 0000000..105d66e --- /dev/null +++ b/src/logo/ascii/n/namib.txt @@ -0,0 +1,20 @@ + .:+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+:. \ No newline at end of file diff --git a/src/logo/ascii/n/nebios.txt b/src/logo/ascii/n/nebios.txt new file mode 100644 index 0000000..eace0c1 --- /dev/null +++ b/src/logo/ascii/n/nebios.txt @@ -0,0 +1,19 @@ + $2:ddl. $3:O0x' + $2.dddddd: $3x00000c + $2:dddddddl $3.O000000k + $2cdddddddo $3.00000000c + $2,dddddddd. $3:000000000. + $1.: $2.dddddddd' $3l0000000000 + $1:oo. $2dddddddd; $3.00000000000 + $1oooo. $2ddddddddc $3.000000000k + $1ooooo, $2ldddddddl $300000000c + $1.oooooo; $2:dddddddo $3k000000. + $1'oooooooc $2'dddddddd. $3c00000 + $1:ooooooool $2.dddddddd' $3'0000 + $1ooooooooooo $2dddddddd; $3.00k + $1oooooooooo. $2ddddddddc $3.0: + $1ooooooooo. $2cdddddddl +$1.oooooooo. $2,dddddddo +$1:ooooooo. $2.dddddddd. +$1loooooo. $2.ddddddd: + $1coooo $2ldddd, diff --git a/src/logo/ascii/n/nekos.txt b/src/logo/ascii/n/nekos.txt new file mode 100644 index 0000000..4ddaf1c --- /dev/null +++ b/src/logo/ascii/n/nekos.txt @@ -0,0 +1,23 @@ + @@@@@ + @@@@@@@@@. + @@@@@@@@ @@@ + @@@@@@@@@@@ @@. + @@@@@@@@@@@@@ . + @@@@@@@@@@@@@@@@@ , + @@@@@@@@@@@@@@@@@@@ + @@@@@$2///$1@@@@@@@$2///$1@@@ + @@@@$2/***$1@@@@@@@$2**//$1@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@ + @@@/ /@@@@@@@@@/ /@@@ + @@@@@@ @@@$3██$1@@@@ @@@@@@ + @@@@@@/ /@$2██$3██$2██$1@@/ /@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + ##########%%%% + ##########%% %% + @ @@@#######@@%%% + @@@ @@@@####@@@@ % + @@@ @@@@@@@#@@@@@@@ + @@@ @@@@@@@@@@@@@@@ + @@@@ @@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ \ No newline at end of file diff --git a/src/logo/ascii/n/neptune.txt b/src/logo/ascii/n/neptune.txt new file mode 100644 index 0000000..28ca7a2 --- /dev/null +++ b/src/logo/ascii/n/neptune.txt @@ -0,0 +1,17 @@ + ./+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//. \ No newline at end of file diff --git a/src/logo/ascii/n/netbsd.txt b/src/logo/ascii/n/netbsd.txt new file mode 100644 index 0000000..6308771 --- /dev/null +++ b/src/logo/ascii/n/netbsd.txt @@ -0,0 +1,17 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/n/netbsd2.txt b/src/logo/ascii/n/netbsd2.txt new file mode 100644 index 0000000..81777a4 --- /dev/null +++ b/src/logo/ascii/n/netbsd2.txt @@ -0,0 +1,20 @@ + __,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 \ No newline at end of file diff --git a/src/logo/ascii/n/netbsd_small.txt b/src/logo/ascii/n/netbsd_small.txt new file mode 100644 index 0000000..b37056c --- /dev/null +++ b/src/logo/ascii/n/netbsd_small.txt @@ -0,0 +1,8 @@ +$2 \\$1`-______,----__ +$2 \\ $1 __,---`_ +$2 \\ $1 `.____ +$2 \\$1-______,----`- +$2 \\ +$2 \\ +$2 \\ +$2 \\ \ No newline at end of file diff --git a/src/logo/ascii/n/nethydra.txt b/src/logo/ascii/n/nethydra.txt new file mode 100644 index 0000000..d4b67c9 --- /dev/null +++ b/src/logo/ascii/n/nethydra.txt @@ -0,0 +1,11 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣶⣶⣦⣤⣄⡀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣦⣤⣌⣙⠻⢶⣄⠀ +⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠊⣤⣶⣶⣶⣦⣤⣼⡧⢤⡈⠙⠳⣦⡙⢷⣄⠀ +⠀⠀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⢾⡷⣢⢄⣠⣬⣭⣝⣿⣿⣄⠈⠓⢄⠈⠳⡄⠙⢷ +⢀⣾⣿⡿⠛⠉⠀⠀⠀⠀⠉⠛⠿⣿⣷⣦⡀⠀⠀⠀⡖⠉⣠⠶⢛⣼⣡⡍⢉⠍⠉⠙⠻⣿⣿⣧⠀⠀⠁⠀⠀⠀⠀⠳⡀ +⢸⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣿⣦⣄⠀⠀⠈⠁⡠⠟⣫⣵⡇⠀⠀⠀⠀⠀⢈⣿⣿ +⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣷⣄⠀⠚⠀⠞⠋⠀⠀⠀⠀⠀⠀⢀⣼⣿⡟ +⠸⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠙⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⢀⣠⣿⣿⠏ +⠀⠹⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⣀⣴⡞⠁⠀⠀⠀⠀⠈⠛⠿⣿⣿⣶⣶⣶⣶⣾⡿⠟⠋ +⠀⠀⠙⠿⣿⣷⣶⣦⣴⣶⣾⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠁ +⠀⠀⠀⠀⠈⠉⠛⠛⠛⠛⠉ diff --git a/src/logo/ascii/n/netrunner.txt b/src/logo/ascii/n/netrunner.txt new file mode 100644 index 0000000..26e8ea1 --- /dev/null +++ b/src/logo/ascii/n/netrunner.txt @@ -0,0 +1,20 @@ + .: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+-` \ No newline at end of file diff --git a/src/logo/ascii/n/nexalinux.txt b/src/logo/ascii/n/nexalinux.txt new file mode 100644 index 0000000..c1ff9cf --- /dev/null +++ b/src/logo/ascii/n/nexalinux.txt @@ -0,0 +1,19 @@ + + **** + ********** + ****************** + ************************ + ****************************** + *************** *************** + ************ **** #*********** + ********* ********** ********* + ****** #****************# #***** + * ************************ *# + ****************************** + #************** **************# + ************ **** ************ + ********* ********** ********* + *** **************** *** + ********************** + ******************** + ***** ***** \ No newline at end of file diff --git a/src/logo/ascii/n/nitrux.txt b/src/logo/ascii/n/nitrux.txt new file mode 100644 index 0000000..571fa74 --- /dev/null +++ b/src/logo/ascii/n/nitrux.txt @@ -0,0 +1,18 @@ +`:/. +`/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+- \ No newline at end of file diff --git a/src/logo/ascii/n/nixos.txt b/src/logo/ascii/n/nixos.txt new file mode 100644 index 0000000..b420805 --- /dev/null +++ b/src/logo/ascii/n/nixos.txt @@ -0,0 +1,20 @@ + $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▀▀▀▘ diff --git a/src/logo/ascii/n/nixos2.txt b/src/logo/ascii/n/nixos2.txt new file mode 100644 index 0000000..0e875b3 --- /dev/null +++ b/src/logo/ascii/n/nixos2.txt @@ -0,0 +1,17 @@ +$1 __ $2____ __ +$1 / \ $2\ \ / \ +$1 \ \ $2\ \/ / +$1 ___\ \___$2\ / +$1 / $2\ / $1/\ +$1 /______________$2\ \ $1/ \ +$2 / / $2\ \$1/ / +$2 ______/ / $2\ $1/ /___ +$2/ / $2\$1/ \ +$2\____ /$1\ $1/ ______/ +$2 / /$1 \ $1/ / +$2 / /$1\ \$2______$1/$2___$1/$2_____ +$2 \ /$1 \ \$2 / +$2 \/$1 / \$2____ ____/ + $1 / \$2 \ \ + $1/ /\ \$2 \ \ + $1\__/ \___\$2 \__/ \ No newline at end of file diff --git a/src/logo/ascii/n/nixos_old.txt b/src/logo/ascii/n/nixos_old.txt new file mode 100644 index 0000000..3b26708 --- /dev/null +++ b/src/logo/ascii/n/nixos_old.txt @@ -0,0 +1,28 @@ +$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 ¯¯¯¯ \ No newline at end of file diff --git a/src/logo/ascii/n/nixos_old_small.txt b/src/logo/ascii/n/nixos_old_small.txt new file mode 100644 index 0000000..f6af0a6 --- /dev/null +++ b/src/logo/ascii/n/nixos_old_small.txt @@ -0,0 +1,7 @@ +$1 \\ $2\\ // +$1 ==\\__$2\\/ $1// +$2 // $2\\$1// +$2==// $1//== +$2 //$1\\$2___$1// +$2// $1/\\ $2\\== + $1// \\ $2\\ \ No newline at end of file diff --git a/src/logo/ascii/n/nixos_small.txt b/src/logo/ascii/n/nixos_small.txt new file mode 100644 index 0000000..8c3b2a7 --- /dev/null +++ b/src/logo/ascii/n/nixos_small.txt @@ -0,0 +1,7 @@ +$1 ▗▄ $2▗▄ ▄▖ +$1 ▄▄🬸█▄▄▄$2🬸█▛ $3▃ +$6 ▟▛ $2▜$3▃▟🬕 +$6🬋🬋🬫█ $3█🬛🬋🬋 +$6 🬷▛🮃$5▙ $3▟▛ +$6 🮃 $5▟█🬴$4▀▀▀█🬴▀▀ + $5▝▀ ▀▘ $4▀▘ \ No newline at end of file diff --git a/src/logo/ascii/n/nobara.txt b/src/logo/ascii/n/nobara.txt new file mode 100644 index 0000000..a0fadc2 --- /dev/null +++ b/src/logo/ascii/n/nobara.txt @@ -0,0 +1,17 @@ +⢀⣤⣴⣶⣶⣶⣦⣤⡀⠀⣀⣠⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣤⣤⣀⡀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠉⠁⠀⠀⠉⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⢀⣀⣀⡀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⢠⣾⣿⣿⣿⣿⣷⡄⠀⠀⠀⠻⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⣀⣀⣬⣽⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠈⠻⢿⣿⣿⡿⠟⠁⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠛⠉⠉⠛⠛⢿⣿⣿⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿ +⠘⢿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⠟⠁ + ⠈⠙⠛⠛⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠛⠉⠁ \ No newline at end of file diff --git a/src/logo/ascii/n/nomadbsd.txt b/src/logo/ascii/n/nomadbsd.txt new file mode 100644 index 0000000..a687e3f --- /dev/null +++ b/src/logo/ascii/n/nomadbsd.txt @@ -0,0 +1,13 @@ + _======__ + (===============\ + (===================\ + _ _---__ + (= ====- + (= ====== + (== ====== + (== ====== + (==\ \=-_ _=/ /====/ + (==\ \========/ /====/ /====-_ + (==\ \=====/ /==/ /===-- +/================/ /===- +\===========/ \ No newline at end of file diff --git a/src/logo/ascii/n/nuros.txt b/src/logo/ascii/n/nuros.txt new file mode 100644 index 0000000..28b2fac --- /dev/null +++ b/src/logo/ascii/n/nuros.txt @@ -0,0 +1,8 @@ + ⢀⣤⣶⠿⠿⠿⠿⢷⣶⣄⡀ + ⣰⡿⠋⠁⢀⣠⣤⣀⡀⠈⠙⢿⣄ +$2⣸⡿⠁⢀⣾⣿⣿⣿⣿⣿⣷⡀⠈⢿⡆ +⣿⠃ ⣾⣿⣿⣿⡿⣟⣾⡽⣷ ⠸⣿ +$3⣿⡄ ⢿⣿⣿⣟⣿⣻⣽⣻⡽ ⢰⣟ +⠸⣷⡀⠈⠿⣷⣻⢷⣻⣞⠷⠁⢀⣾⠇ +$4 ⠙⣷⣄⡀⠈⠉⠉⠁ ⢀⣤⡿⠋ + ⠙⠿⢷⣶⣶⣶⠾⠟⠋ diff --git a/src/logo/ascii/n/nurunner.txt b/src/logo/ascii/n/nurunner.txt new file mode 100644 index 0000000..0546233 --- /dev/null +++ b/src/logo/ascii/n/nurunner.txt @@ -0,0 +1,19 @@ + ,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. \ No newline at end of file diff --git a/src/logo/ascii/n/nutyx.txt b/src/logo/ascii/n/nutyx.txt new file mode 100644 index 0000000..d0ca44d --- /dev/null +++ b/src/logo/ascii/n/nutyx.txt @@ -0,0 +1,23 @@ + . + . + ... + ... + .... .........--. + ..-++-----....--++++++---. + .-++++++-. .-++++++++++++-----.. + .--... .++..-+++--.....-++++++++++--.. + . .-+-. .**- .... ..-+----.. + .+++. .*+. + -++-----. + .+++++- ++. .*+. .....-+++-----. + -+++-++. .+. .-+***++***++--++++. . + -+-. -- -. -*- ...... ..--. +.-. .+- . -+. +. .+- +. + -- -- + -+----. .- + -++-.+. . + .++. -- + +. ----. + . .+. .. + - . + . \ No newline at end of file diff --git a/src/logo/ascii/o.inc b/src/logo/ascii/o.inc new file mode 100644 index 0000000..b1840dc --- /dev/null +++ b/src/logo/ascii/o.inc @@ -0,0 +1,488 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo O[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_OBARUN + // Obarun + { + .names = { "Obarun" }, + .lines = FASTFETCH_DATATEXT_LOGO_OBARUN, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OBREVENGE + // OBRevenge + { + .names = { "OBRevenge" }, + .lines = FASTFETCH_DATATEXT_LOGO_OBREVENGE, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OBSIDIANOS + // ObsidianOS + { + .names = { "ObsidianOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_OBSIDIANOS, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_CYAN, + FF_COLOR_FG_LIGHT_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OMNIOS + // OmniOS + { + .names = { "OmniOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_OMNIOS, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_LIGHT_BLACK, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENKYLIN + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENBSD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENBSD_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENEULER + // OpenEuler + { + .names = { "OpenEuler" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENEULER, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENINDIANA + // OpenIndiana + { + .names = { "OpenIndiana" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENINDIANA, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENMAMBA + // OpenMamba + { + .names = { "OpenMamba" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENMAMBA, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENRUYI + // openRuyi + { + .names = { "openRuyi" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENRUYI, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_LIGHT_YELLOW, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSTAGE + // OpenStage + { + .names = { "OpenStage" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENSTAGE, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_MICROOS + // openSuseMicroOS + { + .names = { "opensuse-microos" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_MICROOS, + .colors = { + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_SLOWROLL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENMANDRIVA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENWRT + // OpenWrt + { + .names = { "openwrt" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENWRT, + .colors = { + FF_COLOR_FG_DEFAULT, + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENWRT_SMALL + // OpenWrtSmall + { + .names = { "openwrt_small" }, + .type = FF_LOGO_LINE_TYPE_SMALL_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_OPENWRT_SMALL, + .colors = { + FF_COLOR_FG_DEFAULT, + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENWRT_OLD + // OpenWrtOld + { + .names = { "openwrt_old" }, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_OPENWRT_OLD, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPNSENSE + // OPNsense + { + .names = { "OPNsense" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPNSENSE, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "202", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ORACLE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ORCHID + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ORCHID_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OREON + // Oreon + { + .names = { "Oreon" }, + .lines = FASTFETCH_DATATEXT_LOGO_OREON, + .colors = { + FF_COLOR_FG_DEFAULT, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ORIGAMI + // Origami + { + .names = { "Origami" }, + .lines = FASTFETCH_DATATEXT_LOGO_ORIGAMI, + .colors = { + FF_COLOR_FG_RGB "185;210;235", // $1 sail — light sky blue + FF_COLOR_FG_RGB "40;90;160", // $2 hull — deeper navy + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ORIGAMI_SMALL + // OrigamiSmall + { + .names = { "Origami_small" }, + .type = FF_LOGO_LINE_TYPE_SMALL_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_ORIGAMI_SMALL, + .colors = { + FF_COLOR_FG_RGB "185;210;235", // $1 sail — light sky blue + FF_COLOR_FG_RGB "40;90;160", // $2 hull — deeper navy + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OS2WARP + // OS/2 Warp + { + .names = { "OS2Warp" }, + .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_LIGHT_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OS_ELBRUS + // OS_Elbrus + { + .names = { "OS Elbrus" }, + .lines = FASTFETCH_DATATEXT_LOGO_OS_ELBRUS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OSMC + // OSMC + { + .names = { "OSMC", "Open Source Media Center" }, + .lines = FASTFETCH_DATATEXT_LOGO_OSMC, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_MACOS_SMALL + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/o/obarun.txt b/src/logo/ascii/o/obarun.txt new file mode 100644 index 0000000..e2428c4 --- /dev/null +++ b/src/logo/ascii/o/obarun.txt @@ -0,0 +1,19 @@ + ,;::::; + ;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; \ No newline at end of file diff --git a/src/logo/ascii/o/obrevenge.txt b/src/logo/ascii/o/obrevenge.txt new file mode 100644 index 0000000..84f833c --- /dev/null +++ b/src/logo/ascii/o/obrevenge.txt @@ -0,0 +1,18 @@ + __ __ + _@@@@ @@@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 \ No newline at end of file diff --git a/src/logo/ascii/o/obsidianos.txt b/src/logo/ascii/o/obsidianos.txt new file mode 100644 index 0000000..b195a1f --- /dev/null +++ b/src/logo/ascii/o/obsidianos.txt @@ -0,0 +1,16 @@ +$2 *+++++++# +$2#*******###$3 @ +$2#*******###$3 @ +$2#*******###$3 @ @ +$2#*******##%$3 @ +$3@@@@@@@@@ @ @ @ + @ @ @ @ @ + @ @ @ @ @ + @ @ @ @ + @ @ @ @ @ + @ @ @@@@@@@@@ + @$1#*******##%$3 + @$1 ##########%$3 + @$1 ##########%$3 + @$1 ##########% + ########% diff --git a/src/logo/ascii/o/omnios.txt b/src/logo/ascii/o/omnios.txt new file mode 100644 index 0000000..a1512dc --- /dev/null +++ b/src/logo/ascii/o/omnios.txt @@ -0,0 +1,8 @@ + ____ __ __ _ _ _ + / __ \ | \/ || \ | || | +| | | || || \| || | +| |__| || |\/| || , `$2_$1||$2_$1| $2____$1 + \____/ |_| |_||_|\$2/ __ \ / ___| + | | | ||(__ + $3community$2 | |__| | ___)| + $3edition$2 \____/ |____/ \ No newline at end of file diff --git a/src/logo/ascii/o/openbsd.txt b/src/logo/ascii/o/openbsd.txt new file mode 100644 index 0000000..f65db67 --- /dev/null +++ b/src/logo/ascii/o/openbsd.txt @@ -0,0 +1,23 @@ +$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 | `. ` + |/`. `-. `._) + / .-.\ + \ ( `\ + `.\ \ No newline at end of file diff --git a/src/logo/ascii/o/openbsd_small.txt b/src/logo/ascii/o/openbsd_small.txt new file mode 100644 index 0000000..b20b4c6 --- /dev/null +++ b/src/logo/ascii/o/openbsd_small.txt @@ -0,0 +1,7 @@ + _____ + \- -/ + \_/ \ + | $2O O$1 | + |_ < ) 3 ) + / \ / + /-_____-\ \ No newline at end of file diff --git a/src/logo/ascii/o/openeuler.txt b/src/logo/ascii/o/openeuler.txt new file mode 100644 index 0000000..344d432 --- /dev/null +++ b/src/logo/ascii/o/openeuler.txt @@ -0,0 +1,23 @@ + `.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.` + `....` \ No newline at end of file diff --git a/src/logo/ascii/o/openindiana.txt b/src/logo/ascii/o/openindiana.txt new file mode 100644 index 0000000..d9b7e56 --- /dev/null +++ b/src/logo/ascii/o/openindiana.txt @@ -0,0 +1,16 @@ +$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+/-.` \ No newline at end of file diff --git a/src/logo/ascii/o/openkylin.txt b/src/logo/ascii/o/openkylin.txt new file mode 100644 index 0000000..288e2b5 --- /dev/null +++ b/src/logo/ascii/o/openkylin.txt @@ -0,0 +1,18 @@ + + /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] \ No newline at end of file diff --git a/src/logo/ascii/o/openmamba.txt b/src/logo/ascii/o/openmamba.txt new file mode 100644 index 0000000..8730bbe --- /dev/null +++ b/src/logo/ascii/o/openmamba.txt @@ -0,0 +1,21 @@ + ````` + .-/+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+/:.` + `````` \ No newline at end of file diff --git a/src/logo/ascii/o/openmandriva.txt b/src/logo/ascii/o/openmandriva.txt new file mode 100644 index 0000000..7a036bb --- /dev/null +++ b/src/logo/ascii/o/openmandriva.txt @@ -0,0 +1,22 @@ + `````` + `-:/+++++++//:-.` + .:+++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+:-` + ``....`` \ No newline at end of file diff --git a/src/logo/ascii/o/openruyi.txt b/src/logo/ascii/o/openruyi.txt new file mode 100644 index 0000000..525bb66 --- /dev/null +++ b/src/logo/ascii/o/openruyi.txt @@ -0,0 +1,12 @@ + ⣠⣴⣶⣾⣿⣿⣷⣶⣦⣄ + ⣠⣾⣿⡿⠟⠛⠉⠉⠛⠻⢿⣿⣷⣄ + ⣼⣿⡿⠋ ⠙⢿⣿⣧ + ⣀⣠⣿⣿⠁ ⠈⣿⣿⣄⣀ + ⢀⣴⣾⣿⣿⡿⠟$2 ⢀⣀⣀⣄⣀⣀ $1⠻⢿⣿⣿⣷⣦⡀ +$1 ⣰⣿⣿⠟⠉ $2 ⣠⣾⣿⡿⠿⠿⠿⢿⣿⣦⡀ $1 ⠉⠻⣿⣿⣆ +$1⢰⣿⡿⠁ $2⢰⣿⡿⠁$3⢀⣠⣄⡀$2 ⠙⣿⣷$1 ⠘⣿⣿⡆ +$1⣿⣿⡇ $2⠙⣿⠁$3⣴⣿⡿⠿⠟$2 ⢀⣿⣿$1 ⢸⣿⣿ +$1⣿⣿⡇ $3 ⣿⡟$2⢀⣀⣀⣴⣾⣿⠋ $1 ⢸⣿⣿ +$1⠸⣿⣷⡀ $3 ⣿⡇$2⠘⢿⣿⣿⡋ $1 ⢠⣾⣿⠇ +$1 ⠙⣿⣿⣶⣤⣀⣀⣀$3⣀⣀⣰⣿⡇$2 ⠙⢿⣿⣦⣀⣀$1⣀⣀⣤⣶⣿⣿⠋ +$1 ⠙⠿⢿⣿⣿⣿$3⣿⣿⣿⠟ $2 ⠉⠻⣿⣿$1⣿⣿⡿⠟⠋ diff --git a/src/logo/ascii/o/openstage.txt b/src/logo/ascii/o/openstage.txt new file mode 100644 index 0000000..5ab943a --- /dev/null +++ b/src/logo/ascii/o/openstage.txt @@ -0,0 +1,17 @@ + /(/ + .(((((((, + /(((((((((/ + .(((((/,/(((((, + *(((((* ,(((((/ + (((((* .*/(( + *((((/ (//(/* + /((((* ((((((((((, + . /((((* (((((((((((((. + ((. *((((/ ,(((((((( + ,(((/ (((((/ ** ,((((((* + /(((((. .(((((/ //(((* *(((((/ + .(((((, ((/ .(((((/. .(((((, + /((((* ,(((((((/ ,((((( + /(((((((((((((((((((/. /(((((((((/ + /(((((((((((((((((, /(((((((((((/ + */(((((//*. */((/(/(/* \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse.txt b/src/logo/ascii/o/opensuse.txt new file mode 100644 index 0000000..a407c4b --- /dev/null +++ b/src/logo/ascii/o/opensuse.txt @@ -0,0 +1,18 @@ + $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:^' \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_leap.txt b/src/logo/ascii/o/opensuse_leap.txt new file mode 100644 index 0000000..fa96b3f --- /dev/null +++ b/src/logo/ascii/o/opensuse_leap.txt @@ -0,0 +1,18 @@ + ==== + ====== + ==== ====+ + +==== +==== + +===+ ==== + ==== ==== ++=== +==== +==== +==== + ===== ==== + +===+ ===== +==+ ===== +===+ === +==== ==== ===== ===== + ==== ======= ==== + ==== === ==== + ====+ ==== + ==== ===== + ====== + == \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_leap_old.txt b/src/logo/ascii/o/opensuse_leap_old.txt new file mode 100644 index 0000000..e0d8cdd --- /dev/null +++ b/src/logo/ascii/o/opensuse_leap_old.txt @@ -0,0 +1,16 @@ + .-++:. + ./oooooo/- + `:oooooooooooo:. + -+oooooooooooooooo+-` + ./oooooooooooooooooooooo/- + :oooooooooooooooooooooooooo: + ` `-+oooooooooooooooooooo/- ` + `:oo/- .:ooooooooooooooo+:` `-+oo/. +`/oooooo:. -/oooooooooo/. ./oooooo/. + `:+ooooo+-` `:+oooo+- `:oooooo+:` + .:oooooo/. .::` -+oooooo/. + -/oooooo:. ./oooooo+- + `:+ooooo+-:+oooooo:` + ./oooooooooo/. + -/oooo+:` + `:/. \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_microos.txt b/src/logo/ascii/o/opensuse_microos.txt new file mode 100644 index 0000000..544087d --- /dev/null +++ b/src/logo/ascii/o/opensuse_microos.txt @@ -0,0 +1,10 @@ + ⣀⣠⣴⣶⣶⣿⣿⣿⣿⣶⣶⣦⣄⣀ + ⢀⣴⣾⣿⠿⠛⠉⠉ ⠉⠉⠛⠿⣿⣷⣦⡀ + ⣴⣿⡿⠋ ⠙⢿⣿⣦ + ⣾⣿⡟ ⣠⣴⣶⣿⣿⣶⣦⣄ ⢻⣿⣷ +⣠⣤⣤⣤⣤⣤⣤⣼⣿⣿ ⣼⣿⡟⠉ ⠉⢻⣿⣧ ⣿⣿⣧⣤⣤⣤⣤⣤⣤⣄ +⠙⠛⠛⠛⠛⠛⠛⢻⣿⣿ ⢻⣿⣧⡀ ⢀⣼⣿⡟ ⣿⣿⡟⠛⠛⠛⠛⠛⠛⠋ + ⢿⣿⣇ ⠙⠿⣿⣿⣿⣿⠿⠋ ⣸⣿⡿ + ⠈⢻⣿⣧⣀ ⣀⣾⣿⡟⠁ + ⠙⠻⣿⣷⣦⣄⣀ ⣀⣠⣴⣾⣿⠟⠋ + ⠉⠛⠿⢿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠉ \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_slowroll.txt b/src/logo/ascii/o/opensuse_slowroll.txt new file mode 100644 index 0000000..e63697f --- /dev/null +++ b/src/logo/ascii/o/opensuse_slowroll.txt @@ -0,0 +1,20 @@ + _,......,_ + _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@??^' \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_small.txt b/src/logo/ascii/o/opensuse_small.txt new file mode 100644 index 0000000..0099a02 --- /dev/null +++ b/src/logo/ascii/o/opensuse_small.txt @@ -0,0 +1,7 @@ + _______ +__| __ \ + / .\ \ + \__/ | + _______| + \_______ +__________/ \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_tumbleweed.txt b/src/logo/ascii/o/opensuse_tumbleweed.txt new file mode 100644 index 0000000..111a61f --- /dev/null +++ b/src/logo/ascii/o/opensuse_tumbleweed.txt @@ -0,0 +1,22 @@ + ,..., + .,: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"' \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_tumbleweed2.txt b/src/logo/ascii/o/opensuse_tumbleweed2.txt new file mode 100644 index 0000000..953bd12 --- /dev/null +++ b/src/logo/ascii/o/opensuse_tumbleweed2.txt @@ -0,0 +1,19 @@ + ⣀⣤⣤⣶⣶⣶⣶⣶⣤⣄⡀ + ⣠⣾⣿⣿⡿⠿⠛⠛⣿⣿⣿⣿⣿⣶⣄ + ⢠⣾⣿⣿⠛⠁ ⣿⣿⣿⠉⠻⣿⣿⣦ +⢀⣿⣿⡿⠁ ⣿⣿⣿ ⠙⣿⣿⣧ +⢸⣿⣿⠃ ⣿⣿⣿ ⢹⣿⣿⡀ +⢸⣿⣿⣤⣤⣤⣤⣤⣤⣤⣤⣿⣿⣿ ⢸⣿⣿⡇ +⠘⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿ ⢸⣿⣿⡇ + ⠹⣿⣿⣷⡀ ⢸⣿⣿⡇ + ⠙⢿⣿⣿⣷⣤⣄⣀⣀⣀⣀⣀⣀⣀⣀⣸⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⣀ + ⠉⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀ + ⠈⠉⠉⠉⠉⠉⠉⠉⠉⢹⣿⣿⡏⠉⠉⠉⠉⠉⠉⠉⠉⠙⠛⢿⣿⣿⣷⡀ + ⢸⣿⣿⡇ ⠈⢻⣿⣿⣆ + ⢸⣿⣿⡇ ⢰⣶⣶⣶⣶⣶⣶⣶⣶⣶⣿⣿⣿⡄ + ⢸⣿⣿⡇ ⢸⣿⣿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⡇ + ⠘⣿⣿⡇ ⢸⣿⣿ ⢰⣿⣿⡇ + ⢻⣿⣿⡄ ⢸⣿⣿ ⢀⣾⣿⡿⠁ + ⢻⣿⣿⣦⣀⢸⣿⣿ ⢀⣴⣿⣿⡿⠁ + ⠙⢿⣿⣿⣿⣿⣿⣤⣴⣶⣾⣿⣿⡿⠋ + ⠈⠙⠻⠿⠿⠿⠿⠿⠛⠋⠁ \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_tumbleweed_old.txt b/src/logo/ascii/o/opensuse_tumbleweed_old.txt new file mode 100644 index 0000000..fefdeee --- /dev/null +++ b/src/logo/ascii/o/opensuse_tumbleweed_old.txt @@ -0,0 +1,13 @@ + ...... + .,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' \ No newline at end of file diff --git a/src/logo/ascii/o/opensuse_tumbleweed_small.txt b/src/logo/ascii/o/opensuse_tumbleweed_small.txt new file mode 100644 index 0000000..d7c50d7 --- /dev/null +++ b/src/logo/ascii/o/opensuse_tumbleweed_small.txt @@ -0,0 +1,9 @@ + .oooo. +o o o +ooooo oo +o oo + 'oooooooooooo. + oo o + oo ooooo + o o o + 'oooo' \ No newline at end of file diff --git a/src/logo/ascii/o/openwrt.txt b/src/logo/ascii/o/openwrt.txt new file mode 100644 index 0000000..cbb4c0b --- /dev/null +++ b/src/logo/ascii/o/openwrt.txt @@ -0,0 +1,18 @@ + ..,,.. + ,;odOMMMMMMMMObo:. + ,odOMMMMMO""''""OMMMMMObo. +.dMMMM" .,,. "OMMMb, + '0" ,;ddMMMMMMMMbb:. "0' + oOMMMMO""''""OMMMMOo + $2;. $1'0" .,,. "0' $2.: + .OMM* $1.odMMMMMMbo. $2*MMO. + MMMO $1"0"````"0" $2OMMM +iOMM $1.oo. $2MMMi +OMMl $1:MMMM: $2lMMO +iMMM $1'oo' $2MMMi + OMMb $2dMMO + 'OMMO. ,OMMO' + "MMMb. ,dMMM" + "MMMMbgo-,,-ogdMMMM" + "*MMMMMMMMMM*" + `'""'` \ No newline at end of file diff --git a/src/logo/ascii/o/openwrt_old.txt b/src/logo/ascii/o/openwrt_old.txt new file mode 100644 index 0000000..47e4e19 --- /dev/null +++ b/src/logo/ascii/o/openwrt_old.txt @@ -0,0 +1,9 @@ + _______ +| |.-----.-----.-----. +| - || _ | -__| | +|_______|| __|_____|__|__| + |__| + ________ __ +| | | |.----.| |_ +| | | || _|| _| +|________||__| |____| \ No newline at end of file diff --git a/src/logo/ascii/o/openwrt_small.txt b/src/logo/ascii/o/openwrt_small.txt new file mode 100644 index 0000000..328cbeb --- /dev/null +++ b/src/logo/ascii/o/openwrt_small.txt @@ -0,0 +1,9 @@ + ..,.. + ,odM"""""Mbo, + `.odMMMMMbo.` + $2d,$1" ,mmm, "$2,b +iM $1" . " $2Mi +Ml $1dMb $2lM +'M, $1' $2.M' + "0b. ,d0" + `"WWWWW"` \ No newline at end of file diff --git a/src/logo/ascii/o/opnsense.txt b/src/logo/ascii/o/opnsense.txt new file mode 100644 index 0000000..618d717 --- /dev/null +++ b/src/logo/ascii/o/opnsense.txt @@ -0,0 +1,20 @@ + .''''''''''''''''''''''''''''''''''' + 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:. \ No newline at end of file diff --git a/src/logo/ascii/o/oracle.txt b/src/logo/ascii/o/oracle.txt new file mode 100644 index 0000000..988ebea --- /dev/null +++ b/src/logo/ascii/o/oracle.txt @@ -0,0 +1,11 @@ + `-/+++++++++++++++++/-.` + `/syyyyyyyyyyyyyyyyyyyyyyys/. + :yyyyo/-...............-/oyyyy/ + /yyys- .oyyy+ +.yyyy` `syyy- +:yyyo /yyy/ +.yyyy` `syyy- + /yyys. .oyyyo + /yyyyo:-...............-:oyyyy/` + `/syyyyyyyyyyyyyyyyyyyyyyys+. + `.:/+ooooooooooooooo+/:.` \ No newline at end of file diff --git a/src/logo/ascii/o/orchid.txt b/src/logo/ascii/o/orchid.txt new file mode 100644 index 0000000..bf084f8 --- /dev/null +++ b/src/logo/ascii/o/orchid.txt @@ -0,0 +1,20 @@ +$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-. + .==. \ No newline at end of file diff --git a/src/logo/ascii/o/orchid_small.txt b/src/logo/ascii/o/orchid_small.txt new file mode 100644 index 0000000..6af179f --- /dev/null +++ b/src/logo/ascii/o/orchid_small.txt @@ -0,0 +1,13 @@ + $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#- + :##: \ No newline at end of file diff --git a/src/logo/ascii/o/oreon.txt b/src/logo/ascii/o/oreon.txt new file mode 100644 index 0000000..73fce33 --- /dev/null +++ b/src/logo/ascii/o/oreon.txt @@ -0,0 +1,22 @@ + @@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@ @@@@@@@@@@@ + @@@@@@@@ @@@@@@@@@ + @@@@@@@@ @@@@@@@@ + @@@@@@@ @@@@@@@ + @@@@@@@@ @@@@@@@ + @@@@@@@@ @@@@@@ + @@@@@@@@@ @@@@@@ +@@@@@@@@@@ @@@@@@ +@@@@@@@@@@ @@@@@@ + @@@@@@@@@ @@@@@@ + @@@@@@@@ @@@@@@ + @@@@@@@@ @@@@@@@ + @@@@@@@ @@@@@@@ + @@@@@@@@ @@@@@@@@ + @@@@@@@@ @@@@@@@@ + @@@@@@@@@@ @@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + @@@@@@@@@@ \ No newline at end of file diff --git a/src/logo/ascii/o/origami.txt b/src/logo/ascii/o/origami.txt new file mode 100644 index 0000000..207f322 --- /dev/null +++ b/src/logo/ascii/o/origami.txt @@ -0,0 +1,12 @@ + -+: + =+++- + .=+++++= + :+++++++++. +$2.8....... $1-++++*++++++:$2 ...88 + .8888888..$1-+++++*+++++*+:$2..8888888 + .8888888=8===$1=++++==$2==8888888888 + .88888==========88888888888888 + .888========888=888888888888. + 8=======888888====88888888. + 8====88888888======88888. + 888888888888=========8. \ No newline at end of file diff --git a/src/logo/ascii/o/origami_small.txt b/src/logo/ascii/o/origami_small.txt new file mode 100644 index 0000000..6cd4130 --- /dev/null +++ b/src/logo/ascii/o/origami_small.txt @@ -0,0 +1,8 @@ + 00 + 01 00 + 0000 0000 + 000001 00000 +$211010011110 $100 $201111001011 + 01111100 $100000000 $200111110 + 0110 00010000001000 0110 + 000000000000000000 \ No newline at end of file diff --git a/src/logo/ascii/o/os2warp.txt b/src/logo/ascii/o/os2warp.txt new file mode 100644 index 0000000..0774979 --- /dev/null +++ b/src/logo/ascii/o/os2warp.txt @@ -0,0 +1,17 @@ + .-==: . . + -+:::+= * *- = -. -%* + ** *# :%- . :.*=: -%% + %@: -@% =%@%%*: - :. -%= +-@@: :@@- -+#%%+ .: :*. +-@@: :@@-- -% = .* +.%@- -%% ++ # . -: . + #* ** =.:*: +- = #%%%%#+-*: + -=====- .:- .. + +$2 =*+ -+*: + .*+ :*+= -+*++**- + +*-++++ -++-:. .:-=++.=*+. =*- + -***:-+:*++=*++*: +*+=:-**+=**++- + :- :***==*+.=++ +*+=++*=.=*+. + .=-=*+***+*= +*+ :**+=++ + . -*+ +*+ .=: diff --git a/src/logo/ascii/o/os_elbrus.txt b/src/logo/ascii/o/os_elbrus.txt new file mode 100644 index 0000000..9e30e83 --- /dev/null +++ b/src/logo/ascii/o/os_elbrus.txt @@ -0,0 +1,14 @@ +▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ +██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ +██ ██ +██ ███████ ███████ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ +██ ██ ███████ ███████ +██ ██ ██ +██ ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██ +██ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ +██ ██ +███████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/o/osmc.txt b/src/logo/ascii/o/osmc.txt new file mode 100644 index 0000000..8bc1f94 --- /dev/null +++ b/src/logo/ascii/o/osmc.txt @@ -0,0 +1,20 @@ + -+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+- \ No newline at end of file diff --git a/src/logo/ascii/p.inc b/src/logo/ascii/p.inc new file mode 100644 index 0000000..be00006 --- /dev/null +++ b/src/logo/ascii/p.inc @@ -0,0 +1,369 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo P[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_PACBSD + // PacBSD + { + .names = { "PacBSD" }, + .lines = FASTFETCH_DATATEXT_LOGO_PACBSD, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PANWAH + // Panwah + { + .names = { "Panwah" }, + .lines = FASTFETCH_DATATEXT_LOGO_PANWAH, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_RED, + FF_COLOR_FG_BLACK, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARABOLA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARABOLA_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARCH + // Parch + { + .names = { "Parch" }, + .lines = FASTFETCH_DATATEXT_LOGO_PARCH, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARDUS + // Pardus + { + .names = { "Pardus" }, + .lines = FASTFETCH_DATATEXT_LOGO_PARDUS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARROT + // Parrot + { + .names = { "Parrot" }, + .lines = FASTFETCH_DATATEXT_LOGO_PARROT, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PARSIX + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PCBSD + // PCBSD + { + .names = { "PCBSD", "TrueOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PCBSD, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PCLINUXOS + // PCLinuxOS + { + .names = { "PCLinuxOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PCLINUXOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PEAROS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PENGWIN + // Pengwin + { + .names = { "Pengwin" }, + .lines = FASTFETCH_DATATEXT_LOGO_PENGWIN, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_LIGHT_MAGENTA, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PENTOO + // Pentoo + { + .names = { "Pentoo" }, + .lines = FASTFETCH_DATATEXT_LOGO_PENTOO, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PEPPERMINT + // Peppermint + { + .names = { "Peppermint" }, + .lines = FASTFETCH_DATATEXT_LOGO_PEPPERMINT, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PEROPESIS + // Peropesis + { + .names = { "Peropesis", "Peropesis Linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_PEROPESIS, + .colors = { + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PHYOS + // PhyOS + { + .names = { "PhyOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PHYOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PIKAOS + // PikaOS + { + .names = { "PikaOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PIKAOS, + .colors = { + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PISI + // PisiLinux + { + .names = { "PisiLinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_PISI, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PNM_LINUX + // 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", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_POP + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_POP_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PORTEUS + // Porteus + { + .names = { "Porteus" }, + .lines = FASTFETCH_DATATEXT_LOGO_PORTEUS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_POSTMARKETOS + // PostMarketOS + { + .names = { "PostMarketOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_POSTMARKETOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_POSTMARKETOS_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PROXMOX + // 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", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PUFFOS + // PuffOS + { + .names = { "PuffOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PUFFOS, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PUPPY + // Puppy + { + .names = { "Puppy" }, + .lines = FASTFETCH_DATATEXT_LOGO_PUPPY, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PUREOS + // PureOS + { + .names = { "PureOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_PUREOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PUREOS_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PRISMLINUX + // PrismLinux + { + .names = { "PrismLinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_PRISMLINUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_PRISMLINUX_SMALL + // 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, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/p/pacbsd.txt b/src/logo/ascii/p/pacbsd.txt new file mode 100644 index 0000000..f4f9e37 --- /dev/null +++ b/src/logo/ascii/p/pacbsd.txt @@ -0,0 +1,24 @@ + :+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+/:. \ No newline at end of file diff --git a/src/logo/ascii/p/panwah.txt b/src/logo/ascii/p/panwah.txt new file mode 100644 index 0000000..8d3dd89 --- /dev/null +++ b/src/logo/ascii/p/panwah.txt @@ -0,0 +1,21 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/p/parabola.txt b/src/logo/ascii/p/parabola.txt new file mode 100644 index 0000000..3fd0776 --- /dev/null +++ b/src/logo/ascii/p/parabola.txt @@ -0,0 +1,16 @@ + `.-. `. + `.` `:++. `-+o+. + `` `:+/. `:+/. `-+oooo+ + ``-::-.:+/. `:+/. `-+oooooo+ + `.-:///- ..` .-. `-+oooooooo- + `..-..` `+ooooooooo: +`` :oooooooo/ + `ooooooo: + `oooooo: + -oooo+. + +ooo/` + -ooo- + `+o/. + /+- + //` + -. \ No newline at end of file diff --git a/src/logo/ascii/p/parabola_small.txt b/src/logo/ascii/p/parabola_small.txt new file mode 100644 index 0000000..da9f704 --- /dev/null +++ b/src/logo/ascii/p/parabola_small.txt @@ -0,0 +1,6 @@ + __ __ __ _ +.`_//_//_/ / `. + / .` + / .` + /.` + /` \ No newline at end of file diff --git a/src/logo/ascii/p/parch.txt b/src/logo/ascii/p/parch.txt new file mode 100644 index 0000000..97609a5 --- /dev/null +++ b/src/logo/ascii/p/parch.txt @@ -0,0 +1,25 @@ + ,: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' \ No newline at end of file diff --git a/src/logo/ascii/p/pardus.txt b/src/logo/ascii/p/pardus.txt new file mode 100644 index 0000000..53a370b --- /dev/null +++ b/src/logo/ascii/p/pardus.txt @@ -0,0 +1,18 @@ + .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- : + -+ `..` +- \ No newline at end of file diff --git a/src/logo/ascii/p/parrot.txt b/src/logo/ascii/p/parrot.txt new file mode 100644 index 0000000..9e9b270 --- /dev/null +++ b/src/logo/ascii/p/parrot.txt @@ -0,0 +1,24 @@ + `: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: + -+ + - \ No newline at end of file diff --git a/src/logo/ascii/p/parsix.txt b/src/logo/ascii/p/parsix.txt new file mode 100644 index 0000000..ddbf912 --- /dev/null +++ b/src/logo/ascii/p/parsix.txt @@ -0,0 +1,21 @@ + $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`.::-` \ No newline at end of file diff --git a/src/logo/ascii/p/pcbsd.txt b/src/logo/ascii/p/pcbsd.txt new file mode 100644 index 0000000..1519d24 --- /dev/null +++ b/src/logo/ascii/p/pcbsd.txt @@ -0,0 +1,27 @@ + .. + 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+++++++++++/- \ No newline at end of file diff --git a/src/logo/ascii/p/pclinuxos.txt b/src/logo/ascii/p/pclinuxos.txt new file mode 100644 index 0000000..9e1cfa7 --- /dev/null +++ b/src/logo/ascii/p/pclinuxos.txt @@ -0,0 +1,19 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/p/pearos.txt b/src/logo/ascii/p/pearos.txt new file mode 100644 index 0000000..80f4b41 --- /dev/null +++ b/src/logo/ascii/p/pearos.txt @@ -0,0 +1,21 @@ + .+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:. \ No newline at end of file diff --git a/src/logo/ascii/p/pengwin.txt b/src/logo/ascii/p/pengwin.txt new file mode 100644 index 0000000..7bec188 --- /dev/null +++ b/src/logo/ascii/p/pengwin.txt @@ -0,0 +1,15 @@ +$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 `.::/:. \ No newline at end of file diff --git a/src/logo/ascii/p/pentoo.txt b/src/logo/ascii/p/pentoo.txt new file mode 100644 index 0000000..4d8a2bd --- /dev/null +++ b/src/logo/ascii/p/pentoo.txt @@ -0,0 +1,20 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/p/peppermint.txt b/src/logo/ascii/p/peppermint.txt new file mode 100644 index 0000000..a579575 --- /dev/null +++ b/src/logo/ascii/p/peppermint.txt @@ -0,0 +1,21 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/p/peropesis.txt b/src/logo/ascii/p/peropesis.txt new file mode 100644 index 0000000..32d1cf2 --- /dev/null +++ b/src/logo/ascii/p/peropesis.txt @@ -0,0 +1,5 @@ +#### #### #### ### #### #### #### # #### +# # # # # # # # # # # # # +#### ### #### # # #### ### # # # +# # # # # # # # # # # +# #### # # ### # #### #### # #### \ No newline at end of file diff --git a/src/logo/ascii/p/phyos.txt b/src/logo/ascii/p/phyos.txt new file mode 100644 index 0000000..ebc148a --- /dev/null +++ b/src/logo/ascii/p/phyos.txt @@ -0,0 +1,17 @@ +.^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.^^^^^. + :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: + .. \ No newline at end of file diff --git a/src/logo/ascii/p/pikaos.txt b/src/logo/ascii/p/pikaos.txt new file mode 100644 index 0000000..ae19ee4 --- /dev/null +++ b/src/logo/ascii/p/pikaos.txt @@ -0,0 +1,18 @@ + - + ---------------- + ------------------ + ------------------- + -- -----------=*-=----##### + -----------------------------####--=###### + ------------------------------++---+#### + =============----------------=+=----- + =================-----------####+---- + ==================----------*###=---- + ===================----------------- + ==================----------------- + =================---------------- + ===============------------:::: + ============-::::::::::::::: + =======--::::::::::::::: + .::::::::::::::::. + ::::::: \ No newline at end of file diff --git a/src/logo/ascii/p/pisi.txt b/src/logo/ascii/p/pisi.txt new file mode 100644 index 0000000..16958be --- /dev/null +++ b/src/logo/ascii/p/pisi.txt @@ -0,0 +1,19 @@ + \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 !!-^ \ No newline at end of file diff --git a/src/logo/ascii/p/pnm_linux.txt b/src/logo/ascii/p/pnm_linux.txt new file mode 100644 index 0000000..5152320 --- /dev/null +++ b/src/logo/ascii/p/pnm_linux.txt @@ -0,0 +1,19 @@ + ``.---..` `--` + ``.---........-:.$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.` ` `/ .-//- \ No newline at end of file diff --git a/src/logo/ascii/p/pop.txt b/src/logo/ascii/p/pop.txt new file mode 100644 index 0000000..b562610 --- /dev/null +++ b/src/logo/ascii/p/pop.txt @@ -0,0 +1,20 @@ + ///////////// + ///////////////////// + ///////$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///// + /////////////////////////// + ///////////////////// + ///////////// \ No newline at end of file diff --git a/src/logo/ascii/p/pop_small.txt b/src/logo/ascii/p/pop_small.txt new file mode 100644 index 0000000..4865dd9 --- /dev/null +++ b/src/logo/ascii/p/pop_small.txt @@ -0,0 +1,8 @@ +______ +\ _ \ __ + \ \ \ \ / / + \ \_\ \ / / + \ ___\ /_/ + \ \ _ + __\_\__(_)_ + (___________)` \ No newline at end of file diff --git a/src/logo/ascii/p/porteus.txt b/src/logo/ascii/p/porteus.txt new file mode 100644 index 0000000..778210a --- /dev/null +++ b/src/logo/ascii/p/porteus.txt @@ -0,0 +1,23 @@ + `.-:::-.` + -+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` + .+: + ` \ No newline at end of file diff --git a/src/logo/ascii/p/postmarketos.txt b/src/logo/ascii/p/postmarketos.txt new file mode 100644 index 0000000..be7bc1e --- /dev/null +++ b/src/logo/ascii/p/postmarketos.txt @@ -0,0 +1,18 @@ + /\ + / \ + / \ + / \ + / \ + / \ + \ \ + /\ \____ \ + / \____ \ \ + / / \ \ + / / \ ___\ + / / \ / ____ + / / \/ / \ + / / __________/ \ + / \ \ \ + / \ \ \ + / / / \ +/___________/ /____________________\ \ No newline at end of file diff --git a/src/logo/ascii/p/postmarketos_small.txt b/src/logo/ascii/p/postmarketos_small.txt new file mode 100644 index 0000000..3d05527 --- /dev/null +++ b/src/logo/ascii/p/postmarketos_small.txt @@ -0,0 +1,9 @@ + /\ + / \ + / \ + \__ \ + /\__ \ _\ + / / \/ __ + / / ____/ \ + / \ \ \ +/_____/ /________\ \ No newline at end of file diff --git a/src/logo/ascii/p/prismlinux.txt b/src/logo/ascii/p/prismlinux.txt new file mode 100644 index 0000000..237ac39 --- /dev/null +++ b/src/logo/ascii/p/prismlinux.txt @@ -0,0 +1,21 @@ + ⣤⣤ ⢀⡄ + ⢀⣾⣦ ⠙⠛ ⢀⣴⣿⣷ ⢠⣤ + ⣤⡄ ⣸⣿⣿⣷⣄ ⣠⣶⣿⣿⣿⣿ ⠈⠉ ⢀⣤⡀ + ⠉⠁ ⢠⣿⣿⣿⣿⣿⣦⡈⢿⣿⣿⣿⣿⣿⡇⢀⣀⣤⣤⣤⣤⣴⣶⠆⠘⠿⠃ ⠉ ⡀ + ⢤⣄⡀ ⣾⣿⣿⣿⣿⣿⣿⣷⡄⠻⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⡏ ⡇ + ⠘⣿⣿⣿⣿⣶⣤⣈⣉⠛⠿⢿⣿⣿⣿⣿⣦⠘⢿⣿⣿⣷⠈⣿⣿⣿⣿⣿⠏ ⠐⠁ + ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣉⡛⠻⢿⣷⣄⠻⣿⣿ ⣿⣿⣿⣿⠟⢠⣀ ⡐⠁ + ⢰⣶ ⠘⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠛⠉⠉ ⠈⠉ ⠘⣿ ⣿⣿⣿⡏⢠⣿⣿⣿⣶⣦⣄⡀ ⠂ + ⠈⠁ ⠙⠋⣉⣉⣤⣤⣶⣶⠾⠉ ⠈ ⢹⣿⠏⢠⣿⣿⣿⣿⣿⣿⣿⣿⠟ + ⢀⣠⣤⣴⣶⣿⣿⣿⣿⣿⣿⣿⠟⠁ ⢸⠟⣰⣿⣿⣿⣿⣿⡿⠟⠉ + ⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⠟⢁⣴⡏ ⠈⣰⣿⣿⣿⠿⠛⣁⡀ ⢀⡒ + ⢤⡀ ⠙⢿⣿⣿⣿⠟⢁⣴⣿⣿ ⣰⡿⠟⢉⣠⣴⣾⣿⣿⣆ + ⠛⠁ ⠔ ⠈⠛⢁⣴⣿⣿⣿⡇⢰⡆ ⠊⣁⣤⣾⣿⣿⣿⣿⣿⣿⣿⣷⡀ + ⡠⠂ ⢀⣴⣿⣿⣿⣿⣿⠃⣾⣿⡄⢢⡀ ⠤⠶⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄ + ⢀⠈ ⢀⣴⣿⣿⣿⣿⣿⣿⡿⢠⣿⣿⣷ ⢿⣷⣄⠑⢶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤ +⠠⠁ ⠚⠛⠿⠿⢿⣿⣿⣿⡇⢸⣿⣿⣿⣧⠈⢿⣿⣿⣦⣌⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⢰⣶ +⡆ ⣼⣿⣿⣿⣿⣇⠘⣿⣿⣿⣿⣿⣦⣈⠛⢿⣿⣿⣿⣿⣿⡇ +⢁ ⣀ ⠄⠂⠁⢀⣿⣿⣿⣿⣿⣿⡆⠹⣿⣿⣿⣿⣿⣿⠗ ⠈⠛⢿⣿⣿⡇ + ⠁⠂ ⠒⠒⠈⠁ ⢸⣿⣿⣿⠿⠛⠉ ⢻⣿⣿⣿⣿⠟ ⠈⠙⠃ + ⠼⠛⠉⠁ ⢻⣿⣿⠏ ⢀⣄ + ⠿⠇ ⠈⠿⠁ ⠘⠋ diff --git a/src/logo/ascii/p/prismlinux_small.txt b/src/logo/ascii/p/prismlinux_small.txt new file mode 100644 index 0000000..35e43d4 --- /dev/null +++ b/src/logo/ascii/p/prismlinux_small.txt @@ -0,0 +1,13 @@ + ⢠⣦⡀⠘⠁⢀⣴⣾ ⢀⡄ + ⠛ ⣸⣿⣿⣄⢰⣿⣿⣿⡄⢀⣀⣀⣀⡀⢾⠆⠐⠂⢄ + ⠰⣤⣄⡀ ⢿⣿⣿⣿⣧⡹⣿⣿⡇⣿⣿⣿⡿⠁ + ⢻⣿⣿⣿⣶⣶⣭⣝⡻⠷⣌⢿⡇⢸⣿⡿⢁⣀ + ⠘⠛ ⠸⠿⠟⣛⣋⡭⠅ ⠻⢸⡿⢡⣿⣿⣿⣶⣤⠁ + ⠠⣤⣶⣶⣿⣿⡿⢋ ⠸⣡⣿⣿⡿⠟⠉ + ⢠ ⠈⠻⣿⡿⢋⣴⡟ ⢰⠟⣋⣥⣾⣦⡀ ⠁ + ⠈⠠⠊ ⢈⣴⣿⣿⣇⣧⢀ ⢀⣠⣴⣿⣿⣿⣿⣿⣷⡄ + ⣰⣿⣿⣿⣿⢹⣿⣆⢳⣦⣐⠶⣶⣦⣤⣬⣭⣭⠉⢉⠉⠉ +⡀ ⠈⠉⠉⢉⢸⣿⣿⣆⢻⣿⣷⣮⡙⢿⣿⣿⣿ ⠉ + ⣀⣀ ⠄⠐⠈ ⣾⣿⣿⠿ ⣿⣿⣿⡿ ⠉⠻⢿ + ⠛⠉ ⢀⡀⠘⣿⠟ ⢠⡀ + ⠈⠁ ⠁ diff --git a/src/logo/ascii/p/proxmox.txt b/src/logo/ascii/p/proxmox.txt new file mode 100644 index 0000000..fda7bf1 --- /dev/null +++ b/src/logo/ascii/p/proxmox.txt @@ -0,0 +1,20 @@ +$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` + `://:` `://:` \ No newline at end of file diff --git a/src/logo/ascii/p/puffos.txt b/src/logo/ascii/p/puffos.txt new file mode 100644 index 0000000..a8e306c --- /dev/null +++ b/src/logo/ascii/p/puffos.txt @@ -0,0 +1,12 @@ + _,..._,m, + ,/' '""; + / ". + ,'mmmMMMMmm. \ + _/-"^^^^^"""%#%mm, ; + ,m,_,' "###) ;, +(###% \#/ ;##mm. + ^#/ __ ___ ; (######) + ; //.\\ //.\\ ; \####/ + _; (#\"// \\"/#) ; ,/ +@##\ \##/ = `"=" ,;mm/ +`\##>.____,...,____,<####@ \ No newline at end of file diff --git a/src/logo/ascii/p/puppy.txt b/src/logo/ascii/p/puppy.txt new file mode 100644 index 0000000..1d66992 --- /dev/null +++ b/src/logo/ascii/p/puppy.txt @@ -0,0 +1,18 @@ + `-/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+/:-. \ No newline at end of file diff --git a/src/logo/ascii/p/pureos.txt b/src/logo/ascii/p/pureos.txt new file mode 100644 index 0000000..4d47de1 --- /dev/null +++ b/src/logo/ascii/p/pureos.txt @@ -0,0 +1,12 @@ +dmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd +dNm//////////////////////////////////mNd +dNd dNd +dNd dNd +dNd dNd +dNd dNd +dNd dNd +dNd dNd +dNd dNd +dNd dNd +dNm//////////////////////////////////mNd +dmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd \ No newline at end of file diff --git a/src/logo/ascii/p/pureos_small.txt b/src/logo/ascii/p/pureos_small.txt new file mode 100644 index 0000000..27f5204 --- /dev/null +++ b/src/logo/ascii/p/pureos_small.txt @@ -0,0 +1,6 @@ + _____________ +| _________ | +| | | | +| | | | +| |_________| | +|_____________| \ No newline at end of file diff --git a/src/logo/ascii/q.inc b/src/logo/ascii/q.inc new file mode 100644 index 0000000..732261e --- /dev/null +++ b/src/logo/ascii/q.inc @@ -0,0 +1,89 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo Q[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_QTS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_QUASAR + // Quasar + { + .names = { "Quasar" }, + .lines = FASTFETCH_DATATEXT_LOGO_QUASAR, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_Q4OS + // Q4OS + { + .names = { "Q4OS" }, + .lines = FASTFETCH_DATATEXT_LOGO_Q4OS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_QUBES + // Qubes + { + .names = { "Qubes" }, + .lines = FASTFETCH_DATATEXT_LOGO_QUBES, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_QUBYT + // Qubyt + { + .names = { "Qubyt" }, + .lines = FASTFETCH_DATATEXT_LOGO_QUBYT, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_BLACK, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_QUIBIAN + // Quibian + { + .names = { "Quibian" }, + .lines = FASTFETCH_DATATEXT_LOGO_QUIBIAN, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_QUIRINUX + // Quirinux + { + .names = { "Quirinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_QUIRINUX, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/q/q4os.txt b/src/logo/ascii/q/q4os.txt new file mode 100644 index 0000000..01e178c --- /dev/null +++ b/src/logo/ascii/q/q4os.txt @@ -0,0 +1,19 @@ + .:***** :=====. + .:******** :========. + .*********** :===========. + .:************ :============- + .************** :============== + :*************** :=============== + :**************. :=============== +.*************: .=============. +*************. .============: + +$1:############. $2:==: +$1:##############. $2:======: + $1:################ $2.==========: + $1:############### $2.===========: + $1:############## $2.===========: + $1:############# $2.=========: + $1:########### $2.=====: + $1.######### $2.=: + $1.##### \ No newline at end of file diff --git a/src/logo/ascii/q/qts.txt b/src/logo/ascii/q/qts.txt new file mode 100644 index 0000000..3fe32a8 --- /dev/null +++ b/src/logo/ascii/q/qts.txt @@ -0,0 +1,14 @@ + $1###########################- + $1############################### +$1=#########**************######### +$1########## ######## +$1########## ######## +$1########## ######## +$1########## ######## +$1########## ######## +$1########## $2. $1######## +$1########## $2+=. $1:##### +$1+#########+=========- $2%%%* $1=# + $1#####################* $2%%%%# + $1-#####################* $2%%%%%. + $2``````` \ No newline at end of file diff --git a/src/logo/ascii/q/quasar.txt b/src/logo/ascii/q/quasar.txt new file mode 100644 index 0000000..c26ba5c --- /dev/null +++ b/src/logo/ascii/q/quasar.txt @@ -0,0 +1,25 @@ +### + ##### + ## #### + ## ##### + ## ##### + ## ### + ## #### + ## #### #### + ## ### ### #### + ##### ## + ### ## ### ## + ## ## ## ## + # ## ## ## + ## ## ## ## + ## #### ## ### + ### ##### + #### ### #### ### + ##### #### ## + ### ## + ### ## + ##### ## + ##### ## + #### ## + ###### + ### \ No newline at end of file diff --git a/src/logo/ascii/q/qubes.txt b/src/logo/ascii/q/qubes.txt new file mode 100644 index 0000000..a938d3d --- /dev/null +++ b/src/logo/ascii/q/qubes.txt @@ -0,0 +1,21 @@ + `..--..` + `.----------.` + `..----------------..` + `.------------------------.`` + `..-------------....-------------..` +.::----------..`` ``..----------:+: +:////:----..` `..---:/ossso +:///////:` `/osssssso +:///////: /ssssssso +:///////: /ssssssso +:///////: /ssssssso +:///////: /ssssssso +:///////: /ssssssso +:////////-` .:sssssssso +:///////////-.` `-/osssssssssso +`//////////////:-```.:+ssssssssssssso- + .-://////////////sssssssssssssso/-` + `.:///////////sssssssssssssso:. + .-:///////ssssssssssssssssss/` + `.:////ssss+/+ssssssssssss. + `--//- `-/osssso/. \ No newline at end of file diff --git a/src/logo/ascii/q/qubyt.txt b/src/logo/ascii/q/qubyt.txt new file mode 100644 index 0000000..f310106 --- /dev/null +++ b/src/logo/ascii/q/qubyt.txt @@ -0,0 +1,15 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/q/quibian.txt b/src/logo/ascii/q/quibian.txt new file mode 100644 index 0000000..647fb8f --- /dev/null +++ b/src/logo/ascii/q/quibian.txt @@ -0,0 +1,18 @@ + `.--::::::::--.` + `.-:::-..`` ``..-::-.` + .::::-` .$2+$1:`` `.-::.` + .::::.` -::::::-` `.::. + `-:::-` -:::::::::--..`` .::` + `::::- .$2oy$1:::::::---.```.: `::` + -:::: `.-:::::::::::-.``` `:: +.::::.`-:::::::::::::. `:. +-::::.::::::::::::::: -: +::::::::::::::::::::` `: +:::::::::::::::::::- `: +::::::::::::::::::: -- +.:::::::::::::::::` `:` +`::::::::::::::::: -` + .:::::::::::::::- -` + `::::::::::::::- `.` + .::::::::::::- `` + `.--:::::-. \ No newline at end of file diff --git a/src/logo/ascii/q/quirinux.txt b/src/logo/ascii/q/quirinux.txt new file mode 100644 index 0000000..4047225 --- /dev/null +++ b/src/logo/ascii/q/quirinux.txt @@ -0,0 +1,17 @@ +$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 @#====#@ \ No newline at end of file diff --git a/src/logo/ascii/r.inc b/src/logo/ascii/r.inc new file mode 100644 index 0000000..1052d2a --- /dev/null +++ b/src/logo/ascii/r.inc @@ -0,0 +1,297 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo R[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_RADIX + // Radix + { + .names = { "Radix" }, + .lines = FASTFETCH_DATATEXT_LOGO_RADIX, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RASPBIAN + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RASPBIAN_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RAVYNOS + // RavynOS + { + .names = { "RavynOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_RAVYNOS, + .colors = { + FF_COLOR_FG_256 "15", + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REBORNOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REBORNOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REDCORE + // RedCore + { + .names = { "RedCore" }, + .lines = FASTFETCH_DATATEXT_LOGO_REDCORE, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RHEL + // RedHatEnterpriseLinux + { + .names = { "rhel", "redhat" }, + .lines = FASTFETCH_DATATEXT_LOGO_RHEL, + .colors = { + FF_COLOR_FG_RED, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RHEL_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RHEL_OLD + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REDOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REDOS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REDSTAR + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REDROSE + // Redrose + { + .names = { "Redrose" }, + .lines = FASTFETCH_DATATEXT_LOGO_REDROSE, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_GREEN, + }, + .colorKeys = FF_COLOR_FG_RED, + .colorTitle = FF_COLOR_FG_RED, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REFRACTA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REGATA + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_REGOLITH + // Regolith + { + .names = { "Regolith" }, + .lines = FASTFETCH_DATATEXT_LOGO_REGOLITH, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RHAYMOS + // RhaymOS + { + .names = { "RhaymOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_RHAYMOS, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ROCKY + // RockyLinux + { + .names = { "rocky" }, + .lines = FASTFETCH_DATATEXT_LOGO_ROCKY, + .colors = { + FF_COLOR_FG_GREEN, + }, + .colorKeys = FF_COLOR_FG_GREEN, + .colorTitle = FF_COLOR_FG_GREEN, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ROCKY_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ROSA + // 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", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RHINO + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_RENGEOS + { + .names = { "RengeOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_RENGEOS, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/r/radix.txt b/src/logo/ascii/r/radix.txt new file mode 100644 index 0000000..4a3aa6f --- /dev/null +++ b/src/logo/ascii/r/radix.txt @@ -0,0 +1,18 @@ + .:oyhdmNo + `/yhyoosdms` + -o+/ohmmho- + ..`.:/:-` + `.--:::-.``$2 + .+ydNMMMMMMNmhs:` +`omMMMMMMMMMMMMMMNh- +oNMMMNmddhhyyhhhddmy. +mMMMMNmmddhhysoo+/:-` +yMMMMMMMMMMMMMMMMNNh. +-dmmmmmNNMMMMMMMMMMs` + -+oossyhmMMMMMMMMd- + `sNMMMMMMMMMMMMMm: + `yMMMMMMNmdhhhh: + `sNMMMMMNmmho. + `+mMMMMMMMy. + .yNMMMm+` + `:yd+. \ No newline at end of file diff --git a/src/logo/ascii/r/raspbian.txt b/src/logo/ascii/r/raspbian.txt new file mode 100644 index 0000000..ec8020c --- /dev/null +++ b/src/logo/ascii/r/raspbian.txt @@ -0,0 +1,23 @@ + $2`.::///+:/-. --///+//-:` + `+oooooooooooo: `+oooooooooooo: + /oooo++//ooooo: ooooo+//+ooooo. + `+ooooooo:-:oo- +o+::/ooooooo: + `:oooooooo+`` `.oooooooo+- + `:++ooo/. :+ooo+/.`$1 + ...` `.----.` ``.. + .::::-``:::::::::.`-:::-` + -:::-` .:::::::-` `-:::- + `::. `.--.` `` `.---.``.::` + .::::::::` -::::::::` ` + .::` .:::::::::- `::::::::::``::. +-:::` ::::::::::. ::::::::::.`:::- +:::: -::::::::. `-:::::::: :::: +-::- .-:::-.``....``.-::-. -::- + .. `` .::::::::. `..`.. + -:::-` -::::::::::` .:::::` + :::::::` -::::::::::` :::::::. + .::::::: -::::::::. :::::::: + `-:::::` ..--.` ::::::. + `...` `...--..` `...` + .:::::::::: + `.-::::-` \ No newline at end of file diff --git a/src/logo/ascii/r/raspbian_small.txt b/src/logo/ascii/r/raspbian_small.txt new file mode 100644 index 0000000..eceb8c3 --- /dev/null +++ b/src/logo/ascii/r/raspbian_small.txt @@ -0,0 +1,10 @@ + $2.~~. .~~. + '. \ ' ' / .'$1 + .~ .~~~..~. + : .~.'~'.~. : + ~ ( ) ( ) ~ +( : '~'.~.'~' : ) + ~ .~ ( ) ~. ~ + ( : '~' : ) + '~ .~~~. ~' + '~' \ No newline at end of file diff --git a/src/logo/ascii/r/ravynos.txt b/src/logo/ascii/r/ravynos.txt new file mode 100644 index 0000000..490b5d0 --- /dev/null +++ b/src/logo/ascii/r/ravynos.txt @@ -0,0 +1,20 @@ + ..oooo.. + .o$$$$$$$$$$$$$$$$$$$$$$$$$$$$o. + od$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o + o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o + .$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$. + d$$$$$$$$$$$$$$$$$********$$$$$$$$$$$$$$$$$$$$$$$$$$$$$b + d$$$$$$$$$$$$$* °****?$$$$$$$$$$$$$$$$b + $$$$$$$$$$$$* °$$$$$$$$$$$$$ + d$$$$** .oo$$$$$$$$$$$$$$$$b + *° o$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P + *$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + ?$$$$$$$$$$$$$$$$$$$$$$$$$$$$P + $$$$$$$$$$$$$$$$$$$$$$$$$P + $$$$$$$$$$$$$$$$$$$$$$$$P + ?$$$$$$$$$$$$$$$$$$$$* + $$$$$$$$$$$$$*° + d$$$$$$$$*° + ° \ No newline at end of file diff --git a/src/logo/ascii/r/rebornos.txt b/src/logo/ascii/r/rebornos.txt new file mode 100644 index 0000000..520c80f --- /dev/null +++ b/src/logo/ascii/r/rebornos.txt @@ -0,0 +1,20 @@ + $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.======================. \ No newline at end of file diff --git a/src/logo/ascii/r/rebornos_small.txt b/src/logo/ascii/r/rebornos_small.txt new file mode 100644 index 0000000..d0bfbdf --- /dev/null +++ b/src/logo/ascii/r/rebornos_small.txt @@ -0,0 +1,7 @@ + _______ + /\_____/\ + / /\___/\ \ +/_/_/ \_\_\ +\ \ \___/ / / + \ \/___\/ / + \/_____\/ \ No newline at end of file diff --git a/src/logo/ascii/r/redcore.txt b/src/logo/ascii/r/redcore.txt new file mode 100644 index 0000000..bc19f58 --- /dev/null +++ b/src/logo/ascii/r/redcore.txt @@ -0,0 +1,16 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/r/redos.txt b/src/logo/ascii/r/redos.txt new file mode 100644 index 0000000..5ad2a1a --- /dev/null +++ b/src/logo/ascii/r/redos.txt @@ -0,0 +1,18 @@ +╭───────────────╮ ╭───────────────╮ +│###############│ │###############│ +│#+++++++++++++#│ │#+++++++++++++#│ +│#+++++++++++++#│ │#+++++++++++++#│ +│#+++++++++++++#│ │#+++++++++++++#│ +│#+++++++++++++#│ │#+++++++++++++#│ +│#+++++++++++++#│ │#+++++++++++++#│ +│###############│ │###############│ +╰───────────────╯ ╰───────────────╯ +╭───────────────╮ +│###############│ +│#+++++++++++++#│ +│#+++++++++++++#│ +│#+++++++++++++#│ +│#+++++++++++++#│ +│#+++++++++++++#│ +│###############│ +╰───────────────╯ \ No newline at end of file diff --git a/src/logo/ascii/r/redos_small.txt b/src/logo/ascii/r/redos_small.txt new file mode 100644 index 0000000..e23e1d0 --- /dev/null +++ b/src/logo/ascii/r/redos_small.txt @@ -0,0 +1,8 @@ +╭─────╮ ╭─────╮ +│ │ │ │ +│ │ │ │ +╰─────╯ ╰─────╯ +╭─────╮ +│ │ +│ │ +╰─────╯ \ No newline at end of file diff --git a/src/logo/ascii/r/redrose.txt b/src/logo/ascii/r/redrose.txt new file mode 100755 index 0000000..316c695 --- /dev/null +++ b/src/logo/ascii/r/redrose.txt @@ -0,0 +1,17 @@ + xxrjr ftttt + xxxxxjjfffft + xxxxxxxjjjjj + xxxxxxxjjjjj + xxxxxxxxxnxx + xxxxxxxn + $2[[ +//// [[ +//// [[ +///////[[ + /////]] + [[ + [[ + [[ + [[ + [[ + [] diff --git a/src/logo/ascii/r/redstar.txt b/src/logo/ascii/r/redstar.txt new file mode 100644 index 0000000..b498663 --- /dev/null +++ b/src/logo/ascii/r/redstar.txt @@ -0,0 +1,18 @@ + .. + .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::::;...';;;;;,,,,,,. + ,::::::;;;,'. ..',,,,'''. + ........ ...... \ No newline at end of file diff --git a/src/logo/ascii/r/refracta.txt b/src/logo/ascii/r/refracta.txt new file mode 100644 index 0000000..6e8f484 --- /dev/null +++ b/src/logo/ascii/r/refracta.txt @@ -0,0 +1,18 @@ + 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/ \ No newline at end of file diff --git a/src/logo/ascii/r/regata.txt b/src/logo/ascii/r/regata.txt new file mode 100644 index 0000000..4f30382 --- /dev/null +++ b/src/logo/ascii/r/regata.txt @@ -0,0 +1,20 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/r/regolith.txt b/src/logo/ascii/r/regolith.txt new file mode 100644 index 0000000..938fe61 --- /dev/null +++ b/src/logo/ascii/r/regolith.txt @@ -0,0 +1,18 @@ + ``....``` + `.:/++++++/::-.` + -/+++++++:.` + -++++++++:` + `/++++++++- + `/++++++++. -/+/ + /++++++++/ `` .:+++:. + -+++++++++/ ./++++:+++/-` + :+++++++++/ `+++++++/-` + :++++++++++` .-/+++++++` + `:++++++++++/``.-/++++:-:::-` ` + `:+++++++++++++++++/:.` ./` +:++/-:+++++++++/:-.. -/+. ++++++++++/::-...:/+++/-..````..-/+++. +`......``.::/+++++++++++++++++++++/. + -/+++++++++++++++++++++/. + .:/+++++++++++++++/-` + `.-:://////:-. \ No newline at end of file diff --git a/src/logo/ascii/r/rengeos.txt b/src/logo/ascii/r/rengeos.txt new file mode 100644 index 0000000..995af0b --- /dev/null +++ b/src/logo/ascii/r/rengeos.txt @@ -0,0 +1,16 @@ + .. + .:~!. + ...::^~!!7?J?: + ..::^^~~!!!7?JYYYYYYY?. + .::::..::^!7?JJYYYJYY! +:~~~:.. .:~!?JJYYYYYYYJ!?J^ + :!?JJ?7!!7?JYYYYYYYYYYYJ~.~?: + :~?YYYYYYYYYYYYYYY?~. ^7. + .~?YYYYYYYYYY?^ .~ + ..:~!7?JYYYYYYYY?^ +.^~!7777777JYYYYY7: + ^YYYJ7: + :YYJ!: + :JJ!. + .7~. + .. \ No newline at end of file diff --git a/src/logo/ascii/r/rhaymos.txt b/src/logo/ascii/r/rhaymos.txt new file mode 100644 index 0000000..859e0cb --- /dev/null +++ b/src/logo/ascii/r/rhaymos.txt @@ -0,0 +1,14 @@ + ### + ##### + + ####### /######## + ############# ########### + ,########### #### ####(.. + #### #### ####* ########## + #### ##### ##### (#### + #### ########### ########### + #### ######### ########## + + ################################### + ##################################### +####################################### \ No newline at end of file diff --git a/src/logo/ascii/r/rhel.txt b/src/logo/ascii/r/rhel.txt new file mode 100644 index 0000000..d2fa4ef --- /dev/null +++ b/src/logo/ascii/r/rhel.txt @@ -0,0 +1,18 @@ + .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' \ No newline at end of file diff --git a/src/logo/ascii/r/rhel_old.txt b/src/logo/ascii/r/rhel_old.txt new file mode 100644 index 0000000..99b8651 --- /dev/null +++ b/src/logo/ascii/r/rhel_old.txt @@ -0,0 +1,16 @@ + `.-..........` + `////////::.`-/. + -: ....-////////. + //:-::///////////` + `--::: `-://////////////: + //////- ``.-:///////// .` + `://////:-.` :///////::///:` + .-/////////:---/////////////: + .-://////////////////////. +$2 yMN+`.-$1::///////////////-` +$2 .-`:NMMNMs` `..-------..` + MN+/mMMMMMhoooyysshsss +MMM MMMMMMMMMMMMMMyyddMMM+ + MMMM MMMMMMMMMMMMMNdyNMMh` hyhMMM + MMMMMMMMMMMMMMMMyoNNNMMM+. MMMMMMMM + MMNMMMNNMMMMMNM+ mhsMNyyyyMNMMMMsMM \ No newline at end of file diff --git a/src/logo/ascii/r/rhel_small.txt b/src/logo/ascii/r/rhel_small.txt new file mode 100644 index 0000000..f0841b7 --- /dev/null +++ b/src/logo/ascii/r/rhel_small.txt @@ -0,0 +1,9 @@ + .M.:MMM + MMMMMMMMMM. + ,MMMMMMMMMMM + .MM MMMMMMMMMMM +MMMM MMMMMMMMM +MMMMMM MM + MMMMMMMMM ,MMMM + MMMMMMMMMMMMMMMM: + `MMMMMMMMMMMM \ No newline at end of file diff --git a/src/logo/ascii/r/rhino.txt b/src/logo/ascii/r/rhino.txt new file mode 100644 index 0000000..7bab3c3 --- /dev/null +++ b/src/logo/ascii/r/rhino.txt @@ -0,0 +1,12 @@ +$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 diff --git a/src/logo/ascii/r/rocky.txt b/src/logo/ascii/r/rocky.txt new file mode 100644 index 0000000..0843131 --- /dev/null +++ b/src/logo/ascii/r/rocky.txt @@ -0,0 +1,19 @@ + __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@~~' \ No newline at end of file diff --git a/src/logo/ascii/r/rocky_small.txt b/src/logo/ascii/r/rocky_small.txt new file mode 100644 index 0000000..e9e9f09 --- /dev/null +++ b/src/logo/ascii/r/rocky_small.txt @@ -0,0 +1,12 @@ + `-/+++++++++/-.` + `-+++++++++++++++++-` +.+++++++++++++++++++++. +-+++++++++++++++++++++++. ++++++++++++++++/-/+++++++ ++++++++++++++/. ./+++++ ++++++++++++:. ./+++ ++++++++++:` `:/:` .:/ +-++++++:` .:+++++:` + .+++-` ./+++++++++:` + `-` ./+++++++++++- + -+++++++++:-.` \ No newline at end of file diff --git a/src/logo/ascii/r/rosa.txt b/src/logo/ascii/r/rosa.txt new file mode 100644 index 0000000..e6caa5f --- /dev/null +++ b/src/logo/ascii/r/rosa.txt @@ -0,0 +1,18 @@ +$2 ⢀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣄ +$2 ⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ +$1 ⢀⣤⣄ $2⣿⣿⣿⡇ +$1 ⢀⣴⣿⣿⣿⠗ $2⣿⣿⣿⡇ +$1 ⢠⣾⣿⣿⡿⠁ $2⣿⣿⣿⡇ +$1 ⢠⣿⣿⣿⡟ $2⣿⣿⣿⡇ +$1⢀⣿⣿⣿⡿ $2⣿⣿⣿⡇ +$1⢸⣿⣿⣿⠃ $2⣿⣿⣿⡇ +$1⢸⣿⣿⣿ $2⣿⣿⣿⡇ +$1⢸⣿⣿⣿ $2⣿⣿⣿⡇ +$1⢸⣿⣿⣿⡄ $2⠙⢿⡿ +$1⠘⣿⣿⣿⣷ + ⠘⣿⣿⣿⣦ + ⠙⣿⣿⣿⣷⡀ ⢀⡀ + ⠘⢿⣿⣿⣿⣦⣄ ⣠⣴⣿⣿⡦ + ⠙⢿⣿⣿⣿⣷⣦⣀⣀⡀ ⢀⣀⣠⣴⣾⣿⣿⣿⠟⠁ + ⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠁ + ⠈⠙⠛⠿⠿⠿⠿⠿⠿⠟⠛⠋⠁ diff --git a/src/logo/ascii/s.inc b/src/logo/ascii/s.inc new file mode 100644 index 0000000..78f64f2 --- /dev/null +++ b/src/logo/ascii/s.inc @@ -0,0 +1,530 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo S[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_SABAYON + // Sabayon + { + .names = { "Sabayon" }, + .lines = FASTFETCH_DATATEXT_LOGO_SABAYON, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SABOTAGE + // Sabotage + { + .names = { "Sabotage" }, + .lines = FASTFETCH_DATATEXT_LOGO_SABOTAGE, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SAILFISH + // Sailfish + { + .names = { "Sailfish" }, + .lines = FASTFETCH_DATATEXT_LOGO_SAILFISH, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_MAGENTA, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SALENTOS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SALIENTOS + // SalientOS + { + .names = { "Salient OS", "SalientOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SALIENTOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SALIX + // Salix + { + .names = { "Salix" }, + .lines = FASTFETCH_DATATEXT_LOGO_SALIX, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SAMBABOX + // SambaBOX + { + .names = { "SambaBOX", "Profelis SambaBOX" }, + .lines = FASTFETCH_DATATEXT_LOGO_SAMBABOX, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SASANQUA + // Sasanqua + { + .names = { "Sasanqua" }, + .lines = FASTFETCH_DATATEXT_LOGO_SASANQUA, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SCIENTIFIC + // Scientific + { + .names = { "Scientific" }, + .lines = FASTFETCH_DATATEXT_LOGO_SCIENTIFIC, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SECUREBLUE + // Secureblue + { + .names = { "secureblue" }, + .lines = FASTFETCH_DATATEXT_LOGO_SECUREBLUE, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SERPENT_OS + // Serpent OS + { + .names = { "Serpent OS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SERPENT_OS, + .colors = { + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SEMC + // Semc + { + .names = { "semc" }, + .lines = FASTFETCH_DATATEXT_LOGO_SEMC, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_LIGHT_BLACK, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SEPTOR + // Septor + { + .names = { "Septor" }, + .lines = FASTFETCH_DATATEXT_LOGO_SEPTOR, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SERENE + // Serene + { + .names = { "Serene" }, + .lines = FASTFETCH_DATATEXT_LOGO_SERENE, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SHARKLINUX + // SharkLinux + { + .names = { "SharkLinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_SHARKLINUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SHASTRAOS + // ShastraOS + { + .names = { "ShastraOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SHASTRAOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SHEBANG + // Shebang + { + .names = { "Shebang" }, + .lines = FASTFETCH_DATATEXT_LOGO_SHEBANG, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SIDUCTION + // Siduction + { + .names = { "Siduction" }, + .lines = FASTFETCH_DATATEXT_LOGO_SIDUCTION, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SKIFFOS + // SkiffOS + { + .names = { "SkiffOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SKIFFOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLEEPEROS + // SleeperOS + { + .names = { "SleeperOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL + // SleeperOSSmall + { + .names = { "SleeperOS_small" }, + .type = FF_LOGO_LINE_TYPE_SMALL_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLITAZ + // Slitaz + { + .names = { "Slitaz" }, + .lines = FASTFETCH_DATATEXT_LOGO_SLITAZ, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SPOINKOS + // SpoinkOS + { + .names = { "SpoinkOS", "spoink-os" }, + .lines = FASTFETCH_DATATEXT_LOGO_SPOINKOS, + .colors = { + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLACKEL + // Slackel + { + .names = { "Slackel" }, + .lines = FASTFETCH_DATATEXT_LOGO_SLACKEL, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLACKWARE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SLACKWARE_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SMARTOS + // SmartOS + { + .names = { "SmartOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SMARTOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SNIGDHAOS + // SnigdhaOS + { + .names = { "SnigdhaOS", "Snigdha" }, + .lines = FASTFETCH_DATATEXT_LOGO_SNIGDHAOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SODA + // Soda + { + .names = { "Soda" }, + .lines = FASTFETCH_DATATEXT_LOGO_SODA, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SOURCE_MAGE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SOLARIS + // Solaris + { + .names = { "solaris", "sunos" }, + .lines = FASTFETCH_DATATEXT_LOGO_SOLARIS, + .colors = { + FF_COLOR_FG_YELLOW, + }, + .colorKeys = FF_COLOR_FG_YELLOW, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SOLARIS_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SOLUS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SPARKY + // Sparky + { + .names = { "Sparky" }, + .lines = FASTFETCH_DATATEXT_LOGO_SPARKY, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STAR + // Star + { + .names = { "Star" }, + .lines = FASTFETCH_DATATEXT_LOGO_STAR, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STOCK_LINUX + // StockLinux + { + .names = { "Stock Linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_STOCK_LINUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STEAMOS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STEAMDECK + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STEAMDECK_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_STEAMDECK + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SULIN + // Sulin + { + .names = { "Sulin" }, + .lines = FASTFETCH_DATATEXT_LOGO_SULIN, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SUMMITOS + // 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", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SUSE + // Suse + { + .names = { "suse", "suse-linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_SUSE, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_SWAGARCH + // Swagarch + { + .names = { "Swagarch" }, + .lines = FASTFETCH_DATATEXT_LOGO_SWAGARCH, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/s/sabayon.txt b/src/logo/ascii/s/sabayon.txt new file mode 100644 index 0000000..355610e --- /dev/null +++ b/src/logo/ascii/s/sabayon.txt @@ -0,0 +1,18 @@ + ........... + .. .. + .. .. + .. $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.. + .. .. + .. .. + ............... \ No newline at end of file diff --git a/src/logo/ascii/s/sabotage.txt b/src/logo/ascii/s/sabotage.txt new file mode 100644 index 0000000..eb0fd68 --- /dev/null +++ b/src/logo/ascii/s/sabotage.txt @@ -0,0 +1,11 @@ + .|'''.| | '||''|. ..|''|| + ||.. ' ||| || || .|' || + ''|||. | || ||'''|. || || +. '|| .''''|. || || '|. || +|'....|' .|. .||. .||...|' ''|...|' + +|''||''| | ..|'''.| '||''''| + || ||| .|' ' || . + || | || || .... ||''| + || .''''|. '|. || || + .||. .|. .||. ''|...'| .||.....| \ No newline at end of file diff --git a/src/logo/ascii/s/sailfish.txt b/src/logo/ascii/s/sailfish.txt new file mode 100644 index 0000000..dc731ac --- /dev/null +++ b/src/logo/ascii/s/sailfish.txt @@ -0,0 +1,13 @@ + _a@b + _#b (b + _@@ @_ _, + _#^@ _#*^^*gg,aa@^^ + #- @@^ _a@^^ + @_ *g#b + ^@_ ^@_ + ^@_ @ + @(b (b + #b(b#^ + _@_#@^ + _a@a*^ +,a@*^ \ No newline at end of file diff --git a/src/logo/ascii/s/salentos.txt b/src/logo/ascii/s/salentos.txt new file mode 100644 index 0000000..08d3eb2 --- /dev/null +++ b/src/logo/ascii/s/salentos.txt @@ -0,0 +1,20 @@ + ``..`` + .-:+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:. \ No newline at end of file diff --git a/src/logo/ascii/s/salientos.txt b/src/logo/ascii/s/salientos.txt new file mode 100644 index 0000000..0c78b54 --- /dev/null +++ b/src/logo/ascii/s/salientos.txt @@ -0,0 +1,20 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/s/salix.txt b/src/logo/ascii/s/salix.txt new file mode 100644 index 0000000..7d3956d --- /dev/null +++ b/src/logo/ascii/s/salix.txt @@ -0,0 +1,18 @@ + __s_aaaaaaaaauuoXSSSSSSSS: + ._xSSSSSSSSSSSSSSSSSSSSSSSSSS: + _aSSSSSSSSSSSSSSSSSSSSSSSSSSSSS: + _xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS: + + nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS} + nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS}` + XSSSSSSSSSSSSSSSSSSSSSSSSSSSS"` + SSSSSSSSSSSSSSSSSSSSSSSSS!"` +-""""""""""""""""""""""` \ No newline at end of file diff --git a/src/logo/ascii/s/sambabox.txt b/src/logo/ascii/s/sambabox.txt new file mode 100644 index 0000000..522e1a7 --- /dev/null +++ b/src/logo/ascii/s/sambabox.txt @@ -0,0 +1,19 @@ + # + *////##### + /////////#########( + .((((((///// ,####(#((((( + /#######(((* (#(((((((((. +//((#(#(#, ((##( ,((((((// +////// #(##########( ////// +////// ((#(#(#(#(##########(///////// +/////( (((((((#########(##((((((///// +/(((#( ((((/ +####(# ((### +#########(((/////////(((((((((, (#(#( +########( /////////(((((((* ##### +####///, *////((( ((((((( +./////////// .//((((((((( + ///////////, *(/////((((* + ,/(((((((((##########/. + .((((((####### + ((##* \ No newline at end of file diff --git a/src/logo/ascii/s/sasanqua.txt b/src/logo/ascii/s/sasanqua.txt new file mode 100644 index 0000000..0d5e1dc --- /dev/null +++ b/src/logo/ascii/s/sasanqua.txt @@ -0,0 +1,18 @@ + __,_ + _╕⌐≡µ,√* º≡, + ñ "' ░ + ╞) _, ▒ __ + _,,,_ _Ñ╜^≡µ ≡' 1µ╕º^el "%µ + ∩' K Yµ& 1l ╞) + ▒ √" ^Ü 1" `1µ + Γ ║h _¿▒∞√;, ^≡, + K ^u_ ⌐* ╙¥ ╓Ñ + ⌠ º≡u,, ║I Å + Ü _∩" ║µ_¿╝" + Yu_ ▒ ╙º≡_ ║l1µ + ║l , Y∞µ___≡ª Γl + ╓hⁿ╖I 1l Ñ ╓Ñ + Ñ ¥,___≡1l ╓Ñ ¿╕ª + Ü ╙L ¿¿∩ª ╓P + ª≡,__ *ⁿ┤ⁿÑⁿ^µ √ª + ⁿ≡,,__√╝* "ⁿⁿ*" \ No newline at end of file diff --git a/src/logo/ascii/s/scientific.txt b/src/logo/ascii/s/scientific.txt new file mode 100644 index 0000000..eab2e45 --- /dev/null +++ b/src/logo/ascii/s/scientific.txt @@ -0,0 +1,20 @@ + =/;;/- + +: // + /; /; + -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. + ;/ #= + // +; + '////' \ No newline at end of file diff --git a/src/logo/ascii/s/secureblue.txt b/src/logo/ascii/s/secureblue.txt new file mode 100644 index 0000000..859cdf1 --- /dev/null +++ b/src/logo/ascii/s/secureblue.txt @@ -0,0 +1,20 @@ + ==++++++++++ + :========++++++++++++: + ===============+++++++++++ + ====================++++++++++ + :=============$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-------------------------- + .--------------------. + ------------ diff --git a/src/logo/ascii/s/semc.txt b/src/logo/ascii/s/semc.txt new file mode 100644 index 0000000..8c5a7d1 --- /dev/null +++ b/src/logo/ascii/s/semc.txt @@ -0,0 +1,9 @@ + /\ + ______/ \ + / |()| $2E M C +$1 | (-- | | + \ \ | | +.----) | |__| +|_______/ / $3"$1 \ + $3" + " \ No newline at end of file diff --git a/src/logo/ascii/s/septor.txt b/src/logo/ascii/s/septor.txt new file mode 100644 index 0000000..1a1a513 --- /dev/null +++ b/src/logo/ascii/s/septor.txt @@ -0,0 +1,20 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/s/serene.txt b/src/logo/ascii/s/serene.txt new file mode 100644 index 0000000..f53fad0 --- /dev/null +++ b/src/logo/ascii/s/serene.txt @@ -0,0 +1,20 @@ + __---''''''---__ + . . + : : + - _______----_- + s __----''' __---- + __h_ _-' _-' h + '-._''--.._ ; _-' y + : ''-._ '-._/ _-' : + y ':_ _--'' y + m .--'' '-._.;' m + m : : m + y '.._ '-__ y + : '--._ '''----___ : + y '--._ ''-- _ y + h '--._ : h + s __'; vs + - __..--'' - + :_..--'' : + . _ . + `''---______---''-`` \ No newline at end of file diff --git a/src/logo/ascii/s/serpent_os.txt b/src/logo/ascii/s/serpent_os.txt new file mode 100644 index 0000000..e09d617 --- /dev/null +++ b/src/logo/ascii/s/serpent_os.txt @@ -0,0 +1,18 @@ + ,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' diff --git a/src/logo/ascii/s/sharklinux.txt b/src/logo/ascii/s/sharklinux.txt new file mode 100644 index 0000000..b504e19 --- /dev/null +++ b/src/logo/ascii/s/sharklinux.txt @@ -0,0 +1,15 @@ + `:shd/ + `:yNMMMMs + `-smMMMMMMN. + .+dNMMMMMMMMs + .smNNMMMMMMMMm` + .sNNNNNNNMMMMMM/ + `omNNNNNNNMMMMMMm + /dNNNNNNNNMMMMMMM+ + .yNNNNNNNNNMMMMMMMN` + +mNNNNNNNNNMMMMMMMMh + .hNNNNNNNNNNMMMMMMMMMs + +mMNNNNNNNNMMMMMMMMMMMs + .hNMMNNNNMMMMMMMMMMMMMMMd + .oNNNNNNNNNNMMMMMMMMMMMMMMMo +`:+syyssoo++++ooooossssssssssso: \ No newline at end of file diff --git a/src/logo/ascii/s/shastraos.txt b/src/logo/ascii/s/shastraos.txt new file mode 100644 index 0000000..5e705ab --- /dev/null +++ b/src/logo/ascii/s/shastraos.txt @@ -0,0 +1,19 @@ + ..,;;,'. + ':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 \ No newline at end of file diff --git a/src/logo/ascii/s/shebang.txt b/src/logo/ascii/s/shebang.txt new file mode 100644 index 0000000..22b98e3 --- /dev/null +++ b/src/logo/ascii/s/shebang.txt @@ -0,0 +1,20 @@ + ' + '#' + '#!#' + '#!#!#' + '#!#!#!#' + '#!#!#!#!#' + '#!#!#!#!#!#' + '#!#!#!#!#!#!#' + #!#!#!#!#!#!' + '#! #!#!#!#!#' + '#!#!#!#! #!#!#!' + '#!#!#!#!#!#!#! #!#!' + '#!#!#!#!#!#!#!#!#!#! #' + '#!#!#!#!#!#!#!#!#!#!#!#! + '#!#!#!#!#!#!#!#!#!#! #!' + '#!#!#!#!#!#!#!#! #!#!#!' + '#!#!#!#!#!#! #!#!#!#!#!' + '#!#!#!#! #!#!#!#!#' + '#!#! #!#!#' +'# #!' diff --git a/src/logo/ascii/s/siduction.txt b/src/logo/ascii/s/siduction.txt new file mode 100644 index 0000000..55b628b --- /dev/null +++ b/src/logo/ascii/s/siduction.txt @@ -0,0 +1,20 @@ + _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??" \ No newline at end of file diff --git a/src/logo/ascii/s/skiffos.txt b/src/logo/ascii/s/skiffos.txt new file mode 100644 index 0000000..e98a647 --- /dev/null +++ b/src/logo/ascii/s/skiffos.txt @@ -0,0 +1,9 @@ +$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'"""""""" \ No newline at end of file diff --git a/src/logo/ascii/s/slackel.txt b/src/logo/ascii/s/slackel.txt new file mode 100644 index 0000000..2c85dd7 --- /dev/null +++ b/src/logo/ascii/s/slackel.txt @@ -0,0 +1,22 @@ + _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 + `````````````` diff --git a/src/logo/ascii/s/sleeperos_small.txt b/src/logo/ascii/s/sleeperos_small.txt new file mode 100644 index 0000000..13c18f3 --- /dev/null +++ b/src/logo/ascii/s/sleeperos_small.txt @@ -0,0 +1,7 @@ + _____ +[$2BBBBB$1] +`^^7$2&$1/` + ,/$2&$1/` +,/$2&$1Z__ +[$2BBBBB$1] +`^^^^^` diff --git a/src/logo/ascii/s/slitaz.txt b/src/logo/ascii/s/slitaz.txt new file mode 100644 index 0000000..020e4a9 --- /dev/null +++ b/src/logo/ascii/s/slitaz.txt @@ -0,0 +1,17 @@ + @ @( @ + @@ @@ @ @/ + @@ @@ @@ @@ + @@ %@@ @@ @@ + @@ %@@@ @@@@@. @@@@ @@ + @@@ @@@@ @@@@@@@ &@@@ @@@ + @@@@@@@ %@@@@@@@@@@@@ &@@@% @@@@@@@/ + ,@@@@@@@@@@@@@@@@@@@@@@@@@ + .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/ +@@@@@@. @@@@@@@@@@@@@@@@@@@@@ /@@@@@@ +@@ @@@@@ @@@@@@@@@@@@, @@@@@ @@@ +@@ @@@@. @@@@@@@@@@@@@% #@@@@ @@. +@@ ,@@ @@@@@@@@@@@@@ @@@ @@ +@ @@. @@@@@@@@@@@@@ @@@ *@ +@ @@ @@@@@@@@@@@@ @@ @ + @ @@@@@@@@@. #@ + @ ,@@@@@ @ \ No newline at end of file diff --git a/src/logo/ascii/s/smartos.txt b/src/logo/ascii/s/smartos.txt new file mode 100644 index 0000000..e385245 --- /dev/null +++ b/src/logo/ascii/s/smartos.txt @@ -0,0 +1,17 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/s/snigdhaos.txt b/src/logo/ascii/s/snigdhaos.txt new file mode 100644 index 0000000..94f2201 --- /dev/null +++ b/src/logo/ascii/s/snigdhaos.txt @@ -0,0 +1,20 @@ + 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 diff --git a/src/logo/ascii/s/soda.txt b/src/logo/ascii/s/soda.txt new file mode 100644 index 0000000..c947682 --- /dev/null +++ b/src/logo/ascii/s/soda.txt @@ -0,0 +1,15 @@ + $2@&&&&$1 *'*, + $2@@@@@@@&&&$1 ** '*, + $2@@@@@@@@@@@&&&& + @&@@@@@@@@@@@@@@@&&& + $1******$2@@@@@@@@@@@@@@@&&&& + $1************$2@@@@@@@@@@@@@@ + $1*****************$2@@@@@@@@@ + $1**********************$2@@@ + @@@@@$1******************** + $2@@@@@@@@@$1*************** +$2@@@@@@@@@@@@@@@$1********* +$2@@@@@@@@@@@@@@@@@@$1**** + $2@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@ + @@@@@@@ \ No newline at end of file diff --git a/src/logo/ascii/s/solaris.txt b/src/logo/ascii/s/solaris.txt new file mode 100644 index 0000000..1af1483 --- /dev/null +++ b/src/logo/ascii/s/solaris.txt @@ -0,0 +1,8 @@ + `- ` + `-- `+- .: + .+: `++: -/+- . + `.::` -++/``:::`./+/ `.-/. + `++/-`.` ` /++:` + `` ./:` .: `..`.- +``./+/:- -+++:- + -/+` :. \ No newline at end of file diff --git a/src/logo/ascii/s/solaris_small.txt b/src/logo/ascii/s/solaris_small.txt new file mode 100644 index 0000000..8769217 --- /dev/null +++ b/src/logo/ascii/s/solaris_small.txt @@ -0,0 +1,5 @@ + . .; . + . :; :: ;: . + .;. .. .. .;. +.. .. .. .. + .;, ,;. \ No newline at end of file diff --git a/src/logo/ascii/s/solus.txt b/src/logo/ascii/s/solus.txt new file mode 100644 index 0000000..e18b5e0 --- /dev/null +++ b/src/logo/ascii/s/solus.txt @@ -0,0 +1,20 @@ +$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+/-` + `.-----..` \ No newline at end of file diff --git a/src/logo/ascii/s/source_mage.txt b/src/logo/ascii/s/source_mage.txt new file mode 100644 index 0000000..7816936 --- /dev/null +++ b/src/logo/ascii/s/source_mage.txt @@ -0,0 +1,21 @@ + :ymNMNho. +.+sdmNMMMMMMMMMMy` +.-::/yMMMMMMMMMMMm- + sMMMMMMMMMMMm/ + /NMMMMMMMMMMMMMm: + .MMMMMMMMMMMMMMMMM: + `MMMMMMMMMMMMMMMMMN. + NMMMMMMMMMMMMMMMMMd + mMMMMMMMMMMMMMMMMMMo + hhMMMMMMMMMMMMMMMMMM. + .`/MMMMMMMMMMMMMMMMMs + :mMMMMMMMMMMMMMMMN` + `sMMMMMMMMMMMMMMM+ + /NMMMMMMMMMMMMMN` + oMMMMMMMMMMMMM+ + ./sd.-hMMMMMMMMmmN` + ./+oyyyh- `MMMMMMMMMmNh + sMMMMMMMMMmmo + `NMMMMMMMMMd: + -dMMMMMMMMMo + -shmNMMms. \ No newline at end of file diff --git a/src/logo/ascii/s/sparky.txt b/src/logo/ascii/s/sparky.txt new file mode 100644 index 0000000..6ee3cbf --- /dev/null +++ b/src/logo/ascii/s/sparky.txt @@ -0,0 +1,20 @@ + . `-:-` + .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+:` `:` + ./+/-` ` + `-:-. + `` \ No newline at end of file diff --git a/src/logo/ascii/s/spoinkos.txt b/src/logo/ascii/s/spoinkos.txt new file mode 100644 index 0000000..1d27800 --- /dev/null +++ b/src/logo/ascii/s/spoinkos.txt @@ -0,0 +1,5 @@ + @..@ + (----) + ( >__< ) + ^^ ~~ ^^ +| Spoink! | \ No newline at end of file diff --git a/src/logo/ascii/s/star.txt b/src/logo/ascii/s/star.txt new file mode 100644 index 0000000..10ba883 --- /dev/null +++ b/src/logo/ascii/s/star.txt @@ -0,0 +1,19 @@ + ./ + `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 \ No newline at end of file diff --git a/src/logo/ascii/s/steamdeck.txt b/src/logo/ascii/s/steamdeck.txt new file mode 100644 index 0000000..9f4b066 --- /dev/null +++ b/src/logo/ascii/s/steamdeck.txt @@ -0,0 +1,24 @@ +$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,.. diff --git a/src/logo/ascii/s/steamdeck_small.txt b/src/logo/ascii/s/steamdeck_small.txt new file mode 100644 index 0000000..287b637 --- /dev/null +++ b/src/logo/ascii/s/steamdeck_small.txt @@ -0,0 +1,5 @@ + $2__ + \ +$1## $2\ +$1## $2/ + __/ \ No newline at end of file diff --git a/src/logo/ascii/s/steamos.txt b/src/logo/ascii/s/steamos.txt new file mode 100644 index 0000000..c8eaa46 --- /dev/null +++ b/src/logo/ascii/s/steamos.txt @@ -0,0 +1,18 @@ +$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,',. '; ,+##############' + '###+. :,. .,; ,###############' + '####.. `'' .,###############' + '#####+++################' + '*##################*' + ''*##########*'' + '''''' \ No newline at end of file diff --git a/src/logo/ascii/s/stock_linux.txt b/src/logo/ascii/s/stock_linux.txt new file mode 100644 index 0000000..7fc4ca1 --- /dev/null +++ b/src/logo/ascii/s/stock_linux.txt @@ -0,0 +1,19 @@ + #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# \ No newline at end of file diff --git a/src/logo/ascii/s/sulin.txt b/src/logo/ascii/s/sulin.txt new file mode 100644 index 0000000..484ea55 --- /dev/null +++ b/src/logo/ascii/s/sulin.txt @@ -0,0 +1,20 @@ + /\ /\ + ( \\ // ) + \ \\ // / + \_\\||||//_/ + \/ _ _ \ + \/|(O)(O)| + \/ | | + ___________________\/ \ / + // // |____| + // || / \ + //| \| \ 0 0 / + // \ ) V / \____/ +// \ / ( / + \ /_________| |_/ + / /\ / | || + / / / / \ || + | | | | | || + | | | | | || + |_| |_| |_|| + \_\ \_\ \_\ \ No newline at end of file diff --git a/src/logo/ascii/s/summitos.txt b/src/logo/ascii/s/summitos.txt new file mode 100644 index 0000000..ff875cb --- /dev/null +++ b/src/logo/ascii/s/summitos.txt @@ -0,0 +1,22 @@ + :~^ + ~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!!!!!!!!!!!!!!!!~ diff --git a/src/logo/ascii/s/suse.txt b/src/logo/ascii/s/suse.txt new file mode 100644 index 0000000..c45aa67 --- /dev/null +++ b/src/logo/ascii/s/suse.txt @@ -0,0 +1,11 @@ + kKKKKKd' Okxol:;,. +kKKKKKKK0kOKKKKKKKKKKOxo:, +KKKKKKKKKKKKKKKKKKK0P^,,,^dx: +KKKKKKKKKKKKKKKKKKk'.oOPPb.'0k. +KKKKKKKKKKKKKKKKKK: kKx..dd lKd +KKKKKKKKKKKKOx0KKKd ^0KKKO' kKKc +KKKKKKKKKKKKK;.;oOKx,..^..;kKKK0. +KKKKKKKKKKKKK0o;...^cdxxOK0O/^^' +KKKKKKKKKKKKKKKKK0x;,,......,;od +kKKKKKKKKKKKKKKKKKKKKKKK00KKOo^ + kKKKKKOxddxkOO00000Okxoc;'' \ No newline at end of file diff --git a/src/logo/ascii/s/swagarch.txt b/src/logo/ascii/s/swagarch.txt new file mode 100644 index 0000000..ad8d209 --- /dev/null +++ b/src/logo/ascii/s/swagarch.txt @@ -0,0 +1,15 @@ +$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:, + '' \ No newline at end of file diff --git a/src/logo/ascii/t.inc b/src/logo/ascii/t.inc new file mode 100644 index 0000000..53947ea --- /dev/null +++ b/src/logo/ascii/t.inc @@ -0,0 +1,152 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo T[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_T2 + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_T2_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TAILS + // Tails + { + .names = { "Tails" }, + .lines = FASTFETCH_DATATEXT_LOGO_TAILS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TATRA + // Tatra + { + .names = { "Tatra" }, + .lines = FASTFETCH_DATATEXT_LOGO_TATRA, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TEARCH + // TeArch + { + .names = { "TeArch" }, + .lines = FASTFETCH_DATATEXT_LOGO_TEARCH, + .colors = { + FF_COLOR_FG_256 "39", + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TEMPLEOS + // TempleOS + { + .names = { "TempleOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_TEMPLEOS, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_RED, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TILEOS + // TileOS + { + .names = { "TileOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_TILEOS, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_BLUE, + FF_COLOR_FG_GREEN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TORIZONCORE + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TRISQUEL + // Trisquel + { + .names = { "Trisquel" }, + .lines = FASTFETCH_DATATEXT_LOGO_TRISQUEL, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TRUENAS_SCALE + // 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", + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TUXEDO_OS + // TuxedoOS + { + .names = { "Tuxedo OS", "tuxedo" }, + .lines = FASTFETCH_DATATEXT_LOGO_TUXEDO_OS, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_TWISTER + // 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, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/t/t2.txt b/src/logo/ascii/t/t2.txt new file mode 100644 index 0000000..c174d0e --- /dev/null +++ b/src/logo/ascii/t/t2.txt @@ -0,0 +1,14 @@ + $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 diff --git a/src/logo/ascii/t/t2_small.txt b/src/logo/ascii/t/t2_small.txt new file mode 100644 index 0000000..83981dc --- /dev/null +++ b/src/logo/ascii/t/t2_small.txt @@ -0,0 +1,6 @@ +$2TTTTTTTTTT + tt $1222$2 + tt $12 2$2 + tt $12$2 + tt $12$2 + tt $122222$2 \ No newline at end of file diff --git a/src/logo/ascii/t/tails.txt b/src/logo/ascii/t/tails.txt new file mode 100644 index 0000000..85c6d42 --- /dev/null +++ b/src/logo/ascii/t/tails.txt @@ -0,0 +1,19 @@ + `` + ./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/.` + .//` \ No newline at end of file diff --git a/src/logo/ascii/t/tatra.txt b/src/logo/ascii/t/tatra.txt new file mode 100644 index 0000000..d0b617e --- /dev/null +++ b/src/logo/ascii/t/tatra.txt @@ -0,0 +1,15 @@ + . .:. . .:. + .^^.!.:::. .^!?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?!. + .^^:..::::::::.....:::::::::::..:. \ No newline at end of file diff --git a/src/logo/ascii/t/tearch.txt b/src/logo/ascii/t/tearch.txt new file mode 100644 index 0000000..76c2861 --- /dev/null +++ b/src/logo/ascii/t/tearch.txt @@ -0,0 +1,21 @@ + @@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@ + @@@@@ @@@@@ + @@ @@ + @% @@ + @ @ + @@@@@@@@@@@@@@@@@@@@@@@@ @@ + .@@@@@@@@@@@@/@@@@@@@@@@@@ + @@@@@@@@@@@@///@@@@@@@@@@@@ + @@@@@@@@@@@@@((((@@@@@@@@@@@@ + @@@@@@@@@@@#(((((((#@@@@@@@@@@@ + @@@@@@@@@@@#//////////@@@@@@@@@@& + @@@@@@@@@@////@@@@@////@@@@@@@@@@ + @@@@@@@@//////@@@@@/////@@@@@@@@@ + @@@@@@@//@@@@@@@@@@@@@@@//@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@ .@@@@@@@@@@@@@@@@@@@@@@@@@ @ + @@@@@@ @@@. @@@@@@@ + @@@@@@@&@@@@@@@# #@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@ \ No newline at end of file diff --git a/src/logo/ascii/t/templeos.txt b/src/logo/ascii/t/templeos.txt new file mode 100644 index 0000000..a9caa95 --- /dev/null +++ b/src/logo/ascii/t/templeos.txt @@ -0,0 +1,20 @@ + $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`` + ``````` + ````` \ No newline at end of file diff --git a/src/logo/ascii/t/tileos.txt b/src/logo/ascii/t/tileos.txt new file mode 100644 index 0000000..42f3d59 --- /dev/null +++ b/src/logo/ascii/t/tileos.txt @@ -0,0 +1,20 @@ +$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 \ No newline at end of file diff --git a/src/logo/ascii/t/torizoncore.txt b/src/logo/ascii/t/torizoncore.txt new file mode 100644 index 0000000..541705a --- /dev/null +++ b/src/logo/ascii/t/torizoncore.txt @@ -0,0 +1,29 @@ +$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 .-///:-. + .-:///:.` `.:///:-` + `-:///:.````.:///:-` + `-:////////:-` + `-::::-` \ No newline at end of file diff --git a/src/logo/ascii/t/trisquel.txt b/src/logo/ascii/t/trisquel.txt new file mode 100644 index 0000000..203345e --- /dev/null +++ b/src/logo/ascii/t/trisquel.txt @@ -0,0 +1,17 @@ + ,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' \ No newline at end of file diff --git a/src/logo/ascii/t/truenas_scale.txt b/src/logo/ascii/t/truenas_scale.txt new file mode 100644 index 0000000..364a6dd --- /dev/null +++ b/src/logo/ascii/t/truenas_scale.txt @@ -0,0 +1,18 @@ + ++ $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++ \ No newline at end of file diff --git a/src/logo/ascii/t/tuxedo_os.txt b/src/logo/ascii/t/tuxedo_os.txt new file mode 100644 index 0000000..08b108e --- /dev/null +++ b/src/logo/ascii/t/tuxedo_os.txt @@ -0,0 +1,16 @@ + #############################+ +.%#######################%@@@+ + . :#@%- + .#@%%%- =@@#. + *@%@@= +@@+ + =@@%@+.#@%- + -@@%@@@#: + .#@%%@- + :#@@%@@= + =@@+=@@%@+ + .#@@= -@@%@# + -%@#: :%@%@%: + *@@+ .#%%%%- + :%@%- . + +@@@%#%%%%%%%%%%%%%%%%%%%%%%%. + +######################******* \ No newline at end of file diff --git a/src/logo/ascii/t/twister.txt b/src/logo/ascii/t/twister.txt new file mode 100644 index 0000000..30ec36d --- /dev/null +++ b/src/logo/ascii/t/twister.txt @@ -0,0 +1,23 @@ +$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....................... \ No newline at end of file diff --git a/src/logo/ascii/u.inc b/src/logo/ascii/u.inc new file mode 100644 index 0000000..79bad1a --- /dev/null +++ b/src/logo/ascii/u.inc @@ -0,0 +1,316 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo U[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_UBLINUX + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBLINUX_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU + // Ubuntu + { + .names = { "ubuntu", "ubuntu-linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_RED, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_BUDGIE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_CINNAMON + // UbuntuCinnamon + { + .names = { "ubuntu cinnamon", "ubuntu-cinnamon" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_CINNAMON, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_GNOME + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_KYLIN + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_MATE + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_STUDIO + // UbuntuStudio + { + .names = { "ubuntu studio", "ubuntu-studio" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_STUDIO, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_SWAY + // UbuntuSway + { + .names = { "ubuntu sway", "ubuntu-sway" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_SWAY, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_TOUCH + // UbuntuTouch + { + .names = { "ubuntu touch", "ubuntu-touch" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_TOUCH, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UBUNTU_UNITY + // UbuntuUnity + { + .names = { "ubuntu unity", "ubuntu-unity" }, + .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_UNITY, + .colors = { + FF_COLOR_FG_MAGENTA, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ULTRAMARINE + // Ultramarine + { + .names = { "Ultramarine", "Ultramarine Linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ULTRAMARINE_SMALL + // Ultramarine Small + { + .names = { "Ultramarine_small" }, + .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE_SMALL, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UNIFI + // Unifi + { + .names = { "Unifi" }, + .lines = FASTFETCH_DATATEXT_LOGO_UNIFI, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UNIVALENT + // Univalent + { + .names = { "Univalent" }, + .lines = FASTFETCH_DATATEXT_LOGO_UNIVALENT, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UNIVENTION + // Univention + { + .names = { "Univention" }, + .lines = FASTFETCH_DATATEXT_LOGO_UNIVENTION, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UOS + // UOS + { + .names = { "UOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_UOS, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_DEFAULT, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_URUKOS + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UWUNTU + // Uwuntu + { + .names = { "uwuntu" }, + .lines = FASTFETCH_DATATEXT_LOGO_UWUNTU, + .colors = { + FF_COLOR_FG_256 "225", + FF_COLOR_FG_256 "206", + FF_COLOR_FG_256 "52", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_UZBEK + // Uzbek + { + .names = { "Uzbek" }, + .lines = FASTFETCH_DATATEXT_LOGO_UZBEK, + .colors = { + FF_COLOR_FG_GREEN, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/u/ublinux.txt b/src/logo/ascii/u/ublinux.txt new file mode 100644 index 0000000..7e9a9e3 --- /dev/null +++ b/src/logo/ascii/u/ublinux.txt @@ -0,0 +1,19 @@ +$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###' diff --git a/src/logo/ascii/u/ublinux_small.txt b/src/logo/ascii/u/ublinux_small.txt new file mode 100644 index 0000000..4b94249 --- /dev/null +++ b/src/logo/ascii/u/ublinux_small.txt @@ -0,0 +1,11 @@ +$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# diff --git a/src/logo/ascii/u/ubuntu.txt b/src/logo/ascii/u/ubuntu.txt new file mode 100644 index 0000000..51515e7 --- /dev/null +++ b/src/logo/ascii/u/ubuntu.txt @@ -0,0 +1,20 @@ + .... + $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:' \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_budgie.txt b/src/logo/ascii/u/ubuntu_budgie.txt new file mode 100644 index 0000000..dca8359 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_budgie.txt @@ -0,0 +1,20 @@ + ./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/. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_cinnamon.txt b/src/logo/ascii/u/ubuntu_cinnamon.txt new file mode 100644 index 0000000..5ed6690 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_cinnamon.txt @@ -0,0 +1,20 @@ + .-/+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+/-. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_gnome.txt b/src/logo/ascii/u/ubuntu_gnome.txt new file mode 100644 index 0000000..41c2633 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_gnome.txt @@ -0,0 +1,16 @@ +$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`` \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_kylin.txt b/src/logo/ascii/u/ubuntu_kylin.txt new file mode 100644 index 0000000..d03aa91 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_kylin.txt @@ -0,0 +1,20 @@ +$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>~~` \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_mate.txt b/src/logo/ascii/u/ubuntu_mate.txt new file mode 100644 index 0000000..71bddca --- /dev/null +++ b/src/logo/ascii/u/ubuntu_mate.txt @@ -0,0 +1,20 @@ +$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+/:. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_old.txt b/src/logo/ascii/u/ubuntu_old.txt new file mode 100644 index 0000000..9193c8f --- /dev/null +++ b/src/logo/ascii/u/ubuntu_old.txt @@ -0,0 +1,20 @@ + --+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+-- \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_old2.txt b/src/logo/ascii/u/ubuntu_old2.txt new file mode 100644 index 0000000..b78f810 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_old2.txt @@ -0,0 +1,18 @@ + ./+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++. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_old2_small.txt b/src/logo/ascii/u/ubuntu_old2_small.txt new file mode 100644 index 0000000..491b534 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_old2_small.txt @@ -0,0 +1,6 @@ + _ + ---(_) + _/ --- \ +(_) | | + \ --- _/ + ---(_) \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_small.txt b/src/logo/ascii/u/ubuntu_small.txt new file mode 100644 index 0000000..62c4223 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_small.txt @@ -0,0 +1,11 @@ + $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'''' \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_studio.txt b/src/logo/ascii/u/ubuntu_studio.txt new file mode 100644 index 0000000..129869b --- /dev/null +++ b/src/logo/ascii/u/ubuntu_studio.txt @@ -0,0 +1,20 @@ + ..-::::::-.` + `.:+++++++++++$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++:. + ..-::::::-.. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_sway.txt b/src/logo/ascii/u/ubuntu_sway.txt new file mode 100644 index 0000000..deb53ad --- /dev/null +++ b/src/logo/ascii/u/ubuntu_sway.txt @@ -0,0 +1,20 @@ + .-/+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+/-. \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_touch.txt b/src/logo/ascii/u/ubuntu_touch.txt new file mode 100644 index 0000000..50d5808 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_touch.txt @@ -0,0 +1,7 @@ + ############### + ## ## +## $2##$1 $2##$1 ## +## $2##$1 $2#$1 $2#$1 $2##$1 ## +## $2###$1 ## + ## ## + ############### \ No newline at end of file diff --git a/src/logo/ascii/u/ubuntu_unity.txt b/src/logo/ascii/u/ubuntu_unity.txt new file mode 100644 index 0000000..b288ef8 --- /dev/null +++ b/src/logo/ascii/u/ubuntu_unity.txt @@ -0,0 +1,25 @@ + .-/+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+/-' \ No newline at end of file diff --git a/src/logo/ascii/u/ultramarine.txt b/src/logo/ascii/u/ultramarine.txt new file mode 100644 index 0000000..4021d05 --- /dev/null +++ b/src/logo/ascii/u/ultramarine.txt @@ -0,0 +1,20 @@ + .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' \ No newline at end of file diff --git a/src/logo/ascii/u/ultramarine_small.txt b/src/logo/ascii/u/ultramarine_small.txt new file mode 100644 index 0000000..a9d93c6 --- /dev/null +++ b/src/logo/ascii/u/ultramarine_small.txt @@ -0,0 +1,14 @@ + @@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@*=+*%@@@@@@@@@@@@ +@@@@@@@@@@: :#@@@@@@@@@@ +@@@@@@@@@# -#@@@@@@@@ +@@%###*=. :=*#@@@@ +@@= =@@ + @@= -@@ + @@*: :*@@ + @%*=: :=*@@ + @@@%%##%%@@@ diff --git a/src/logo/ascii/u/unifi.txt b/src/logo/ascii/u/unifi.txt new file mode 100644 index 0000000..dff4632 --- /dev/null +++ b/src/logo/ascii/u/unifi.txt @@ -0,0 +1,6 @@ + ___ ___ .__________.__ + | | |____ |__\_ ____/__| + | | / \| || __) | | + | | | | \ || \ | | + |______|___| /__||__/ |__| + |_/ \ No newline at end of file diff --git a/src/logo/ascii/u/univalent.txt b/src/logo/ascii/u/univalent.txt new file mode 100644 index 0000000..b8aab22 --- /dev/null +++ b/src/logo/ascii/u/univalent.txt @@ -0,0 +1,18 @@ +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 \ No newline at end of file diff --git a/src/logo/ascii/u/univention.txt b/src/logo/ascii/u/univention.txt new file mode 100644 index 0000000..ceafbae --- /dev/null +++ b/src/logo/ascii/u/univention.txt @@ -0,0 +1,20 @@ + ./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+:` \ No newline at end of file diff --git a/src/logo/ascii/u/uos.txt b/src/logo/ascii/u/uos.txt new file mode 100644 index 0000000..82d9688 --- /dev/null +++ b/src/logo/ascii/u/uos.txt @@ -0,0 +1,20 @@ + ....... + .............. + ...................... + ............................. +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 + ............................. + ..................... + ............. + ...... \ No newline at end of file diff --git a/src/logo/ascii/u/urukos.txt b/src/logo/ascii/u/urukos.txt new file mode 100644 index 0000000..004df17 --- /dev/null +++ b/src/logo/ascii/u/urukos.txt @@ -0,0 +1,20 @@ + $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::::::::::::::::::. \ No newline at end of file diff --git a/src/logo/ascii/u/uwuntu.txt b/src/logo/ascii/u/uwuntu.txt new file mode 100644 index 0000000..e77f14d --- /dev/null +++ b/src/logo/ascii/u/uwuntu.txt @@ -0,0 +1,20 @@ + && + &&&&&&&& + , *&&&&&& &&&&&&&&( + &%%%%&&&& &&&&&&&&&&&& ,&&&&& + %%$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%%%%%%% \ No newline at end of file diff --git a/src/logo/ascii/u/uzbek.txt b/src/logo/ascii/u/uzbek.txt new file mode 100644 index 0000000..3a9ab6e --- /dev/null +++ b/src/logo/ascii/u/uzbek.txt @@ -0,0 +1,11 @@ + *++++++++++++++* +*=:-::-::-::-::-::=* ++============. ===+ ++===========. ====+ ++++++++++++ +++++* +*+++ =+++ ++++++* +*++++ .+ +++++++* +*+++++ ++++++++* +*++++++ =+++++++++* +*++++++++++++++++++* + *++++++++++++++* \ No newline at end of file diff --git a/src/logo/ascii/v.inc b/src/logo/ascii/v.inc new file mode 100644 index 0000000..0ad5969 --- /dev/null +++ b/src/logo/ascii/v.inc @@ -0,0 +1,170 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo V[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_VALHALLA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VANILLA + // Vanilla + { + .names = { "vanilla" }, + .lines = FASTFETCH_DATATEXT_LOGO_VANILLA, + .colors = { + FF_COLOR_FG_YELLOW, + }, + .colorKeys = FF_COLOR_FG_YELLOW, + .colorTitle = FF_COLOR_FG_YELLOW, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VANILLA2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VANILLA_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VENOM + // Venom + { + .names = { "Venom" }, + .lines = FASTFETCH_DATATEXT_LOGO_VENOM, + .colors = { + FF_COLOR_FG_LIGHT_BLACK, + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VENOM_SMALL + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VINCENTOS + // VincentOS + { + .names = { "VincentOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_VINCENTOS, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VNUX + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VZLINUX + // Vzlinux + { + .names = { "Vzlinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_VZLINUX, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_YELLOW, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VOID + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VOID_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VOID2 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_VOID2_SMALL + // 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, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/v/valhalla.txt b/src/logo/ascii/v/valhalla.txt new file mode 100644 index 0000000..e961179 --- /dev/null +++ b/src/logo/ascii/v/valhalla.txt @@ -0,0 +1,10 @@ + .:~: + !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 + .:.^:^^:!.!:!!:!:!:^^:^.:. \ No newline at end of file diff --git a/src/logo/ascii/v/vanilla.txt b/src/logo/ascii/v/vanilla.txt new file mode 100644 index 0000000..ab5631c --- /dev/null +++ b/src/logo/ascii/v/vanilla.txt @@ -0,0 +1,18 @@ + .----: + .-------. + :---::----: + .----::-----. + ......... :----::-----: ..:::-::::.. +.-----------------::------------------: + ----::-----------::----------::::---: + -----:::--------::-------:::------- + :------::::--::...:::::---------: + .---------::.. ..:---------. + .::-----::.. .::----::. + .:------:.......:-------: + .--------::::::::-:::-------. + .-------::-----.:-----::------. + -----::------: :------::----- + :--::--------: .-------::---: + :----------:: .:---------- + :--------: :--------: \ No newline at end of file diff --git a/src/logo/ascii/v/vanilla2.txt b/src/logo/ascii/v/vanilla2.txt new file mode 100644 index 0000000..343ad8b --- /dev/null +++ b/src/logo/ascii/v/vanilla2.txt @@ -0,0 +1,16 @@ + . + 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(" + ^" \ No newline at end of file diff --git a/src/logo/ascii/v/void.txt b/src/logo/ascii/v/void.txt new file mode 100644 index 0000000..4b1ad9a --- /dev/null +++ b/src/logo/ascii/v/void.txt @@ -0,0 +1,18 @@ + __.;=====;.__ + _.=+==++=++=+=+===;. + -=+++=+===+=+=+++++=_ + . -=:`` `--==+=++==. + _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}*|~ \ No newline at end of file diff --git a/src/logo/ascii/v/void2.txt b/src/logo/ascii/v/void2.txt new file mode 100644 index 0000000..f00a568 --- /dev/null +++ b/src/logo/ascii/v/void2.txt @@ -0,0 +1,25 @@ + .......... + .::::::::::::::::::.. + ..:::::::::::::::::::::::::. + '::::::::::::::::::::::::::::. + ':::::'' '':::::::::::::. +$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 '|||||||||||:... ...:||||. + ':||||||||||||||||||||||||||. + ':|||||||||||||||||||||||'' + '':||||||||||||||:'' + '''''' diff --git a/src/logo/ascii/v/void2_small.txt b/src/logo/ascii/v/void2_small.txt new file mode 100644 index 0000000..e9602b0 --- /dev/null +++ b/src/logo/ascii/v/void2_small.txt @@ -0,0 +1,7 @@ + _______ + _ \______ - +| \ ___ \ | +| | / \ | | +| | \___/ | | +| \______ \_| + -_______\ \ No newline at end of file diff --git a/src/logo/ascii/v/void_small.txt b/src/logo/ascii/v/void_small.txt new file mode 100644 index 0000000..344be7d --- /dev/null +++ b/src/logo/ascii/v/void_small.txt @@ -0,0 +1,7 @@ + ____ + 'pfPfp.% +// _._ \\ +UU |===| UU +\\ ^~^ // + `0PpppP' + ````` \ No newline at end of file diff --git a/src/logo/ascii/v/vzlinux.txt b/src/logo/ascii/v/vzlinux.txt new file mode 100644 index 0000000..36ecd2c --- /dev/null +++ b/src/logo/ascii/v/vzlinux.txt @@ -0,0 +1,20 @@ + $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. + ----. \ No newline at end of file diff --git a/src/logo/ascii/w.inc b/src/logo/ascii/w.inc new file mode 100644 index 0000000..dea8eaf --- /dev/null +++ b/src/logo/ascii/w.inc @@ -0,0 +1,123 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo W[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_WII_LINUX + // WiiLinux + { + .names = { "WiiLinuxNgx", "WiiLinux", "Wii-Linux", "Wii Linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_WII_LINUX, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS_2025 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS_11 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS_11_SMALL + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS_8 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WINDOWS_95 + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_WOLFOS + // WolfOS + { + .names = { "WolfOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_WOLFOS, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_GREEN, + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/w/wii_linux.txt b/src/logo/ascii/w/wii_linux.txt new file mode 100644 index 0000000..66d3270 --- /dev/null +++ b/src/logo/ascii/w/wii_linux.txt @@ -0,0 +1,16 @@ +''''''' `~;:` -'''''' ~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 \ No newline at end of file diff --git a/src/logo/ascii/w/windows.txt b/src/logo/ascii/w/windows.txt new file mode 100644 index 0000000..ffe0410 --- /dev/null +++ b/src/logo/ascii/w/windows.txt @@ -0,0 +1,16 @@ +$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>*` \ No newline at end of file diff --git a/src/logo/ascii/w/windows_11.txt b/src/logo/ascii/w/windows_11.txt new file mode 100644 index 0000000..1fd9667 --- /dev/null +++ b/src/logo/ascii/w/windows_11.txt @@ -0,0 +1,17 @@ +$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///////////////// \ No newline at end of file diff --git a/src/logo/ascii/w/windows_11_small.txt b/src/logo/ascii/w/windows_11_small.txt new file mode 100644 index 0000000..45cde6f --- /dev/null +++ b/src/logo/ascii/w/windows_11_small.txt @@ -0,0 +1,7 @@ +$1lllllll $2lllllll +$1lllllll $2lllllll +$1lllllll $2lllllll + +$3lllllll $4lllllll +$3lllllll $4lllllll +$3lllllll $4lllllll \ No newline at end of file diff --git a/src/logo/ascii/w/windows_2025.txt b/src/logo/ascii/w/windows_2025.txt new file mode 100644 index 0000000..1c02084 --- /dev/null +++ b/src/logo/ascii/w/windows_2025.txt @@ -0,0 +1,17 @@ +$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%%%%%%%%%######## \ No newline at end of file diff --git a/src/logo/ascii/w/windows_8.txt b/src/logo/ascii/w/windows_8.txt new file mode 100644 index 0000000..a357dda --- /dev/null +++ b/src/logo/ascii/w/windows_8.txt @@ -0,0 +1,19 @@ + $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 + `` \ No newline at end of file diff --git a/src/logo/ascii/w/windows_95.txt b/src/logo/ascii/w/windows_95.txt new file mode 100644 index 0000000..7cb886c --- /dev/null +++ b/src/logo/ascii/w/windows_95.txt @@ -0,0 +1,18 @@ +$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" _ @@@@@@@@@@@@@@@@@ + *:+:.__ :+# @@@ @%#=+""""""+==%#@ + "+.-#+ +%* %+" " ":@ + " " \ No newline at end of file diff --git a/src/logo/ascii/w/wolfos.txt b/src/logo/ascii/w/wolfos.txt new file mode 100644 index 0000000..f0a7fed --- /dev/null +++ b/src/logo/ascii/w/wolfos.txt @@ -0,0 +1,5 @@ +__ __ _ __ ___ ____ +\ \ / /__ | |/ _|/ _ \/ ___| + \ \ /\ / / _ \| | |_| | | \___ \ + \ V V / (_) | | _| |_| |___) | + \_/\_/ \___/|_|_| \___/|____/ diff --git a/src/logo/ascii/x.inc b/src/logo/ascii/x.inc new file mode 100644 index 0000000..8c2341e --- /dev/null +++ b/src/logo/ascii/x.inc @@ -0,0 +1,132 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo X[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_XCP_NG + // 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, + } }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XENIA + // 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, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XENIA_OLD + // 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, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XEROARCH + // 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", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XFERIENCE + // Xferience + { + .names = { "Xferience" }, + .lines = FASTFETCH_DATATEXT_LOGO_XFERIENCE, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XUBUNTU + // Xubuntu + { + .names = { "Xubuntu" }, + .lines = FASTFETCH_DATATEXT_LOGO_XUBUNTU, + .colors = { + FF_COLOR_FG_256 "25", + FF_COLOR_FG_DEFAULT, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XRAY_OS + // 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", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XINUX + // Xinux + { + .names = { "Xinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_XINUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XJ380 + // XJ380 + { + .names = { "XJ380" }, + .lines = FASTFETCH_DATATEXT_LOGO_XJ380, + .colors = { + FF_COLOR_FG_RGB "0;162;232", + FF_COLOR_FG_RGB "255;242;0", + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_XIMPER + // Ximper + { + .names = { "Ximper" }, + .lines = FASTFETCH_DATATEXT_LOGO_XIMPER, + .colors = { + FF_COLOR_FG_256 "21", + FF_COLOR_FG_256 "57", + FF_COLOR_FG_256 "92", + FF_COLOR_FG_256 "128", + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/x/xcp_ng.txt b/src/logo/ascii/x/xcp_ng.txt new file mode 100644 index 0000000..a957e44 --- /dev/null +++ b/src/logo/ascii/x/xcp_ng.txt @@ -0,0 +1,15 @@ + $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... \ No newline at end of file diff --git a/src/logo/ascii/x/xenia.txt b/src/logo/ascii/x/xenia.txt new file mode 100644 index 0000000..e1aa543 --- /dev/null +++ b/src/logo/ascii/x/xenia.txt @@ -0,0 +1,15 @@ + ** + * ** */ + $2@$1///// / *** *** **** ***/ + //////////////**** ** * ** *** **/ + ,, ,,//////////* ****** ** ** ** / + , , ,,, /////******* ********* / + , ,,,,, ** ** *********** + ., ,,,**** ******* *** /// + ,, *********** ** //// + ,,,,, ******* //// // + ,,,,,,. ///// // + ,,, , /////// + ,,,, ///// + ,, / / + / diff --git a/src/logo/ascii/x/xenia_old.txt b/src/logo/ascii/x/xenia_old.txt new file mode 100644 index 0000000..6ddf64b --- /dev/null +++ b/src/logo/ascii/x/xenia_old.txt @@ -0,0 +1,21 @@ +$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. \ No newline at end of file diff --git a/src/logo/ascii/x/xeroarch.txt b/src/logo/ascii/x/xeroarch.txt new file mode 100644 index 0000000..c30a2a2 --- /dev/null +++ b/src/logo/ascii/x/xeroarch.txt @@ -0,0 +1,19 @@ +$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████████████████████████████████████████ \ No newline at end of file diff --git a/src/logo/ascii/x/xferience.txt b/src/logo/ascii/x/xferience.txt new file mode 100644 index 0000000..dd61400 --- /dev/null +++ b/src/logo/ascii/x/xferience.txt @@ -0,0 +1,19 @@ + ``--:::::::-.` + .-/+++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++/-` + `.-//++++++//:-.` + `````` \ No newline at end of file diff --git a/src/logo/ascii/x/ximper.txt b/src/logo/ascii/x/ximper.txt new file mode 100644 index 0000000..a803975 --- /dev/null +++ b/src/logo/ascii/x/ximper.txt @@ -0,0 +1,13 @@ +$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⢛⣫⡇⣤⠨⣝$3⡛⠁ $4 ⣀⡄⣤⠙⠿⣧⡀ +$1 ⢀⠞⣡⠞⣼⣿⣧⢹⣶⡄⣤⣶⣾$2⣿⠋⣼⣿⣧⠙$3⣿⣷⣶⣄$4⢠⣾⡟⣼⣿⣧⠳⣌⠳⡀ +$1⢀⣡⠾⢋⣾⣿⠿⣋⣤⢻⡇⢿⣿⡟$2⣱⠑⠟⠛⠻⠂$3⣎⢻⣿⣿$4⢸⡟⣠⣙⠿⣿⣷⡙⠷⣌ +$1 ⢠⡾⢟⣡⣾⣿⣿⣆⠇⢸⢋⣾$2⣿⣷⣄ ⣠⣴$3⣿⣷⡹⣿$4⠸⣰⣿⣿⣷⣌⡛⢷⡄ +$1 ⠐⣫⣴⣿⠿⠿⠿⠿⠛⠂⠰⢿⣿$2⣿⠟⠁ ⠈⠛$3⢿⣿⣿⠆$4⠐⠛⠻⠿⠿⠿⣿⣦⣍ +$1 ⠉$2 $3 ⠉ $4 \ No newline at end of file diff --git a/src/logo/ascii/x/xinux.txt b/src/logo/ascii/x/xinux.txt new file mode 100644 index 0000000..be34e68 --- /dev/null +++ b/src/logo/ascii/x/xinux.txt @@ -0,0 +1,14 @@ +$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******* ***** diff --git a/src/logo/ascii/x/xj380.txt b/src/logo/ascii/x/xj380.txt new file mode 100644 index 0000000..fd86e45 --- /dev/null +++ b/src/logo/ascii/x/xj380.txt @@ -0,0 +1,19 @@ + % + *++++++% + *+++++++++++++% + *++++++++++++++++++++% + *++++++++++++++++++++++++++% + *++++++*++++++++++$2%%$1+++++++++++++% + *+++++++++++$2*%%*$1+++++++++++$2%$1+++++++++++% + *++++++++++++++++$2%%%%$1+++++++++++++$2%$1+++++++++% +*++++++++++++++++++$2*%*$1+$2%%%%%%%%%$1++++++$2%$1++++++++% +*++++++++++++++$2**$2%%%%%**$2%*$1++$2%%$1++++++++$2%%$1+++++++% +*+++++++++++$2%%%*$1++$2*%$1++++$2%%%%*$1++++++++$2%%%%$1++++++% +*+++++$2%%%*$1++++$2%%%$1+$2%%$1++++$2%%%$1++++++++++$2*%%*$1++++++% +*+++++$2%%%%$1+++++++$2%%*$1++$2*%%*%*$1+++++++++++++++++++% +*++++++$2%$1+++++++++$2%%%%%%$1+++$2%%$1+++++++++++++++++++% +*++++++$2%*$1+++++++$2*%+%%*%%*$1++$2%%$1++++++++++++++++++% +*+++++++$2%$1+++++++$2%%%*$1++++$2*%%*%*$1+++++++++++++++++% +*++++++++$2*%*$1+++$2%%*$1+++++++++$2%%%$1+++++++++++++++++% +*++++++++++++$2%%$1++++++++++++++$2%$1+++++++++++++++++% +*++++++++++++++++++++++++++++++++++++++++++++++% \ No newline at end of file diff --git a/src/logo/ascii/x/xray_os.txt b/src/logo/ascii/x/xray_os.txt new file mode 100644 index 0000000..8b2d66d --- /dev/null +++ b/src/logo/ascii/x/xray_os.txt @@ -0,0 +1,21 @@ + $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 \ No newline at end of file diff --git a/src/logo/ascii/x/xubuntu.txt b/src/logo/ascii/x/xubuntu.txt new file mode 100644 index 0000000..5b52ed4 --- /dev/null +++ b/src/logo/ascii/x/xubuntu.txt @@ -0,0 +1,19 @@ + __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=~~~ \ No newline at end of file diff --git a/src/logo/ascii/y.inc b/src/logo/ascii/y.inc new file mode 100644 index 0000000..27caa86 --- /dev/null +++ b/src/logo/ascii/y.inc @@ -0,0 +1,31 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo Y[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_YIFFOS + // YiffOS + { + .names = { "YiffOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_YIFFOS, + .colors = { + FF_COLOR_FG_256 "93", + FF_COLOR_FG_256 "92", + }, + }, + #endif + + #ifdef FASTFETCH_DATATEXT_LOGO_YAKUBOS + // YiffOS + { + .names = { "YakubOS", "yakubos" }, + .lines = FASTFETCH_DATATEXT_LOGO_YAKUBOS, + .colors = { + FF_COLOR_FG_256 "130", + FF_COLOR_FG_256 "255", + }, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/y/yakubos.txt b/src/logo/ascii/y/yakubos.txt new file mode 100644 index 0000000..f123cda --- /dev/null +++ b/src/logo/ascii/y/yakubos.txt @@ -0,0 +1,20 @@ +$1 ...;;;;;;;;.... +$1 ...;;;;;;;;;;;;;;;;;.. +$1 ..;;;;;;;;;;;;;;;;;;;;;;;;.. +$1 .;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;. +$1 .;;;;;;;::;;;;;;;;;;;;;;;;;;;;;;;; +$1 .;;;;;;:$2YYY$1:::::::::::::::$2YY$1:;;;;;;; +$1 .;;;;;;;:$2Y@@Y$1:::::::::::::$2Y@@Y$1;;;;;;;; +$1.;;;;;;;;;:$2Y@@Y$1:::::::::::$2Y@@Y$1:;;;;;;;;. +$1.;;;;;;;;;;:$2@@Y$1::::::::::$2Y@@Y$1:;;;;;;;;;; +$1;;$2YYYYYY$1::;:$2Y@@Y$1:::::::::$2Y@Y$1::;;::$2YYYY$1;; +$1.;:$2YYY@@@@YYYY@@Y$1:::::::$2Y@@YYYYY@@@YYY$1:; +$1 ;;;;;::$2YYYY@@@@@Y$1:::::$2Y@@@@@YYYY$1::;;;;. +$1 .;;;;;;;;;::$2YYY@@@YYY@@@YYY$1::;;;;;;;;; +$1 ;;;;;;;;;:::::$2YY@@@@@Y$1::::::;;;;;;;; +$1 .;;;;;;;;;;;;;;$2YY@Y$1:;;;;;;;;;;;;;; +$1 ;;;;;;;;;;;;;;::;;;;;;;;;;;;;;. +$1 .;;;;;;;;;;;;;;;;;;;;;;;;; +$1 ;;;;;;;;;;;;;;;;;;;;. +$1 ;;;;;;;;;;. + diff --git a/src/logo/ascii/y/yiffos.txt b/src/logo/ascii/y/yiffos.txt new file mode 100644 index 0000000..0943284 --- /dev/null +++ b/src/logo/ascii/y/yiffos.txt @@ -0,0 +1,19 @@ + 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 \ No newline at end of file diff --git a/src/logo/ascii/z.inc b/src/logo/ascii/z.inc new file mode 100644 index 0000000..393c906 --- /dev/null +++ b/src/logo/ascii/z.inc @@ -0,0 +1,42 @@ +#include "../logo.h" +#include "logo_builtin.h" +#include "common/color.h" + +static const FFlogo Z[] = { + #ifdef FASTFETCH_DATATEXT_LOGO_ZORIN + // Zorin + { + .names = { "Zorin" }, + .lines = FASTFETCH_DATATEXT_LOGO_ZORIN, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ZOS + // Z/OS + { + .names = { "z/OS", "zos" }, + .lines = FASTFETCH_DATATEXT_LOGO_ZOS, + .colors = { + FF_COLOR_FG_BLUE, + }, + }, + #endif + #ifdef FASTFETCH_DATATEXT_LOGO_ZRAXYL + // Zraxyl + { + .names = { "Zraxyl" }, + .lines = FASTFETCH_DATATEXT_LOGO_ZRAXYL, + .colors = { + FF_COLOR_FG_BLUE, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_BLUE, + }, + #endif + // LAST + {}, +}; diff --git a/src/logo/ascii/z/zorin.txt b/src/logo/ascii/z/zorin.txt new file mode 100644 index 0000000..886f74b --- /dev/null +++ b/src/logo/ascii/z/zorin.txt @@ -0,0 +1,17 @@ + `osssssssssssssssssssso` + .osssssssssssssssssssssso. + .+oooooooooooooooooooooooo+. + + + `::::::::::::::::::::::. .:` + `+ssssssssssssssssss+:.` `.:+ssso` +.ossssssssssssssso/. `-+ossssssso. +ssssssssssssso/-` `-/osssssssssssss +.ossssssso/-` .-/ossssssssssssssso. + `+sss+:. `.:+ssssssssssssssssss+` + `:. .::::::::::::::::::::::` + + + .+oooooooooooooooooooooooo+. + -osssssssssssssssssssssso- + `osssssssssssssssssssso` \ No newline at end of file diff --git a/src/logo/ascii/z/zos.txt b/src/logo/ascii/z/zos.txt new file mode 100644 index 0000000..15884dd --- /dev/null +++ b/src/logo/ascii/z/zos.txt @@ -0,0 +1,7 @@ + // OOOOOOO SSSSSSS + // OO OO SS + zzzzzz // OO OO SS + zz // OO OO SSSS + zz // OO OO SS + zz // OO OO SS +zzzzzz // OOOOOOO SSSSSSS \ No newline at end of file diff --git a/src/logo/ascii/z/zraxyl.txt b/src/logo/ascii/z/zraxyl.txt new file mode 100644 index 0000000..ee30979 --- /dev/null +++ b/src/logo/ascii/z/zraxyl.txt @@ -0,0 +1,10 @@ + __________ + / __________ \ + / / \ \ + / / .-"````"-. \ \ +| | |/ \| | | +| | Z R A X Y L | | +| | \ ¯¯¯¯¯¯¯¯¯¯¯¯ / | | + \ \ `-.___..___.-' / / + '.'._ _.' .' + '-.| M - C | .-' \ No newline at end of file diff --git a/src/logo/builtin.c b/src/logo/builtin.c new file mode 100644 index 0000000..d056315 --- /dev/null +++ b/src/logo/builtin.c @@ -0,0 +1,67 @@ +#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, + }, +}; + +#include "./ascii/a.inc" +#include "./ascii/b.inc" +#include "./ascii/c.inc" +#include "./ascii/d.inc" +#include "./ascii/e.inc" +#include "./ascii/f.inc" +#include "./ascii/g.inc" +#include "./ascii/h.inc" +#include "./ascii/i.inc" +#include "./ascii/j.inc" +#include "./ascii/k.inc" +#include "./ascii/l.inc" +#include "./ascii/m.inc" +#include "./ascii/n.inc" +#include "./ascii/o.inc" +#include "./ascii/p.inc" +#include "./ascii/q.inc" +#include "./ascii/r.inc" +#include "./ascii/s.inc" +#include "./ascii/t.inc" +#include "./ascii/u.inc" +#include "./ascii/v.inc" +#include "./ascii/w.inc" +#include "./ascii/x.inc" +#include "./ascii/y.inc" +#include "./ascii/z.inc" + +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, +}; diff --git a/src/logo/image/im6.c b/src/logo/image/im6.c new file mode 100644 index 0000000..3e9d722 --- /dev/null +++ b/src/logo/image/im6.c @@ -0,0 +1,27 @@ +#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 diff --git a/src/logo/image/im7.c b/src/logo/image/im7.c new file mode 100644 index 0000000..636a5fa --- /dev/null +++ b/src/logo/image/im7.c @@ -0,0 +1,28 @@ +#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 diff --git a/src/logo/image/image.c b/src/logo/image/image.c new file mode 100644 index 0000000..6dd3212 --- /dev/null +++ b/src/logo/image/image.c @@ -0,0 +1,1032 @@ +#include "image.h" +#include "common/io.h" +#include "common/printing.h" +#include "common/processing.h" +#include "common/strutil.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) { + #if 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, const char* cacheFileName) { + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + readCachedStrbuf(requestData, &content, cacheFileName); + + 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 getCharacterPixelDimensions(FFLogoRequestData* requestData) { + #ifdef _WIN32 + + CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(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) { + bool cacheValid = requestData.type == FF_LOGO_TYPE_IMAGE_CHAFA + ? printCachedChars(&requestData, FF_CACHE_FILE_CHAFA) + : printCachedPixel(&requestData); + if (cacheValid) { + 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 !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 +} diff --git a/src/logo/image/image.h b/src/logo/image/image.h new file mode 100644 index 0000000..da3b2d2 --- /dev/null +++ b/src/logo/image/image.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../logo.h" + +#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) + +typedef enum FF_A_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 diff --git a/src/logo/logo.c b/src/logo/logo.c new file mode 100644 index 0000000..571f1aa --- /dev/null +++ b/src/logo/logo.c @@ -0,0 +1,813 @@ +#include "logo/logo.h" +#include "common/io.h" +#include "common/printing.h" +#include "common/processing.h" +#include "common/textModifier.h" +#include "common/strutil.h" +#include "detection/media/media.h" +#include "detection/os/os.h" +#include "detection/terminalshell/terminalshell.h" + +#include +#include +#include + +typedef struct FFLogoCachedLine { + FFstrbuf chars; + uint32_t width; +} FFLogoCachedLine; + +static void logoLineCacheClear(FFLogoLineCacheState* cache) { + FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { + ffStrbufDestroy(&line->chars); + } + ffListDestroy(&cache->lines); + cache->nextLine = 0; + cache->rightOffset = 0; +} + +static void logoLineCachePush(const FFstrbuf* chars, uint32_t width, FFLogoLineCacheState* cache) { + FFLogoCachedLine* line = FF_LIST_ADD(FFLogoCachedLine, cache->lines); + if (width > 0) { + ffStrbufInitCopy(&line->chars, chars); + if (!instance.config.display.pipe) { + ffStrbufAppendS(&line->chars, FASTFETCH_TEXT_MODIFIER_RESET); + } + } else { + ffStrbufInit(&line->chars); + } + line->width = width; +} + +static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bool doColorReplacement) { + FFOptionsLogo* options = &instance.config.logo; + bool keepCarryColor = options->type != FF_LOGO_TYPE_IMAGE_CHAFA; + + logoLineCacheClear(cache); + + // Overrides the auto detected max-width with the configured width, if set. + // In case we fail to get the actual width of the logo, as we don't use `wcwidth` + uint32_t maxLineWidth = options->width; + uint32_t parsedHeight = 0; + + FF_STRBUF_AUTO_DESTROY carryColor = ffStrbufCreate(); + if (keepCarryColor && doColorReplacement && !instance.config.display.pipe) { + ffStrbufSetF(&carryColor, "\e[%sm", options->colors[0].chars); + } + + for (uint32_t i = 0; i < options->paddingTop; ++i) { + logoLineCachePush(NULL, 0, cache); + } + + if (*data != '\0') { + while (true) { + FF_STRBUF_AUTO_DESTROY line = ffStrbufCreateA(256); + uint32_t lineWidth = 0; + + if (!instance.config.display.pipe && instance.config.display.brightColor) { + ffStrbufAppendS(&line, FASTFETCH_TEXT_MODIFIER_BOLT); + } + + if (keepCarryColor && carryColor.length > 0) { + ffStrbufAppend(&line, &carryColor); + } + + if ((options->position != FF_LOGO_POSITION_RIGHT) && options->paddingLeft > 0) { + ffStrbufAppendNC(&line, options->paddingLeft, ' '); + lineWidth += options->paddingLeft; + } + + while (*data != '\0' && *data != '\n' && !(*data == '\r' && *(data + 1) == '\n')) { + if (*data == '\t') { + ffStrbufAppendNC(&line, 4, ' '); + lineWidth += 4; + ++data; + continue; + } + + if (*data == '\e' && *(data + 1) == '[') { + const char* start = data; + data += 2; + + while (ffCharIsDigit(*data) || *data == ';') { + ++data; + } + + if (isascii(*data)) { + ++data; + + uint32_t escLen = (uint32_t) (data - start); + ffStrbufAppendNS(&line, escLen, start); + + if (keepCarryColor && start[escLen - 1] == 'm') { + ffStrbufSetNS(&carryColor, escLen, start); + } + continue; + } + + lineWidth += (uint32_t) (data - start - 1); + } + + if (doColorReplacement && *data == '$') { + ++data; + + if (*data == '$' || *data == '\0') { + ffStrbufAppendC(&line, '$'); + ++lineWidth; + ++data; + continue; + } + + if (!instance.config.display.pipe) { + int index = *data - '1'; + if (index >= 0 && index < FASTFETCH_LOGO_MAX_COLORS) { + if (keepCarryColor) { + ffStrbufSetF(&carryColor, "\e[%sm", options->colors[index].chars); + ffStrbufAppend(&line, &carryColor); + } else { + ffStrbufAppendF(&line, "\e[%sm", options->colors[index].chars); + } + ++data; + continue; + } + + ffStrbufAppendC(&line, '$'); + ++lineWidth; + } else { + ++data; + continue; + } + } + + uint8_t charWidth; + uint8_t bytes = ffUtf8CharLenWidth(data, UINT32_MAX, &charWidth); + lineWidth += charWidth; + + for (uint8_t i = 0; i < bytes; ++i) { + if (*data == '\0') { + break; + } + + ffStrbufAppendC(&line, *data); + ++data; + } + } + + logoLineCachePush(&line, lineWidth, cache); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + + if (*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) { + if (*data == '\r') { + ++data; + } + ++data; + ++parsedHeight; + continue; + } + + break; + } + } + + if (options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > parsedHeight) { + parsedHeight = options->height; + } + + instance.state.logoHeight = options->paddingTop + parsedHeight; + if (options->position == FF_LOGO_POSITION_LEFT) { + instance.state.logoWidth = maxLineWidth + options->paddingRight; + } else { + instance.state.logoWidth = 0; + } + + uint32_t totalLines = instance.state.logoHeight + 1; + while (cache->lines.length < totalLines) { + logoLineCachePush(NULL, 0, cache); + } + + cache->nextLine = 0; + cache->rightOffset = maxLineWidth + options->paddingRight - 1; +} + +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; +} + +void ffLogoPrintChars(const char* data, bool doColorReplacement) { + FFOptionsLogo* options = &instance.config.logo; + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + + logoLineCacheBuild(cache, data, doColorReplacement); + + if (options->position != FF_LOGO_POSITION_TOP) { + return; + } + + FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(4096); + FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { + ffStrbufAppend(&result, &line->chars); + ffStrbufAppendC(&result, '\n'); + } + ffStrbufAppendNC(&result, options->paddingBottom, '\n'); + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result); + instance.state.logoWidth = instance.state.logoHeight = 0; + logoLineCacheClear(cache); +} + +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; +} + +void ffLogoPrintDetected(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 !FF_MODULE_DISABLE_MEDIA + 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; + } +#endif + + 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) { + ffLogoPrintDetected(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); + } + } + + ffLogoPrintDetected(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")) { +#if !FF_MODULE_DISABLE_TERMINAL + const FFTerminalResult* terminal = ffDetectTerminal(); + + bool supportsIterm2 = ffStrbufEqualS(&terminal->prettyName, "iTerm"); + + if (supportsIterm2 && logoPrintImageIfExists(FF_LOGO_TYPE_IMAGE_ITERM, false)) { + return; + } + + // 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; +#else + bool supportsKitty = false; +#endif + + // 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); + } + } + + ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN); +} + +void ffLogoPrintLine(void) { + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + FFOptionsLogo* logo = &instance.config.logo; + + if (cache->lines.length > 0) { + // Line cache is enabled. Always move cursor with whitespaces to make lolcat happy + if (cache->nextLine < cache->lines.length) { + // Print logo line and move cursor + FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine); + + if (logo->position == FF_LOGO_POSITION_RIGHT) { + printf("\033[9999999C\033[%uD", cache->rightOffset); + ffStrbufWriteTo(&line->chars, stdout); + + fputs("\033[G", stdout); + } else { + ffStrbufWriteTo(&line->chars, stdout); + + uint32_t remaining = instance.state.logoWidth; + remaining = line->width < remaining ? remaining - line->width : 0; + ffPrintCharTimes(' ', remaining); + } + ++cache->nextLine; + } else if (logo->position == FF_LOGO_POSITION_LEFT) { + // Move cursor to the start position + ffPrintCharTimes(' ', instance.state.logoWidth); + } + } else if (instance.state.logoWidth > 0) { + printf("\033[%uC", instance.state.logoWidth); + } + + if (instance.state.dynamicInterval > 0 && logo->position == FF_LOGO_POSITION_LEFT) { + fputs("\033[K", stdout); // Clear to the end of the line + // Note that we don't clear the line when the logo is on the right, as it will also clear the logo itself + } + + ++instance.state.keysHeight; +} + +void ffLogoPrintRemaining(void) { + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + FFOptionsLogo* logo = &instance.config.logo; + + if (cache->lines.length > 0 && (logo->position == FF_LOGO_POSITION_LEFT || logo->position == FF_LOGO_POSITION_RIGHT)) { + while (cache->nextLine < cache->lines.length) { + FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine); + + if (logo->position == FF_LOGO_POSITION_RIGHT) { + printf("\033[9999999C\033[%uD", cache->rightOffset); + } + ffStrbufPutTo(&line->chars, stdout); + + ++cache->nextLine; + } + + if (!instance.config.display.pipe) { + fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); + } + + instance.state.keysHeight = instance.state.logoHeight + 1; + logoLineCacheClear(cache); + return; + } + + 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); +} diff --git a/src/logo/logo.h b/src/logo/logo.h new file mode 100644 index 0000000..105b964 --- /dev/null +++ b/src/logo/logo.h @@ -0,0 +1,44 @@ +#pragma once + +#include "fastfetch.h" + +typedef enum FF_A_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 FF_A_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); +void ffLogoPrintDetected(FFLogoSize size); +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); diff --git a/src/logo/logo.svg b/src/logo/logo.svg new file mode 100644 index 0000000..474cd6a --- /dev/null +++ b/src/logo/logo.svg @@ -0,0 +1,39 @@ + + + + + + + + + + FASTFETCH + + + + + + + + + + + + + + + + + + + + + ►►►►►►► + ►►►►►► + ►►►►► + ►►►► + ►►► + ►► + + diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c new file mode 100644 index 0000000..61cef84 --- /dev/null +++ b/src/modules/battery/battery.c @@ -0,0 +1,326 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/duration.h" +#include "common/temps.h" +#include "common/strutil.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; + + 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) { + bool hasOtherInfo = str.length > 0; + if (hasOtherInfo) { + ffStrbufAppendS(&str, " ["); + } + if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { + ffStrbufAppendS(&str, "AC Connected, "); + } + if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { + ffStrbufAppendS(&str, "USB Connected, "); + } + if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + ffStrbufAppendS(&str, "Wireless Connected, "); + } + if (result->status & FF_BATTERY_STATUS_CHARGING) { + ffStrbufAppendS(&str, "Charging, "); + } + if (result->status & FF_BATTERY_STATUS_DISCHARGING) { + ffStrbufAppendS(&str, "Discharging, "); + } + if (result->status & FF_BATTERY_STATUS_CRITICAL) { + ffStrbufAppendS(&str, "Critical, "); + } + if (result->status & FF_BATTERY_STATUS_UNKNOWN) { + ffStrbufAppendS(&str, "Unknown, "); + } + ffStrbufSubstrBefore(&str, str.length - 2); // Remove last ", " + if (hasOtherInfo) { + ffStrbufAppendC(&str, ']'); + } + } + + 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_STRBUF_AUTO_DESTROY statusStr = ffStrbufCreate(); + if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { + ffStrbufAppendS(&statusStr, "AC Connected, "); + } + if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { + ffStrbufAppendS(&statusStr, "USB Connected, "); + } + if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + ffStrbufAppendS(&statusStr, "Wireless Connected, "); + } + if (result->status & FF_BATTERY_STATUS_CHARGING) { + ffStrbufAppendS(&statusStr, "Charging, "); + } + if (result->status & FF_BATTERY_STATUS_DISCHARGING) { + ffStrbufAppendS(&statusStr, "Discharging, "); + } + if (result->status & FF_BATTERY_STATUS_CRITICAL) { + ffStrbufAppendS(&statusStr, "Critical, "); + } + if (result->status & FF_BATTERY_STATUS_UNKNOWN) { + ffStrbufAppendS(&statusStr, "Unknown, "); + } + ffStrbufSubstrBefore(&statusStr, statusStr.length - 2); // Remove last ", " + + 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(statusStr, "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(); + + 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->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; + } + + 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); + + 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(); + + 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, "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"); + } + yyjson_mut_val* status = yyjson_mut_obj_add_arr(doc, obj, "status"); + if (battery->status & FF_BATTERY_STATUS_AC_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "AC Connected"); + } + if (battery->status & FF_BATTERY_STATUS_USB_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "USB Connected"); + } + if (battery->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { + yyjson_mut_arr_add_str(doc, status, "Wireless Connected"); + } + if (battery->status & FF_BATTERY_STATUS_CHARGING) { + yyjson_mut_arr_add_str(doc, status, "Charging"); + } + if (battery->status & FF_BATTERY_STATUS_DISCHARGING) { + yyjson_mut_arr_add_str(doc, status, "Discharging"); + } + if (battery->status & FF_BATTERY_STATUS_CRITICAL) { + yyjson_mut_arr_add_str(doc, status, "Critical"); + } + if (battery->status & FF_BATTERY_STATUS_UNKNOWN) { + yyjson_mut_arr_add_str(doc, status, "Unknown"); + } + } + + FF_LIST_FOR_EACH (FFBatteryResult, battery, results) { + ffStrbufDestroy(&battery->manufacturer); + ffStrbufDestroy(&battery->manufactureDate); + ffStrbufDestroy(&battery->modelName); + ffStrbufDestroy(&battery->technology); + 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 }; +} + +void ffDestroyBatteryOptions(FFBatteryOptions* options) { + ffOptionDestroyModuleArg(&options->moduleArgs); +} + +FFModuleBaseInfo ffBatteryModuleInfo = { + .name = FF_BATTERY_MODULE_NAME, + .description = "Print battery information", + .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 manufacture 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" }, + })) +}; diff --git a/src/modules/battery/battery.h b/src/modules/battery/battery.h new file mode 100644 index 0000000..16ad027 --- /dev/null +++ b/src/modules/battery/battery.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/battery/option.h b/src/modules/battery/option.h new file mode 100644 index 0000000..d65fd1f --- /dev/null +++ b/src/modules/battery/option.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef struct FFBatteryOptions { + FFModuleArgs moduleArgs; + + bool temp; + FFColorRangeConfig tempConfig; + FFPercentageModuleConfig percent; +} FFBatteryOptions; + +static_assert(sizeof(FFBatteryOptions) <= FF_OPTION_MAX_SIZE, "FFBatteryOptions size exceeds maximum allowed size"); diff --git a/src/modules/bios/bios.c b/src/modules/bios/bios.c new file mode 100644 index 0000000..8f8700f --- /dev/null +++ b/src/modules/bios/bios.c @@ -0,0 +1,148 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 first-stage bootloader information (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" }, + })) +}; diff --git a/src/modules/bios/bios.h b/src/modules/bios/bios.h new file mode 100644 index 0000000..d3b077e --- /dev/null +++ b/src/modules/bios/bios.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/bios/option.h b/src/modules/bios/option.h new file mode 100644 index 0000000..aae4c8c --- /dev/null +++ b/src/modules/bios/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/bluetooth/bluetooth.c b/src/modules/bluetooth/bluetooth.c new file mode 100644 index 0000000..6721c33 --- /dev/null +++ b/src/modules/bluetooth/bluetooth.c @@ -0,0 +1,170 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + const char* error = ffDetectBluetooth(options, &devices); + + if (error) { + ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); + return false; + } + + if (devices.length == 0) { + ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No bluetooth devices found"); + return false; + } + + uint8_t i = 1; + FF_LIST_FOR_EACH (FFBluetoothResult, device, devices) { + printDevice(options, device, devices.length == 0 ? 0 : i); + ++i; + } + + 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(); + + 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" }, + })) +}; diff --git a/src/modules/bluetooth/bluetooth.h b/src/modules/bluetooth/bluetooth.h new file mode 100644 index 0000000..d9aa059 --- /dev/null +++ b/src/modules/bluetooth/bluetooth.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/bluetooth/option.h b/src/modules/bluetooth/option.h new file mode 100644 index 0000000..1f11d29 --- /dev/null +++ b/src/modules/bluetooth/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/bluetoothradio/bluetoothradio.c b/src/modules/bluetoothradio/bluetoothradio.c new file mode 100644 index 0000000..594b00c --- /dev/null +++ b/src/modules/bluetoothradio/bluetoothradio.c @@ -0,0 +1,210 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + 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"); + } + return false; + } + + 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_A_UNUSED FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + 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 (supported versions, vendors, etc.)", + .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" }, + })) +}; diff --git a/src/modules/bluetoothradio/bluetoothradio.h b/src/modules/bluetoothradio/bluetoothradio.h new file mode 100644 index 0000000..4707f1b --- /dev/null +++ b/src/modules/bluetoothradio/bluetoothradio.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/bluetoothradio/option.h b/src/modules/bluetoothradio/option.h new file mode 100644 index 0000000..579e899 --- /dev/null +++ b/src/modules/bluetoothradio/option.h @@ -0,0 +1,10 @@ +#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"); diff --git a/src/modules/board/board.c b/src/modules/board/board.c new file mode 100644 index 0000000..a0801a2 --- /dev/null +++ b/src/modules/board/board.c @@ -0,0 +1,125 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 information", + .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" }, + })) +}; diff --git a/src/modules/board/board.h b/src/modules/board/board.h new file mode 100644 index 0000000..eae3d73 --- /dev/null +++ b/src/modules/board/board.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/board/option.h b/src/modules/board/option.h new file mode 100644 index 0000000..c45ddd8 --- /dev/null +++ b/src/modules/board/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/bootmgr/bootmgr.c b/src/modules/bootmgr/bootmgr.c new file mode 100644 index 0000000..f44d67a --- /dev/null +++ b/src/modules/bootmgr/bootmgr.c @@ -0,0 +1,121 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 second-stage bootloader information (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" }, + })) +}; diff --git a/src/modules/bootmgr/bootmgr.h b/src/modules/bootmgr/bootmgr.h new file mode 100644 index 0000000..4f6a2ba --- /dev/null +++ b/src/modules/bootmgr/bootmgr.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/bootmgr/option.h b/src/modules/bootmgr/option.h new file mode 100644 index 0000000..2a01fb4 --- /dev/null +++ b/src/modules/bootmgr/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/break/break.c b/src/modules/break/break.c new file mode 100644 index 0000000..e560e18 --- /dev/null +++ b/src/modules/break/break.c @@ -0,0 +1,36 @@ +#include "common/printing.h" +#include "logo/logo.h" +#include "modules/break/break.h" + +bool ffPrintBreak(FF_A_UNUSED FFBreakOptions* options) { + ffLogoPrintLine(); + putchar('\n'); + return true; +} + +void ffParseBreakJsonObject(FF_A_UNUSED FFBreakOptions* options, FF_A_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_A_UNUSED FFBreakOptions* options) { +} + +void ffDestroyBreakOptions(FF_A_UNUSED FFBreakOptions* options) { +} + +FFModuleBaseInfo ffBreakModuleInfo = { + .name = FF_BREAK_MODULE_NAME, + .description = "Print an empty line", + .initOptions = (void*) ffInitBreakOptions, + .destroyOptions = (void*) ffDestroyBreakOptions, + .parseJsonObject = (void*) ffParseBreakJsonObject, + .printModule = (void*) ffPrintBreak, +}; diff --git a/src/modules/break/break.h b/src/modules/break/break.h new file mode 100644 index 0000000..f611b73 --- /dev/null +++ b/src/modules/break/break.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/break/option.h b/src/modules/break/option.h new file mode 100644 index 0000000..90a44df --- /dev/null +++ b/src/modules/break/option.h @@ -0,0 +1,8 @@ +#pragma once + +#include "common/option.h" + +typedef struct FFBreakOptions { +} FFBreakOptions; + +static_assert(sizeof(FFBreakOptions) <= FF_OPTION_MAX_SIZE, "FFBreakOptions size exceeds maximum allowed size"); diff --git a/src/modules/brightness/brightness.c b/src/modules/brightness/brightness.c new file mode 100644 index 0000000..54bb989 --- /dev/null +++ b/src/modules/brightness/brightness.c @@ -0,0 +1,210 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.h" +#include "detection/brightness/brightness.h" +#include "modules/brightness/brightness.h" + +bool ffPrintBrightness(FFBrightnessOptions* options) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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")) { + if (yyjson_is_null(val)) { + options->ddcciSleep = FF_BRIGHTNESS_DDCCI_SLEEP_SKIP; + } else { + 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); + + if (options->ddcciSleep == FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) { + yyjson_mut_obj_add_null(doc, module, "ddcciSleep"); + } else { + 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_A_UNUSED FFBrightnessOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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 the 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" }, + })) +}; diff --git a/src/modules/brightness/brightness.h b/src/modules/brightness/brightness.h new file mode 100644 index 0000000..0853661 --- /dev/null +++ b/src/modules/brightness/brightness.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/brightness/option.h b/src/modules/brightness/option.h new file mode 100644 index 0000000..a31228d --- /dev/null +++ b/src/modules/brightness/option.h @@ -0,0 +1,14 @@ +#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"); diff --git a/src/modules/btrfs/btrfs.c b/src/modules/btrfs/btrfs.c new file mode 100644 index 0000000..238d177 --- /dev/null +++ b/src/modules/btrfs/btrfs.c @@ -0,0 +1,224 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/size.h" +#include "common/strutil.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(); + + 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_A_UNUSED FFBtrfsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + 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" }, + })) +}; diff --git a/src/modules/btrfs/btrfs.h b/src/modules/btrfs/btrfs.h new file mode 100644 index 0000000..fc0c6ab --- /dev/null +++ b/src/modules/btrfs/btrfs.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/btrfs/option.h b/src/modules/btrfs/option.h new file mode 100644 index 0000000..0530cef --- /dev/null +++ b/src/modules/btrfs/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/camera/camera.c b/src/modules/camera/camera.c new file mode 100644 index 0000000..7067bbc --- /dev/null +++ b/src/modules/camera/camera.c @@ -0,0 +1,134 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + 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_A_UNUSED FFCameraOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + 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" }, + })) +}; diff --git a/src/modules/camera/camera.h b/src/modules/camera/camera.h new file mode 100644 index 0000000..e8b3560 --- /dev/null +++ b/src/modules/camera/camera.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/camera/option.h b/src/modules/camera/option.h new file mode 100644 index 0000000..51eb29f --- /dev/null +++ b/src/modules/camera/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/chassis/chassis.c b/src/modules/chassis/chassis.c new file mode 100644 index 0000000..d944baa --- /dev/null +++ b/src/modules/chassis/chassis.c @@ -0,0 +1,127 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 information (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" }, + })), +}; diff --git a/src/modules/chassis/chassis.h b/src/modules/chassis/chassis.h new file mode 100644 index 0000000..d3ba489 --- /dev/null +++ b/src/modules/chassis/chassis.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/chassis/option.h b/src/modules/chassis/option.h new file mode 100644 index 0000000..dc54227 --- /dev/null +++ b/src/modules/chassis/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/codec/codec.c b/src/modules/codec/codec.c new file mode 100644 index 0000000..4e6889e --- /dev/null +++ b/src/modules/codec/codec.c @@ -0,0 +1,268 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "detection/codec/codec.h" +#include "modules/codec/codec.h" + +static const char* ffCodecTypeToString(FFCodecType type) { + switch (type) { + case FF_CODEC_TYPE_H261: + return "H.261"; + case FF_CODEC_TYPE_H263: + return "H.263"; + case FF_CODEC_TYPE_MJPEG: + return "MJPEG"; + case FF_CODEC_TYPE_MPEG1: + return "MPEG-1"; + case FF_CODEC_TYPE_MPEG2: + return "MPEG-2"; + case FF_CODEC_TYPE_DIVX_XVID: + return "DivX / Xvid"; + case FF_CODEC_TYPE_H264: + return "H.264"; + case FF_CODEC_TYPE_WMV8: + return "WMV-8"; + case FF_CODEC_TYPE_WMV9: + return "WMV-9"; + case FF_CODEC_TYPE_VC1: + return "VC-1"; + case FF_CODEC_TYPE_VP8: + return "VP8"; + case FF_CODEC_TYPE_HEVC: + return "HEVC / H.265"; + case FF_CODEC_TYPE_VP9: + return "VP9"; + case FF_CODEC_TYPE_AV1: + return "AV1"; + case FF_CODEC_TYPE_VVC: + return "VVC / H.266"; + case FF_CODEC_TYPE_DOLBY_VISION_HEVC: + return "Dolby Vision (HEVC)"; + case FF_CODEC_TYPE_PRORES: + return "Apple ProRes"; + case FF_CODEC_TYPE_PRORES_RAW: + return "Apple ProRes RAW"; + case FF_CODEC_TYPE_UNKNOWN: + default: + return "Unknown"; + } +} + +static void printCodecLine(const FFCodecOptions* options, uint8_t index, FFstrbuf* gpu, const char* direction, FFCodecType types, const char* platformApi) { + FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); + if (options->moduleArgs.key.length == 0) { + if (gpu->length > 0) { + ffStrbufSetF(&key, "%s (%s - %s)", FF_CODEC_MODULE_NAME, direction, gpu->chars); + } else { + ffStrbufSetF(&key, "%s (%s)", FF_CODEC_MODULE_NAME, direction); + } + } else { + FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]){ + FF_ARG(index, "index"), + FF_ARG(*gpu, "gpu"), + FF_ARG(direction, "direction"), + FF_ARG(options->moduleArgs.keyIcon, "icon"), + })); + } + + if (options->moduleArgs.outputFormat.length == 0) { + FF_STRBUF_AUTO_DESTROY typesJoined = ffStrbufCreate(); + for (FFCodecType type = FF_CODEC_TYPE_H261; type <= FF_CODEC_TYPE_MAX; type <<= 1) { + if ((types & type) == 0) { + continue; + } + if (typesJoined.length > 0) { + ffStrbufAppendS(&typesJoined, ", "); + } + ffStrbufAppendS(&typesJoined, ffCodecTypeToString(type)); + } + ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); + puts(typesJoined.length ? typesJoined.chars : "None"); + } else { + FF_LIST_AUTO_DESTROY typeList = ffListCreate(); // Use list instead of pre-joined string for qjs and lua + for (FFCodecType type = FF_CODEC_TYPE_H261; type <= FF_CODEC_TYPE_MAX; type <<= 1) { + if ((types & type) == 0) { + continue; + } + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, typeList), ffCodecTypeToString(type)); + } + FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ + FF_ARG(*gpu, "gpu"), + FF_ARG(direction, "direction"), + FF_ARG(typeList, "types"), + FF_ARG(platformApi, "platform-api"), + })); + // No need to destroy strings in lists, as they are static strings + } +} + +bool ffPrintCodec(FFCodecOptions* options) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + const char* error = ffDetectCodec(options, &result); + + if (error) { + ffPrintError(FF_CODEC_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); + return false; + } + + if (result.length == 0) { + ffPrintError(FF_CODEC_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No hardware video acceleration found"); + return false; + } + + FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); + if (options->splitGPU) { + for (uint32_t i = 0; i < result.length; ++i) { + FFCodecResult* item = FF_LIST_GET(FFCodecResult, result, i); + uint8_t index = (uint8_t) (result.length == 1 ? 0 : i + 1); + if (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) { + printCodecLine(options, index, &item->gpu, "Encoder", item->encoders, item->platformApi); + } + if (options->showType & FF_CODEC_SHOW_TYPE_DECODER) { + printCodecLine(options, index, &item->gpu, "Decoder", item->decoders, item->platformApi); + } + } + } else { + FFCodecResult merged = { + .gpu = ffStrbufCreate(), + .decoders = FF_CODEC_TYPE_NONE, + .encoders = FF_CODEC_TYPE_NONE, + }; + + FF_LIST_FOR_EACH (FFCodecResult, item, result) { + merged.decoders |= item->decoders; + merged.encoders |= item->encoders; + merged.platformApi = item->platformApi; + } + if (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) { + printCodecLine(options, 0, &merged.gpu, "Encoder", merged.encoders, merged.platformApi); + } + if (options->showType & FF_CODEC_SHOW_TYPE_DECODER) { + printCodecLine(options, 0, &merged.gpu, "Decoder", merged.decoders, merged.platformApi); + } + } + + FF_LIST_FOR_EACH (FFCodecResult, item, result) { + ffStrbufDestroy(&item->gpu); + } + return true; +} + +void ffParseCodecJsonObject(FFCodecOptions* 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, "splitGPU")) { + options->splitGPU = yyjson_get_bool(val); + continue; + } + + if (unsafe_yyjson_equals_str(key, "useVulkan")) { + options->useVulkan = yyjson_get_bool(val); + continue; + } + + if (unsafe_yyjson_equals_str(key, "showType")) { + int value; + const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){ + { "encoder", FF_CODEC_SHOW_TYPE_ENCODER }, + { "decoder", FF_CODEC_SHOW_TYPE_DECODER }, + { "both", FF_CODEC_SHOW_TYPE_BOTH }, + {}, + }); + if (error) { + ffPrintError(FF_CODEC_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); + } else { + options->showType = (FFCodecShowType) value; + } + continue; + } + + ffPrintError(FF_CODEC_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); + } +} + +void ffGenerateCodecJsonConfig(FFCodecOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); + yyjson_mut_obj_add_bool(doc, module, "splitGPU", options->splitGPU); + yyjson_mut_obj_add_bool(doc, module, "useVulkan", options->useVulkan); + switch (options->showType) { + case FF_CODEC_SHOW_TYPE_ENCODER: + yyjson_mut_obj_add_str(doc, module, "showType", "encoder"); + break; + case FF_CODEC_SHOW_TYPE_DECODER: + yyjson_mut_obj_add_str(doc, module, "showType", "decoder"); + break; + case FF_CODEC_SHOW_TYPE_BOTH: + yyjson_mut_obj_add_str(doc, module, "showType", "both"); + break; + default: + break; + } +} + +bool ffGenerateCodecJsonResult(FFCodecOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + const char* error = ffDetectCodec(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 (FFCodecResult, item, result) { + yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); + yyjson_mut_obj_add_strbuf(doc, obj, "gpu", &item->gpu); + + yyjson_mut_val* encoders = yyjson_mut_obj_add_arr(doc, obj, "encoders"); + yyjson_mut_val* decoders = yyjson_mut_obj_add_arr(doc, obj, "decoders"); + + for (FFCodecType type = FF_CODEC_TYPE_UNKNOWN; type <= FF_CODEC_TYPE_MAX; type <<= 1) { + if (item->encoders & type) { + yyjson_mut_arr_add_str(doc, encoders, ffCodecTypeToString(type)); + } + if (item->decoders & type) { + yyjson_mut_arr_add_str(doc, decoders, ffCodecTypeToString(type)); + } + } + yyjson_mut_obj_add_str(doc, obj, "platformApi", item->platformApi); + } + + FF_LIST_FOR_EACH (FFCodecResult, item, result) { + ffStrbufDestroy(&item->gpu); + } + return true; +} + +void ffInitCodecOptions(FFCodecOptions* options) { + ffOptionInitModuleArg(&options->moduleArgs, "󰈫"); + options->splitGPU = false; + options->useVulkan = false; + options->showType = FF_CODEC_SHOW_TYPE_BOTH; +} + +void ffDestroyCodecOptions(FFCodecOptions* options) { + ffOptionDestroyModuleArg(&options->moduleArgs); +} + +FFModuleBaseInfo ffCodecModuleInfo = { + .name = FF_CODEC_MODULE_NAME, + .description = "Print hardware video acceleration codec types (decode / encode)", + .initOptions = (void*) ffInitCodecOptions, + .destroyOptions = (void*) ffDestroyCodecOptions, + .parseJsonObject = (void*) ffParseCodecJsonObject, + .printModule = (void*) ffPrintCodec, + .generateJsonResult = (void*) ffGenerateCodecJsonResult, + .generateJsonConfig = (void*) ffGenerateCodecJsonConfig, + .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){ + { "GPU name", "gpu" }, + { "Decoder / Encoder", "direction" }, + { "Compatibility alias of codec types", "types" }, + { "Platform API used for detection", "platform-api" }, + })) +}; diff --git a/src/modules/codec/codec.h b/src/modules/codec/codec.h new file mode 100644 index 0000000..efd3d7c --- /dev/null +++ b/src/modules/codec/codec.h @@ -0,0 +1,11 @@ +#pragma once + +#include "option.h" + +#define FF_CODEC_MODULE_NAME "Codec" + +bool ffPrintCodec(FFCodecOptions* options); +void ffInitCodecOptions(FFCodecOptions* options); +void ffDestroyCodecOptions(FFCodecOptions* options); + +extern FFModuleBaseInfo ffCodecModuleInfo; diff --git a/src/modules/codec/option.h b/src/modules/codec/option.h new file mode 100644 index 0000000..bcc9201 --- /dev/null +++ b/src/modules/codec/option.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_PACKED FFCodecShowType { + FF_CODEC_SHOW_TYPE_NONE = 0, + FF_CODEC_SHOW_TYPE_ENCODER = 1 << 0, + FF_CODEC_SHOW_TYPE_DECODER = 1 << 1, + FF_CODEC_SHOW_TYPE_BOTH = FF_CODEC_SHOW_TYPE_ENCODER | FF_CODEC_SHOW_TYPE_DECODER, + FF_CODEC_SHOW_TYPE_FORCE_UNSIGNED = UINT8_MAX, +} FFCodecShowType; + +typedef struct FFCodecOptions { + FFModuleArgs moduleArgs; + bool splitGPU; + bool useVulkan; + FFCodecShowType showType; +} FFCodecOptions; + +static_assert(sizeof(FFCodecOptions) <= FF_OPTION_MAX_SIZE, "FFCodecOptions size exceeds maximum allowed size"); diff --git a/src/modules/colors/colors.c b/src/modules/colors/colors.c new file mode 100644 index 0000000..ff821d1 --- /dev/null +++ b/src/modules/colors/colors.c @@ -0,0 +1,305 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/textModifier.h" +#include "common/color.h" +#include "common/strutil.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) { + if (options->brightness != FF_COLORS_BRIGHTNESS_LIGHT) { + // 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 + + if (options->brightness != FF_COLORS_BRIGHTNESS_NORMAL) { + // 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; + } + if (options->brightness == FF_COLORS_BRIGHTNESS_DEFAULT) { + for (int i = 8; i >= 1; --i) { + if (!instance.config.display.pipe) { + ffStrbufAppendF(&result, "\e[" FF_COLOR_FG_256 "%dm", i); + } + ffStrbufAppendS(&result, symbol); + } + } else { + for (int i = 0; i <= 7; ++i) { + if (!instance.config.display.pipe) { + ffStrbufAppendF(&result, "\e[%c%dm", options->brightness == FF_COLORS_BRIGHTNESS_NORMAL ? '3' : '9', 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; + } + + if (unsafe_yyjson_equals_str(key, "brightness")) { + int value; + const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { + { "default", FF_COLORS_BRIGHTNESS_DEFAULT }, + { "normal", FF_COLORS_BRIGHTNESS_NORMAL }, + { "light", FF_COLORS_BRIGHTNESS_LIGHT }, + {}, + }); + 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->brightness = (FFColorsBrightness) value; + } + 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]); + } + } + + switch (options->brightness) { + case FF_COLORS_BRIGHTNESS_NORMAL: + yyjson_mut_obj_add_str(doc, module, "brightness", "normal"); + break; + case FF_COLORS_BRIGHTNESS_LIGHT: + yyjson_mut_obj_add_str(doc, module, "brightness", "light"); + break; + default: + yyjson_mut_obj_add_str(doc, module, "brightness", "default"); + break; + } +} + +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 }, + }; + options->brightness = FF_COLORS_BRIGHTNESS_DEFAULT; +} + +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, +}; diff --git a/src/modules/colors/colors.h b/src/modules/colors/colors.h new file mode 100644 index 0000000..c88ccd7 --- /dev/null +++ b/src/modules/colors/colors.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/colors/option.h b/src/modules/colors/option.h new file mode 100644 index 0000000..c9971c7 --- /dev/null +++ b/src/modules/colors/option.h @@ -0,0 +1,35 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_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 enum FF_A_PACKED FFColorsBrightness { + FF_COLORS_BRIGHTNESS_DEFAULT, + FF_COLORS_BRIGHTNESS_NORMAL, + FF_COLORS_BRIGHTNESS_LIGHT, +} FFColorsBrightness; + +typedef struct FFColorsOptions { + FFModuleArgs moduleArgs; + + FFColorsSymbol symbol; + uint32_t paddingLeft; + FFBlockConfig block; + FFColorsBrightness brightness; +} FFColorsOptions; + +static_assert(sizeof(FFColorsOptions) <= FF_OPTION_MAX_SIZE, "FFColorsOptions size exceeds maximum allowed size"); diff --git a/src/modules/command/command.c b/src/modules/command/command.c new file mode 100644 index 0000000..9890911 --- /dev/null +++ b/src/modules/command/command.c @@ -0,0 +1,167 @@ +#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_A_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" }, + })) +}; diff --git a/src/modules/command/command.h b/src/modules/command/command.h new file mode 100644 index 0000000..e0e143e --- /dev/null +++ b/src/modules/command/command.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/command/option.h b/src/modules/command/option.h new file mode 100644 index 0000000..6b2108f --- /dev/null +++ b/src/modules/command/option.h @@ -0,0 +1,16 @@ +#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"); diff --git a/src/modules/cpu/cpu.c b/src/modules/cpu/cpu.c new file mode 100644 index 0000000..0d3803a --- /dev/null +++ b/src/modules/cpu/cpu.c @@ -0,0 +1,262 @@ +#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); + + yyjson_mut_obj_add_strbuf(doc, module, "tempSensor", &options->tempSensor); +} + +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" }, + })) +}; diff --git a/src/modules/cpu/cpu.h b/src/modules/cpu/cpu.h new file mode 100644 index 0000000..33fff6b --- /dev/null +++ b/src/modules/cpu/cpu.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/cpu/option.h b/src/modules/cpu/option.h new file mode 100644 index 0000000..0378635 --- /dev/null +++ b/src/modules/cpu/option.h @@ -0,0 +1,14 @@ +#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"); diff --git a/src/modules/cpucache/cpucache.c b/src/modules/cpucache/cpucache.c new file mode 100644 index 0000000..7263a51 --- /dev/null +++ b/src/modules/cpucache/cpucache.c @@ -0,0 +1,238 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/size.h" +#include "common/strutil.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(), + ffListCreate(), + ffListCreate(), + ffListCreate(), + }, + }; + + 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_A_UNUSED FFCPUCacheOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + bool success = false; + FFCPUCacheResult result = { + .caches = { + ffListCreate(), + ffListCreate(), + ffListCreate(), + ffListCreate(), + }, + }; + + 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" }, + })) +}; diff --git a/src/modules/cpucache/cpucache.h b/src/modules/cpucache/cpucache.h new file mode 100644 index 0000000..5061b44 --- /dev/null +++ b/src/modules/cpucache/cpucache.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/cpucache/option.h b/src/modules/cpucache/option.h new file mode 100644 index 0000000..186df9a --- /dev/null +++ b/src/modules/cpucache/option.h @@ -0,0 +1,11 @@ +#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"); diff --git a/src/modules/cpuusage/cpuusage.c b/src/modules/cpuusage/cpuusage.c new file mode 100644 index 0000000..5c01e91 --- /dev/null +++ b/src/modules/cpuusage/cpuusage.c @@ -0,0 +1,202 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/strutil.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(); + 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(); + 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. Collecting data takes some time", + .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" }, + })) +}; diff --git a/src/modules/cpuusage/cpuusage.h b/src/modules/cpuusage/cpuusage.h new file mode 100644 index 0000000..efa8b48 --- /dev/null +++ b/src/modules/cpuusage/cpuusage.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/cpuusage/option.h b/src/modules/cpuusage/option.h new file mode 100644 index 0000000..da9512b --- /dev/null +++ b/src/modules/cpuusage/option.h @@ -0,0 +1,14 @@ +#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"); diff --git a/src/modules/cursor/cursor.c b/src/modules/cursor/cursor.c new file mode 100644 index 0000000..0b6f6c7 --- /dev/null +++ b/src/modules/cursor/cursor.c @@ -0,0 +1,115 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })), +}; diff --git a/src/modules/cursor/cursor.h b/src/modules/cursor/cursor.h new file mode 100644 index 0000000..e769569 --- /dev/null +++ b/src/modules/cursor/cursor.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/cursor/option.h b/src/modules/cursor/option.h new file mode 100644 index 0000000..1a8e172 --- /dev/null +++ b/src/modules/cursor/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/custom/custom.c b/src/modules/custom/custom.c new file mode 100644 index 0000000..054f370 --- /dev/null +++ b/src/modules/custom/custom.c @@ -0,0 +1,45 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/textModifier.h" +#include "common/strutil.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, +}; diff --git a/src/modules/custom/custom.h b/src/modules/custom/custom.h new file mode 100644 index 0000000..628a621 --- /dev/null +++ b/src/modules/custom/custom.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/custom/option.h b/src/modules/custom/option.h new file mode 100644 index 0000000..2a7e5e4 --- /dev/null +++ b/src/modules/custom/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/datetime/datetime.c b/src/modules/datetime/datetime.c new file mode 100644 index 0000000..e902fff --- /dev/null +++ b/src/modules/datetime/datetime.c @@ -0,0 +1,189 @@ +#include "common/time.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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]; + char amPm[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); + strftime(result.amPm, sizeof(result.amPm), "%p", tm); + + FF_PRINT_FORMAT_CHECKED( + FF_DATETIME_DISPLAY_NAME, + 0, + moduleArgs, + FF_PRINT_TYPE_DEFAULT, + ((FFformatarg[]){ + FF_ARG(result.year, "year"), // 1 + FF_ARG(result.yearShort, "year-short"), // 2 + FF_ARG(result.month, "month"), // 3 + FF_ARG(result.monthPretty, "month-pretty"), // 4 + FF_ARG(result.monthName, "month-name"), // 5 + FF_ARG(result.monthNameShort, "month-name-short"), // 6 + FF_ARG(result.week, "week"), // 7 + FF_ARG(result.weekday, "weekday"), // 8 + FF_ARG(result.weekdayShort, "weekday-short"), // 9 + FF_ARG(result.dayInYear, "day-in-year"), // 10 + FF_ARG(result.dayInMonth, "day-in-month"), // 11 + FF_ARG(result.dayInWeek, "day-in-week"), // 12 + FF_ARG(result.hour, "hour"), // 13 + FF_ARG(result.hourPretty, "hour-pretty"), // 14 + FF_ARG(result.hour12, "hour-12"), // 15 + FF_ARG(result.hour12Pretty, "hour-12-pretty"), // 16 + FF_ARG(result.minute, "minute"), // 17 + FF_ARG(result.minutePretty, "minute-pretty"), // 18 + FF_ARG(result.second, "second"), // 19 + FF_ARG(result.secondPretty, "second-pretty"), // 20 + FF_ARG(result.offsetFromUtc, "offset-from-utc"), // 21 + FF_ARG(result.timezoneName, "timezone-name"), // 22 + FF_ARG(result.dayPretty, "day-pretty"), // 23 + FF_ARG(result.amPm, "am-pm"), // 24 + })); +} + +bool ffPrintDateTime(FFDateTimeOptions* options) { + 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_A_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 the 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" }, + { "AM or PM", "am-pm" }, + })) +}; diff --git a/src/modules/datetime/datetime.h b/src/modules/datetime/datetime.h new file mode 100644 index 0000000..29c6f54 --- /dev/null +++ b/src/modules/datetime/datetime.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/datetime/option.h b/src/modules/datetime/option.h new file mode 100644 index 0000000..a060b01 --- /dev/null +++ b/src/modules/datetime/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/de/de.c b/src/modules/de/de.c new file mode 100644 index 0000000..d66d205 --- /dev/null +++ b/src/modules/de/de.c @@ -0,0 +1,98 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/de/de.h b/src/modules/de/de.h new file mode 100644 index 0000000..8aaf642 --- /dev/null +++ b/src/modules/de/de.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/de/option.h b/src/modules/de/option.h new file mode 100644 index 0000000..c6113ad --- /dev/null +++ b/src/modules/de/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/disk/disk.c b/src/modules/disk/disk.c new file mode 100644 index 0000000..cb06d07 --- /dev/null +++ b/src/modules/disk/disk.c @@ -0,0 +1,490 @@ +#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(); + 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(); + 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" }, + })) +}; diff --git a/src/modules/disk/disk.h b/src/modules/disk/disk.h new file mode 100644 index 0000000..5f7820a --- /dev/null +++ b/src/modules/disk/disk.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/disk/option.h b/src/modules/disk/option.h new file mode 100644 index 0000000..b639db2 --- /dev/null +++ b/src/modules/disk/option.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef enum FF_A_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 FF_A_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"); diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c new file mode 100644 index 0000000..3ff31fa --- /dev/null +++ b/src/modules/diskio/diskio.c @@ -0,0 +1,193 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/size.h" +#include "common/strutil.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(); + 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, sizeof(FFDiskIOResult), (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(); + 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" }, + })) +}; diff --git a/src/modules/diskio/diskio.h b/src/modules/diskio/diskio.h new file mode 100644 index 0000000..7fba81c --- /dev/null +++ b/src/modules/diskio/diskio.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/diskio/option.h b/src/modules/diskio/option.h new file mode 100644 index 0000000..5d41704 --- /dev/null +++ b/src/modules/diskio/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/display/display.c b/src/modules/display/display.c new file mode 100644 index 0000000..b11a2aa --- /dev/null +++ b/src/modules/display/display.c @@ -0,0 +1,457 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/size.h" +#include "common/strutil.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, sizeof(FFDisplayResult), (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 / 2) / result->dpi; + uint32_t scaledHeight = (result->height * 96 + result->dpi / 2) / 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 / 2) / result->dpi; + uint32_t scaledHeight = (result->height * 96 + result->dpi / 2) / 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_A_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 / 2) / item->dpi; + uint32_t scaledHeight = (item->height * 96 + item->dpi / 2) / 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" }, + })) +}; diff --git a/src/modules/display/display.h b/src/modules/display/display.h new file mode 100644 index 0000000..a0a4b30 --- /dev/null +++ b/src/modules/display/display.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/display/option.h b/src/modules/display/option.h new file mode 100644 index 0000000..1ae73a4 --- /dev/null +++ b/src/modules/display/option.h @@ -0,0 +1,27 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_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 FF_A_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"); diff --git a/src/modules/dns/dns.c b/src/modules/dns/dns.c new file mode 100644 index 0000000..d7c90c4 --- /dev/null +++ b/src/modules/dns/dns.c @@ -0,0 +1,148 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.h" +#include "detection/dns/dns.h" +#include "modules/dns/dns.h" + +bool ffPrintDNS(FFDNSOptions* options) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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(); + + 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" }, + })) +}; diff --git a/src/modules/dns/dns.h b/src/modules/dns/dns.h new file mode 100644 index 0000000..aec4e85 --- /dev/null +++ b/src/modules/dns/dns.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/dns/option.h b/src/modules/dns/option.h new file mode 100644 index 0000000..e20bbec --- /dev/null +++ b/src/modules/dns/option.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_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"); diff --git a/src/modules/editor/editor.c b/src/modules/editor/editor.c new file mode 100644 index 0000000..23b1937 --- /dev/null +++ b/src/modules/editor/editor.c @@ -0,0 +1,121 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 about 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" }, + })) +}; diff --git a/src/modules/editor/editor.h b/src/modules/editor/editor.h new file mode 100644 index 0000000..293d0a6 --- /dev/null +++ b/src/modules/editor/editor.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/editor/option.h b/src/modules/editor/option.h new file mode 100644 index 0000000..e812ca9 --- /dev/null +++ b/src/modules/editor/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/font/font.c b/src/modules/font/font.c new file mode 100644 index 0000000..a32a7e8 --- /dev/null +++ b/src/modules/font/font.c @@ -0,0 +1,113 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/font/font.h b/src/modules/font/font.h new file mode 100644 index 0000000..b793b9f --- /dev/null +++ b/src/modules/font/font.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/font/option.h b/src/modules/font/option.h new file mode 100644 index 0000000..b2e2753 --- /dev/null +++ b/src/modules/font/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/gamepad/gamepad.c b/src/modules/gamepad/gamepad.c new file mode 100644 index 0000000..d34dcca --- /dev/null +++ b/src/modules/gamepad/gamepad.c @@ -0,0 +1,206 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + + 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(); + 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 = FF_LIST_ADD(FFGamepadDevice*, 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 = FF_LIST_ADD(FFstrbuf, 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_A_UNUSED FFGamepadOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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); + 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" }, + })) +}; diff --git a/src/modules/gamepad/gamepad.h b/src/modules/gamepad/gamepad.h new file mode 100644 index 0000000..395c90b --- /dev/null +++ b/src/modules/gamepad/gamepad.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/gamepad/option.h b/src/modules/gamepad/option.h new file mode 100644 index 0000000..64728c0 --- /dev/null +++ b/src/modules/gamepad/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c new file mode 100644 index 0000000..5be8442 --- /dev/null +++ b/src/modules/gpu/gpu.c @@ -0,0 +1,488 @@ +#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/strutil.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(); + 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 = ffListCreateA(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; + } + + *FF_LIST_ADD(const FFGPUResult*, selectedGPUs) = gpu; + } + + uint32_t i = 0; + FF_LIST_FOR_EACH (const FFGPUResult*, pgpu, selectedGPUs) { + printGPUResult(options, selectedGPUs.length == 1 ? 0 : (uint8_t) (i + 1), *pgpu); + ++i; + } + + FF_LIST_FOR_EACH (FFGPUResult, gpu, gpus) { + ffStrbufDestroy(&gpu->vendor); + ffStrbufDestroy(&gpu->name); + ffStrbufDestroy(&gpu->driver); + ffStrbufDestroy(&gpu->platformApi); + ffStrbufDestroy(&gpu->memoryType); + } + + if (selectedGPUs.length == 0) { + ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, (gpus.length > 0 ? "GPUs found but all hidden by hideType option" : "No GPUs detected")); + return false; + } + + 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(); + 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, memory sizes, types, 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" }, + })), +}; diff --git a/src/modules/gpu/gpu.h b/src/modules/gpu/gpu.h new file mode 100644 index 0000000..e2a6f14 --- /dev/null +++ b/src/modules/gpu/gpu.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/gpu/option.h b/src/modules/gpu/option.h new file mode 100644 index 0000000..7ec0df3 --- /dev/null +++ b/src/modules/gpu/option.h @@ -0,0 +1,32 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef enum FF_A_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 FF_A_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"); diff --git a/src/modules/host/host.c b/src/modules/host/host.c new file mode 100644 index 0000000..2a53cd3 --- /dev/null +++ b/src/modules/host/host.c @@ -0,0 +1,156 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 your computer's product name", + .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" }, + })) +}; diff --git a/src/modules/host/host.h b/src/modules/host/host.h new file mode 100644 index 0000000..1f98fd4 --- /dev/null +++ b/src/modules/host/host.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/host/option.h b/src/modules/host/option.h new file mode 100644 index 0000000..e46c0eb --- /dev/null +++ b/src/modules/host/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/icons/icons.c b/src/modules/icons/icons.c new file mode 100644 index 0000000..d3764ad --- /dev/null +++ b/src/modules/icons/icons.c @@ -0,0 +1,109 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/icons/icons.h b/src/modules/icons/icons.h new file mode 100644 index 0000000..a583a22 --- /dev/null +++ b/src/modules/icons/icons.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/icons/option.h b/src/modules/icons/option.h new file mode 100644 index 0000000..29b82ab --- /dev/null +++ b/src/modules/icons/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/initsystem/initsystem.c b/src/modules/initsystem/initsystem.c new file mode 100644 index 0000000..3c3f367 --- /dev/null +++ b/src/modules/initsystem/initsystem.c @@ -0,0 +1,120 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/initsystem/initsystem.h b/src/modules/initsystem/initsystem.h new file mode 100644 index 0000000..381b955 --- /dev/null +++ b/src/modules/initsystem/initsystem.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/initsystem/option.h b/src/modules/initsystem/option.h new file mode 100644 index 0000000..f592849 --- /dev/null +++ b/src/modules/initsystem/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/kernel/kernel.c b/src/modules/kernel/kernel.c new file mode 100644 index 0000000..c405cbf --- /dev/null +++ b/src/modules/kernel/kernel.c @@ -0,0 +1,81 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/size.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/kernel/kernel.h b/src/modules/kernel/kernel.h new file mode 100644 index 0000000..010ec54 --- /dev/null +++ b/src/modules/kernel/kernel.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/kernel/option.h b/src/modules/kernel/option.h new file mode 100644 index 0000000..a6520a9 --- /dev/null +++ b/src/modules/kernel/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/keyboard/keyboard.c b/src/modules/keyboard/keyboard.c new file mode 100644 index 0000000..09c3cd5 --- /dev/null +++ b/src/modules/keyboard/keyboard.c @@ -0,0 +1,164 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + + 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(); + 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 = FF_LIST_ADD(FFKeyboardDevice*, 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 = FF_LIST_ADD(FFstrbuf, 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_A_UNUSED FFKeyboardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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); +} + +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" }, + })) +}; diff --git a/src/modules/keyboard/keyboard.h b/src/modules/keyboard/keyboard.h new file mode 100644 index 0000000..bd6e69c --- /dev/null +++ b/src/modules/keyboard/keyboard.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/keyboard/option.h b/src/modules/keyboard/option.h new file mode 100644 index 0000000..5d691d5 --- /dev/null +++ b/src/modules/keyboard/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/lm/lm.c b/src/modules/lm/lm.c new file mode 100644 index 0000000..a1317d2 --- /dev/null +++ b/src/modules/lm/lm.c @@ -0,0 +1,122 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/lm/lm.h b/src/modules/lm/lm.h new file mode 100644 index 0000000..1ec33b6 --- /dev/null +++ b/src/modules/lm/lm.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/lm/option.h b/src/modules/lm/option.h new file mode 100644 index 0000000..918ae9b --- /dev/null +++ b/src/modules/lm/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/loadavg/loadavg.c b/src/modules/loadavg/loadavg.c new file mode 100644 index 0000000..32ced12 --- /dev/null +++ b/src/modules/loadavg/loadavg.c @@ -0,0 +1,164 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/strutil.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_A_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" }, + })) +}; diff --git a/src/modules/loadavg/loadavg.h b/src/modules/loadavg/loadavg.h new file mode 100644 index 0000000..9af2786 --- /dev/null +++ b/src/modules/loadavg/loadavg.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/loadavg/option.h b/src/modules/loadavg/option.h new file mode 100644 index 0000000..b66d422 --- /dev/null +++ b/src/modules/loadavg/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/locale/locale.c b/src/modules/locale/locale.c new file mode 100644 index 0000000..ef97acf --- /dev/null +++ b/src/modules/locale/locale.c @@ -0,0 +1,81 @@ +#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_A_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" }, + })) +}; diff --git a/src/modules/locale/locale.h b/src/modules/locale/locale.h new file mode 100644 index 0000000..fd86e42 --- /dev/null +++ b/src/modules/locale/locale.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/locale/option.h b/src/modules/locale/option.h new file mode 100644 index 0000000..19cc9f2 --- /dev/null +++ b/src/modules/locale/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/localip/localip.c b/src/modules/localip/localip.c new file mode 100644 index 0000000..ef4a110 --- /dev/null +++ b/src/modules/localip/localip.c @@ -0,0 +1,443 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + + 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, sizeof(FFLocalIpResult), (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_A_UNUSED FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + 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 (IPv4 or IPv6), 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" }, + })) +}; diff --git a/src/modules/localip/localip.h b/src/modules/localip/localip.h new file mode 100644 index 0000000..f9e9c4e --- /dev/null +++ b/src/modules/localip/localip.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/localip/option.h b/src/modules/localip/option.h new file mode 100644 index 0000000..0eae247 --- /dev/null +++ b/src/modules/localip/option.h @@ -0,0 +1,44 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_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 FF_A_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"); diff --git a/src/modules/logo/logo.c b/src/modules/logo/logo.c new file mode 100644 index 0000000..54a0f97 --- /dev/null +++ b/src/modules/logo/logo.c @@ -0,0 +1,87 @@ +#include "common/printing.h" +#include "logo/logo.h" +#include "modules/logo/logo.h" +#include "options/logo.h" + +bool ffPrintLogo(FF_A_UNUSED FFLogoOptions* options) { + ffPrintError(FF_LOGO_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_DEFAULT, "Supported in JSON format only"); + return false; +} + +void ffParseLogoJsonObject(FF_A_UNUSED FFLogoOptions* options, FF_A_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_A_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_A_UNUSED FFLogoOptions* options) { +} + +void ffDestroyLogoOptions(FF_A_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, +}; diff --git a/src/modules/logo/logo.h b/src/modules/logo/logo.h new file mode 100644 index 0000000..7f48e2d --- /dev/null +++ b/src/modules/logo/logo.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/logo/option.h b/src/modules/logo/option.h new file mode 100644 index 0000000..605cdfe --- /dev/null +++ b/src/modules/logo/option.h @@ -0,0 +1,8 @@ +#pragma once + +#include "common/option.h" + +typedef struct FFLogoOptions { +} FFLogoOptions; + +static_assert(sizeof(FFLogoOptions) <= FF_OPTION_MAX_SIZE, "FFLogoOptions size exceeds maximum allowed size"); diff --git a/src/modules/media/media.c b/src/modules/media/media.c new file mode 100644 index 0000000..0876a9d --- /dev/null +++ b/src/modules/media/media.c @@ -0,0 +1,277 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.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; +} + +static void appendProgress(FFstrbuf* buffer, const FFMediaResult* media) { + uint32_t sLen = media->length / 1000, sPos = media->position / 1000; + if (sLen > 60 * 60) { + ffStrbufAppendF(buffer, "%02u:%02u:%02u / %02u:%02u:%02u", sPos / 3600, (sPos % 3600) / 60, sPos % 60, sLen / 3600, (sLen % 3600) / 60, sLen % 60); + } else { + ffStrbufAppendF(buffer, "%02u:%02u / %02u:%02u", sPos / 60, sPos % 60, sLen / 60, sLen % 60); + } +} + +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); + } + + FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; + + if (options->moduleArgs.outputFormat.length == 0) { + // We don't expose artistPretty to the format, as it might be empty (when the think that the artist is already in the song title) + FF_STRBUF_AUTO_DESTROY artistPretty = ffStrbufCreateCopy(&media->artist); + 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->length > 0) { + if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { + ffStrbufAppendS(&songPretty, " - "); + appendProgress(&songPretty, media); + } + + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + ffStrbufAppendC(&songPretty, ' '); + ffPercentAppendNum( + &songPretty, + media->position * 100.0 / media->length, + options->percent, + true, + &options->moduleArgs); + } + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + ffStrbufAppendC(&songPretty, ' '); + ffPercentAppendBar( + &songPretty, + media->position * 100.0 / media->length, + options->percent, + &options->moduleArgs); + } + } + + if (media->status.length > 0) { + ffStrbufAppendF(&songPretty, " [%s]", media->status.chars); + } + + ffStrbufPutTo(&songPretty, stdout); + } else { + FF_STRBUF_AUTO_DESTROY progress = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); + if (media->length > 0) { + if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { + appendProgress(&progress, media); + } + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + ffPercentAppendNum( + &percentageNum, + media->position * 100.0 / media->length, + options->percent, + false, + &options->moduleArgs); + } + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + ffPercentAppendBar( + &percentageBar, + media->position * 100.0 / media->length, + options->percent, + &options->moduleArgs); + } + } + FF_PRINT_FORMAT_CHECKED(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ + FF_ARG(songPretty, "combined"), + FF_ARG(media->song, "title"), + FF_ARG(media->artist, "artist"), + FF_ARG(media->album, "album"), + FF_ARG(media->status, "status"), + FF_ARG(progress, "progress"), + FF_ARG(percentageNum, "progress-num"), + FF_ARG(percentageBar, "progress-bar"), + FF_ARG(media->player, "player-name"), + FF_ARG(media->playerId, "player-id"), + FF_ARG(media->url, "url"), + })); + } + + 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; + } + + if (ffPercentParseJsonObject(key, val, &options->percent)) { + continue; + } + + ffPrintError(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); + } +} + +void ffGenerateMediaJsonConfig(FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); + + ffPercentGenerateJsonConfig(doc, module, options->percent); +} + +bool ffGenerateMediaJsonResult(FF_A_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + 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); + yyjson_mut_obj_add_uint(doc, song, "length", media->length); + yyjson_mut_obj_add_uint(doc, song, "position", media->position); + if (media->cover.length > 0) { + yyjson_mut_obj_add_strbuf(doc, song, "cover", &media->cover); + } else { + 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, ""); + + options->percent = (FFPercentageModuleConfig){ 100, 100, 0 }; +} + +void ffDestroyMediaOptions(FFMediaOptions* options) { + ffOptionDestroyModuleArg(&options->moduleArgs); +} + +FFModuleBaseInfo ffMediaModuleInfo = { + .name = FF_MEDIA_MODULE_NAME, + .description = "Print the name of the currently playing song", + .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" }, + { "Progress in text", "progress" }, + { "Progress in percentage (number)", "progress-num" }, + { "Progress in percentage (bar)", "progress-bar" }, + { "Player name", "player-name" }, + { "Player ID", "player-id" }, + { "URL", "url" }, + })) +}; diff --git a/src/modules/media/media.h b/src/modules/media/media.h new file mode 100644 index 0000000..8320227 --- /dev/null +++ b/src/modules/media/media.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/media/option.h b/src/modules/media/option.h new file mode 100644 index 0000000..0207832 --- /dev/null +++ b/src/modules/media/option.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef struct FFMediaOptions { + FFModuleArgs moduleArgs; + FFPercentageModuleConfig percent; +} FFMediaOptions; + +static_assert(sizeof(FFMediaOptions) <= FF_OPTION_MAX_SIZE, "FFMediaOptions size exceeds maximum allowed size"); diff --git a/src/modules/memory/memory.c b/src/modules/memory/memory.c new file mode 100644 index 0000000..4fbdf75 --- /dev/null +++ b/src/modules/memory/memory.c @@ -0,0 +1,135 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/size.h" +#include "common/strutil.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_A_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 information", + .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" }, + })) +}; diff --git a/src/modules/memory/memory.h b/src/modules/memory/memory.h new file mode 100644 index 0000000..7ed026f --- /dev/null +++ b/src/modules/memory/memory.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/memory/option.h b/src/modules/memory/option.h new file mode 100644 index 0000000..e612a98 --- /dev/null +++ b/src/modules/memory/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/modules.c b/src/modules/modules.c new file mode 100644 index 0000000..8378711 --- /dev/null +++ b/src/modules/modules.c @@ -0,0 +1,364 @@ +#include "modules/modules.h" + +// Note: +// FF_MODULE_DISABLE_ only controls if the module is registered, +// the module code itself is still compiled. +// We rely `LTO` to remove the unused code (only enabled in Release mode) + +static FFModuleBaseInfo* A[] = { + NULL, +}; + +static FFModuleBaseInfo* B[] = { +#if !FF_MODULE_DISABLE_BATTERY + &ffBatteryModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BIOS + &ffBiosModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BLUETOOTH + &ffBluetoothModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BLUETOOTHRADIO + &ffBluetoothRadioModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BOARD + &ffBoardModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BOOTMGR + &ffBootmgrModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BREAK + &ffBreakModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BRIGHTNESS + &ffBrightnessModuleInfo, +#endif +#if !FF_MODULE_DISABLE_BTRFS + &ffBtrfsModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* C[] = { +#if !FF_MODULE_DISABLE_CAMERA + &ffCameraModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CHASSIS + &ffChassisModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CODEC + &ffCodecModuleInfo, +#endif +#if !FF_MODULE_DISABLE_COMMAND + &ffCommandModuleInfo, +#endif +#if !FF_MODULE_DISABLE_COLORS + &ffColorsModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CPU + &ffCPUModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CPUCACHE + &ffCPUCacheModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CPUUSAGE + &ffCPUUsageModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CURSOR + &ffCursorModuleInfo, +#endif +#if !FF_MODULE_DISABLE_CUSTOM + &ffCustomModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* D[] = { +#if !FF_MODULE_DISABLE_DATETIME + &ffDateTimeModuleInfo, +#endif +#if !FF_MODULE_DISABLE_DE + &ffDEModuleInfo, +#endif +#if !FF_MODULE_DISABLE_DISPLAY + &ffDisplayModuleInfo, +#endif +#if !FF_MODULE_DISABLE_DISK + &ffDiskModuleInfo, +#endif +#if !FF_MODULE_DISABLE_DISKIO + &ffDiskIOModuleInfo, +#endif +#if !FF_MODULE_DISABLE_DNS + &ffDNSModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* E[] = { +#if !FF_MODULE_DISABLE_EDITOR + &ffEditorModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* F[] = { +#if !FF_MODULE_DISABLE_FONT + &ffFontModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* G[] = { +#if !FF_MODULE_DISABLE_GAMEPAD + &ffGamepadModuleInfo, +#endif +#if !FF_MODULE_DISABLE_GPU + &ffGPUModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* H[] = { +#if !FF_MODULE_DISABLE_HOST + &ffHostModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* I[] = { +#if !FF_MODULE_DISABLE_ICONS + &ffIconsModuleInfo, +#endif +#if !FF_MODULE_DISABLE_INITSYSTEM + &ffInitSystemModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* J[] = { + NULL, +}; + +static FFModuleBaseInfo* K[] = { +#if !FF_MODULE_DISABLE_KERNEL + &ffKernelModuleInfo, +#endif +#if !FF_MODULE_DISABLE_KEYBOARD + &ffKeyboardModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* L[] = { +#if !FF_MODULE_DISABLE_LM + &ffLMModuleInfo, +#endif +#if !FF_MODULE_DISABLE_LOADAVG + &ffLoadavgModuleInfo, +#endif +#if !FF_MODULE_DISABLE_LOCALE + &ffLocaleModuleInfo, +#endif +#if !FF_MODULE_DISABLE_LOCALIP + &ffLocalIPModuleInfo, +#endif +#if !FF_MODULE_DISABLE_LOGO + &ffLogoModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* M[] = { +#if !FF_MODULE_DISABLE_MEDIA + &ffMediaModuleInfo, +#endif +#if !FF_MODULE_DISABLE_MEMORY + &ffMemoryModuleInfo, +#endif +#if !FF_MODULE_DISABLE_MONITOR + &ffMonitorModuleInfo, +#endif +#if !FF_MODULE_DISABLE_MOUSE + &ffMouseModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* N[] = { +#if !FF_MODULE_DISABLE_NETIO + &ffNetIOModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* O[] = { +#if !FF_MODULE_DISABLE_OPENCL + &ffOpenCLModuleInfo, +#endif +#if !FF_MODULE_DISABLE_OPENGL + &ffOpenGLModuleInfo, +#endif +#if !FF_MODULE_DISABLE_OS + &ffOSModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* P[] = { +#if !FF_MODULE_DISABLE_PACKAGES + &ffPackagesModuleInfo, +#endif +#if !FF_MODULE_DISABLE_PHYSICALDISK + &ffPhysicalDiskModuleInfo, +#endif +#if !FF_MODULE_DISABLE_PHYSICALMEMORY + &ffPhysicalMemoryModuleInfo, +#endif +#if !FF_MODULE_DISABLE_PLAYER + &ffPlayerModuleInfo, +#endif +#if !FF_MODULE_DISABLE_POWERADAPTER + &ffPowerAdapterModuleInfo, +#endif +#if !FF_MODULE_DISABLE_PROCESSES + &ffProcessesModuleInfo, +#endif +#if !FF_MODULE_DISABLE_PUBLICIP + &ffPublicIPModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* Q[] = { + NULL, +}; + +static FFModuleBaseInfo* R[] = { + NULL, +}; + +static FFModuleBaseInfo* S[] = { +#if !FF_MODULE_DISABLE_SEPARATOR + &ffSeparatorModuleInfo, +#endif +#if !FF_MODULE_DISABLE_SHELL + &ffShellModuleInfo, +#endif +#if !FF_MODULE_DISABLE_SOUND + &ffSoundModuleInfo, +#endif +#if !FF_MODULE_DISABLE_SWAP + &ffSwapModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* T[] = { +#if !FF_MODULE_DISABLE_TERMINAL + &ffTerminalModuleInfo, +#endif +#if !FF_MODULE_DISABLE_TERMINALFONT + &ffTerminalFontModuleInfo, +#endif +#if !FF_MODULE_DISABLE_TERMINALSIZE + &ffTerminalSizeModuleInfo, +#endif +#if !FF_MODULE_DISABLE_TERMINALTHEME + &ffTerminalThemeModuleInfo, +#endif +#if !FF_MODULE_DISABLE_TITLE + &ffTitleModuleInfo, +#endif +#if !FF_MODULE_DISABLE_THEME + &ffThemeModuleInfo, +#endif +#if !FF_MODULE_DISABLE_TPM + &ffTPMModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* U[] = { +#if !FF_MODULE_DISABLE_UPTIME + &ffUptimeModuleInfo, +#endif +#if !FF_MODULE_DISABLE_USERS + &ffUsersModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* V[] = { +#if !FF_MODULE_DISABLE_VERSION + &ffVersionModuleInfo, +#endif +#if !FF_MODULE_DISABLE_VULKAN + &ffVulkanModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* W[] = { +#if !FF_MODULE_DISABLE_WALLPAPER + &ffWallpaperModuleInfo, +#endif +#if !FF_MODULE_DISABLE_WEATHER + &ffWeatherModuleInfo, +#endif +#if !FF_MODULE_DISABLE_WM + &ffWMModuleInfo, +#endif +#if !FF_MODULE_DISABLE_WIFI + &ffWifiModuleInfo, +#endif +#if !FF_MODULE_DISABLE_WMTHEME + &ffWMThemeModuleInfo, +#endif + NULL, +}; + +static FFModuleBaseInfo* X[] = { + NULL, +}; + +static FFModuleBaseInfo* Y[] = { + NULL, +}; + +static FFModuleBaseInfo* Z[] = { +#if !FF_MODULE_DISABLE_ZPOOL + &ffZpoolModuleInfo, +#endif + 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, +}; diff --git a/src/modules/modules.h b/src/modules/modules.h new file mode 100644 index 0000000..37ee242 --- /dev/null +++ b/src/modules/modules.h @@ -0,0 +1,77 @@ +#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/codec/codec.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" diff --git a/src/modules/monitor/monitor.c b/src/modules/monitor/monitor.c new file mode 100644 index 0000000..105ee67 --- /dev/null +++ b/src/modules/monitor/monitor.c @@ -0,0 +1,137 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 = "Same as Display module, but with a different default output format", + .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" }, + })) +}; diff --git a/src/modules/monitor/monitor.h b/src/modules/monitor/monitor.h new file mode 100644 index 0000000..82f9491 --- /dev/null +++ b/src/modules/monitor/monitor.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/monitor/option.h b/src/modules/monitor/option.h new file mode 100644 index 0000000..46f4859 --- /dev/null +++ b/src/modules/monitor/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/mouse/mouse.c b/src/modules/mouse/mouse.c new file mode 100644 index 0000000..abc4678 --- /dev/null +++ b/src/modules/mouse/mouse.c @@ -0,0 +1,167 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + + 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(); + 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 = FF_LIST_ADD(FFMouseDevice*, 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 = FF_LIST_ADD(FFstrbuf, 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_A_UNUSED FFMouseOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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); +} + +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 mice", + .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" }, + })) +}; diff --git a/src/modules/mouse/mouse.h b/src/modules/mouse/mouse.h new file mode 100644 index 0000000..afbc20f --- /dev/null +++ b/src/modules/mouse/mouse.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/mouse/option.h b/src/modules/mouse/option.h new file mode 100644 index 0000000..ec2c26e --- /dev/null +++ b/src/modules/mouse/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/netio/netio.c b/src/modules/netio/netio.c new file mode 100644 index 0000000..17047ff --- /dev/null +++ b/src/modules/netio/netio.c @@ -0,0 +1,224 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/size.h" +#include "common/strutil.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(); + 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, sizeof(FFNetIOResult), (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(); + 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" }, + })) +}; diff --git a/src/modules/netio/netio.h b/src/modules/netio/netio.h new file mode 100644 index 0000000..653b7e9 --- /dev/null +++ b/src/modules/netio/netio.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/netio/option.h b/src/modules/netio/option.h new file mode 100644 index 0000000..f5552c1 --- /dev/null +++ b/src/modules/netio/option.h @@ -0,0 +1,14 @@ +#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"); diff --git a/src/modules/opencl/opencl.c b/src/modules/opencl/opencl.c new file mode 100644 index 0000000..d69b3fe --- /dev/null +++ b/src/modules/opencl/opencl.c @@ -0,0 +1,136 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/opencl/opencl.h b/src/modules/opencl/opencl.h new file mode 100644 index 0000000..aafab36 --- /dev/null +++ b/src/modules/opencl/opencl.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/opencl/option.h b/src/modules/opencl/option.h new file mode 100644 index 0000000..aa624c6 --- /dev/null +++ b/src/modules/opencl/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/opengl/opengl.c b/src/modules/opengl/opengl.c new file mode 100644 index 0000000..0754cb2 --- /dev/null +++ b/src/modules/opengl/opengl.c @@ -0,0 +1,145 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/opengl/opengl.h b/src/modules/opengl/opengl.h new file mode 100644 index 0000000..8caffa5 --- /dev/null +++ b/src/modules/opengl/opengl.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/opengl/option.h b/src/modules/opengl/option.h new file mode 100644 index 0000000..869c6ec --- /dev/null +++ b/src/modules/opengl/option.h @@ -0,0 +1,17 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_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"); diff --git a/src/modules/options.h b/src/modules/options.h new file mode 100644 index 0000000..7cf98c1 --- /dev/null +++ b/src/modules/options.h @@ -0,0 +1,77 @@ +#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" diff --git a/src/modules/os/option.h b/src/modules/os/option.h new file mode 100644 index 0000000..46c7a1b --- /dev/null +++ b/src/modules/os/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/os/os.c b/src/modules/os/os.c new file mode 100644 index 0000000..e402699 --- /dev/null +++ b/src/modules/os/os.c @@ -0,0 +1,197 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/option.h" +#include "common/strutil.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_A_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 the OS or Linux distribution 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" }, + })) +}; diff --git a/src/modules/os/os.h b/src/modules/os/os.h new file mode 100644 index 0000000..a53d285 --- /dev/null +++ b/src/modules/os/os.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/packages/option.h b/src/modules/packages/option.h new file mode 100644 index 0000000..1e89533 --- /dev/null +++ b/src/modules/packages/option.h @@ -0,0 +1,55 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_PACKED FFPackagesFlags { + FF_PACKAGES_FLAG_NONE = 0, + FF_PACKAGES_FLAG_APK_BIT = UINT64_C(1) << 0U, + FF_PACKAGES_FLAG_BREW_BIT = UINT64_C(1) << 1U, + FF_PACKAGES_FLAG_CHOCO_BIT = UINT64_C(1) << 2U, + FF_PACKAGES_FLAG_DPKG_BIT = UINT64_C(1) << 3U, + FF_PACKAGES_FLAG_EMERGE_BIT = UINT64_C(1) << 4U, + FF_PACKAGES_FLAG_EOPKG_BIT = UINT64_C(1) << 5U, + FF_PACKAGES_FLAG_FLATPAK_BIT = UINT64_C(1) << 6U, + FF_PACKAGES_FLAG_NIX_BIT = UINT64_C(1) << 7U, + FF_PACKAGES_FLAG_OPKG_BIT = UINT64_C(1) << 8U, + FF_PACKAGES_FLAG_PACMAN_BIT = UINT64_C(1) << 9U, + FF_PACKAGES_FLAG_PALUDIS_BIT = UINT64_C(1) << 10U, + FF_PACKAGES_FLAG_PKG_BIT = UINT64_C(1) << 11U, + FF_PACKAGES_FLAG_PKGTOOL_BIT = UINT64_C(1) << 12U, + FF_PACKAGES_FLAG_MACPORTS_BIT = UINT64_C(1) << 13U, + FF_PACKAGES_FLAG_RPM_BIT = UINT64_C(1) << 14U, + FF_PACKAGES_FLAG_SCOOP_BIT = UINT64_C(1) << 15U, + FF_PACKAGES_FLAG_SNAP_BIT = UINT64_C(1) << 16U, + FF_PACKAGES_FLAG_WINGET_BIT = UINT64_C(1) << 17U, + FF_PACKAGES_FLAG_XBPS_BIT = UINT64_C(1) << 18U, + FF_PACKAGES_FLAG_AM_BIT = UINT64_C(1) << 19U, + FF_PACKAGES_FLAG_SORCERY_BIT = UINT64_C(1) << 20U, + FF_PACKAGES_FLAG_LPKG_BIT = UINT64_C(1) << 21U, + FF_PACKAGES_FLAG_LPKGBUILD_BIT = UINT64_C(1) << 22U, + FF_PACKAGES_FLAG_GUIX_BIT = UINT64_C(1) << 23U, + FF_PACKAGES_FLAG_LINGLONG_BIT = UINT64_C(1) << 24U, + FF_PACKAGES_FLAG_PACSTALL_BIT = UINT64_C(1) << 25U, + FF_PACKAGES_FLAG_MPORT_BIT = UINT64_C(1) << 26U, + FF_PACKAGES_FLAG_PKGSRC_BIT = UINT64_C(1) << 27U, + FF_PACKAGES_FLAG_HPKG_BIT = UINT64_C(1) << 28U, + FF_PACKAGES_FLAG_PISI_BIT = UINT64_C(1) << 29U, + FF_PACKAGES_FLAG_SOAR_BIT = UINT64_C(1) << 30U, + FF_PACKAGES_FLAG_KISS_BIT = UINT64_C(1) << 31U, + FF_PACKAGES_FLAG_MOSS_BIT = UINT64_C(1) << 32U, + FF_PACKAGES_FLAG_APPIMAGE_BIT = UINT64_C(1) << 33U, + FF_PACKAGES_FLAG_CARDS_BIT = UINT64_C(1) << 34U, + FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX, +} FFPackagesFlags; +static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), ""); + +typedef struct FFPackagesOptions { + FFModuleArgs moduleArgs; + +#if !FF_PACKAGES_REMOVE_DISABLED + FFPackagesFlags disabled; +#endif + bool combined; +} FFPackagesOptions; + +static_assert(sizeof(FFPackagesOptions) <= FF_OPTION_MAX_SIZE, "FFPackagesOptions size exceeds maximum allowed size"); diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c new file mode 100644 index 0000000..4ef1d04 --- /dev/null +++ b/src/modules/packages/packages.c @@ -0,0 +1,560 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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; + } + + if (counts.all == 0) { + ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No packages from known package managers found"); + 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); + + FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); + +#define FF_PRINT_PACKAGE_NAME(var, name) \ + if (counts.var > 0) { \ + ffStrbufAppendUInt(&output, counts.var); \ + ffStrbufAppendS(&output, " (" name "), "); \ + } + +#define FF_PRINT_PACKAGE(name) FF_PRINT_PACKAGE_NAME(name, #name) + +#define FF_PRINT_PACKAGE_ALL(name) \ + if (name##All > 0) { \ + ffStrbufAppendUInt(&output, name##All); \ + ffStrbufAppendS(&output, " (" #name "), "); \ + } + + if (options->combined) { + FF_PRINT_PACKAGE_ALL(am); + } else { + FF_PRINT_PACKAGE_NAME(amSystem, "am") + FF_PRINT_PACKAGE_NAME(amUser, "appman") + } + FF_PRINT_PACKAGE(appimage) + FF_PRINT_PACKAGE(apk) + 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(cards) + FF_PRINT_PACKAGE(choco) + FF_PRINT_PACKAGE(dpkg) + FF_PRINT_PACKAGE(emerge) + FF_PRINT_PACKAGE(eopkg) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(flatpak); + } else if (counts.flatpakUser > 0) { + FF_PRINT_PACKAGE_NAME(flatpakSystem, "flatpak-system") + FF_PRINT_PACKAGE_NAME(flatpakUser, "flatpak-user") + } else { + FF_PRINT_PACKAGE_NAME(flatpakSystem, "flatpak") + } + 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") + } + if (options->combined) { + FF_PRINT_PACKAGE_ALL(hpkg) + } else if (counts.hpkgUser > 0) { + FF_PRINT_PACKAGE_NAME(hpkgSystem, "hpkg-system") + FF_PRINT_PACKAGE_NAME(hpkgUser, "hpkg-user") + } else { + FF_PRINT_PACKAGE_NAME(hpkgSystem, "hpkg") + } + FF_PRINT_PACKAGE(kiss) + FF_PRINT_PACKAGE(linglong) + FF_PRINT_PACKAGE(lpkg) + FF_PRINT_PACKAGE(lpkgbuild) + FF_PRINT_PACKAGE(macports) + FF_PRINT_PACKAGE(mport) + FF_PRINT_PACKAGE(moss) + 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(opkg) + if (counts.pacman > 0) { + ffStrbufAppendF(&output, "%u (pacman)", counts.pacman); + if (counts.pacmanBranch.length > 0) { + ffStrbufAppendC(&output, '['); + ffStrbufAppend(&output, &counts.pacmanBranch); + ffStrbufAppendC(&output, ']'); + } + ffStrbufAppendS(&output, ", "); + }; + FF_PRINT_PACKAGE(pacstall) + FF_PRINT_PACKAGE(paludis) + FF_PRINT_PACKAGE(pisi) + FF_PRINT_PACKAGE(pkg) + FF_PRINT_PACKAGE(pkgsrc) + FF_PRINT_PACKAGE(pkgtool) + FF_PRINT_PACKAGE(rpm) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(scoop); + } else if (counts.scoopGlobal > 0) { + FF_PRINT_PACKAGE_NAME(scoopUser, "scoop-user") + FF_PRINT_PACKAGE_NAME(scoopGlobal, "scoop-global") + } else { + FF_PRINT_PACKAGE_NAME(scoopUser, "scoop") + } + FF_PRINT_PACKAGE(snap) + FF_PRINT_PACKAGE(soar) + FF_PRINT_PACKAGE(sorcery) + FF_PRINT_PACKAGE(winget) + FF_PRINT_PACKAGE(xbps) + + assert(output.length >= 2); // counts.all > 0 guarantees that at least one package count was printed, which guarantees that ", " was appended at least once + ffStrbufSubstrBefore(&output, output.length - 1); + output.chars[output.length - 1] = '\n'; + ffStrbufWriteTo(&output, stdout); + } 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.amSystem, "am-system"), + FF_ARG(counts.amUser, "am-user"), + FF_ARG(counts.appimage, "appimage"), + FF_ARG(counts.apk, "apk"), + FF_ARG(counts.brew, "brew"), + FF_ARG(brewAll, "brew-all"), + FF_ARG(counts.brewCask, "brew-cask"), + FF_ARG(counts.cards, "cards"), + FF_ARG(counts.choco, "choco"), + FF_ARG(counts.dpkg, "dpkg"), + FF_ARG(counts.emerge, "emerge"), + FF_ARG(counts.eopkg, "eopkg"), + FF_ARG(flatpakAll, "flatpak-all"), + FF_ARG(counts.flatpakSystem, "flatpak-system"), + FF_ARG(counts.flatpakUser, "flatpak-user"), + FF_ARG(guixAll, "guix-all"), + FF_ARG(counts.guixHome, "guix-home"), + FF_ARG(counts.guixSystem, "guix-system"), + FF_ARG(counts.guixUser, "guix-user"), + FF_ARG(hpkgAll, "hpkg-all"), + FF_ARG(counts.hpkgSystem, "hpkg-system"), + FF_ARG(counts.hpkgUser, "hpkg-user"), + FF_ARG(counts.kiss, "kiss"), + FF_ARG(counts.linglong, "linglong"), + FF_ARG(counts.lpkg, "lpkg"), + FF_ARG(counts.lpkgbuild, "lpkgbuild"), + FF_ARG(counts.macports, "macports"), + FF_ARG(counts.mport, "mport"), + FF_ARG(counts.moss, "moss"), + FF_ARG(nixAll, "nix-all"), + FF_ARG(counts.nixDefault, "nix-default"), + FF_ARG(counts.nixSystem, "nix-system"), + FF_ARG(counts.nixUser, "nix-user"), + FF_ARG(counts.opkg, "opkg"), + FF_ARG(counts.pacman, "pacman"), + FF_ARG(counts.pacmanBranch, "pacman-branch"), + FF_ARG(counts.pacstall, "pacstall"), + FF_ARG(counts.paludis, "paludis"), + FF_ARG(counts.pisi, "pisi"), + FF_ARG(counts.pkg, "pkg"), + FF_ARG(counts.pkgsrc, "pkgsrc"), + FF_ARG(counts.pkgtool, "pkgtool"), + FF_ARG(counts.rpm, "rpm"), + FF_ARG(counts.scoopGlobal, "scoop-global"), + FF_ARG(counts.scoopUser, "scoop-user"), + FF_ARG(counts.snap, "snap"), + FF_ARG(counts.soar, "soar"), + FF_ARG(counts.sorcery, "sorcery"), + FF_ARG(counts.winget, "winget"), + FF_ARG(counts.xbps, "xbps"), + })); + } + + 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 !FF_PACKAGES_REMOVE_DISABLED + 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(AM) + FF_TEST_PACKAGE_NAME(APK) + FF_TEST_PACKAGE_NAME(APPIMAGE) + break; + case 'B': + if (false) + ; + FF_TEST_PACKAGE_NAME(BREW) + break; + case 'C': + if (false) + ; + FF_TEST_PACKAGE_NAME(CARDS) + 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(LINGLONG) + FF_TEST_PACKAGE_NAME(LPKG) + FF_TEST_PACKAGE_NAME(LPKGBUILD) + break; + case 'M': + if (false) + ; + FF_TEST_PACKAGE_NAME(MACPORTS) + FF_TEST_PACKAGE_NAME(MOSS) + FF_TEST_PACKAGE_NAME(MPORT) + 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(PKGSRC) + FF_TEST_PACKAGE_NAME(PKGTOOL) + 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; + } + } +#endif + + 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); + +#if !FF_PACKAGES_REMOVE_DISABLED + 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(APPIMAGE) + FF_TEST_PACKAGE_NAME(BREW) + FF_TEST_PACKAGE_NAME(CARDS) + FF_TEST_PACKAGE_NAME(CHOCO) + FF_TEST_PACKAGE_NAME(DPKG) + FF_TEST_PACKAGE_NAME(EMERGE) + 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(MOSS) + FF_TEST_PACKAGE_NAME(MPORT) + 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(PKGSRC) + FF_TEST_PACKAGE_NAME(PKGTOOL) + 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 +#endif + + yyjson_mut_obj_add_bool(doc, module, "combined", options->combined); +} + +bool ffGeneratePackagesJsonResult(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"); + + yyjson_mut_obj_add_uint(doc, obj, "all", counts.all); + +#define FF_APPEND_PACKAGE_COUNT(name) \ + if (counts.name > 0) yyjson_mut_obj_add_uint(doc, obj, #name, counts.name); + FF_APPEND_PACKAGE_COUNT(amSystem) + FF_APPEND_PACKAGE_COUNT(amUser) + FF_APPEND_PACKAGE_COUNT(appimage) + FF_APPEND_PACKAGE_COUNT(apk) + FF_APPEND_PACKAGE_COUNT(brew) + FF_APPEND_PACKAGE_COUNT(brewCask) + FF_APPEND_PACKAGE_COUNT(cards) + FF_APPEND_PACKAGE_COUNT(choco) + FF_APPEND_PACKAGE_COUNT(dpkg) + FF_APPEND_PACKAGE_COUNT(emerge) + FF_APPEND_PACKAGE_COUNT(eopkg) + FF_APPEND_PACKAGE_COUNT(flatpakSystem) + FF_APPEND_PACKAGE_COUNT(flatpakUser) + FF_APPEND_PACKAGE_COUNT(guixHome) + FF_APPEND_PACKAGE_COUNT(guixSystem) + FF_APPEND_PACKAGE_COUNT(guixUser) + FF_APPEND_PACKAGE_COUNT(hpkgSystem) + FF_APPEND_PACKAGE_COUNT(hpkgUser) + FF_APPEND_PACKAGE_COUNT(kiss) + FF_APPEND_PACKAGE_COUNT(linglong) + FF_APPEND_PACKAGE_COUNT(lpkg) + FF_APPEND_PACKAGE_COUNT(lpkgbuild) + 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(pkgsrc) + FF_APPEND_PACKAGE_COUNT(pkgtool) + FF_APPEND_PACKAGE_COUNT(rpm) + FF_APPEND_PACKAGE_COUNT(scoopGlobal) + FF_APPEND_PACKAGE_COUNT(scoopUser) + 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) + if (counts.pacmanBranch.length > 0) { + yyjson_mut_obj_add_strbuf(doc, obj, "pacmanBranch", &counts.pacmanBranch); + } + + return true; +} + +void ffInitPackagesOptions(FFPackagesOptions* options) { + ffOptionInitModuleArg(&options->moduleArgs, "󰏖"); + + #if !FF_PACKAGES_REMOVE_DISABLED + options->disabled = FF_PACKAGES_DISABLE_LIST; + #endif + 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 am-system packages", "am-system" }, + { "Number of am-user (aka appman) packages", "am-user" }, + { "Number of appimage packages", "appimage" }, + { "Number of apk packages", "apk" }, + { "Number of brew packages", "brew" }, + { "Total number of all brew packages", "brew-all" }, + { "Number of brew-cask packages", "brew-cask" }, + { "Number of cards packages", "cards" }, + { "Number of choco packages", "choco" }, + { "Number of dpkg packages", "dpkg" }, + { "Number of emerge packages", "emerge" }, + { "Number of eopkg packages", "eopkg" }, + { "Total number of all flatpak app packages", "flatpak-all" }, + { "Number of flatpak-system app packages", "flatpak-system" }, + { "Number of flatpak-user app packages", "flatpak-user" }, + { "Total number of all guix packages", "guix-all" }, + { "Number of guix-home packages", "guix-home" }, + { "Number of guix-system packages", "guix-system" }, + { "Number of guix-user packages", "guix-user" }, + { "Total number of all hpkg packages", "hpkg-all" }, + { "Number of hpkg-system packages", "hpkg-system" }, + { "Number of hpkg-user packages", "hpkg-user" }, + { "Number of kiss packages", "kiss" }, + { "Number of linglong packages", "linglong" }, + { "Number of lpkg packages", "lpkg" }, + { "Number of lpkgbuild packages", "lpkgbuild" }, + { "Number of macports packages", "macports" }, + { "Number of mport packages", "mport" }, + { "Number of moss packages", "moss" }, + { "Total number of all nix packages", "nix-all" }, + { "Number of nix-default packages", "nix-default" }, + { "Number of nix-system packages", "nix-system" }, + { "Number of nix-user packages", "nix-user" }, + { "Number of opkg packages", "opkg" }, + { "Number of pacman packages", "pacman" }, + { "Pacman branch on manjaro", "pacman-branch" }, + { "Number of pacstall packages", "pacstall" }, + { "Number of paludis packages", "paludis" }, + { "Number of pisi packages", "pisi" }, + { "Number of pkg packages", "pkg" }, + { "Number of pkgsrc packages", "pkgsrc" }, + { "Number of pkgtool packages", "pkgtool" }, + { "Number of rpm packages", "rpm" }, + { "Number of scoop-global packages", "scoop-global" }, + { "Number of scoop-user packages", "scoop-user" }, + { "Number of snap packages", "snap" }, + { "Number of soar packages", "soar" }, + { "Number of sorcery packages", "sorcery" }, + { "Number of winget packages", "winget" }, + { "Number of xbps packages", "xbps" }, + })) +}; diff --git a/src/modules/packages/packages.h b/src/modules/packages/packages.h new file mode 100644 index 0000000..4e58768 --- /dev/null +++ b/src/modules/packages/packages.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/physicaldisk/option.h b/src/modules/physicaldisk/option.h new file mode 100644 index 0000000..00ba013 --- /dev/null +++ b/src/modules/physicaldisk/option.h @@ -0,0 +1,34 @@ +#pragma once + +#include "common/option.h" + +typedef enum FF_A_PACKED FFPhysicalDiskType { + FF_PHYSICALDISK_TYPE_NONE = 0, + + // If none is set, it's unknown + FF_PHYSICALDISK_TYPE_HDD = 1 << 0, + FF_PHYSICALDISK_TYPE_SSD = 1 << 1, + FF_PHYSICALDISK_TYPE_VIRTUAL = 1 << 2, + + FF_PHYSICALDISK_TYPE_FIXED = 1 << 3, + FF_PHYSICALDISK_TYPE_REMOVABLE = 1 << 4, + + FF_PHYSICALDISK_TYPE_READWRITE = 1 << 5, + FF_PHYSICALDISK_TYPE_READONLY = 1 << 6, + + FF_PHYSICALDISK_TYPE_UNUSED = 1 << 7, + + FF_PHYSICALDISK_TYPE_FORCE_UNSIGNED = UINT8_MAX, +} FFPhysicalDiskType; +static_assert(sizeof(FFPhysicalDiskType) == sizeof(uint8_t), ""); + +typedef struct FFPhysicalDiskOptions { + FFModuleArgs moduleArgs; + + FFstrbuf namePrefix; + FFPhysicalDiskType hideType; + bool temp; + FFColorRangeConfig tempConfig; +} FFPhysicalDiskOptions; + +static_assert(sizeof(FFPhysicalDiskOptions) <= FF_OPTION_MAX_SIZE, "FFPhysicalDiskOptions size exceeds maximum allowed size"); diff --git a/src/modules/physicaldisk/physicaldisk.c b/src/modules/physicaldisk/physicaldisk.c new file mode 100644 index 0000000..b0b7c58 --- /dev/null +++ b/src/modules/physicaldisk/physicaldisk.c @@ -0,0 +1,288 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/temps.h" +#include "common/size.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(); + 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, sizeof(FFPhysicalDiskResult), (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_VIRTUAL + ? "Virtual" + : 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 (unsafe_yyjson_equals_str(key, "hideVirtual")) { + if (!yyjson_is_bool(val)) { + ffPrintError(FF_PHYSICALDISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "hideVirtual must be a boolean"); + } else { + if (unsafe_yyjson_is_true(val)) { + options->hideType |= FF_PHYSICALDISK_TYPE_VIRTUAL; + } else { + options->hideType &= ~FF_PHYSICALDISK_TYPE_VIRTUAL; + } + } + continue; + } + + if (unsafe_yyjson_equals_str(key, "hideUnused")) { + if (!yyjson_is_bool(val)) { + ffPrintError(FF_PHYSICALDISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "hideUnused must be a boolean"); + } else { + if (unsafe_yyjson_is_true(val)) { + options->hideType |= FF_PHYSICALDISK_TYPE_UNUSED; + } else { + options->hideType &= ~FF_PHYSICALDISK_TYPE_UNUSED; + } + } + 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); + + yyjson_mut_obj_add_bool(doc, module, "hideVirtual", !!(options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL)); + yyjson_mut_obj_add_bool(doc, module, "hideUnused", !!(options->hideType & FF_PHYSICALDISK_TYPE_UNUSED)); +} + +bool ffGeneratePhysicalDiskJsonResult(FFPhysicalDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + 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_VIRTUAL) { + yyjson_mut_obj_add_str(doc, obj, "kind", "Virtual"); + } else 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_bool(doc, obj, "unknown", !!(dev->type & FF_PHYSICALDISK_TYPE_UNUSED)); + + 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 }; + options->hideType = FF_PHYSICALDISK_TYPE_UNUSED; +} + +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" }, + })) +}; diff --git a/src/modules/physicaldisk/physicaldisk.h b/src/modules/physicaldisk/physicaldisk.h new file mode 100644 index 0000000..aaad888 --- /dev/null +++ b/src/modules/physicaldisk/physicaldisk.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/physicalmemory/option.h b/src/modules/physicalmemory/option.h new file mode 100644 index 0000000..faaff95 --- /dev/null +++ b/src/modules/physicalmemory/option.h @@ -0,0 +1,10 @@ +#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"); diff --git a/src/modules/physicalmemory/physicalmemory.c b/src/modules/physicalmemory/physicalmemory.c new file mode 100644 index 0000000..d4bee7d --- /dev/null +++ b/src/modules/physicalmemory/physicalmemory.c @@ -0,0 +1,184 @@ +#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(); + const char* error = ffDetectPhysicalMemory(options, &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 devices detected"); + return false; + } + + FF_STRBUF_AUTO_DESTROY prettySize = ffStrbufCreate(); + + uint32_t i = 0; + FF_LIST_FOR_EACH (FFPhysicalMemoryResult, device, result) { + ffStrbufClear(&prettySize); + if (device->installed) { + ffSizeAppendNum(device->size, &prettySize); + } + + if (options->moduleArgs.outputFormat.length == 0) { + ffPrintLogoAndKey(FF_PHYSICALMEMORY_DISPLAY_NAME, result.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"), + })); + } + + i++; + } + + 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(FFPhysicalMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + const char* error = ffDetectPhysicalMemory(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 (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" }, + })) +}; diff --git a/src/modules/physicalmemory/physicalmemory.h b/src/modules/physicalmemory/physicalmemory.h new file mode 100644 index 0000000..feb3d1d --- /dev/null +++ b/src/modules/physicalmemory/physicalmemory.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/player/option.h b/src/modules/player/option.h new file mode 100644 index 0000000..17aa9ba --- /dev/null +++ b/src/modules/player/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/player/player.c b/src/modules/player/player.c new file mode 100644 index 0000000..58d1b3b --- /dev/null +++ b/src/modules/player/player.c @@ -0,0 +1,126 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the music player name that is currently active", + .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" }, + })) +}; diff --git a/src/modules/player/player.h b/src/modules/player/player.h new file mode 100644 index 0000000..8b18d29 --- /dev/null +++ b/src/modules/player/player.h @@ -0,0 +1,12 @@ + +#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; diff --git a/src/modules/poweradapter/option.h b/src/modules/poweradapter/option.h new file mode 100644 index 0000000..6f28754 --- /dev/null +++ b/src/modules/poweradapter/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/poweradapter/poweradapter.c b/src/modules/poweradapter/poweradapter.c new file mode 100644 index 0000000..2e70c00 --- /dev/null +++ b/src/modules/poweradapter/poweradapter.c @@ -0,0 +1,129 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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(); + + 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_A_UNUSED FFPowerAdapterOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + 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" }, + })) +}; diff --git a/src/modules/poweradapter/poweradapter.h b/src/modules/poweradapter/poweradapter.h new file mode 100644 index 0000000..3e61025 --- /dev/null +++ b/src/modules/poweradapter/poweradapter.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/processes/option.h b/src/modules/processes/option.h new file mode 100644 index 0000000..2ee2563 --- /dev/null +++ b/src/modules/processes/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/processes/processes.c b/src/modules/processes/processes.c new file mode 100644 index 0000000..44ab59d --- /dev/null +++ b/src/modules/processes/processes.c @@ -0,0 +1,76 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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" } })) +}; diff --git a/src/modules/processes/processes.h b/src/modules/processes/processes.h new file mode 100644 index 0000000..0220c17 --- /dev/null +++ b/src/modules/processes/processes.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/publicip/option.h b/src/modules/publicip/option.h new file mode 100644 index 0000000..aca7236 --- /dev/null +++ b/src/modules/publicip/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/publicip/publicip.c b/src/modules/publicip/publicip.c new file mode 100644 index 0000000..5521733 --- /dev/null +++ b/src/modules/publicip/publicip.c @@ -0,0 +1,125 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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 and related information", + .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" }, + })) +}; diff --git a/src/modules/publicip/publicip.h b/src/modules/publicip/publicip.h new file mode 100644 index 0000000..92768cb --- /dev/null +++ b/src/modules/publicip/publicip.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/separator/option.h b/src/modules/separator/option.h new file mode 100644 index 0000000..510132c --- /dev/null +++ b/src/modules/separator/option.h @@ -0,0 +1,11 @@ +#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"); diff --git a/src/modules/separator/separator.c b/src/modules/separator/separator.c new file mode 100644 index 0000000..4c5b4bc --- /dev/null +++ b/src/modules/separator/separator.c @@ -0,0 +1,130 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.h" +#include "common/textModifier.h" +#include "logo/logo.h" +#include "modules/separator/separator.h" + +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 { + const FFPlatform* platform = &instance.state.platform; + + uint32_t titleLength = 1 // @ + + ffUtf8StrWidth(platform->userName.chars, platform->userName.length) // 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 = ffUtf8StrWidth(options->string.chars, options->string.length); + + 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; + uint32_t remainBytes = options->string.length; + while (remaining > 0 && remainBytes > 0 && *ptr != '\0') { + uint8_t charWidth = 0; + uint8_t bytes = ffUtf8CharLenWidth(ptr, remainBytes, &charWidth); + if (__builtin_expect(bytes == 0, false)) { + break; + } + + remaining -= (int) charWidth; + ptr += bytes; + remainBytes -= bytes; + } + fwrite(options->string.chars, (size_t) (ptr - options->string.chars), 1, stdout); + } else { + fwrite(options->string.chars, (size_t) remaining, 1, stdout); + } + } + } + } + + 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, +}; diff --git a/src/modules/separator/separator.h b/src/modules/separator/separator.h new file mode 100644 index 0000000..eebf39a --- /dev/null +++ b/src/modules/separator/separator.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/shell/option.h b/src/modules/shell/option.h new file mode 100644 index 0000000..bd8ed24 --- /dev/null +++ b/src/modules/shell/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/shell/shell.c b/src/modules/shell/shell.c new file mode 100644 index 0000000..f18c1bf --- /dev/null +++ b/src/modules/shell/shell.c @@ -0,0 +1,110 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/shell/shell.h b/src/modules/shell/shell.h new file mode 100644 index 0000000..50a094c --- /dev/null +++ b/src/modules/shell/shell.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/sound/option.h b/src/modules/sound/option.h new file mode 100644 index 0000000..9892569 --- /dev/null +++ b/src/modules/sound/option.h @@ -0,0 +1,21 @@ +#pragma once + +#include "common/option.h" +#include "common/percent.h" + +typedef enum FF_A_PACKED FFSoundType { + FF_SOUND_TYPE_NONE = 0, + FF_SOUND_TYPE_MAIN = 1 << 0, + FF_SOUND_TYPE_ACTIVE = 1 << 1, +} FFSoundType; + +typedef struct FFSoundOptions { + FFModuleArgs moduleArgs; + + // Reports matched device only, otherwise reports all devices + // NOTE: for FF_SOUND_TYPE_NONE, reports all devices + FFSoundType soundType; + FFPercentageModuleConfig percent; +} FFSoundOptions; + +static_assert(sizeof(FFSoundOptions) <= FF_OPTION_MAX_SIZE, "FFSoundOptions size exceeds maximum allowed size"); diff --git a/src/modules/sound/sound.c b/src/modules/sound/sound.c new file mode 100644 index 0000000..bf37c1f --- /dev/null +++ b/src/modules/sound/sound.c @@ -0,0 +1,219 @@ +#include "common/percent.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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->type & FF_SOUND_TYPE_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); + } + } + + bool isMain = !!(device->type & FF_SOUND_TYPE_MAIN); + bool isActive = !!(device->type & FF_SOUND_TYPE_ACTIVE); + FF_PRINT_FORMAT_CHECKED(FF_SOUND_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { + FF_ARG(isMain, "is-main"), + FF_ARG(isActive, "is-active"), + 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(); + + const char* error = ffDetectSound(options, &result); + + if (error) { + ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); + return false; + } + + if (result.length == 0) { + ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No matched sound devices found"); + return false; + } + + uint8_t index = 1; + FF_LIST_FOR_EACH (FFSoundDevice, device, result) { + printDevice(options, device, result.length == 1 ? 0 : index++); + } + + 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_NONE }, // Don't filter devices + {}, + }); + 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_NONE: + yyjson_mut_obj_add_str(doc, module, "soundType", "all"); + break; + } + + ffPercentGenerateJsonConfig(doc, module, options->percent); +} + +bool ffGenerateSoundJsonResult(FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + const char* error = ffDetectSound(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 (FFSoundDevice, 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_strbuf(doc, obj, "identifier", &item->identifier); + yyjson_mut_obj_add_strbuf(doc, obj, "platformApi", &item->platformApi); + + yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, obj, "type"); + if (item->type & FF_SOUND_TYPE_MAIN) { + yyjson_mut_arr_add_str(doc, type, "main"); + } + if (item->type & FF_SOUND_TYPE_ACTIVE) { + yyjson_mut_arr_add_str(doc, type, "active"); + } + + 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"); + } + } + + 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 levels, 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" }, + { "Is active sound device", "is-active" }, + { "Device name", "name" }, + { "Volume (in percentage num)", "volume-percentage" }, + { "Identifier", "identifier" }, + { "Volume (in percentage bar)", "volume-percentage-bar" }, + { "Platform API used", "platform-api" }, + })) +}; diff --git a/src/modules/sound/sound.h b/src/modules/sound/sound.h new file mode 100644 index 0000000..a23f8d5 --- /dev/null +++ b/src/modules/sound/sound.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/swap/option.h b/src/modules/swap/option.h new file mode 100644 index 0000000..860da9c --- /dev/null +++ b/src/modules/swap/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/swap/swap.c b/src/modules/swap/swap.c new file mode 100644 index 0000000..78af475 --- /dev/null +++ b/src/modules/swap/swap.c @@ -0,0 +1,200 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/size.h" +#include "common/strutil.h" +#include "detection/swap/swap.h" +#include "modules/swap/swap.h" + +void printSwap(FFSwapOptions* options, uint8_t index, uint32_t totalCount, 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, totalCount ? "Unused" : "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(); + 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 && result.length > 0) { + uint8_t index = 0; + FF_LIST_FOR_EACH (FFSwapResult, storage, result) { + ++index; + printSwap(options, index, result.length, storage); + } + } else { + FFSwapResult total = { + .name = ffStrbufCreate(), + }; + FF_LIST_FOR_EACH (FFSwapResult, storage, result) { + total.bytesUsed += storage->bytesUsed; + total.bytesTotal += storage->bytesTotal; + } + printSwap(options, 0, result.length, &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_A_UNUSED FFSwapOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + 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" }, + })) +}; diff --git a/src/modules/swap/swap.h b/src/modules/swap/swap.h new file mode 100644 index 0000000..e6b9a8b --- /dev/null +++ b/src/modules/swap/swap.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/terminal/option.h b/src/modules/terminal/option.h new file mode 100644 index 0000000..79f6970 --- /dev/null +++ b/src/modules/terminal/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/terminal/terminal.c b/src/modules/terminal/terminal.c new file mode 100644 index 0000000..8dc6f4c --- /dev/null +++ b/src/modules/terminal/terminal.c @@ -0,0 +1,104 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/terminal/terminal.h b/src/modules/terminal/terminal.h new file mode 100644 index 0000000..bb7c9a1 --- /dev/null +++ b/src/modules/terminal/terminal.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/terminalfont/option.h b/src/modules/terminalfont/option.h new file mode 100644 index 0000000..6da3d1e --- /dev/null +++ b/src/modules/terminalfont/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/terminalfont/terminalfont.c b/src/modules/terminalfont/terminalfont.c new file mode 100644 index 0000000..91e45c7 --- /dev/null +++ b/src/modules/terminalfont/terminalfont.c @@ -0,0 +1,122 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the font name and size used by the 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" }, + })), +}; diff --git a/src/modules/terminalfont/terminalfont.h b/src/modules/terminalfont/terminalfont.h new file mode 100644 index 0000000..04987a6 --- /dev/null +++ b/src/modules/terminalfont/terminalfont.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/terminalsize/option.h b/src/modules/terminalsize/option.h new file mode 100644 index 0000000..482f754 --- /dev/null +++ b/src/modules/terminalsize/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/terminalsize/terminalsize.c b/src/modules/terminalsize/terminalsize.c new file mode 100644 index 0000000..4a7c555 --- /dev/null +++ b/src/modules/terminalsize/terminalsize.c @@ -0,0 +1,93 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })), +}; diff --git a/src/modules/terminalsize/terminalsize.h b/src/modules/terminalsize/terminalsize.h new file mode 100644 index 0000000..3cd05d5 --- /dev/null +++ b/src/modules/terminalsize/terminalsize.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/terminaltheme/option.h b/src/modules/terminaltheme/option.h new file mode 100644 index 0000000..25a0cc3 --- /dev/null +++ b/src/modules/terminaltheme/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/terminaltheme/terminaltheme.c b/src/modules/terminaltheme/terminaltheme.c new file mode 100644 index 0000000..80bcd29 --- /dev/null +++ b/src/modules/terminaltheme/terminaltheme.c @@ -0,0 +1,110 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/terminaltheme/terminaltheme.h b/src/modules/terminaltheme/terminaltheme.h new file mode 100644 index 0000000..db120f7 --- /dev/null +++ b/src/modules/terminaltheme/terminaltheme.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/theme/option.h b/src/modules/theme/option.h new file mode 100644 index 0000000..66a68ba --- /dev/null +++ b/src/modules/theme/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/theme/theme.c b/src/modules/theme/theme.c new file mode 100644 index 0000000..70cb778 --- /dev/null +++ b/src/modules/theme/theme.c @@ -0,0 +1,102 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the current desktop environment theme", + .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" }, + })) +}; diff --git a/src/modules/theme/theme.h b/src/modules/theme/theme.h new file mode 100644 index 0000000..56ba49c --- /dev/null +++ b/src/modules/theme/theme.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/title/option.h b/src/modules/title/option.h new file mode 100644 index 0000000..e66fdba --- /dev/null +++ b/src/modules/title/option.h @@ -0,0 +1,14 @@ +#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"); diff --git a/src/modules/title/title.c b/src/modules/title/title.c new file mode 100644 index 0000000..97e5843 --- /dev/null +++ b/src/modules/title/title.c @@ -0,0 +1,201 @@ +#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_A_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 the title, including your username and 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" }, + })) +}; diff --git a/src/modules/title/title.h b/src/modules/title/title.h new file mode 100644 index 0000000..83c9496 --- /dev/null +++ b/src/modules/title/title.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/tpm/option.h b/src/modules/tpm/option.h new file mode 100644 index 0000000..d40806f --- /dev/null +++ b/src/modules/tpm/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/tpm/tpm.c b/src/modules/tpm/tpm.c new file mode 100644 index 0000000..ecdd386 --- /dev/null +++ b/src/modules/tpm/tpm.c @@ -0,0 +1,98 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 information about the 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" }, + })) +}; diff --git a/src/modules/tpm/tpm.h b/src/modules/tpm/tpm.h new file mode 100644 index 0000000..215f8c9 --- /dev/null +++ b/src/modules/tpm/tpm.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/uptime/option.h b/src/modules/uptime/option.h new file mode 100644 index 0000000..34be61a --- /dev/null +++ b/src/modules/uptime/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c new file mode 100644 index 0000000..e096bfc --- /dev/null +++ b/src/modules/uptime/uptime.c @@ -0,0 +1,106 @@ +#include "common/duration.h" +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/time.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/uptime/uptime.h b/src/modules/uptime/uptime.h new file mode 100644 index 0000000..dec5f95 --- /dev/null +++ b/src/modules/uptime/uptime.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/users/option.h b/src/modules/users/option.h new file mode 100644 index 0000000..6863d49 --- /dev/null +++ b/src/modules/users/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/users/users.c b/src/modules/users/users.c new file mode 100644 index 0000000..5575bbe --- /dev/null +++ b/src/modules/users/users.c @@ -0,0 +1,206 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/time.h" +#include "common/strutil.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(); + + 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(); + + 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 who are 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" }, + })) +}; diff --git a/src/modules/users/users.h b/src/modules/users/users.h new file mode 100644 index 0000000..44c8b01 --- /dev/null +++ b/src/modules/users/users.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/version/option.h b/src/modules/version/option.h new file mode 100644 index 0000000..8647420 --- /dev/null +++ b/src/modules/version/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/version/version.c b/src/modules/version/version.c new file mode 100644 index 0000000..dcc4bbe --- /dev/null +++ b/src/modules/version/version.c @@ -0,0 +1,117 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the Fastfetch version and build information", + .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" }, + })) +}; diff --git a/src/modules/version/version.h b/src/modules/version/version.h new file mode 100644 index 0000000..add5e11 --- /dev/null +++ b/src/modules/version/version.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/vulkan/option.h b/src/modules/vulkan/option.h new file mode 100644 index 0000000..2ab42c5 --- /dev/null +++ b/src/modules/vulkan/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/vulkan/vulkan.c b/src/modules/vulkan/vulkan.c new file mode 100644 index 0000000..9ecbd32 --- /dev/null +++ b/src/modules/vulkan/vulkan.c @@ -0,0 +1,149 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/vulkan/vulkan.h b/src/modules/vulkan/vulkan.h new file mode 100644 index 0000000..c838028 --- /dev/null +++ b/src/modules/vulkan/vulkan.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/wallpaper/option.h b/src/modules/wallpaper/option.h new file mode 100644 index 0000000..8f7008d --- /dev/null +++ b/src/modules/wallpaper/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/wallpaper/wallpaper.c b/src/modules/wallpaper/wallpaper.c new file mode 100644 index 0000000..a4d17c0 --- /dev/null +++ b/src/modules/wallpaper/wallpaper.c @@ -0,0 +1,90 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the file path of the 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" }, + })) +}; diff --git a/src/modules/wallpaper/wallpaper.h b/src/modules/wallpaper/wallpaper.h new file mode 100644 index 0000000..c3e7291 --- /dev/null +++ b/src/modules/wallpaper/wallpaper.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/weather/option.h b/src/modules/weather/option.h new file mode 100644 index 0000000..136277f --- /dev/null +++ b/src/modules/weather/option.h @@ -0,0 +1,13 @@ +#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"); diff --git a/src/modules/weather/weather.c b/src/modules/weather/weather.c new file mode 100644 index 0000000..6c15692 --- /dev/null +++ b/src/modules/weather/weather.c @@ -0,0 +1,105 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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" }, + })) +}; diff --git a/src/modules/weather/weather.h b/src/modules/weather/weather.h new file mode 100644 index 0000000..f28a4c2 --- /dev/null +++ b/src/modules/weather/weather.h @@ -0,0 +1,13 @@ +#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; diff --git a/src/modules/wifi/option.h b/src/modules/wifi/option.h new file mode 100644 index 0000000..45ff966 --- /dev/null +++ b/src/modules/wifi/option.h @@ -0,0 +1,11 @@ +#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"); diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c new file mode 100644 index 0000000..42485dc --- /dev/null +++ b/src/modules/wifi/wifi.c @@ -0,0 +1,239 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.h" +#include "detection/wifi/wifi.h" +#include "modules/wifi/wifi.h" + +bool ffPrintWifi(FFWifiOptions* options) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + + 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"); + } else 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_A_UNUSED FFWifiOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY result = ffListCreate(); + 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" }, + })) +}; diff --git a/src/modules/wifi/wifi.h b/src/modules/wifi/wifi.h new file mode 100644 index 0000000..506e1e2 --- /dev/null +++ b/src/modules/wifi/wifi.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/wm/option.h b/src/modules/wm/option.h new file mode 100644 index 0000000..7d343c9 --- /dev/null +++ b/src/modules/wm/option.h @@ -0,0 +1,11 @@ +#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"); diff --git a/src/modules/wm/wm.c b/src/modules/wm/wm.c new file mode 100644 index 0000000..5ec6646 --- /dev/null +++ b/src/modules/wm/wm.c @@ -0,0 +1,138 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the 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" }, + })) +}; diff --git a/src/modules/wm/wm.h b/src/modules/wm/wm.h new file mode 100644 index 0000000..4819d73 --- /dev/null +++ b/src/modules/wm/wm.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/wmtheme/option.h b/src/modules/wmtheme/option.h new file mode 100644 index 0000000..5b0af07 --- /dev/null +++ b/src/modules/wmtheme/option.h @@ -0,0 +1,9 @@ +#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"); diff --git a/src/modules/wmtheme/wmtheme.c b/src/modules/wmtheme/wmtheme.c new file mode 100644 index 0000000..775e88a --- /dev/null +++ b/src/modules/wmtheme/wmtheme.c @@ -0,0 +1,75 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/strutil.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_A_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 the current window manager theme", + .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" }, + })) +}; diff --git a/src/modules/wmtheme/wmtheme.h b/src/modules/wmtheme/wmtheme.h new file mode 100644 index 0000000..5142ca4 --- /dev/null +++ b/src/modules/wmtheme/wmtheme.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/modules/zpool/option.h b/src/modules/zpool/option.h new file mode 100644 index 0000000..e4efdcb --- /dev/null +++ b/src/modules/zpool/option.h @@ -0,0 +1,12 @@ +#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"); diff --git a/src/modules/zpool/zpool.c b/src/modules/zpool/zpool.c new file mode 100644 index 0000000..44a6de2 --- /dev/null +++ b/src/modules/zpool/zpool.c @@ -0,0 +1,218 @@ +#include "common/printing.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/size.h" +#include "common/FFstrbuf.h" +#include "common/strutil.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(); + + 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_A_UNUSED FFZpoolOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { + FF_LIST_AUTO_DESTROY results = ffListCreate(); + + 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", "size-used" }, + { "Size allocated", "size-allocated" }, + { "Size total", "size-total" }, + { "Size used percentage num", "used-percentage" }, + { "Size allocated percentage num", "allocated-percentage" }, + { "Fragmentation percentage num", "frag-percentage" }, + { "Size used percentage bar", "used-percentage-bar" }, + { "Size allocated percentage bar", "allocated-percentage-bar" }, + { "Fragmentation percentage bar", "frag-percentage-bar" }, + { "Is read-only", "is-readonly" }, + })) +}; diff --git a/src/modules/zpool/zpool.h b/src/modules/zpool/zpool.h new file mode 100644 index 0000000..35706bb --- /dev/null +++ b/src/modules/zpool/zpool.h @@ -0,0 +1,11 @@ +#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; diff --git a/src/options/display.c b/src/options/display.c new file mode 100644 index 0000000..ec80a37 --- /dev/null +++ b/src/options/display.c @@ -0,0 +1,1168 @@ +#include "fastfetch.h" +#include "common/color.h" +#include "common/jsonconfig.h" +#include "common/percent.h" +#include "common/strutil.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) { + FFstrbuf* buffer = FF_LIST_ADD(FFstrbuf, options->constants); + ffStrbufInitJsonVal(buffer, 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 if (unsafe_yyjson_equals_str(key, "common")) { + if (!yyjson_is_obj(val)) { + return "display.common must be an object"; + } + + yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); + if (ndigits) { + if (!yyjson_is_uint(ndigits)) { + return "display.common.ndigits must be an unsigned integer"; + } + uint64_t val = yyjson_get_uint(ndigits); + if (val > 9) { + return "display.common.ndigits must be between 0 and 9"; + } + options->fractionNdigits = (int8_t) val; + options->freqNdigits = (int8_t) val; + options->percentNdigits = (uint8_t) val; + options->sizeNdigits = (uint8_t) val; + options->tempNdigits = (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->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + options->tempSpaceBeforeUnit = (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")) { + uint32_t num = ffOptionParseUInt32(key, value); + if (num > 9) { + fprintf(stderr, "Error: %s must be between 0 and 9\n", key); + exit(479); + } + options->sizeNdigits = (uint8_t) num; + } else if (ffStrEqualsIgnCase(subkey, "max-prefix")) { + options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum( + key, + value, + (FFKeyValuePair[]) { + { "B", 0 }, + { "kB", 1 }, + { "MB", 2 }, + { "GB", 3 }, + { "TB", 4 }, + { "PB", 5 }, + { "EB", 6 }, + { "ZB", 7 }, + { "YB", 8 }, + {}, + }); + } 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 = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT; + 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); +} + +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, temperature, "unit", "C"); + break; + case FF_TEMPERATURE_UNIT_FAHRENHEIT: + yyjson_mut_obj_add_str(doc, temperature, "unit", "F"); + break; + case FF_TEMPERATURE_UNIT_KELVIN: + yyjson_mut_obj_add_str(doc, temperature, "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, "bar"); + } + 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); + } + + switch (options->fractionTrailingZeros) { + case FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT: + yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "default"); + break; + case FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS: + yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "always"); + break; + case FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER: + yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "never"); + break; + } + } + + 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_0: + yyjson_mut_obj_add_str(doc, key, "type", "both-0"); + break; + case FF_MODULE_KEY_TYPE_BOTH_1: // alias: both + yyjson_mut_obj_add_str(doc, key, "type", "both"); + break; + case FF_MODULE_KEY_TYPE_BOTH_2: + yyjson_mut_obj_add_str(doc, key, "type", "both-2"); + break; + case FF_MODULE_KEY_TYPE_BOTH_3: + yyjson_mut_obj_add_str(doc, key, "type", "both-3"); + break; + case FF_MODULE_KEY_TYPE_BOTH_4: + yyjson_mut_obj_add_str(doc, key, "type", "both-4"); + 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->freqSpaceBeforeUnit) { + 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); + } + } +} diff --git a/src/options/display.h b/src/options/display.h new file mode 100644 index 0000000..8c674ea --- /dev/null +++ b/src/options/display.h @@ -0,0 +1,97 @@ +#pragma once + +#include "common/ffdata.h" +#include "common/percent.h" +#include "common/FFstrbuf.h" +#include "common/FFlist.h" + +typedef enum FF_A_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 FF_A_PACKED FFTemperatureUnit { + FF_TEMPERATURE_UNIT_DEFAULT, + FF_TEMPERATURE_UNIT_CELSIUS, + FF_TEMPERATURE_UNIT_FAHRENHEIT, + FF_TEMPERATURE_UNIT_KELVIN, +} FFTemperatureUnit; + +typedef enum FF_A_PACKED FFSpaceBeforeUnitType { + FF_SPACE_BEFORE_UNIT_DEFAULT, + FF_SPACE_BEFORE_UNIT_ALWAYS, + FF_SPACE_BEFORE_UNIT_NEVER, +} FFSpaceBeforeUnitType; + +typedef enum FF_A_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); diff --git a/src/options/general.c b/src/options/general.c new file mode 100644 index 0000000..6257a0f --- /dev/null +++ b/src/options/general.c @@ -0,0 +1,134 @@ +#include "fastfetch.h" +#include "common/jsonconfig.h" +#include "common/processing.h" +#include "common/strutil.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); + } else if (unsafe_yyjson_equals_str(key, "playerName")) { + ffStrbufSetJsonVal(&options->playerName, val); + } +#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) + 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; + } + } +#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); + } else if (ffStrEqualsIgnCase(key, "--player-name")) { + ffOptionParseString(key, value, &options->playerName); + } +#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) + 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; + } + } +#endif + else { + return false; + } + + return true; +} + +void ffOptionsInitGeneral(FFOptionsGeneral* options) { + options->processingTimeout = 5000; + options->multithreading = true; + options->detectVersion = true; + ffStrbufInit(&options->playerName); + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) + options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE; +#endif +} + +void ffOptionsDestroyGeneral(FFOptionsGeneral* options) { + ffStrbufDestroy(&options->playerName); +} + +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); + + yyjson_mut_obj_add_strbuf(doc, obj, "playerName", &options->playerName); + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) + + 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; + } +#endif +} diff --git a/src/options/general.h b/src/options/general.h new file mode 100644 index 0000000..454ca60 --- /dev/null +++ b/src/options/general.h @@ -0,0 +1,27 @@ +#pragma once + +#include "common/ffdata.h" + +typedef enum FF_A_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; + FFstrbuf playerName; + +// 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__) + FFDsForceDrmType dsForceDrm; +#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); diff --git a/src/options/logo.c b/src/options/logo.c new file mode 100644 index 0000000..30fbccd --- /dev/null +++ b/src/options/logo.c @@ -0,0 +1,562 @@ +#include "logo/logo.h" + +#include "common/jsonconfig.h" +#include "common/strutil.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->paddingBottom = 0; + options->paddingLeft = 0; + options->paddingRight = 4; + options->printRemaining = true; + options->preserveAspectRatio = false; + options->recache = false; + options->position = FF_LOGO_POSITION_LEFT; + +#if FF_HAVE_CHAFA + 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; +#endif +} + +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-bottom")) { + options->paddingBottom = 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; +#if FF_HAVE_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 }, + {}, + }); +#endif + } else { + return false; + } + } else { + return false; + } + + return true; +} + +void ffOptionsDestroyLogo(FFOptionsLogo* options) { + ffStrbufDestroy(&options->source); +#if FF_HAVE_CHAFA + ffStrbufDestroy(&options->chafaSymbols); +#endif + 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->paddingBottom = 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); + FF_PARSE_PADDING_POSITON(bottom, paddingBottom); +#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; +#if FF_HAVE_CHAFA + } 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; +#endif + } 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, "bottom", options->paddingBottom); + 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]); + +#if FF_HAVE_CHAFA + { + 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); + } +#endif + + yyjson_mut_obj_add_val(doc, doc->root, "logo", obj); +} diff --git a/src/options/logo.h b/src/options/logo.h new file mode 100644 index 0000000..c77c678 --- /dev/null +++ b/src/options/logo.h @@ -0,0 +1,61 @@ +#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 FF_A_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 FF_A_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; + uint32_t paddingBottom; + bool printRemaining; + bool preserveAspectRatio; + bool recache; + +#if FF_HAVE_CHAFA + bool chafaFgOnly; + FFstrbuf chafaSymbols; + uint32_t chafaCanvasMode; + uint32_t chafaColorSpace; + uint32_t chafaDitherMode; +#endif +} 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); -- cgit v1.2.3