diff options
| author | dec05eba <dec05eba@protonmail.com> | 2026-08-01 21:32:06 +0200 |
|---|---|---|
| committer | dec05eba <dec05eba@protonmail.com> | 2026-08-01 21:32:06 +0200 |
| commit | de8954256578e1805fc5b92a0719190de784d3c0 (patch) | |
| tree | 58fad345f2906108b04d62b0f0a4ae3e4e32ce82 /src | |
| parent | cdb8493eed42c386919280b17bbd31b713a5ac35 (diff) | |
Move capture source parsing and capture creation from main.cpp into C modules
Capture sources are C structs with fixed size names and a growable array,
and the lazily initialized kms client, night light and cursor globals are
now owned by gsr_capture_deps.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 929 | ||||
| -rw-r--r-- | src/recorder/capture_setup.c | 543 | ||||
| -rw-r--r-- | src/recorder/capture_source.c | 390 |
3 files changed, 1016 insertions, 846 deletions
diff --git a/src/main.cpp b/src/main.cpp index ec5a260..d64be78 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,8 @@ extern "C" { #include "../include/recorder/video_codec.h" #include "../include/recorder/codec_select.h" #include "../include/recorder/windowing.h" +#include "../include/recorder/capture_source.h" +#include "../include/recorder/capture_setup.h" #include "../include/recorder/error.h" #include "../include/capture/nvfbc.h" #include "../include/capture/xcomposite.h" @@ -99,59 +101,6 @@ struct MergedAudioInputs { static const int VIDEO_STREAM_INDEX = 0; -typedef struct { - const gsr_window *window; -} MonitorOutputCallbackUserdata; - -static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) { - const MonitorOutputCallbackUserdata *options = (MonitorOutputCallbackUserdata*)userdata; - vec2i monitor_position = monitor->pos; - vec2i monitor_size = monitor->size; - if(gsr_window_get_display_server(options->window) == GSR_DISPLAY_SERVER_WAYLAND) { - gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0; - drm_monitor_get_display_server_data(options->window, monitor, &monitor_rotation, &monitor_position); - if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270) - std::swap(monitor_size.x, monitor_size.y); - } - fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y); -} - -typedef struct { - char *output_name; -} FirstOutputCallback; - -static void get_first_output_callback(const gsr_monitor *monitor, void *userdata) { - FirstOutputCallback *data = (FirstOutputCallback*)userdata; - if(!data->output_name) - data->output_name = strdup(monitor->name); -} - -typedef struct { - gsr_window *window; - vec2i position; - char *output_name; - vec2i monitor_pos; - vec2i monitor_size; - double monitor_scale_inverted; -} MonitorByPositionCallback; - -static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *userdata) { - MonitorByPositionCallback *data = (MonitorByPositionCallback*)userdata; - - const vec2i monitor_position = monitor->logical_pos; - const vec2i monitor_size = monitor->size; - const vec2i monitor_logical_size = monitor->logical_size; - - if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_logical_size.x - && data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_logical_size.y) - { - data->output_name = strdup(monitor->name); - data->monitor_pos = monitor_position; - data->monitor_size = monitor_size; - data->monitor_scale_inverted = (double)monitor_size.x / (double)monitor_logical_size.x; - } -} - /* AV1/VP9 use the 0-255 qindex quantizer scale and VP8 uses 0-127. Unlike h264 qp the quantizer step size is not exponential in qindex, so the mapping to an equivalent h264 qp is not linear in the quantizer range. qindex = h264 qp * 4 matches the qindex = crf * 4 scale used by libvpx/libaom, where crf is roughly equivalent to h264 qp. */ @@ -363,41 +312,7 @@ struct RecordingStartResult { std::vector<RecordingStartAudio> audio_inputs; }; -typedef enum { - VVEC2I_TYPE_PIXELS, - VVEC2I_TYPE_SCALAR -} vvec2i_type; - -typedef struct { - int x, y; - vvec2i_type x_type; - vvec2i_type y_type; -} vvec2i; - -struct CaptureSource { - std::string name; - CaptureSourceType type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; - gsr_capture_alignment halign = GSR_CAPTURE_ALIGN_CENTER; - gsr_capture_alignment valign = GSR_CAPTURE_ALIGN_CENTER; - gsr_capture_v4l2_pixfmt v4l2_pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO; - uint32_t flip = GSR_FLIP_NONE; - vvec2i pos = {0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS}; - vvec2i size = {100, 100, VVEC2I_TYPE_SCALAR, VVEC2I_TYPE_SCALAR}; - vec2i region_pos = {0, 0}; - vec2i region_size = {0, 0}; - bool region_set = false; - int64_t window_id = 0; - int camera_fps = 0; - vec2i camera_resolution = {0, 0}; -}; - -struct VideoSource { - gsr_capture *capture; - gsr_capture_metadata metadata; - CaptureSource *capture_source; -}; - -static RecordingStartResult start_recording_create_streams(const char *filename, const args_parser &arg_parser, AVCodecContext *video_codec_context, const std::vector<AudioTrack> &audio_tracks, bool hdr, std::vector<VideoSource> &video_sources) { +static RecordingStartResult start_recording_create_streams(const char *filename, const args_parser &arg_parser, AVCodecContext *video_codec_context, const std::vector<AudioTrack> &audio_tracks, bool hdr, gsr_video_sources *video_sources) { AVFormatContext *av_format_context; avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.settings.container_format, filename); set_format_context_options(av_format_context); @@ -437,7 +352,8 @@ static RecordingStartResult start_recording_create_streams(const char *filename, return result; } - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; if(hdr && add_hdr_metadata_to_video_stream(video_source.capture, video_stream)) break; } @@ -494,7 +410,7 @@ struct AudioPtsOffset { int stream_index = 0; }; -static bool save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_encoder *encoder, const args_parser &arg_parser, const std::string &file_extension, bool date_folders, bool hdr, std::vector<VideoSource> &video_sources, int current_save_replay_seconds) { +static bool save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_encoder *encoder, const args_parser &arg_parser, const std::string &file_extension, bool date_folders, bool hdr, gsr_video_sources *video_sources, int current_save_replay_seconds) { if(save_replay_thread.valid()) return true; @@ -1103,262 +1019,7 @@ static void list_monitors_command(void *userdata) { _exit(0); } -static std::string validate_monitor_get_valid(const gsr_egl *egl, const char* window) { - const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; - const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; - const bool capture_use_drm = monitor_capture_use_drm(egl->window, egl->gpu_info.vendor); - - std::string capture_source_result = window; - if(strcmp(capture_source_result.c_str(), "screen") == 0) { - FirstOutputCallback data; - data.output_name = NULL; - for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_first_output_callback, &data); - - if(data.output_name) { - capture_source_result = data.output_name; - free(data.output_name); - } else { - gsr_log(GSR_LOG_LEVEL_ERROR, "no usable output found"); - _exit(51); - } - } else if(capture_use_drm || (strcmp(capture_source_result.c_str(), "screen-direct") != 0 && strcmp(capture_source_result.c_str(), "screen-direct-force") != 0)) { - gsr_monitor gmon; - if(!get_monitor_by_name(egl, connection_type, capture_source_result.c_str(), &gmon)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "display \"%s\" not found, expected one of:", capture_source_result.c_str()); - fprintf(stderr, " \"screen\"\n"); - if(!capture_use_drm) - fprintf(stderr, " \"screen-direct\"\n"); - - MonitorOutputCallbackUserdata userdata; - userdata.window = egl->window; - for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata); - _exit(51); - } - } - return capture_source_result; -} - -static std::string get_monitor_by_region_center(const gsr_egl *egl, vec2i region_position, vec2i region_size, vec2i *monitor_pos, vec2i *monitor_size, double *monitor_scale_inverted) { - const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; - const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND; - - MonitorByPositionCallback data; - data.window = egl->window; - data.position = { region_position.x + region_size.x / 2, region_position.y + region_size.y / 2 }; - data.output_name = NULL; - data.monitor_pos = {0, 0}; - data.monitor_size = {0, 0}; - data.monitor_scale_inverted = 1.0; - for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_monitor_by_position_callback, &data); - - std::string result; - if(data.output_name) { - result = data.output_name; - free(data.output_name); - } - *monitor_pos = data.monitor_pos; - *monitor_size = data.monitor_size; - *monitor_scale_inverted = data.monitor_scale_inverted; - return result; -} - -static gsr_kms_client kms_client; -static bool kms_client_initialized = false; -static gsr_kms_response kms_response; -static gsr_kde_night_light *kde_night_light = nullptr; -static bool kde_night_light_initialized = false; - -static gsr_cursor x11_cursor; -static Display *x11_cursor_display = NULL; - -static gsr_capture* create_monitor_capture(const args_parser &arg_parser, gsr_egl *egl, const CaptureSource &capture_source, bool prefer_ximage) { - if(gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11 && prefer_ximage) { - gsr_capture_ximage_params ximage_params; - memset(&ximage_params, 0, sizeof(ximage_params)); - ximage_params.egl = egl; - ximage_params.cursor = &x11_cursor; - ximage_params.display_to_capture = capture_source.name.c_str(); - ximage_params.record_cursor = arg_parser.settings.record_cursor; - ximage_params.output_resolution = arg_parser.settings.output_resolution; - ximage_params.region_size = capture_source.region_size; - ximage_params.region_position = capture_source.region_pos; - return gsr_capture_ximage_create(&ximage_params); - } - - if(monitor_capture_use_drm(egl->window, egl->gpu_info.vendor)) { - if(!kms_client_initialized) { - kms_client_initialized = true; - const int kms_init_res = gsr_kms_client_init(&kms_client, egl->card_path); - if(kms_init_res != 0) - _exit(kms_init_res < 0 ? 1 : kms_init_res); - } - - if(!kde_night_light_initialized && gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND) { - kde_night_light_initialized = true; - kde_night_light = gsr_kde_night_light_create(); - } - - gsr_capture_kms_params kms_params; - memset(&kms_params, 0, sizeof(kms_params)); - kms_params.egl = egl; - kms_params.x11_cursor = &x11_cursor; - kms_params.kms_response = &kms_response; - kms_params.kde_night_light = kde_night_light; - kms_params.display_to_capture = capture_source.name.c_str(); - kms_params.record_cursor = arg_parser.settings.record_cursor; - kms_params.hdr = video_codec_is_hdr(arg_parser.settings.video_codec); - kms_params.fps = arg_parser.settings.fps; - kms_params.output_resolution = arg_parser.settings.output_resolution; - kms_params.region_size = capture_source.region_size; - kms_params.region_position = capture_source.region_pos; - return gsr_capture_kms_create(&kms_params); - } else { - const char *capture_source_real = capture_source.name.c_str(); - const bool direct_capture = strcmp(capture_source.name.c_str(), "screen-direct") == 0 || strcmp(capture_source.name.c_str(), "screen-direct-force") == 0; - if(direct_capture) { - capture_source_real = "screen"; - gsr_log(GSR_LOG_LEVEL_WARNING, "%s capture option is not recommended unless you use G-SYNC as Nvidia has driver issues that can cause your system or games to freeze/crash.", capture_source.name.c_str()); - } - - gsr_capture_nvfbc_params nvfbc_params; - memset(&nvfbc_params, 0, sizeof(nvfbc_params)); - nvfbc_params.egl = egl; - nvfbc_params.display_to_capture = capture_source_real; - nvfbc_params.fps = arg_parser.settings.fps; - nvfbc_params.direct_capture = direct_capture; - nvfbc_params.record_cursor = arg_parser.settings.record_cursor; - nvfbc_params.output_resolution = arg_parser.settings.output_resolution; - nvfbc_params.region_size = capture_source.region_size; - nvfbc_params.region_position = capture_source.region_pos; - return gsr_capture_nvfbc_create(&nvfbc_params); - } -} - -static void monitor_output_callback_print_region(const gsr_monitor *monitor, void *userdata) { - const vec2i monitor_position = monitor->logical_pos; - const vec2i monitor_size = monitor->logical_size; - fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y); -} - -static std::string region_get_data(gsr_egl *egl, vec2i *region_size, vec2i *region_position) { - vec2i monitor_pos = {0, 0}; - vec2i monitor_size = {0, 0}; - double monitor_scale_inverted = 1.0; - std::string window = get_monitor_by_region_center(egl, *region_position, *region_size, &monitor_pos, &monitor_size, &monitor_scale_inverted); - if(window.empty()) { - const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; - const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND; - gsr_log(GSR_LOG_LEVEL_ERROR, "the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:", region_size->x, region_size->y, region_position->x, region_position->y); - - MonitorOutputCallbackUserdata userdata; - userdata.window = egl->window; - for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print_region, &userdata); - _exit(51); - } - - // Capture whole monitor when region size is set to 0x0 - if(region_size->x == 0 && region_size->y == 0) { - region_position->x = 0; - region_position->y = 0; - } else { - region_position->x -= monitor_pos.x; - region_position->y -= monitor_pos.y; - // Match drm plane coordinate space (1x scaling) to wayland coordinate space (which may have scaling set by user) - region_position->x *= monitor_scale_inverted; - region_position->y *= monitor_scale_inverted; - - region_size->x *= monitor_scale_inverted; - region_size->y *= monitor_scale_inverted; - } - return window; -} - -static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *egl, CaptureSource &capture_source, bool prefer_ximage) { - bool follow_focused = false; - const bool wayland = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND; - - gsr_capture *capture = nullptr; - if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) { - if(wayland) { - gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland"); - _exit(2); - } - - if(arg_parser.settings.output_resolution.x <= 0 || arg_parser.settings.output_resolution.y <= 0) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0", arg_parser.settings.output_resolution.x, arg_parser.settings.output_resolution.y); - args_parser_print_usage(); - _exit(1); - } - - follow_focused = true; - } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_PORTAL) { -#ifdef GSR_PORTAL - // Desktop portal capture on x11 doesn't seem to be hardware accelerated - if(!wayland) { - gsr_log(GSR_LOG_LEVEL_ERROR, "desktop portal capture is not supported on X11"); - _exit(1); - } - - gsr_capture_portal_params portal_params; - memset(&portal_params, 0, sizeof(portal_params)); - portal_params.egl = egl; - portal_params.record_cursor = arg_parser.settings.record_cursor; - portal_params.restore_portal_session = arg_parser.settings.restore_portal_session; - portal_params.portal_session_token_filepath = arg_parser.settings.portal_session_token_filepath; - portal_params.output_resolution = arg_parser.settings.output_resolution; - capture = gsr_capture_portal_create(&portal_params); - if(!capture) - _exit(1); -#else - gsr_log(GSR_LOG_LEVEL_ERROR, "option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support. Please recompile GPU Screen recorder with the -Dportal=true option"); - _exit(2); -#endif - } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION) { - capture_source.name = region_get_data(egl, &capture_source.region_size, &capture_source.region_pos); - capture = create_monitor_capture(arg_parser, egl, capture_source, prefer_ximage); - if(!capture) - _exit(1); - } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) { - capture_source.name = validate_monitor_get_valid(egl, capture_source.name.c_str()); - capture = create_monitor_capture(arg_parser, egl, capture_source, prefer_ximage); - if(!capture) - _exit(1); - } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_V4L2) { - gsr_capture_v4l2_params v4l2_params; - memset(&v4l2_params, 0, sizeof(v4l2_params)); - v4l2_params.egl = egl; - v4l2_params.output_resolution = arg_parser.settings.output_resolution; - v4l2_params.device_path = capture_source.name.c_str(); - v4l2_params.pixfmt = capture_source.v4l2_pixfmt; - v4l2_params.camera_fps = capture_source.camera_fps; - v4l2_params.camera_resolution.width = capture_source.camera_resolution.x; - v4l2_params.camera_resolution.height = capture_source.camera_resolution.y; - capture = gsr_capture_v4l2_create(&v4l2_params); - if(!capture) - _exit(1); - } else { - if(wayland) { - gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland or use -w portal option which supports window capture if your wayland compositor supports window capture"); - _exit(2); - } - } - - if(!capture) { - gsr_capture_xcomposite_params xcomposite_params; - memset(&xcomposite_params, 0, sizeof(xcomposite_params)); - xcomposite_params.egl = egl; - xcomposite_params.cursor = &x11_cursor; - xcomposite_params.window = capture_source.window_id; - xcomposite_params.follow_focused = follow_focused; - xcomposite_params.record_cursor = arg_parser.settings.record_cursor; - xcomposite_params.output_resolution = arg_parser.settings.output_resolution; - capture = gsr_capture_xcomposite_create(&xcomposite_params); - if(!capture) - _exit(1); - } - - return capture; -} +static gsr_capture_deps capture_deps; static gsr_color_range image_format_to_color_range(gsr_image_format image_format, int image_quality) { switch(image_format) { @@ -1384,115 +1045,6 @@ static int video_quality_to_image_quality_value(gsr_video_quality video_quality) return 90; } -static bool any_video_sources_uses_external_image(std::vector<VideoSource> &video_sources) { - for(VideoSource &video_source : video_sources) { - if(gsr_capture_uses_external_image(video_source.capture)) - return true; - } - return false; -} - -static std::vector<VideoSource> create_video_sources(const args_parser &arg_parser, gsr_egl *egl, bool prefer_ximage, std::vector<CaptureSource> &capture_sources, vec2i &video_size) { - std::vector<VideoSource> video_sources; - video_sources.reserve(capture_sources.size()); - - for(CaptureSource &capture_source : capture_sources) { - gsr_capture_metadata capture_metadata; - memset(&capture_metadata, 0, sizeof(capture_metadata)); - capture_metadata.fps = arg_parser.settings.fps; - capture_metadata.halign = capture_source.halign; - capture_metadata.valign = capture_source.valign; - capture_metadata.flip = (gsr_flip)capture_source.flip; - video_sources.push_back(VideoSource{create_capture_impl(arg_parser, egl, capture_source, prefer_ximage), capture_metadata, &capture_source}); - } - - for(VideoSource &video_source : video_sources) { - int capture_result = gsr_capture_start(video_source.capture, &video_source.metadata); - if(capture_result != 0) { - gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_capture_start failed"); - _exit(capture_result); - } - } - - vec2i start_pos = {99999, 99999}; - vec2i end_pos = {-99999, -99999}; - for(const VideoSource &video_source : video_sources) { - // TODO: Skip scalar positions for now, but this should be handled in a better way. - // Maybe handle scalars at the next loop by multiplying video size by the scalar. - if(video_source.capture_source->pos.x_type == VVEC2I_TYPE_SCALAR || video_source.capture_source->pos.y_type == VVEC2I_TYPE_SCALAR - || (video_source.capture_source->size.x_type == VVEC2I_TYPE_SCALAR && video_source.capture_source->size.x != 100) - || (video_source.capture_source->size.y_type == VVEC2I_TYPE_SCALAR && video_source.capture_source->size.y != 100)) - { - continue; - } - const vec2i video_source_start_pos = {video_source.capture_source->pos.x, video_source.capture_source->pos.y}; - const vec2i video_source_end_pos = {video_source_start_pos.x + video_source.metadata.video_size.x, video_source_start_pos.y + video_source.metadata.video_size.y}; - - start_pos.x = std::min(start_pos.x, video_source_start_pos.x); - start_pos.y = std::min(start_pos.y, video_source_start_pos.y); - - end_pos.x = std::max(end_pos.x, video_source_end_pos.x); - end_pos.y = std::max(end_pos.y, video_source_end_pos.y); - } - - video_size.x = std::max(0, end_pos.x - start_pos.x); - video_size.y = std::max(0, end_pos.y - start_pos.y); - - for(VideoSource &video_source : video_sources) { - video_source.metadata.video_size = video_size; - } - - return video_sources; -} - -static void video_sources_update_with_real_video_size(std::vector<CaptureSource> &capture_sources, std::vector<VideoSource> &video_sources, vec2i video_size) { - assert(capture_sources.size() == video_sources.size()); - for(size_t i = 0; i < capture_sources.size(); ++i) { - CaptureSource &capture_source = capture_sources[i]; - VideoSource &video_source = video_sources[i]; - - video_source.metadata.recording_size = video_source.metadata.video_size; - // TODO: What if this updated resolution is above max resolution? - video_source.metadata.video_size = video_size; - - if(capture_source.pos.x != 0 || capture_source.pos.y != 0) { - video_source.metadata.position.x = capture_source.pos.x; - video_source.metadata.position.y = capture_source.pos.y; - - if(capture_source.pos.x_type == VVEC2I_TYPE_SCALAR) - video_source.metadata.position.x = video_source.metadata.video_size.x * ((double)video_source.metadata.position.x / 100.0); - - if(capture_source.pos.y_type == VVEC2I_TYPE_SCALAR) - video_source.metadata.position.y = video_source.metadata.video_size.y * ((double)video_source.metadata.position.y / 100.0); - } - - if(capture_source.size.x != 0 || capture_source.size.y != 0) { - video_source.metadata.recording_size.x = capture_source.size.x; - video_source.metadata.recording_size.y = capture_source.size.y; - - if(capture_source.size.x_type == VVEC2I_TYPE_SCALAR) - video_source.metadata.recording_size.x = video_source.metadata.video_size.x * ((double)video_source.metadata.recording_size.x / 100.0); - - if(capture_source.size.y_type == VVEC2I_TYPE_SCALAR) - video_source.metadata.recording_size.y = video_source.metadata.video_size.y * ((double)video_source.metadata.recording_size.y / 100.0); - } - } -} - -static void gsr_capture_kms_cleanup_kms_fds() { - for(int i = 0; i < kms_response.num_items; ++i) { - for(int j = 0; j < kms_response.items[i].num_dma_bufs; ++j) { - gsr_kms_response_dma_buf *dma_buf = &kms_response.items[i].dma_buf[j]; - if(dma_buf->fd > 0) { - close(dma_buf->fd); - dma_buf->fd = -1; - } - } - kms_response.items[i].num_dma_bufs = 0; - } - kms_response.num_items = 0; -} - static void load_plugins(gsr_plugins *plugins, args_parser &arg_parser, gsr_egl *egl, vec2i video_size) { const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p"); assert(plugin_arg); @@ -1520,14 +1072,18 @@ static void load_plugins(gsr_plugins *plugins, args_parser &arg_parser, gsr_egl } // TODO: 10-bit and hdr. -static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_window *window, gsr_image_format image_format, std::vector<CaptureSource> &capture_sources) { +static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_window *window, gsr_image_format image_format, gsr_capture_sources *capture_sources) { const int image_quality = video_quality_to_image_quality_value(arg_parser.settings.video_quality); const gsr_color_range color_range = image_format_to_color_range(image_format, image_quality); arg_parser.settings.fps = 60; // We want to capture an image as soon as possible vec2i video_size = {0, 0}; - std::vector<VideoSource> video_sources = create_video_sources(arg_parser, egl, true, capture_sources, video_size); - video_sources_update_with_real_video_size(capture_sources, video_sources, video_size); + gsr_video_sources video_sources_data; + const int video_sources_result = gsr_video_sources_create(&video_sources_data, &arg_parser.settings, egl, &capture_deps, true, capture_sources, &video_size); + if(video_sources_result != GSR_ERROR_OK) + _exit(gsr_error_to_exit_code(video_sources_result)); + gsr_video_sources *video_sources = &video_sources_data; + gsr_video_sources_update_with_real_video_size(video_sources, video_size); const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p"); assert(plugin_arg); @@ -1548,7 +1104,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win memset(&color_conversion_params, 0, sizeof(color_conversion_params)); color_conversion_params.color_range = color_range; color_conversion_params.egl = egl; - color_conversion_params.load_external_image_shader = any_video_sources_uses_external_image(video_sources); + color_conversion_params.load_external_image_shader = gsr_video_sources_uses_external_image(video_sources); color_conversion_params.destination_textures[0] = image_writer.texture; color_conversion_params.destination_textures_size[0] = video_size; @@ -1570,26 +1126,25 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win while(running) { while(gsr_window_process_event(window)) { - if(x11_cursor_display && arg_parser.settings.record_cursor) - gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window)); + if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor) + gsr_cursor_on_event(&capture_deps.x11_cursor, gsr_window_get_event_data(window)); - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; gsr_capture_on_event(video_source.capture, egl); } } - if(x11_cursor_display && arg_parser.settings.record_cursor) - gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display)); + if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor) + gsr_cursor_tick(&capture_deps.x11_cursor, DefaultRootWindow(capture_deps.x11_cursor_display)); - gsr_capture_kms_cleanup_kms_fds(); + gsr_capture_deps_cleanup_kms_fds(&capture_deps); - if(kms_client_initialized) { - if(gsr_kms_client_get_kms(&kms_client, &kms_response) != 0) - gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", kms_response.result, kms_response.err_msg); - } + gsr_capture_deps_update_kms(&capture_deps); should_stop_error = false; - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; gsr_capture_tick(video_source.capture); if(gsr_capture_should_stop(video_source.capture, &should_stop_error)) { running = 0; @@ -1597,7 +1152,8 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win } } - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; if(video_source.capture->pre_capture) video_source.capture->pre_capture(video_source.capture, &video_source.metadata, output_color_conversion); } @@ -1608,14 +1164,15 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win } bool all_sources_captured = true; - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; // It can fail, for example when capturing portal and the target is a monitor that hasn't been updated. // This can also happen for example if the system suspends and the monitor to capture's framebuffer is gone, or if the target window disappeared. if(gsr_capture_capture(video_source.capture, &video_source.metadata, output_color_conversion) != 0) all_sources_captured = false; } - gsr_capture_kms_cleanup_kms_fds(); + gsr_capture_deps_cleanup_kms_fds(&capture_deps); if(all_sources_captured) break; @@ -1646,9 +1203,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win gsr_plugins_deinit(&plugins); gsr_image_writer_deinit(&image_writer); - for(VideoSource &video_source : video_sources) { - gsr_capture_destroy(video_source.capture); - } + gsr_video_sources_deinit(video_sources); _exit(should_stop_error ? 3 : 0); } @@ -1782,293 +1337,6 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const gsr_audio_devices return requested_audio_inputs; } -static bool is_hex_num(char c) { - return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); -} - -static bool contains_non_hex_number(const char *str) { - bool hex_start = false; - size_t len = strlen(str); - if(len >= 2 && memcmp(str, "0x", 2) == 0) { - str += 2; - len -= 2; - hex_start = true; - } - - bool is_hex = false; - for(size_t i = 0; i < len; ++i) { - char c = str[i]; - if(c == '\0') - return false; - if(!is_hex_num(c)) - return true; - if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) - is_hex = true; - } - - return is_hex && !hex_start; -} - -static void capture_source_type_from_string(const char *capture_source_str, size_t size, CaptureSource &capture_source) { - char capture_source_str_n[64]; - snprintf(capture_source_str_n, sizeof(capture_source_str_n), "%.*s", (int)size, capture_source_str); - - if(size == 7 && memcmp(capture_source_str_n, "focused", 7) == 0) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW; - } else if(size == 6 && memcmp(capture_source_str_n, "portal", 6) == 0) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_PORTAL; - } else if(size == 6 && memcmp(capture_source_str_n, "region", 6) == 0) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_REGION; - } else if(size >= 10 && memcmp(capture_source_str_n, "/dev/video", 10) == 0) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2; - } else if(sscanf(capture_source_str_n, "%dx%d+%d+%d", &capture_source.region_size.x, &capture_source.region_size.y, &capture_source.region_pos.x, &capture_source.region_pos.y) == 4) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_REGION; - capture_source.region_set = true; - } else if(contains_non_hex_number(capture_source_str_n)) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR; - } else { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; - } -} - -static bool string_to_capture_alignment(const char *str, size_t len, gsr_capture_alignment *alignment) { - if(len == 5 && memcmp(str, "start", 5) == 0) { - *alignment = GSR_CAPTURE_ALIGN_START; - return true; - } else if(len == 6 && memcmp(str, "center", 6) == 0) { - *alignment = GSR_CAPTURE_ALIGN_CENTER; - return true; - } else if(len == 3 && memcmp(str, "end", 3) == 0) { - *alignment = GSR_CAPTURE_ALIGN_END; - return true; - } else { - return false; - } -} - -static bool string_to_v4l2_pixfmt(const char *str, size_t len, gsr_capture_v4l2_pixfmt *pixfmt) { - if(len == 4 && memcmp(str, "auto", 4) == 0) { - *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO; - return true; - } else if(len == 4 && memcmp(str, "yuyv", 4) == 0) { - *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_YUYV; - return true; - } else if(len == 5 && memcmp(str, "mjpeg", 5) == 0) { - *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_MJPEG; - return true; - } else { - return false; - } -} - -static bool string_to_bool(const char *str, size_t len, bool *value) { - if(len == 4 && memcmp(str, "true", 4) == 0) { - *value = true; - return true; - } else if(len == 5 && memcmp(str, "false", 5) == 0) { - *value = false; - return true; - } else { - return false; - } -} - -struct ParseCaptureSourceOptionsUserdata { - CaptureSource *capture_source; - bool is_first_column; -}; - -static bool parse_capture_source_options_callback(const char *sub, size_t size, void *userdata) { - ParseCaptureSourceOptionsUserdata *parse_userdata = (ParseCaptureSourceOptionsUserdata*)userdata; - CaptureSource &capture_source = *parse_userdata->capture_source; - if(size == 0) - return true; - - if(parse_userdata->is_first_column) { - parse_userdata->is_first_column = false; - return true; - } - - if(gsr_string_starts_with(sub, size, "x=")) { - capture_source.pos.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; - sub += 2; - size -= 2; - if(!gsr_string_to_int(sub, size, &capture_source.pos.x)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option x: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "y=")) { - capture_source.pos.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; - sub += 2; - size -= 2; - if(!gsr_string_to_int(sub, size, &capture_source.pos.y)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option y: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - - } else if(gsr_string_starts_with(sub, size, "width=")) { - capture_source.size.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; - sub += 6; - size -= 6; - if(!gsr_string_to_int(sub, size, &capture_source.size.x)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option width: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "height=")) { - capture_source.size.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; - sub += 7; - size -= 7; - if(!gsr_string_to_int(sub, size, &capture_source.size.y)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option height: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "halign=")) { - sub += 7; - size -= 7; - if(!string_to_capture_alignment(sub, size, &capture_source.halign)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option halign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "valign=")) { - sub += 7; - size -= 7; - if(!string_to_capture_alignment(sub, size, &capture_source.valign)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option valign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "pixfmt=")) { - sub += 7; - size -= 7; - if(!string_to_v4l2_pixfmt(sub, size, &capture_source.v4l2_pixfmt)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid v4l2 pixfmt value for option pixfmt: \"%.*s\", expected a \"auto\", \"yuyv\" or \"mjpeg\"", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "hflip=")) { - sub += 6; - size -= 6; - bool hflip = false; - if(!string_to_bool(sub, size, &hflip)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option hflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub); - _exit(1); - } - - if(hflip) - capture_source.flip |= GSR_FLIP_HORIZONTAL; - } else if(gsr_string_starts_with(sub, size, "vflip=")) { - sub += 6; - size -= 6; - bool vflip = false; - if(!string_to_bool(sub, size, &vflip)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option vflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub); - _exit(1); - } - - if(vflip) - capture_source.flip |= GSR_FLIP_VERTICAL; - } else if(gsr_string_starts_with(sub, size, "camera_fps=")) { - sub += 11; - size -= 11; - if(!gsr_string_to_int(sub, size, &capture_source.camera_fps)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_fps: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "camera_width=")) { - sub += 13; - size -= 13; - if(!gsr_string_to_int(sub, size, &capture_source.camera_resolution.x)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_width: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else if(gsr_string_starts_with(sub, size, "camera_height=")) { - sub += 14; - size -= 14; - if(!gsr_string_to_int(sub, size, &capture_source.camera_resolution.y)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_height: \"%.*s\", expected a number", (int)size, sub); - _exit(1); - } - } else { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target option \"%.*s\", expected x, y, width, height, halign, valign, pixfmt, hflip, vflip, camera_fps, camera_width or camera_height", (int)size, sub); - _exit(1); - } - - return true; -} - -static void parse_capture_source_options(const std::string &capture_source_str, CaptureSource &capture_source) { - ParseCaptureSourceOptionsUserdata userdata; - userdata.capture_source = &capture_source; - userdata.is_first_column = true; - gsr_string_split(capture_source_str.c_str(), ';', parse_capture_source_options_callback, &userdata); -} - -struct ParseCaptureSourceArgUserdata { - std::vector<CaptureSource> *requested_capture_sources; - const args_parser *arg_parser; - bool has_multiple_capture_sources; -}; - -static bool parse_capture_source_arg_callback(const char *sub, size_t size, void *userdata) { - ParseCaptureSourceArgUserdata *parse_userdata = (ParseCaptureSourceArgUserdata*)userdata; - if(size == 0) - return true; - - const char *substr_start = sub; - size_t capture_source_size = size; - const char *capture_source_end = (const char*)memchr(sub, ';', size); - if(capture_source_end) - capture_source_size = capture_source_end - sub; - - CaptureSource capture_source; - capture_source.region_pos = parse_userdata->arg_parser->settings.region_position; - capture_source.region_size = parse_userdata->arg_parser->settings.region_size; - - if(gsr_string_starts_with(sub, capture_source_size, "monitor:")) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR; - sub += 8; - capture_source_size -= 8; - } else if(gsr_string_starts_with(sub, capture_source_size, "window:")) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; - sub += 7; - capture_source_size -= 7; - } else if(gsr_string_starts_with(sub, capture_source_size, "v4l2:")) { - capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2; - sub += 5; - capture_source_size -= 5; - } else { - capture_source_type_from_string(sub, capture_source_size, capture_source); - } - - capture_source.name.assign(sub, capture_source_size); - - if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_WINDOW) { - if(!gsr_string_to_int64(capture_source.name.c_str(), capture_source.name.size(), &capture_source.window_id)) { - gsr_log(GSR_LOG_LEVEL_ERROR, "invalid window number %s", capture_source.name.c_str()); - args_parser_print_usage(); - _exit(1); - } - } - - if(parse_userdata->has_multiple_capture_sources) { - capture_source.halign = GSR_CAPTURE_ALIGN_START; - capture_source.valign = GSR_CAPTURE_ALIGN_START; - capture_source.pos = {0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS}; - } - - parse_capture_source_options(std::string(substr_start, size), capture_source); - parse_userdata->requested_capture_sources->push_back(capture_source); - return true; -} - -static std::vector<CaptureSource> parse_capture_source_arg(const char *capture_source_arg, const args_parser &arg_parser) { - std::vector<CaptureSource> requested_capture_sources; - ParseCaptureSourceArgUserdata userdata; - userdata.requested_capture_sources = &requested_capture_sources; - userdata.arg_parser = &arg_parser; - userdata.has_multiple_capture_sources = strchr(capture_source_arg, '|') != nullptr; - gsr_string_split(capture_source_arg, '|', parse_capture_source_arg_callback, &userdata); - return requested_capture_sources; -} - static bool audio_inputs_has_app_audio(const std::vector<AudioInput> &audio_inputs) { for(const auto &audio_input : audio_inputs) { if(audio_input.type == AudioInputType::APPLICATION) @@ -2278,54 +1546,22 @@ static void set_display_server_environment_variables() { } } -static bool is_capturing_damage_tracked_target(const std::vector<CaptureSource> &capture_sources) { - for(const CaptureSource &capture_source : capture_sources) { - if(capture_source.type != GSR_CAPTURE_SOURCE_TYPE_V4L2) - return true; - } - return false; -} - -static bool is_capturing_type(const std::vector<CaptureSource> &capture_sources, CaptureSourceType target_type) { - for(const CaptureSource &capture_source : capture_sources) { - if(capture_source.type == target_type) - return true; - } - return false; -} - -static bool has_capture_source_with_region_set(const std::vector<CaptureSource> &capture_sources) { - for(const CaptureSource &capture_source : capture_sources) { - if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION && capture_source.region_set) - return true; - } - return false; -} - -static bool is_capturing_monitor_or_region(const std::vector<CaptureSource> &capture_sources) { - for(const CaptureSource &capture_source : capture_sources) { - if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_MONITOR || capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION) - return true; - } - return false; -} - -static void validate_args_with_capture_sources(args_parser &arg_parser, const std::vector<CaptureSource> &capture_sources) { +static void validate_args_with_capture_sources(args_parser &arg_parser, const gsr_capture_sources *capture_sources) { const Arg *output_resolution_arg = args_parser_get_arg(&arg_parser, "-s"); assert(output_resolution_arg); const Arg *region_arg = args_parser_get_arg(&arg_parser, "-region"); assert(region_arg); - if(is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) && output_resolution_arg->num_values == 0) { + if(gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) && output_resolution_arg->num_values == 0) { gsr_log(GSR_LOG_LEVEL_ERROR, "option -s is required when using '-w focused' option"); args_parser_print_usage(); _exit(1); } - const bool is_capturing_region = is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_REGION); + const bool is_capturing_region = gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_REGION); if(region_arg->num_values == 0) { - if(is_capturing_region && !has_capture_source_with_region_set(capture_sources)) { + if(is_capturing_region && !gsr_capture_sources_has_region_set(capture_sources)) { gsr_log(GSR_LOG_LEVEL_ERROR, "option -region is required when '-w region' is used"); args_parser_print_usage(); _exit(1); @@ -2340,7 +1576,7 @@ static void validate_args_with_capture_sources(args_parser &arg_parser, const st } } - if(!arg_parser.settings.restore_portal_session && is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) + if(!arg_parser.settings.restore_portal_session && gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) gsr_log(GSR_LOG_LEVEL_INFO, "option '-w portal' was used without '-restore-portal-session yes'. The previous screencast session will be ignored"); } @@ -2453,8 +1689,13 @@ int main(int argc, char **argv) { setenv("AMD_DEBUG", "lowlatencyenc", true); } - std::vector<CaptureSource> capture_sources = parse_capture_source_arg(arg_parser.settings.capture_source, arg_parser); - if(capture_sources.empty()) { + gsr_capture_sources capture_sources_data; + const int parse_capture_sources_result = gsr_capture_sources_parse(&capture_sources_data, arg_parser.settings.capture_source, arg_parser.settings.region_position, arg_parser.settings.region_size); + if(parse_capture_sources_result != GSR_ERROR_OK) + _exit(gsr_error_to_exit_code(parse_capture_sources_result)); + + gsr_capture_sources *capture_sources = &capture_sources_data; + if(capture_sources->num_items == 0) { gsr_log(GSR_LOG_LEVEL_ERROR, "option -w can't be empty. You need to capture video from at least one source"); args_parser_print_usage(); _exit(1); @@ -2507,7 +1748,7 @@ int main(int argc, char **argv) { gsr_windowing windowing; gsr_windowing_params windowing_params; - windowing_params.monitor_capture = is_capturing_monitor_or_region(capture_sources); + windowing_params.monitor_capture = gsr_capture_sources_has_monitor_or_region(capture_sources); windowing_params.gl_debug = arg_parser.settings.gl_debug; windowing_params.listen_to_x11_events = true; if(gsr_windowing_init(&windowing, &windowing_params) != GSR_ERROR_OK) @@ -2516,7 +1757,7 @@ int main(int argc, char **argv) { Display *dpy = windowing.display; gsr_window *window = windowing.window; - if(is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) { + if(gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) { if(gsr_windowing_is_using_prime_run()) { gsr_log(GSR_LOG_LEVEL_WARNING, "use of prime-run with -w portal option is currently not supported. Disabling prime-run"); gsr_windowing_disable_prime_run(); @@ -2546,12 +1787,7 @@ int main(int argc, char **argv) { _exit(2); } - memset(&x11_cursor, 0, sizeof(x11_cursor)); - x11_cursor_display = NULL; - if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11 && arg_parser.settings.record_cursor) { - x11_cursor_display = (Display*)gsr_window_get_display(egl.window); - gsr_cursor_init(&x11_cursor, &egl, x11_cursor_display); - } + gsr_capture_deps_init_cursor(&capture_deps, &egl, arg_parser.settings.record_cursor); // if(wayland && arg_parser.capture_source_type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) { // fprintf(stderr, "gsr warning: it's not possible to sync video to recorded monitor exactly on wayland when recording a monitor." @@ -2604,7 +1840,12 @@ int main(int argc, char **argv) { arg_parser.settings.audio_codec = select_audio_codec_with_fallback(arg_parser.settings.audio_codec, file_extension.c_str(), uses_amix); vec2i video_size = {0, 0}; - std::vector<VideoSource> video_sources = create_video_sources(arg_parser, &egl, false, capture_sources, video_size); + gsr_video_sources video_sources_data; + const int video_sources_result = gsr_video_sources_create(&video_sources_data, &arg_parser.settings, &egl, &capture_deps, false, capture_sources, &video_size); + if(video_sources_result != GSR_ERROR_OK) + _exit(gsr_error_to_exit_code(video_sources_result)); + + gsr_video_sources *video_sources = &video_sources_data; // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -2668,7 +1909,7 @@ int main(int argc, char **argv) { video_size.x = video_codec_context->width; video_size.y = video_codec_context->height; - video_sources_update_with_real_video_size(capture_sources, video_sources, video_size); + gsr_video_sources_update_with_real_video_size(video_sources, video_size); const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p"); assert(plugin_arg); @@ -2683,7 +1924,7 @@ int main(int argc, char **argv) { memset(&color_conversion_params, 0, sizeof(color_conversion_params)); color_conversion_params.color_range = arg_parser.settings.color_range; color_conversion_params.egl = &egl; - color_conversion_params.load_external_image_shader = any_video_sources_uses_external_image(video_sources); + color_conversion_params.load_external_image_shader = gsr_video_sources_uses_external_image(video_sources); gsr_video_encoder_get_textures(video_encoder, color_conversion_params.destination_textures, color_conversion_params.destination_textures_size, &color_conversion_params.num_destination_textures, &color_conversion_params.destination_color); gsr_color_conversion color_conversion; @@ -3044,43 +2285,45 @@ int main(int argc, char **argv) { bool use_damage_tracking = false; gsr_damage damage; memset(&damage, 0, sizeof(damage)); - if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && is_capturing_damage_tracked_target(capture_sources)) { + if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && gsr_capture_sources_has_damage_tracked_target(capture_sources)) { if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11) { - gsr_damage_init(&damage, &egl, &x11_cursor, arg_parser.settings.record_cursor); + gsr_damage_init(&damage, &egl, &capture_deps.x11_cursor, arg_parser.settings.record_cursor); use_damage_tracking = true; - for(const CaptureSource &capture_source : capture_sources) { - switch(capture_source.type) { + for(size_t i = 0; i < capture_sources->num_items; ++i) { + const gsr_capture_source *capture_source = &capture_sources->items[i]; + switch(capture_source->type) { case GSR_CAPTURE_SOURCE_TYPE_WINDOW: - gsr_damage_start_tracking_window(&damage, capture_source.window_id); + gsr_damage_start_tracking_window(&damage, capture_source->window_id); break; case GSR_CAPTURE_SOURCE_TYPE_MONITOR: case GSR_CAPTURE_SOURCE_TYPE_REGION: // TODO: When capturing a region only track damage in that region - gsr_damage_start_tracking_monitor(&damage, capture_source.name.c_str()); + gsr_damage_start_tracking_monitor(&damage, capture_source->name); break; default: break; } } - } else if(is_capturing_monitor_or_region(capture_sources)) { + } else if(gsr_capture_sources_has_monitor_or_region(capture_sources)) { gsr_log(GSR_LOG_LEVEL_WARNING, "\"-fm content\" has no effect on Wayland when recording a monitor. Either record a monitor on X11 or capture with desktop portal instead (-w portal)"); } } while(running) { while(gsr_window_process_event(window)) { - if(x11_cursor_display && arg_parser.settings.record_cursor) - gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window)); + if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor) + gsr_cursor_on_event(&capture_deps.x11_cursor, gsr_window_get_event_data(window)); gsr_damage_on_event(&damage, gsr_window_get_event_data(window)); - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; gsr_capture_on_event(video_source.capture, &egl); } } - if(x11_cursor_display && arg_parser.settings.record_cursor) - gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display)); + if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor) + gsr_cursor_tick(&capture_deps.x11_cursor, DefaultRootWindow(capture_deps.x11_cursor_display)); gsr_damage_tick(&damage); @@ -3090,7 +2333,8 @@ int main(int argc, char **argv) { if(use_damage_tracking) damaged = gsr_damage_is_damaged(&damage); - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; gsr_capture_tick(video_source.capture); if(gsr_capture_should_stop(video_source.capture, &should_stop_error)) { @@ -3149,15 +2393,13 @@ int main(int argc, char **argv) { gsr_damage_clear(&damage); gsr_plugins_clear_damage(&plugins); - gsr_capture_kms_cleanup_kms_fds(); + gsr_capture_deps_cleanup_kms_fds(&capture_deps); - if(kms_client_initialized) { - if(gsr_kms_client_get_kms(&kms_client, &kms_response) != 0) - gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", kms_response.result, kms_response.err_msg); - } + gsr_capture_deps_update_kms(&capture_deps); bool capture_has_synchronous_task = false; - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; if(video_source.capture->clear_damage) video_source.capture->clear_damage(video_source.capture); @@ -3170,7 +2412,8 @@ int main(int argc, char **argv) { } } - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; if(video_source.capture->pre_capture) video_source.capture->pre_capture(video_source.capture, &video_source.metadata, output_color_conversion); } @@ -3180,11 +2423,12 @@ int main(int argc, char **argv) { gsr_color_conversion_clear(output_color_conversion); } - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; gsr_capture_capture(video_source.capture, &video_source.metadata, output_color_conversion); } - gsr_capture_kms_cleanup_kms_fds(); + gsr_capture_deps_cleanup_kms_fds(&capture_deps); if(plugins.num_plugins > 0) { gsr_plugins_draw(&plugins); @@ -3202,7 +2446,8 @@ int main(int argc, char **argv) { gsr_egl_swap_buffers(&egl); gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion); - for(VideoSource &video_source : video_sources) { + for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) { + gsr_video_source &video_source = video_sources->items[video_source_index]; if(hdr && !hdr_metadata_set && !arg_parser.settings.is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream)) hdr_metadata_set = true; } @@ -3421,23 +2666,15 @@ int main(int argc, char **argv) { avformat_free_context(av_format_context); } - gsr_cursor_deinit(&x11_cursor); gsr_damage_deinit(&damage); gsr_color_conversion_deinit(&color_conversion); gsr_video_encoder_destroy(video_encoder, video_codec_context); gsr_encoder_deinit(&encoder); - for(VideoSource &video_source : video_sources) { - gsr_capture_destroy(video_source.capture); - } + gsr_video_sources_deinit(video_sources); #ifdef GSR_APP_AUDIO gsr_pipewire_audio_deinit(&pipewire_audio); #endif - if(kms_client_initialized) { - gsr_capture_kms_cleanup_kms_fds(); - gsr_kms_client_deinit(&kms_client); - } - - gsr_kde_night_light_destroy(kde_night_light); + gsr_capture_deps_deinit(&capture_deps); if(!arg_parser.settings.is_replaying && arg_parser.settings.recording_saved_script) run_recording_saved_script_async(arg_parser.settings.recording_saved_script, arg_parser.settings.filename, "regular"); diff --git a/src/recorder/capture_setup.c b/src/recorder/capture_setup.c new file mode 100644 index 0000000..93dbe37 --- /dev/null +++ b/src/recorder/capture_setup.c @@ -0,0 +1,543 @@ +#include "../../include/recorder/capture_setup.h" +#include "../../include/recorder/error.h" +#include "../../include/recorder/windowing.h" +#include "../../include/capture/nvfbc.h" +#include "../../include/capture/xcomposite.h" +#include "../../include/capture/ximage.h" +#include "../../include/capture/kms.h" +#include "../../include/capture/v4l2.h" +#ifdef GSR_PORTAL +#include "../../include/capture/portal.h" +#endif +#include "../../include/args_parser.h" +#include "../../include/utils.h" +#include "../../include/window/window.h" +#include "../../include/log.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +typedef struct { + gsr_window *window; +} monitor_output_callback_userdata; + +typedef struct { + char *output_name; +} first_output_callback_userdata; + +typedef struct { + gsr_window *window; + vec2i position; + char *output_name; + vec2i monitor_pos; + vec2i monitor_size; + double monitor_scale_inverted; +} monitor_by_position_callback_userdata; + +static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) { + const monitor_output_callback_userdata *options = userdata; + vec2i monitor_position = monitor->pos; + vec2i monitor_size = monitor->size; + if(gsr_window_get_display_server(options->window) == GSR_DISPLAY_SERVER_WAYLAND) { + gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0; + drm_monitor_get_display_server_data(options->window, monitor, &monitor_rotation, &monitor_position); + if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270) { + const int tmp = monitor_size.x; + monitor_size.x = monitor_size.y; + monitor_size.y = tmp; + } + } + fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y); +} + +static void monitor_output_callback_print_region(const gsr_monitor *monitor, void *userdata) { + (void)userdata; + const vec2i monitor_position = monitor->logical_pos; + const vec2i monitor_size = monitor->logical_size; + fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y); +} + +static void get_first_output_callback(const gsr_monitor *monitor, void *userdata) { + first_output_callback_userdata *data = userdata; + if(!data->output_name) + data->output_name = strdup(monitor->name); +} + +static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *userdata) { + monitor_by_position_callback_userdata *data = userdata; + + const vec2i monitor_position = monitor->logical_pos; + const vec2i monitor_size = monitor->size; + const vec2i monitor_logical_size = monitor->logical_size; + + if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_logical_size.x + && data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_logical_size.y) + { + data->output_name = strdup(monitor->name); + data->monitor_pos = monitor_position; + data->monitor_size = monitor_size; + data->monitor_scale_inverted = (double)monitor_size.x / (double)monitor_logical_size.x; + } +} + +void gsr_capture_deps_init(gsr_capture_deps *self) { + memset(self, 0, sizeof(*self)); +} + +void gsr_capture_deps_init_cursor(gsr_capture_deps *self, gsr_egl *egl, bool record_cursor) { + if(gsr_window_get_display_server(egl->window) != GSR_DISPLAY_SERVER_X11 || !record_cursor) + return; + + self->x11_cursor_display = (Display*)gsr_window_get_display(egl->window); + gsr_cursor_init(&self->x11_cursor, egl, self->x11_cursor_display); +} + +void gsr_capture_deps_deinit(gsr_capture_deps *self) { + gsr_cursor_deinit(&self->x11_cursor); + self->x11_cursor_display = NULL; + + if(self->kms_client_initialized) { + gsr_capture_deps_cleanup_kms_fds(self); + gsr_kms_client_deinit(&self->kms_client); + self->kms_client_initialized = false; + } + + gsr_kde_night_light_destroy(self->kde_night_light); + self->kde_night_light = NULL; + self->kde_night_light_initialized = false; +} + +void gsr_capture_deps_cleanup_kms_fds(gsr_capture_deps *self) { + for(int i = 0; i < self->kms_response.num_items; ++i) { + for(int j = 0; j < self->kms_response.items[i].num_dma_bufs; ++j) { + gsr_kms_response_dma_buf *dma_buf = &self->kms_response.items[i].dma_buf[j]; + if(dma_buf->fd > 0) { + close(dma_buf->fd); + dma_buf->fd = 0; + } + } + self->kms_response.items[i].num_dma_bufs = 0; + } + self->kms_response.num_items = 0; +} + +void gsr_capture_deps_update_kms(gsr_capture_deps *self) { + if(!self->kms_client_initialized) + return; + + if(gsr_kms_client_get_kms(&self->kms_client, &self->kms_response) != 0) + gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", self->kms_response.result, self->kms_response.err_msg); +} + +static int validate_monitor_get_valid(const gsr_egl *egl, const char *window, char *output_name, size_t output_name_size) { + const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + const bool capture_use_drm = monitor_capture_use_drm(egl->window, egl->gpu_info.vendor); + + snprintf(output_name, output_name_size, "%s", window); + if(strcmp(output_name, "screen") == 0) { + first_output_callback_userdata data; + data.output_name = NULL; + for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_first_output_callback, &data); + + if(data.output_name) { + snprintf(output_name, output_name_size, "%s", data.output_name); + free(data.output_name); + } else { + gsr_log(GSR_LOG_LEVEL_ERROR, "no usable output found"); + return GSR_ERROR_MONITOR_NOT_FOUND; + } + } else if(capture_use_drm || (strcmp(output_name, "screen-direct") != 0 && strcmp(output_name, "screen-direct-force") != 0)) { + gsr_monitor gmon; + if(!get_monitor_by_name(egl, connection_type, output_name, &gmon)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "display \"%s\" not found, expected one of:", output_name); + fprintf(stderr, " \"screen\"\n"); + if(!capture_use_drm) + fprintf(stderr, " \"screen-direct\"\n"); + + monitor_output_callback_userdata userdata; + userdata.window = egl->window; + for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata); + return GSR_ERROR_MONITOR_NOT_FOUND; + } + } + + return GSR_ERROR_OK; +} + +static bool get_monitor_by_region_center(const gsr_egl *egl, vec2i region_position, vec2i region_size, char *output_name, size_t output_name_size, vec2i *monitor_pos, vec2i *monitor_size, double *monitor_scale_inverted) { + const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND; + + monitor_by_position_callback_userdata data; + data.window = egl->window; + data.position = (vec2i){ region_position.x + region_size.x / 2, region_position.y + region_size.y / 2 }; + data.output_name = NULL; + data.monitor_pos = (vec2i){0, 0}; + data.monitor_size = (vec2i){0, 0}; + data.monitor_scale_inverted = 1.0; + for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_monitor_by_position_callback, &data); + + output_name[0] = '\0'; + if(data.output_name) { + snprintf(output_name, output_name_size, "%s", data.output_name); + free(data.output_name); + } + *monitor_pos = data.monitor_pos; + *monitor_size = data.monitor_size; + *monitor_scale_inverted = data.monitor_scale_inverted; + return output_name[0] != '\0'; +} + +static int region_get_data(gsr_egl *egl, vec2i *region_size, vec2i *region_position, char *output_name, size_t output_name_size) { + vec2i monitor_pos = {0, 0}; + vec2i monitor_size = {0, 0}; + double monitor_scale_inverted = 1.0; + if(!get_monitor_by_region_center(egl, *region_position, *region_size, output_name, output_name_size, &monitor_pos, &monitor_size, &monitor_scale_inverted)) { + const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND; + gsr_log(GSR_LOG_LEVEL_ERROR, "the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:", region_size->x, region_size->y, region_position->x, region_position->y); + for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print_region, NULL); + return GSR_ERROR_MONITOR_NOT_FOUND; + } + + /* Capture whole monitor when region size is set to 0x0 */ + if(region_size->x == 0 && region_size->y == 0) { + region_position->x = 0; + region_position->y = 0; + } else { + region_position->x -= monitor_pos.x; + region_position->y -= monitor_pos.y; + /* Match drm plane coordinate space (1x scaling) to wayland coordinate space (which may have scaling set by user) */ + region_position->x *= monitor_scale_inverted; + region_position->y *= monitor_scale_inverted; + + region_size->x *= monitor_scale_inverted; + region_size->y *= monitor_scale_inverted; + } + + return GSR_ERROR_OK; +} + +static gsr_capture* create_monitor_capture(const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, const gsr_capture_source *capture_source, bool prefer_ximage, int *error) { + *error = GSR_ERROR_OK; + + if(gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11 && prefer_ximage) { + gsr_capture_ximage_params ximage_params; + memset(&ximage_params, 0, sizeof(ximage_params)); + ximage_params.egl = egl; + ximage_params.cursor = &deps->x11_cursor; + ximage_params.display_to_capture = capture_source->name; + ximage_params.record_cursor = settings->record_cursor; + ximage_params.output_resolution = settings->output_resolution; + ximage_params.region_size = capture_source->region_size; + ximage_params.region_position = capture_source->region_pos; + return gsr_capture_ximage_create(&ximage_params); + } + + if(monitor_capture_use_drm(egl->window, egl->gpu_info.vendor)) { + if(!deps->kms_client_initialized) { + deps->kms_client_initialized = true; + const int kms_init_res = gsr_kms_client_init(&deps->kms_client, egl->card_path); + if(kms_init_res != 0) { + *error = kms_init_res < 0 ? GSR_ERROR_GENERIC : -kms_init_res; + return NULL; + } + } + + if(!deps->kde_night_light_initialized && gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND) { + deps->kde_night_light_initialized = true; + deps->kde_night_light = gsr_kde_night_light_create(); + } + + gsr_capture_kms_params kms_params; + memset(&kms_params, 0, sizeof(kms_params)); + kms_params.egl = egl; + kms_params.x11_cursor = &deps->x11_cursor; + kms_params.kms_response = &deps->kms_response; + kms_params.kde_night_light = deps->kde_night_light; + kms_params.display_to_capture = capture_source->name; + kms_params.record_cursor = settings->record_cursor; + kms_params.hdr = video_codec_is_hdr(settings->video_codec); + kms_params.fps = settings->fps; + kms_params.output_resolution = settings->output_resolution; + kms_params.region_size = capture_source->region_size; + kms_params.region_position = capture_source->region_pos; + return gsr_capture_kms_create(&kms_params); + } else { + const char *capture_source_real = capture_source->name; + const bool direct_capture = strcmp(capture_source->name, "screen-direct") == 0 || strcmp(capture_source->name, "screen-direct-force") == 0; + if(direct_capture) { + capture_source_real = "screen"; + gsr_log(GSR_LOG_LEVEL_WARNING, "%s capture option is not recommended unless you use G-SYNC as Nvidia has driver issues that can cause your system or games to freeze/crash.", capture_source->name); + } + + gsr_capture_nvfbc_params nvfbc_params; + memset(&nvfbc_params, 0, sizeof(nvfbc_params)); + nvfbc_params.egl = egl; + nvfbc_params.display_to_capture = capture_source_real; + nvfbc_params.fps = settings->fps; + nvfbc_params.direct_capture = direct_capture; + nvfbc_params.record_cursor = settings->record_cursor; + nvfbc_params.output_resolution = settings->output_resolution; + nvfbc_params.region_size = capture_source->region_size; + nvfbc_params.region_position = capture_source->region_pos; + return gsr_capture_nvfbc_create(&nvfbc_params); + } +} + +static gsr_capture* create_capture_impl(const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, gsr_capture_source *capture_source, bool prefer_ximage, int *error) { + bool follow_focused = false; + const bool wayland = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND; + + *error = GSR_ERROR_OK; + gsr_capture *capture = NULL; + if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) { + if(wayland) { + gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland"); + *error = GSR_ERROR_UNSUPPORTED; + return NULL; + } + + if(settings->output_resolution.x <= 0 || settings->output_resolution.y <= 0) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0", settings->output_resolution.x, settings->output_resolution.y); + args_parser_print_usage(); + *error = GSR_ERROR_GENERIC; + return NULL; + } + + follow_focused = true; + } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_PORTAL) { +#ifdef GSR_PORTAL + /* Desktop portal capture on x11 doesn't seem to be hardware accelerated */ + if(!wayland) { + gsr_log(GSR_LOG_LEVEL_ERROR, "desktop portal capture is not supported on X11"); + *error = GSR_ERROR_GENERIC; + return NULL; + } + + gsr_capture_portal_params portal_params; + memset(&portal_params, 0, sizeof(portal_params)); + portal_params.egl = egl; + portal_params.record_cursor = settings->record_cursor; + portal_params.restore_portal_session = settings->restore_portal_session; + portal_params.portal_session_token_filepath = settings->portal_session_token_filepath; + portal_params.output_resolution = settings->output_resolution; + capture = gsr_capture_portal_create(&portal_params); + if(!capture) { + *error = GSR_ERROR_GENERIC; + return NULL; + } +#else + gsr_log(GSR_LOG_LEVEL_ERROR, "option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support. Please recompile GPU Screen recorder with the -Dportal=true option"); + *error = GSR_ERROR_UNSUPPORTED; + return NULL; +#endif + } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_REGION) { + const int region_result = region_get_data(egl, &capture_source->region_size, &capture_source->region_pos, capture_source->name, sizeof(capture_source->name)); + if(region_result != GSR_ERROR_OK) { + *error = region_result; + return NULL; + } + + capture = create_monitor_capture(settings, egl, deps, capture_source, prefer_ximage, error); + if(!capture) { + if(*error == GSR_ERROR_OK) + *error = GSR_ERROR_GENERIC; + return NULL; + } + } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) { + char monitor_name[GSR_CAPTURE_SOURCE_NAME_MAX_SIZE]; + const int monitor_result = validate_monitor_get_valid(egl, capture_source->name, monitor_name, sizeof(monitor_name)); + if(monitor_result != GSR_ERROR_OK) { + *error = monitor_result; + return NULL; + } + snprintf(capture_source->name, sizeof(capture_source->name), "%s", monitor_name); + + capture = create_monitor_capture(settings, egl, deps, capture_source, prefer_ximage, error); + if(!capture) { + if(*error == GSR_ERROR_OK) + *error = GSR_ERROR_GENERIC; + return NULL; + } + } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_V4L2) { + gsr_capture_v4l2_params v4l2_params; + memset(&v4l2_params, 0, sizeof(v4l2_params)); + v4l2_params.egl = egl; + v4l2_params.output_resolution = settings->output_resolution; + v4l2_params.device_path = capture_source->name; + v4l2_params.pixfmt = capture_source->v4l2_pixfmt; + v4l2_params.camera_fps = capture_source->camera_fps; + v4l2_params.camera_resolution.width = capture_source->camera_resolution.x; + v4l2_params.camera_resolution.height = capture_source->camera_resolution.y; + capture = gsr_capture_v4l2_create(&v4l2_params); + if(!capture) { + *error = GSR_ERROR_GENERIC; + return NULL; + } + } else { + if(wayland) { + gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland or use -w portal option which supports window capture if your wayland compositor supports window capture"); + *error = GSR_ERROR_UNSUPPORTED; + return NULL; + } + } + + if(!capture) { + gsr_capture_xcomposite_params xcomposite_params; + memset(&xcomposite_params, 0, sizeof(xcomposite_params)); + xcomposite_params.egl = egl; + xcomposite_params.cursor = &deps->x11_cursor; + xcomposite_params.window = capture_source->window_id; + xcomposite_params.follow_focused = follow_focused; + xcomposite_params.record_cursor = settings->record_cursor; + xcomposite_params.output_resolution = settings->output_resolution; + capture = gsr_capture_xcomposite_create(&xcomposite_params); + if(!capture) { + *error = GSR_ERROR_GENERIC; + return NULL; + } + } + + return capture; +} + +int gsr_video_sources_create(gsr_video_sources *self, const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, bool prefer_ximage, gsr_capture_sources *capture_sources, vec2i *video_size) { + memset(self, 0, sizeof(*self)); + if(capture_sources->num_items == 0) + return GSR_ERROR_GENERIC; + + self->items = calloc(capture_sources->num_items, sizeof(gsr_video_source)); + if(!self->items) { + gsr_log(GSR_LOG_LEVEL_ERROR, "failed to allocate video sources"); + return GSR_ERROR_GENERIC; + } + + for(size_t i = 0; i < capture_sources->num_items; ++i) { + gsr_capture_source *capture_source = &capture_sources->items[i]; + gsr_video_source *video_source = &self->items[i]; + + memset(&video_source->metadata, 0, sizeof(video_source->metadata)); + video_source->metadata.fps = settings->fps; + video_source->metadata.halign = capture_source->halign; + video_source->metadata.valign = capture_source->valign; + video_source->metadata.flip = (gsr_flip)capture_source->flip; + video_source->capture_source = capture_source; + + int error = GSR_ERROR_OK; + video_source->capture = create_capture_impl(settings, egl, deps, capture_source, prefer_ximage, &error); + if(!video_source->capture) { + gsr_video_sources_deinit(self); + return error; + } + + ++self->num_items; + } + + for(size_t i = 0; i < self->num_items; ++i) { + const int capture_result = gsr_capture_start(self->items[i].capture, &self->items[i].metadata); + if(capture_result != 0) { + gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_capture_start failed"); + gsr_video_sources_deinit(self); + return -capture_result; + } + } + + vec2i start_pos = {99999, 99999}; + vec2i end_pos = {-99999, -99999}; + for(size_t i = 0; i < self->num_items; ++i) { + const gsr_video_source *video_source = &self->items[i]; + /* TODO: Skip scalar positions for now, but this should be handled in a better way. + Maybe handle scalars at the next loop by multiplying video size by the scalar. */ + if(video_source->capture_source->pos.x_type == VVEC2I_TYPE_SCALAR || video_source->capture_source->pos.y_type == VVEC2I_TYPE_SCALAR + || (video_source->capture_source->size.x_type == VVEC2I_TYPE_SCALAR && video_source->capture_source->size.x != 100) + || (video_source->capture_source->size.y_type == VVEC2I_TYPE_SCALAR && video_source->capture_source->size.y != 100)) + { + continue; + } + const vec2i video_source_start_pos = {video_source->capture_source->pos.x, video_source->capture_source->pos.y}; + const vec2i video_source_end_pos = {video_source_start_pos.x + video_source->metadata.video_size.x, video_source_start_pos.y + video_source->metadata.video_size.y}; + + if(video_source_start_pos.x < start_pos.x) + start_pos.x = video_source_start_pos.x; + if(video_source_start_pos.y < start_pos.y) + start_pos.y = video_source_start_pos.y; + + if(video_source_end_pos.x > end_pos.x) + end_pos.x = video_source_end_pos.x; + if(video_source_end_pos.y > end_pos.y) + end_pos.y = video_source_end_pos.y; + } + + video_size->x = end_pos.x - start_pos.x; + video_size->y = end_pos.y - start_pos.y; + if(video_size->x < 0) + video_size->x = 0; + if(video_size->y < 0) + video_size->y = 0; + + for(size_t i = 0; i < self->num_items; ++i) { + self->items[i].metadata.video_size = *video_size; + } + + return GSR_ERROR_OK; +} + +void gsr_video_sources_update_with_real_video_size(gsr_video_sources *self, vec2i video_size) { + for(size_t i = 0; i < self->num_items; ++i) { + gsr_video_source *video_source = &self->items[i]; + const gsr_capture_source *capture_source = video_source->capture_source; + + video_source->metadata.recording_size = video_source->metadata.video_size; + /* TODO: What if this updated resolution is above max resolution? */ + video_source->metadata.video_size = video_size; + + if(capture_source->pos.x != 0 || capture_source->pos.y != 0) { + video_source->metadata.position.x = capture_source->pos.x; + video_source->metadata.position.y = capture_source->pos.y; + + if(capture_source->pos.x_type == VVEC2I_TYPE_SCALAR) + video_source->metadata.position.x = video_source->metadata.video_size.x * ((double)video_source->metadata.position.x / 100.0); + + if(capture_source->pos.y_type == VVEC2I_TYPE_SCALAR) + video_source->metadata.position.y = video_source->metadata.video_size.y * ((double)video_source->metadata.position.y / 100.0); + } + + if(capture_source->size.x != 0 || capture_source->size.y != 0) { + video_source->metadata.recording_size.x = capture_source->size.x; + video_source->metadata.recording_size.y = capture_source->size.y; + + if(capture_source->size.x_type == VVEC2I_TYPE_SCALAR) + video_source->metadata.recording_size.x = video_source->metadata.video_size.x * ((double)video_source->metadata.recording_size.x / 100.0); + + if(capture_source->size.y_type == VVEC2I_TYPE_SCALAR) + video_source->metadata.recording_size.y = video_source->metadata.video_size.y * ((double)video_source->metadata.recording_size.y / 100.0); + } + } +} + +bool gsr_video_sources_uses_external_image(const gsr_video_sources *self) { + for(size_t i = 0; i < self->num_items; ++i) { + if(gsr_capture_uses_external_image(self->items[i].capture)) + return true; + } + return false; +} + +void gsr_video_sources_deinit(gsr_video_sources *self) { + for(size_t i = 0; i < self->num_items; ++i) { + if(self->items[i].capture) { + gsr_capture_destroy(self->items[i].capture); + self->items[i].capture = NULL; + } + } + + if(self->items) { + free(self->items); + self->items = NULL; + } + self->num_items = 0; +} diff --git a/src/recorder/capture_source.c b/src/recorder/capture_source.c new file mode 100644 index 0000000..5407abc --- /dev/null +++ b/src/recorder/capture_source.c @@ -0,0 +1,390 @@ +#include "../../include/recorder/capture_source.h" +#include "../../include/recorder/error.h" +#include "../../include/utils.h" +#include "../../include/log.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +typedef struct { + gsr_capture_source *capture_source; + bool is_first_column; + int error; +} parse_capture_source_options_userdata; + +typedef struct { + gsr_capture_sources *capture_sources; + vec2i region_position; + vec2i region_size; + bool has_multiple_capture_sources; + int error; +} parse_capture_source_arg_userdata; + +void gsr_capture_source_init(gsr_capture_source *self, vec2i region_position, vec2i region_size) { + memset(self, 0, sizeof(*self)); + self->type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; + self->halign = GSR_CAPTURE_ALIGN_CENTER; + self->valign = GSR_CAPTURE_ALIGN_CENTER; + self->v4l2_pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO; + self->flip = GSR_FLIP_NONE; + self->pos = (vvec2i){0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS}; + self->size = (vvec2i){100, 100, VVEC2I_TYPE_SCALAR, VVEC2I_TYPE_SCALAR}; + self->region_pos = region_position; + self->region_size = region_size; +} + +static bool is_hex_num(char c) { + return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); +} + +static bool contains_non_hex_number(const char *str) { + bool hex_start = false; + size_t len = strlen(str); + if(len >= 2 && memcmp(str, "0x", 2) == 0) { + str += 2; + len -= 2; + hex_start = true; + } + + bool is_hex = false; + for(size_t i = 0; i < len; ++i) { + char c = str[i]; + if(c == '\0') + return false; + if(!is_hex_num(c)) + return true; + if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + is_hex = true; + } + + return is_hex && !hex_start; +} + +static void capture_source_type_from_string(const char *capture_source_str, size_t size, gsr_capture_source *capture_source) { + char capture_source_str_n[64]; + snprintf(capture_source_str_n, sizeof(capture_source_str_n), "%.*s", (int)size, capture_source_str); + + if(size == 7 && memcmp(capture_source_str_n, "focused", 7) == 0) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW; + } else if(size == 6 && memcmp(capture_source_str_n, "portal", 6) == 0) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_PORTAL; + } else if(size == 6 && memcmp(capture_source_str_n, "region", 6) == 0) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_REGION; + } else if(size >= 10 && memcmp(capture_source_str_n, "/dev/video", 10) == 0) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_V4L2; + } else if(sscanf(capture_source_str_n, "%dx%d+%d+%d", &capture_source->region_size.x, &capture_source->region_size.y, &capture_source->region_pos.x, &capture_source->region_pos.y) == 4) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_REGION; + capture_source->region_set = true; + } else if(contains_non_hex_number(capture_source_str_n)) { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_MONITOR; + } else { + capture_source->type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; + } +} + +static bool string_to_capture_alignment(const char *str, size_t len, gsr_capture_alignment *alignment) { + if(len == 5 && memcmp(str, "start", 5) == 0) { + *alignment = GSR_CAPTURE_ALIGN_START; + return true; + } else if(len == 6 && memcmp(str, "center", 6) == 0) { + *alignment = GSR_CAPTURE_ALIGN_CENTER; + return true; + } else if(len == 3 && memcmp(str, "end", 3) == 0) { + *alignment = GSR_CAPTURE_ALIGN_END; + return true; + } else { + return false; + } +} + +static bool string_to_v4l2_pixfmt(const char *str, size_t len, gsr_capture_v4l2_pixfmt *pixfmt) { + if(len == 4 && memcmp(str, "auto", 4) == 0) { + *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO; + return true; + } else if(len == 4 && memcmp(str, "yuyv", 4) == 0) { + *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_YUYV; + return true; + } else if(len == 5 && memcmp(str, "mjpeg", 5) == 0) { + *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_MJPEG; + return true; + } else { + return false; + } +} + +static bool string_to_bool(const char *str, size_t len, bool *value) { + if(len == 4 && memcmp(str, "true", 4) == 0) { + *value = true; + return true; + } else if(len == 5 && memcmp(str, "false", 5) == 0) { + *value = false; + return true; + } else { + return false; + } +} + +static bool parse_capture_source_options_callback(const char *sub, size_t size, void *userdata) { + parse_capture_source_options_userdata *parse_userdata = userdata; + gsr_capture_source *capture_source = parse_userdata->capture_source; + if(size == 0) + return true; + + /* First column contains the capture target */ + if(parse_userdata->is_first_column) { + parse_userdata->is_first_column = false; + return true; + } + + if(gsr_string_starts_with(sub, size, "x=")) { + capture_source->pos.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; + sub += 2; + size -= 2; + if(!gsr_string_to_int(sub, size, &capture_source->pos.x)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option x: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "y=")) { + capture_source->pos.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; + sub += 2; + size -= 2; + if(!gsr_string_to_int(sub, size, &capture_source->pos.y)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option y: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "width=")) { + capture_source->size.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; + sub += 6; + size -= 6; + if(!gsr_string_to_int(sub, size, &capture_source->size.x)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option width: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "height=")) { + capture_source->size.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS; + sub += 7; + size -= 7; + if(!gsr_string_to_int(sub, size, &capture_source->size.y)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option height: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "halign=")) { + sub += 7; + size -= 7; + if(!string_to_capture_alignment(sub, size, &capture_source->halign)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option halign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "valign=")) { + sub += 7; + size -= 7; + if(!string_to_capture_alignment(sub, size, &capture_source->valign)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option valign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "pixfmt=")) { + sub += 7; + size -= 7; + if(!string_to_v4l2_pixfmt(sub, size, &capture_source->v4l2_pixfmt)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid v4l2 pixfmt value for option pixfmt: \"%.*s\", expected a \"auto\", \"yuyv\" or \"mjpeg\"", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "hflip=")) { + sub += 6; + size -= 6; + bool hflip = false; + if(!string_to_bool(sub, size, &hflip)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option hflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + + if(hflip) + capture_source->flip |= GSR_FLIP_HORIZONTAL; + } else if(gsr_string_starts_with(sub, size, "vflip=")) { + sub += 6; + size -= 6; + bool vflip = false; + if(!string_to_bool(sub, size, &vflip)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option vflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + + if(vflip) + capture_source->flip |= GSR_FLIP_VERTICAL; + } else if(gsr_string_starts_with(sub, size, "camera_fps=")) { + sub += 11; + size -= 11; + if(!gsr_string_to_int(sub, size, &capture_source->camera_fps)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_fps: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "camera_width=")) { + sub += 13; + size -= 13; + if(!gsr_string_to_int(sub, size, &capture_source->camera_resolution.x)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_width: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else if(gsr_string_starts_with(sub, size, "camera_height=")) { + sub += 14; + size -= 14; + if(!gsr_string_to_int(sub, size, &capture_source->camera_resolution.y)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_height: \"%.*s\", expected a number", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } else { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target option \"%.*s\", expected x, y, width, height, halign, valign, pixfmt, hflip, vflip, camera_fps, camera_width or camera_height", (int)size, sub); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + + return true; +} + +static int parse_capture_source_options(const char *capture_source_str, size_t capture_source_str_size, gsr_capture_source *capture_source) { + char options[1024]; + snprintf(options, sizeof(options), "%.*s", (int)capture_source_str_size, capture_source_str); + + parse_capture_source_options_userdata userdata; + userdata.capture_source = capture_source; + userdata.is_first_column = true; + userdata.error = GSR_ERROR_OK; + gsr_string_split(options, ';', parse_capture_source_options_callback, &userdata); + return userdata.error; +} + +static bool parse_capture_source_arg_callback(const char *sub, size_t size, void *userdata) { + parse_capture_source_arg_userdata *parse_userdata = userdata; + if(size == 0) + return true; + + const char *substr_start = sub; + const size_t substr_size = size; + size_t capture_source_size = size; + const char *capture_source_end = memchr(sub, ';', size); + if(capture_source_end) + capture_source_size = capture_source_end - sub; + + gsr_capture_source capture_source; + gsr_capture_source_init(&capture_source, parse_userdata->region_position, parse_userdata->region_size); + + if(gsr_string_starts_with(sub, capture_source_size, "monitor:")) { + capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR; + sub += 8; + capture_source_size -= 8; + } else if(gsr_string_starts_with(sub, capture_source_size, "window:")) { + capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW; + sub += 7; + capture_source_size -= 7; + } else if(gsr_string_starts_with(sub, capture_source_size, "v4l2:")) { + capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2; + sub += 5; + capture_source_size -= 5; + } else { + capture_source_type_from_string(sub, capture_source_size, &capture_source); + } + + snprintf(capture_source.name, sizeof(capture_source.name), "%.*s", (int)capture_source_size, sub); + + if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_WINDOW) { + if(!gsr_string_to_int64(capture_source.name, strlen(capture_source.name), &capture_source.window_id)) { + gsr_log(GSR_LOG_LEVEL_ERROR, "invalid window number %s", capture_source.name); + args_parser_print_usage(); + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + } + + if(parse_userdata->has_multiple_capture_sources) { + capture_source.halign = GSR_CAPTURE_ALIGN_START; + capture_source.valign = GSR_CAPTURE_ALIGN_START; + capture_source.pos = (vvec2i){0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS}; + } + + const int parse_options_result = parse_capture_source_options(substr_start, substr_size, &capture_source); + if(parse_options_result != GSR_ERROR_OK) { + parse_userdata->error = parse_options_result; + return false; + } + + gsr_capture_sources *capture_sources = parse_userdata->capture_sources; + if(!gsr_array_ensure_capacity((void**)&capture_sources->items, capture_sources->num_items, &capture_sources->capacity_items, sizeof(gsr_capture_source))) { + parse_userdata->error = GSR_ERROR_GENERIC; + return false; + } + + capture_sources->items[capture_sources->num_items] = capture_source; + ++capture_sources->num_items; + return true; +} + +int gsr_capture_sources_parse(gsr_capture_sources *self, const char *capture_source_arg, vec2i region_position, vec2i region_size) { + memset(self, 0, sizeof(*self)); + + parse_capture_source_arg_userdata userdata; + userdata.capture_sources = self; + userdata.region_position = region_position; + userdata.region_size = region_size; + userdata.has_multiple_capture_sources = strchr(capture_source_arg, '|') != NULL; + userdata.error = GSR_ERROR_OK; + + gsr_string_split(capture_source_arg, '|', parse_capture_source_arg_callback, &userdata); + if(userdata.error != GSR_ERROR_OK) + gsr_capture_sources_deinit(self); + + return userdata.error; +} + +void gsr_capture_sources_deinit(gsr_capture_sources *self) { + if(self->items) { + free(self->items); + self->items = NULL; + } + self->num_items = 0; + self->capacity_items = 0; +} + +bool gsr_capture_sources_has_type(const gsr_capture_sources *self, CaptureSourceType type) { + for(size_t i = 0; i < self->num_items; ++i) { + if(self->items[i].type == type) + return true; + } + return false; +} + +bool gsr_capture_sources_has_damage_tracked_target(const gsr_capture_sources *self) { + for(size_t i = 0; i < self->num_items; ++i) { + if(self->items[i].type != GSR_CAPTURE_SOURCE_TYPE_V4L2) + return true; + } + return false; +} + +bool gsr_capture_sources_has_region_set(const gsr_capture_sources *self) { + for(size_t i = 0; i < self->num_items; ++i) { + if(self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_REGION && self->items[i].region_set) + return true; + } + return false; +} + +bool gsr_capture_sources_has_monitor_or_region(const gsr_capture_sources *self) { + for(size_t i = 0; i < self->num_items; ++i) { + if(self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_MONITOR || self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_REGION) + return true; + } + return false; +} |
