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/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 + 228 files changed, 15662 insertions(+) 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 (limited to 'src/modules') 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; -- cgit v1.2.3