diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/camera | |
Add the files
Diffstat (limited to 'src/detection/camera')
| -rw-r--r-- | src/detection/camera/camera.h | 15 | ||||
| -rw-r--r-- | src/detection/camera/camera_android.c | 58 | ||||
| -rw-r--r-- | src/detection/camera/camera_apple.m | 61 | ||||
| -rw-r--r-- | src/detection/camera/camera_linux.c | 94 | ||||
| -rw-r--r-- | src/detection/camera/camera_nosupport.c | 5 | ||||
| -rw-r--r-- | src/detection/camera/camera_windows.cpp | 154 |
6 files changed, 387 insertions, 0 deletions
diff --git a/src/detection/camera/camera.h b/src/detection/camera/camera.h new file mode 100644 index 0000000..52036b5 --- /dev/null +++ b/src/detection/camera/camera.h @@ -0,0 +1,15 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/camera/option.h" + +typedef struct FFCameraResult { + FFstrbuf name; + FFstrbuf vendor; + FFstrbuf id; + FFstrbuf colorspace; + uint32_t width; + uint32_t height; +} FFCameraResult; + +const char* ffDetectCamera(FFlist* result /* list of FFCameraResult */); diff --git a/src/detection/camera/camera_android.c b/src/detection/camera/camera_android.c new file mode 100644 index 0000000..b09314d --- /dev/null +++ b/src/detection/camera/camera_android.c @@ -0,0 +1,58 @@ +#include "camera.h" + +#include "common/processing.h" +#include "common/properties.h" + +#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" +#define FF_TERMUX_API_PARAM "CameraInfo" + +static inline void wrapYyjsonFree(yyjson_doc** doc) { + assert(doc); + if (*doc) { + yyjson_doc_free(*doc); + } +} + +const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffProcessAppendStdOut(&buffer, (char* const[]) { FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) { + return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; + } + + yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); + if (!doc) { + return "Failed to parse camera info"; + } + + yyjson_val* root = yyjson_doc_get_root(doc); + if (!yyjson_is_arr(root)) { + return "Camera info result is not a JSON array"; + } + + yyjson_val* device; + size_t idx, max; + yyjson_arr_foreach (root, idx, max, device) { + FFCameraResult* camera = FF_LIST_ADD(FFCameraResult, *result); + { + const char* facing = yyjson_get_str(yyjson_obj_get(device, "facing")); + if (facing) { + ffStrbufInitF(&camera->name, "builtin-%s", facing); + } else { + ffStrbufInitStatic(&camera->name, "Unknown"); + } + } + ffStrbufInit(&camera->vendor); + ffStrbufInitJsonVal(&camera->id, yyjson_obj_get(device, "id")); + yyjson_val* sizes = yyjson_arr_get_first(yyjson_obj_get(device, "jpeg_output_sizes")); + if (yyjson_is_obj(sizes)) { + camera->width = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, "width")); + camera->height = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, "height")); + } else { + camera->width = camera->height = 0; + } + ffStrbufInit(&camera->colorspace); + } + + return NULL; +} diff --git a/src/detection/camera/camera_apple.m b/src/detection/camera/camera_apple.m new file mode 100644 index 0000000..aa5c1cf --- /dev/null +++ b/src/detection/camera/camera_apple.m @@ -0,0 +1,61 @@ +#include "camera.h" +#include "common/io.h" + +#import <AVFoundation/AVCaptureDevice.h> + +// warning: 'AVCaptureDeviceTypeExternalUnknown' is deprecated +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#ifdef MAC_OS_VERSION_14_0 +// To make fastfetch compiled on newer macOS versions runs on older ones +AVF_EXPORT FF_A_WEAK_IMPORT AVCaptureDeviceType const AVCaptureDeviceTypeExternal; +#endif + +const char* ffDetectCamera(FFlist* result) +{ + #ifdef MAC_OS_X_VERSION_10_15 + FF_SUPPRESS_IO(); // #822 + + AVCaptureDeviceType deviceType = NULL; + + #ifdef MAC_OS_VERSION_14_0 + // Strangely `@available(macOS 14.0, *)` doesn't work here (#1594) + if (@available(macOS 14.0, *)) + { + if (&AVCaptureDeviceTypeExternal) + deviceType = AVCaptureDeviceTypeExternal; + } + #endif + if (deviceType == NULL) + deviceType = AVCaptureDeviceTypeExternalUnknown; + + AVCaptureDeviceDiscoverySession* session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType] + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + if (!session) + return "Failed to create AVCaptureDeviceDiscoverySession"; + + for (AVCaptureDevice* device in session.devices) + { + FFCameraResult* camera = FF_LIST_ADD(FFCameraResult, *result); + ffStrbufInitS(&camera->name, device.localizedName.UTF8String); + ffStrbufInitS(&camera->vendor, device.manufacturer.UTF8String); + ffStrbufInitS(&camera->id, device.uniqueID.UTF8String); + switch (device.activeColorSpace) + { + case AVCaptureColorSpace_sRGB: ffStrbufInitStatic(&camera->colorspace, "sRGB"); break; + case AVCaptureColorSpace_P3_D65: ffStrbufInitStatic(&camera->colorspace, "P3-D65"); break; + case 2 /*AVCaptureColorSpace_HLG_BT2020*/: ffStrbufInitStatic(&camera->colorspace, "BT2020-HLG"); break; + case 3 /*AVCaptureColorSpace_AppleLog*/: ffStrbufInitStatic(&camera->colorspace, "AppleLog"); break; + case 4 /*AVCaptureColorSpace_AppleLog2*/: ffStrbufInitStatic(&camera->colorspace, "AppleLog2"); break; + } + + CMVideoDimensions size = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription); + camera->width = size.width < 0 ? 0 : (uint32_t) size.width; + camera->height = size.height < 0 ? 0 : (uint32_t) size.height; + } + return NULL; + #else + return "No support for old MacOS version"; + #endif +} diff --git a/src/detection/camera/camera_linux.c b/src/detection/camera/camera_linux.c new file mode 100644 index 0000000..2fea2b4 --- /dev/null +++ b/src/detection/camera/camera_linux.c @@ -0,0 +1,94 @@ +#include "camera.h" +#include "common/io.h" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#if FF_HAVE_LINUX_VIDEODEV2 + #include <linux/videodev2.h> +#elif __has_include(<sys/videoio.h>) // OpenBSD + #include <sys/videoio.h> + #define FF_HAVE_LINUX_VIDEODEV2 1 +#endif + +const char* ffDetectCamera(FFlist* result) { +#if FF_HAVE_LINUX_VIDEODEV2 + char path[] = "/dev/videoN"; + + for (uint32_t i = 0; i <= 9; ++i) { + path[ARRAY_SIZE(path) - 2] = (char) (i + '0'); + FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + break; + } + if (errno == ENXIO) { + continue; + } + return "Failed to open /dev/videoN"; + } + + struct v4l2_capability cap = {}; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + continue; + } + + struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; + if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) { + continue; + } + + FFCameraResult* camera = FF_LIST_ADD(FFCameraResult, *result); + ffStrbufInitS(&camera->name, (const char*) cap.card); + ffStrbufInit(&camera->vendor); + ffStrbufInitS(&camera->id, (const char*) cap.bus_info); + switch (fmt.fmt.pix.colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + ffStrbufInitStatic(&camera->colorspace, "SMPTE 170M"); + break; + case V4L2_COLORSPACE_SMPTE240M: + ffStrbufInitStatic(&camera->colorspace, "SMPTE 240M"); + break; + case V4L2_COLORSPACE_BT878: + ffStrbufInitStatic(&camera->colorspace, "BT.878"); + break; + case V4L2_COLORSPACE_470_SYSTEM_M: + ffStrbufInitStatic(&camera->colorspace, "NTSC"); + break; + case V4L2_COLORSPACE_470_SYSTEM_BG: + ffStrbufInitStatic(&camera->colorspace, "EBU 3213"); + break; + case V4L2_COLORSPACE_JPEG: + ffStrbufInitStatic(&camera->colorspace, "JPEG"); + break; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + ffStrbufInitStatic(&camera->colorspace, "sRGB"); + break; + case 9 /* V4L2_COLORSPACE_OPRGB */: + ffStrbufInitStatic(&camera->colorspace, "Adobe RGB"); + break; + case 10 /* V4L2_COLORSPACE_BT2020 */: + ffStrbufInitStatic(&camera->colorspace, "BT.2020"); + break; + case 11 /* V4L2_COLORSPACE_RAW */: + ffStrbufInitStatic(&camera->colorspace, "RAW"); + break; + case 12 /* V4L2_COLORSPACE_DCI_P3 */: + ffStrbufInitStatic(&camera->colorspace, "DCI-P3"); + break; + default: + ffStrbufInit(&camera->colorspace); + break; + } + camera->width = fmt.fmt.pix.width; + camera->height = fmt.fmt.pix.height; + } + + return NULL; +#else + FF_UNUSED(result); + return "Fastfetch was compiled without <linux/videodev2.h>"; +#endif +} diff --git a/src/detection/camera/camera_nosupport.c b/src/detection/camera/camera_nosupport.c new file mode 100644 index 0000000..ae5b243 --- /dev/null +++ b/src/detection/camera/camera_nosupport.c @@ -0,0 +1,5 @@ +#include "camera.h" + +const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { + return "Not support on this platform"; +} diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp new file mode 100644 index 0000000..1f42306 --- /dev/null +++ b/src/detection/camera/camera_windows.cpp @@ -0,0 +1,154 @@ +extern "C" { +#include "camera.h" +#include "common/library.h" +#include "common/windows/com.h" +} +#include "common/windows/unicode.hpp" +#include "common/windows/util.hpp" + +#include <initguid.h> +#include <mfapi.h> +#include <mfidl.h> + +extern "C" const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { + FF_LIBRARY_LOAD_MESSAGE(mfplat, "mfplat" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFCreateAttributes) + FF_LIBRARY_LOAD_MESSAGE(mf, "mf" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mf, MFEnumDeviceSources) + + const char* error = ffInitCom(); + if (error) { + return error; + } + + IMFAttributes* FF_AUTO_RELEASE_COM_OBJECT attrs = nullptr; + if (FAILED(ffMFCreateAttributes(&attrs, 1))) { + return "MFCreateAttributes() failed"; + } + + if (FAILED(attrs->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID))) { + return "SetGUID(MF_*) failed"; + } + + IMFActivate** devices = NULL; + uint32_t count; + + if (FAILED(ffMFEnumDeviceSources(attrs, &devices, &count))) { + return "MFEnumDeviceSources() failed"; + } + + for (uint32_t i = 0; i < count; i++) { + IMFActivate* FF_AUTO_RELEASE_COM_OBJECT device = devices[i]; + + wchar_t buffer[256]; + uint32_t length = 0; + if (FAILED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buffer, ARRAY_SIZE(buffer), &length)) || length == 0) { + continue; + } + + FFCameraResult* camera = FF_LIST_ADD(FFCameraResult, *result); + ffStrbufInitNWS(&camera->name, length, buffer); + ffStrbufInit(&camera->colorspace); + ffStrbufInit(&camera->vendor); + ffStrbufInit(&camera->id); + camera->width = 0; + camera->height = 0; + + if (SUCCEEDED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buffer, ARRAY_SIZE(buffer), &length)) && length > 0) { + ffStrbufSetNWS(&camera->id, length, buffer); + } + + IMFMediaSource* FF_AUTO_RELEASE_COM_OBJECT source = nullptr; + if (FAILED(device->ActivateObject(IID_PPV_ARGS(&source)))) { + continue; + } + + on_scope_exit destroySource([&] { source->Shutdown(); }); + + IMFPresentationDescriptor* FF_AUTO_RELEASE_COM_OBJECT pd = nullptr; + if (FAILED(source->CreatePresentationDescriptor(&pd))) { + continue; + } + + IMFStreamDescriptor* FF_AUTO_RELEASE_COM_OBJECT sd = nullptr; + BOOL selected; + if (FAILED(pd->GetStreamDescriptorByIndex(0, &selected, &sd))) { + continue; + } + + IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = nullptr; + if (FAILED(sd->GetMediaTypeHandler(&handler))) { + continue; + } + + DWORD mediaTypeCount; + if (FAILED(handler->GetMediaTypeCount(&mediaTypeCount))) { + continue; + } + + // Assume first type is the maximum resolution + IMFMediaType* type = NULL; + for (DWORD idx = 0; SUCCEEDED(handler->GetMediaTypeByIndex(idx, &type)); ++idx) { + on_scope_exit destroyType([=] { type->Release(); }); + + GUID majorType; + if (FAILED(type->GetMajorType(&majorType)) || majorType != MFMediaType_Video) { + continue; + } + + MFVideoPrimaries primaries; + static_assert(sizeof(primaries) == sizeof(uint32_t), ""); + if (SUCCEEDED(type->GetUINT32(MF_MT_VIDEO_PRIMARIES, (uint32_t*) &primaries))) { + switch (primaries) { + case MFVideoPrimaries_BT709: + ffStrbufSetStatic(&camera->colorspace, "sRGB"); + break; + case MFVideoPrimaries_BT470_2_SysM: + case MFVideoPrimaries_BT470_2_SysBG: + ffStrbufSetStatic(&camera->colorspace, "NTSC"); + break; + case MFVideoPrimaries_SMPTE170M: + ffStrbufSetStatic(&camera->colorspace, "SMPTE 170M"); + break; + case MFVideoPrimaries_SMPTE240M: + ffStrbufSetStatic(&camera->colorspace, "SMPTE 240M"); + break; + case MFVideoPrimaries_EBU3213: + ffStrbufSetStatic(&camera->colorspace, "EBU 3213"); + break; + case MFVideoPrimaries_SMPTE_C: + ffStrbufSetStatic(&camera->colorspace, "SMPTE C"); + break; + case MFVideoPrimaries_BT2020: + ffStrbufSetStatic(&camera->colorspace, "BT.2020"); + break; + case MFVideoPrimaries_XYZ: + ffStrbufSetStatic(&camera->colorspace, "XYZ"); + break; + case MFVideoPrimaries_DCI_P3: + ffStrbufSetStatic(&camera->colorspace, "DCI-P3"); + break; + case MFVideoPrimaries_ACES: + ffStrbufSetStatic(&camera->colorspace, "ACES"); + break; + case (MFVideoPrimaries) 13: // MFVideoPrimaries_Display_P3 + ffStrbufSetStatic(&camera->colorspace, "Display P3"); + break; + default: + break; + } + } + + MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &camera->width, &camera->height); + break; + } + } + + if (devices) { + CoTaskMemFree(devices); + } + + return nullptr; +} |