#include "gsr-info.h" #include #include #include #include /* ── Helpers ─────────────────────────────────────────────────────── */ static char * read_command_output(const char *cmd, int *exit_code) { FILE *f = popen(cmd, "r"); if (!f) { if (exit_code) *exit_code = -1; return NULL; } size_t capacity = 8192; size_t len = 0; char *buf = g_malloc(capacity); for (;;) { size_t n = fread(buf + len, 1, capacity - len - 1, f); if (n == 0) break; len += n; if (len + 1 >= capacity) { capacity *= 2; buf = g_realloc(buf, capacity); } } buf[len] = '\0'; int status = pclose(f); if (exit_code) { if (status >= 0 && WIFEXITED(status)) *exit_code = WEXITSTATUS(status); else *exit_code = -1; } return buf; } typedef void (*LineCallback)(const char *line, size_t len, void *user_data); static void for_each_line(const char *text, LineCallback cb, void *user_data) { const char *p = text; while (*p) { const char *nl = strchr(p, '\n'); size_t len = nl ? (size_t)(nl - p) : strlen(p); if (len > 0) cb(p, len, user_data); if (!nl) break; p = nl + 1; } } static bool str_eq(const char *s, size_t len, const char *literal) { size_t llen = strlen(literal); return len == llen && memcmp(s, literal, llen) == 0; } static bool str_starts_with(const char *s, size_t len, const char *prefix) { size_t plen = strlen(prefix); return len >= plen && memcmp(s, prefix, plen) == 0; } /* ── Section parsing ─────────────────────────────────────────────── */ typedef enum { SECTION_UNKNOWN, SECTION_SYSTEM_INFO, SECTION_GPU_INFO, SECTION_VIDEO_CODECS, SECTION_CAPTURE_OPTIONS, } InfoSection; typedef struct { GsrInfo *info; InfoSection section; /* temporary growable monitor array */ GsrMonitor *monitors; int n_monitors; int monitors_capacity; } ParseState; static void parse_system_info(ParseState *st, const char *line, size_t len) { const char *sep = memchr(line, '|', len); if (!sep) return; size_t klen = (size_t)(sep - line); const char *val = sep + 1; size_t vlen = len - klen - 1; if (str_eq(line, klen, "display_server")) { if (str_eq(val, vlen, "x11")) st->info->system_info.display_server = GSR_DISPLAY_SERVER_X11; else if (str_eq(val, vlen, "wayland")) st->info->system_info.display_server = GSR_DISPLAY_SERVER_WAYLAND; } else if (str_eq(line, klen, "is_steam_deck")) { st->info->system_info.is_steam_deck = str_eq(val, vlen, "yes"); } else if (str_eq(line, klen, "supports_app_audio")) { st->info->system_info.supports_app_audio = str_eq(val, vlen, "yes"); } } static void parse_gpu_info(ParseState *st, const char *line, size_t len) { const char *sep = memchr(line, '|', len); if (!sep) return; size_t klen = (size_t)(sep - line); const char *val = sep + 1; size_t vlen = len - klen - 1; if (str_eq(line, klen, "vendor")) { if (str_eq(val, vlen, "amd")) st->info->gpu_info.vendor = GSR_GPU_VENDOR_AMD; else if (str_eq(val, vlen, "intel")) st->info->gpu_info.vendor = GSR_GPU_VENDOR_INTEL; else if (str_eq(val, vlen, "nvidia")) st->info->gpu_info.vendor = GSR_GPU_VENDOR_NVIDIA; else if (str_eq(val, vlen, "broadcom")) st->info->gpu_info.vendor = GSR_GPU_VENDOR_BROADCOM; else if (str_eq(val, vlen, "apple")) st->info->gpu_info.vendor = GSR_GPU_VENDOR_APPLE; } } static void parse_video_codecs(ParseState *st, const char *line, size_t len) { GsrSupportedVideoCodecs *vc = &st->info->supported_video_codecs; if (str_eq(line, len, "h264")) vc->h264 = true; else if (str_eq(line, len, "h264_software")) vc->h264_software = true; else if (str_eq(line, len, "hevc")) vc->hevc = true; else if (str_eq(line, len, "hevc_hdr")) vc->hevc_hdr = true; else if (str_eq(line, len, "hevc_10bit")) vc->hevc_10bit = true; else if (str_eq(line, len, "av1")) vc->av1 = true; else if (str_eq(line, len, "av1_hdr")) vc->av1_hdr = true; else if (str_eq(line, len, "av1_10bit")) vc->av1_10bit = true; else if (str_eq(line, len, "vp8")) vc->vp8 = true; else if (str_eq(line, len, "vp9")) vc->vp9 = true; } static void parse_capture_options(ParseState *st, const char *line, size_t len) { if (str_eq(line, len, "window")) { st->info->supported_capture_options.window = true; } else if (str_eq(line, len, "focused")) { st->info->supported_capture_options.focused = true; } else if (str_eq(line, len, "portal")) { st->info->supported_capture_options.portal = true; } else if (str_eq(line, len, "region") || (len > 0 && line[0] == '/')) { /* skip "region" and DRM card paths */ } else { /* monitor entry: name|WxH */ if (st->n_monitors >= st->monitors_capacity) { st->monitors_capacity = st->monitors_capacity ? st->monitors_capacity * 2 : 8; st->monitors = g_realloc_n(st->monitors, (size_t)st->monitors_capacity, sizeof(GsrMonitor)); } GsrMonitor *m = &st->monitors[st->n_monitors]; memset(m, 0, sizeof(*m)); const char *sep = memchr(line, '|', len); if (sep) { size_t name_len = (size_t)(sep - line); m->name = g_strndup(line, name_len); sscanf(sep + 1, "%dx%d", &m->width, &m->height); } else { m->name = g_strndup(line, len); } st->n_monitors++; } } static void info_line_cb(const char *line, size_t len, void *user_data) { ParseState *st = user_data; if (str_starts_with(line, len, "section=")) { const char *name = line + 8; size_t nlen = len - 8; if (str_eq(name, nlen, "system_info")) st->section = SECTION_SYSTEM_INFO; else if (str_eq(name, nlen, "gpu_info")) st->section = SECTION_GPU_INFO; else if (str_eq(name, nlen, "video_codecs")) st->section = SECTION_VIDEO_CODECS; else if (str_eq(name, nlen, "capture_options")) st->section = SECTION_CAPTURE_OPTIONS; else st->section = SECTION_UNKNOWN; return; } switch (st->section) { case SECTION_SYSTEM_INFO: parse_system_info(st, line, len); break; case SECTION_GPU_INFO: parse_gpu_info(st, line, len); break; case SECTION_VIDEO_CODECS: parse_video_codecs(st, line, len); break; case SECTION_CAPTURE_OPTIONS: parse_capture_options(st, line, len); break; case SECTION_UNKNOWN: break; } } /* ── Public API ──────────────────────────────────────────────────── */ GsrInfoExitStatus gsr_info_load(GsrInfo *info) { memset(info, 0, sizeof(*info)); int exit_code = -1; char *output = read_command_output("gpu-screen-recorder --info", &exit_code); if (!output) { g_warning("'gpu-screen-recorder --info' failed to run"); return GSR_INFO_EXIT_FAILED_TO_RUN; } ParseState st = { .info = info, .section = SECTION_UNKNOWN, }; for_each_line(output, info_line_cb, &st); g_free(output); /* Transfer monitors */ info->supported_capture_options.monitors = st.monitors; info->supported_capture_options.n_monitors = st.n_monitors; switch (exit_code) { case 0: return GSR_INFO_EXIT_OK; case 22: return GSR_INFO_EXIT_OPENGL_FAILED; case 23: return GSR_INFO_EXIT_NO_DRM_CARD; default: return GSR_INFO_EXIT_FAILED_TO_RUN; } } void gsr_info_clear(GsrInfo *info) { for (int i = 0; i < info->supported_capture_options.n_monitors; i++) g_free(info->supported_capture_options.monitors[i].name); g_free(info->supported_capture_options.monitors); memset(info, 0, sizeof(*info)); } bool gsr_info_is_codec_supported(const GsrInfo *info, const char *codec_id) { if (g_strcmp0(codec_id, "auto") == 0) return true; const GsrSupportedVideoCodecs *vc = &info->supported_video_codecs; if (g_strcmp0(codec_id, "h264") == 0) return vc->h264; if (g_strcmp0(codec_id, "h264_software") == 0) return vc->h264_software; if (g_strcmp0(codec_id, "hevc") == 0) return vc->hevc; if (g_strcmp0(codec_id, "hevc_hdr") == 0) return vc->hevc_hdr; if (g_strcmp0(codec_id, "hevc_10bit") == 0) return vc->hevc_10bit; if (g_strcmp0(codec_id, "av1") == 0) return vc->av1; if (g_strcmp0(codec_id, "av1_hdr") == 0) return vc->av1_hdr; if (g_strcmp0(codec_id, "av1_10bit") == 0) return vc->av1_10bit; if (g_strcmp0(codec_id, "vp8") == 0) return vc->vp8; if (g_strcmp0(codec_id, "vp9") == 0) return vc->vp9; return false; } bool gsr_info_is_capture_option_enabled(const GsrInfo *info, const char *option_id) { if (info->system_info.display_server == GSR_DISPLAY_SERVER_WAYLAND) { if (g_strcmp0(option_id, "window") == 0 || g_strcmp0(option_id, "focused") == 0) return false; } if (g_strcmp0(option_id, "portal") == 0) return info->supported_capture_options.portal; return true; } const char * gsr_info_get_first_usable_hw_video_codec(const GsrInfo *info) { const GsrSupportedVideoCodecs *vc = &info->supported_video_codecs; if (vc->h264) return "h264"; if (vc->hevc) return "hevc"; if (vc->av1) return "av1"; if (vc->vp8) return "vp8"; if (vc->vp9) return "vp9"; return NULL; } /* ── Audio device queries ────────────────────────────────────────── */ GsrAudioDevice * gsr_audio_devices_get(int *n_devices) { *n_devices = 0; int exit_code = -1; char *output = read_command_output("gpu-screen-recorder --list-audio-devices", &exit_code); if (!output) return NULL; /* Count lines */ int capacity = 16; GsrAudioDevice *devs = g_new0(GsrAudioDevice, capacity); int count = 0; const char *p = output; while (*p) { const char *nl = strchr(p, '\n'); size_t len = nl ? (size_t)(nl - p) : strlen(p); if (len > 0) { const char *sep = memchr(p, '|', len); if (sep) { if (count >= capacity) { capacity *= 2; devs = g_realloc_n(devs, (size_t)capacity, sizeof(GsrAudioDevice)); } devs[count].name = g_strndup(p, (size_t)(sep - p)); devs[count].description = g_strndup(sep + 1, len - (size_t)(sep - p) - 1); count++; } } if (!nl) break; p = nl + 1; } g_free(output); *n_devices = count; return devs; } void gsr_audio_devices_free(GsrAudioDevice *devices, int n_devices) { for (int i = 0; i < n_devices; i++) { g_free(devices[i].name); g_free(devices[i].description); } g_free(devices); } char ** gsr_application_audio_get(int *n_apps) { *n_apps = 0; int exit_code = -1; char *output = read_command_output("gpu-screen-recorder --list-application-audio", &exit_code); if (!output) return NULL; int capacity = 16; char **apps = g_new0(char *, capacity + 1); int count = 0; const char *p = output; while (*p) { const char *nl = strchr(p, '\n'); size_t len = nl ? (size_t)(nl - p) : strlen(p); if (len > 0) { if (count >= capacity) { capacity *= 2; apps = g_realloc_n(apps, (size_t)(capacity + 1), sizeof(char *)); } apps[count] = g_strndup(p, len); count++; } if (!nl) break; p = nl + 1; } apps[count] = NULL; g_free(output); *n_apps = count; return apps; } void gsr_application_audio_free(char **apps, int n_apps) { for (int i = 0; i < n_apps; i++) g_free(apps[i]); g_free(apps); }