aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/cli/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/main.c')
-rw-r--r--src/cli/main.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/src/cli/main.c b/src/cli/main.c
new file mode 100644
index 0000000..aaa315c
--- /dev/null
+++ b/src/cli/main.c
@@ -0,0 +1,708 @@
+#include "../../include/cli/commands.h"
+#include "../../include/cli/ipc.h"
+#include "../../include/recorder/recorder.h"
+#include "../../include/recorder/screenshot.h"
+#include "../../include/recorder/capture_source.h"
+#include "../../include/recorder/capture_setup.h"
+#include "../../include/recorder/windowing.h"
+#include "../../include/recorder/audio_input.h"
+#include "../../include/recorder/replay_save.h"
+#include "../../include/recorder/error.h"
+#include "../../include/args_parser.h"
+#include "../../include/sound.h"
+#include "../../include/shader.h"
+#include "../../include/utils.h"
+#include "../../include/log.h"
+#ifdef GSR_APP_AUDIO
+#include "../../include/pipewire_audio.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <unistd.h>
+#include <malloc.h>
+
+static atomic_int running = 1;
+static gsr_recorder *recorder = NULL;
+/* Signals that are received before the recorder has been created are applied when it has been created */
+static volatile sig_atomic_t pending_toggle_pause = 0;
+static volatile sig_atomic_t pending_toggle_replay_recording = 0;
+static volatile sig_atomic_t pending_save_replay_seconds = 0;
+
+static void stop_handler(int signal_value) {
+ (void)signal_value;
+ atomic_store(&running, 0);
+ if(recorder)
+ gsr_recorder_stop(recorder);
+}
+
+static void toggle_pause_handler(int signal_value) {
+ (void)signal_value;
+ if(recorder)
+ gsr_recorder_toggle_pause(recorder);
+ else
+ pending_toggle_pause = 1;
+}
+
+static void toggle_replay_recording_handler(int signal_value) {
+ (void)signal_value;
+ if(recorder)
+ gsr_recorder_toggle_replay_recording(recorder);
+ else
+ pending_toggle_replay_recording = 1;
+}
+
+static void save_replay_seconds_handler(gsr_recorder *rec, int seconds) {
+ if(rec)
+ gsr_recorder_save_replay(rec, seconds, GSR_RESTART_REPLAY_USE_OPTION);
+ else
+ pending_save_replay_seconds = seconds;
+}
+
+static void apply_pending_signals(gsr_recorder *rec) {
+ if(pending_toggle_pause) {
+ pending_toggle_pause = 0;
+ gsr_recorder_toggle_pause(rec);
+ }
+
+ if(pending_toggle_replay_recording) {
+ pending_toggle_replay_recording = 0;
+ gsr_recorder_toggle_replay_recording(rec);
+ }
+
+ if(pending_save_replay_seconds != 0) {
+ const int seconds = pending_save_replay_seconds;
+ pending_save_replay_seconds = 0;
+ gsr_recorder_save_replay(rec, seconds, GSR_RESTART_REPLAY_USE_OPTION);
+ }
+}
+
+static void save_replay_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, GSR_SAVE_REPLAY_SECONDS_FULL);
+}
+
+static void save_replay_10_seconds_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 10);
+}
+
+static void save_replay_30_seconds_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 30);
+}
+
+static void save_replay_1_minute_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 60);
+}
+
+static void save_replay_5_minutes_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 60*5);
+}
+
+static void save_replay_10_minutes_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 60*10);
+}
+
+static void save_replay_30_minutes_handler(int signal_value) {
+ (void)signal_value;
+ save_replay_seconds_handler(recorder, 60*30);
+}
+
+static bool ipc_stop_handler(char *error_message, size_t error_message_size, void *userdata) {
+ (void)error_message;
+ (void)error_message_size;
+ (void)userdata;
+ atomic_store(&running, 0);
+ gsr_recorder_stop(recorder);
+ return true;
+}
+
+static bool ipc_toggle_pause_handler(char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(settings->is_replaying) {
+ snprintf(error_message, error_message_size, "pausing is not supported when recording a replay");
+ return false;
+ }
+
+ gsr_recorder_toggle_pause(recorder);
+ return true;
+}
+
+static bool ipc_set_paused_handler(bool paused, char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(settings->is_replaying) {
+ snprintf(error_message, error_message_size, "pausing is not supported when recording a replay");
+ return false;
+ }
+
+ gsr_recorder_set_paused(recorder, paused);
+ return true;
+}
+
+static bool ipc_toggle_replay_recording_handler(char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(!settings->replay_recording_directory) {
+ snprintf(error_message, error_message_size, "option -ro is required to start a recording");
+ return false;
+ }
+
+ gsr_recorder_toggle_replay_recording(recorder);
+ return true;
+}
+
+static bool ipc_start_replay_recording_handler(char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(!settings->replay_recording_directory) {
+ snprintf(error_message, error_message_size, "option -ro is required to start a recording");
+ return false;
+ }
+
+ gsr_recorder_start_replay_recording(recorder);
+ return true;
+}
+
+static bool ipc_stop_replay_recording_handler(char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(!settings->replay_recording_directory) {
+ snprintf(error_message, error_message_size, "option -ro is required to start a recording");
+ return false;
+ }
+
+ if(!gsr_recorder_is_replay_recording(recorder)) {
+ snprintf(error_message, error_message_size, "no recording is running");
+ return false;
+ }
+
+ gsr_recorder_stop_replay_recording(recorder);
+ return true;
+}
+
+static bool ipc_save_replay_handler(int seconds, bool has_restart_replay, bool restart_replay, char *error_message, size_t error_message_size, void *userdata) {
+ const gsr_recorder_settings *settings = userdata;
+ if(!settings->is_replaying) {
+ snprintf(error_message, error_message_size, "option -r is required to save a replay");
+ return false;
+ }
+
+ int restart_replay_request = GSR_RESTART_REPLAY_USE_OPTION;
+ if(has_restart_replay)
+ restart_replay_request = restart_replay ? GSR_RESTART_REPLAY_ENABLE : GSR_RESTART_REPLAY_DISABLE;
+
+ gsr_recorder_save_replay(recorder, seconds, restart_replay_request);
+ return true;
+}
+
+static void install_signal_handlers(void) {
+ signal(SIGINT, stop_handler);
+ signal(SIGTERM, stop_handler);
+ signal(SIGUSR1, save_replay_handler);
+ signal(SIGUSR2, toggle_pause_handler);
+ signal(SIGRTMIN, toggle_replay_recording_handler);
+ signal(SIGRTMIN+1, save_replay_10_seconds_handler);
+ signal(SIGRTMIN+2, save_replay_30_seconds_handler);
+ signal(SIGRTMIN+3, save_replay_1_minute_handler);
+ signal(SIGRTMIN+4, save_replay_5_minutes_handler);
+ signal(SIGRTMIN+5, save_replay_10_minutes_handler);
+ signal(SIGRTMIN+6, save_replay_30_minutes_handler);
+}
+
+static void set_display_server_environment_variables(void) {
+ /* Some users dont have properly setup environments (no display manager that does systemctl --user import-environment DISPLAY WAYLAND_DISPLAY) */
+ const char *display = getenv("DISPLAY");
+ if(!display) {
+ display = ":0";
+ setenv("DISPLAY", display, true);
+ }
+
+ const char *wayland_display = getenv("WAYLAND_DISPLAY");
+ if(!wayland_display) {
+ wayland_display = "wayland-0";
+ setenv("WAYLAND_DISPLAY", wayland_display, true);
+ }
+}
+
+static void set_environment_variables(void) {
+ set_display_server_environment_variables();
+
+ /* Linux nvidia driver 580.105.08 added the environment variable CUDA_DISABLE_PERF_BOOST to disable the p2 power level issue,
+ where running cuda (which includes nvenc) causes the gpu to be forcefully set to p2 power level which on many nvidia gpus
+ decreases gpu performance in games. On my GTX 1080 it decreased game performance by 10% for absolutely no reason. */
+ setenv("CUDA_DISABLE_PERF_BOOST", "1", true);
+ /* Stop nvidia driver from buffering frames */
+ setenv("__GL_MaxFramesAllowed", "1", true);
+ /* If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context,
+ so we overwrite it */
+ setenv("__GL_THREADED_OPTIMIZATIONS", "0", true);
+ /* Some people set this to nvidia (for nvdec) or vdpau (for nvidia vdpau), which breaks gpu screen recorder since
+ nvidia doesn't support vaapi and nvidia-vaapi-driver doesn't support encoding yet.
+ Let vaapi find the right vaapi driver instead of forcing a specific one. */
+ unsetenv("LIBVA_DRIVER_NAME");
+ /* Some people set this to force all applications to vsync on nvidia, but this makes eglSwapBuffers never return. */
+ unsetenv("__GL_SYNC_TO_VBLANK");
+ /* Same as above, but for amd/intel */
+ unsetenv("vblank_mode");
+}
+
+static void install_cuda_no_stable_perf_limit(void) {
+ if(access("/proc/driver/nvidia/version", F_OK) != 0)
+ return;
+
+ const char *home = getenv("HOME");
+ if(!home) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "install_cuda_no_stable_perf_limit: $HOME not set");
+ return;
+ }
+
+ char nv_profiles_path[4096];
+ snprintf(nv_profiles_path, sizeof(nv_profiles_path), "%s/.nv/nvidia-application-profiles-rc.d", home);
+
+ if(create_directory_recursive(nv_profiles_path) != 0) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "install_cuda_no_stable_perf_limit: failed to create directory: %s", nv_profiles_path);
+ return;
+ }
+
+ snprintf(nv_profiles_path, sizeof(nv_profiles_path), "%s/.nv/nvidia-application-profiles-rc.d/10-gsr-cuda-no-stable-perf-limit", home);
+
+ FILE *f = fopen(nv_profiles_path, "wb");
+ if(!f) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "install_cuda_no_stable_perf_limit: failed to create file: %s", nv_profiles_path);
+ return;
+ }
+
+ const char *profile_data =
+ "{\n"
+ " \"profiles\": [\n"
+ " {\n"
+ " \"name\": \"CudaNoStablePerfLimit\",\n"
+ " \"settings\": [\"0x166c5e\", 0]\n"
+ " }\n"
+ " ],\n"
+ " \"rules\": [\n"
+ " { \"pattern\": \"gpu-screen-recorder\", \"profile\": \"CudaNoStablePerfLimit\" }\n"
+ " ]\n"
+ "}\n";
+
+ fwrite(profile_data, 1, strlen(profile_data), f);
+ fclose(f);
+}
+
+static int 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(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();
+ return GSR_ERROR_GENERIC;
+ }
+
+ 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 && !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();
+ return GSR_ERROR_GENERIC;
+ }
+ } else {
+ if(is_capturing_region) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "option -region is deprecated, use -w with region directly instead, for example: -w %s", region_arg->values[0]);
+ } else {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "option -region can only be used when option '-w region' is used");
+ args_parser_print_usage();
+ return GSR_ERROR_GENERIC;
+ }
+ }
+
+ 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");
+
+ return GSR_ERROR_OK;
+}
+
+static void screenshot_saved_callback(const char *filepath, void *userdata) {
+ const char *recording_saved_script = userdata;
+ if(recording_saved_script)
+ run_recording_saved_script_async(recording_saved_script, filepath, "screenshot");
+}
+
+typedef struct {
+ const char *recording_saved_script;
+ gsr_ipc *ipc;
+} recorder_callbacks_context;
+
+static void replay_saved_callback(const char *filepath, void *userdata) {
+ recorder_callbacks_context *context = userdata;
+ if(!filepath) {
+ printf("gsr error: Failed to save replay\n");
+ fflush(stdout);
+ gsr_ipc_complete_request(context->ipc, GSR_IPC_DEFERRED_REQUEST_SAVE_REPLAY, false, NULL);
+ return;
+ }
+
+ puts(filepath);
+ fflush(stdout);
+ if(context->recording_saved_script)
+ run_recording_saved_script_async(context->recording_saved_script, filepath, "replay");
+
+ gsr_ipc_complete_request(context->ipc, GSR_IPC_DEFERRED_REQUEST_SAVE_REPLAY, true, filepath);
+}
+
+static void recording_started_callback(const char *filepath, void *userdata) {
+ (void)userdata;
+ if(!filepath) {
+ printf("gsr error: Failed to start recording\n");
+ fflush(stdout);
+ }
+}
+
+static void recording_stopped_callback(const char *filepath, void *userdata) {
+ recorder_callbacks_context *context = userdata;
+ if(!filepath) {
+ printf("gsr error: Failed to save recording\n");
+ fflush(stdout);
+ gsr_ipc_complete_request(context->ipc, GSR_IPC_DEFERRED_REQUEST_STOP_REPLAY_RECORDING, false, NULL);
+ return;
+ }
+
+ puts(filepath);
+ fflush(stdout);
+ if(context->recording_saved_script)
+ run_recording_saved_script_async(context->recording_saved_script, filepath, "regular");
+
+ gsr_ipc_complete_request(context->ipc, GSR_IPC_DEFERRED_REQUEST_STOP_REPLAY_RECORDING, true, filepath);
+}
+
+#ifdef GSR_APP_AUDIO
+static gsr_pipewire_audio pipewire_audio;
+
+static bool app_audio_name_callback(const char *app_name, void *userdata) {
+ gsr_app_audio_names *app_audio_names = userdata;
+ gsr_app_audio_names_add(app_audio_names, app_name);
+ return true;
+}
+
+static int setup_app_audio(gsr_app_audio_names *app_audio_names) {
+ if(!pulseaudio_server_is_pipewire()) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "your sound server is not PipeWire. Application audio is only available when running PipeWire audio server");
+ return GSR_ERROR_UNSUPPORTED;
+ }
+
+ if(!gsr_pipewire_audio_init(&pipewire_audio)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to setup PipeWire audio for application audio capture");
+ return GSR_ERROR_UNSUPPORTED;
+ }
+
+ gsr_pipewire_audio_for_each_app(&pipewire_audio, app_audio_name_callback, app_audio_names);
+ return GSR_ERROR_OK;
+}
+#endif
+
+static int parse_audio_inputs(args_parser *arg_parser, gsr_audio_input_tracks *audio_input_tracks) {
+ const Arg *audio_input_arg = args_parser_get_arg(arg_parser, "-a");
+ assert(audio_input_arg);
+
+ gsr_audio_devices audio_devices;
+ memset(&audio_devices, 0, sizeof(audio_devices));
+ if(audio_input_arg->num_values > 0)
+ get_pulseaudio_inputs(&audio_devices);
+
+ const int parse_result = gsr_audio_input_tracks_parse(audio_input_tracks, audio_input_arg->values, audio_input_arg->num_values, &audio_devices);
+ gsr_audio_devices_deinit(&audio_devices);
+ return parse_result;
+}
+
+static int take_screenshot(args_parser *arg_parser, gsr_windowing *windowing, gsr_capture_deps *capture_deps, gsr_capture_sources *capture_sources, gsr_image_format image_format) {
+ const Arg *plugin_arg = args_parser_get_arg(arg_parser, "-p");
+ assert(plugin_arg);
+
+ arg_parser->settings.fps = 60; /* We want to capture an image as soon as possible */
+
+ gsr_screenshot_params screenshot_params;
+ memset(&screenshot_params, 0, sizeof(screenshot_params));
+ screenshot_params.settings = &arg_parser->settings;
+ screenshot_params.egl = &windowing->egl;
+ screenshot_params.window = windowing->window;
+ screenshot_params.capture_deps = capture_deps;
+ screenshot_params.capture_sources = capture_sources;
+ screenshot_params.image_format = image_format;
+ screenshot_params.plugin_filepaths = plugin_arg->values;
+ screenshot_params.num_plugin_filepaths = plugin_arg->num_values;
+ screenshot_params.running = &running;
+ screenshot_params.screenshot_saved = screenshot_saved_callback;
+ screenshot_params.userdata = (void*)arg_parser->settings.recording_saved_script;
+
+ return gsr_screenshot_take(&screenshot_params);
+}
+
+static int record(args_parser *arg_parser, gsr_windowing *windowing, gsr_capture_deps *capture_deps, gsr_capture_sources *capture_sources, gsr_audio_input_tracks *audio_input_tracks, gsr_ipc *ipc) {
+ const Arg *plugin_arg = args_parser_get_arg(arg_parser, "-p");
+ assert(plugin_arg);
+
+ gsr_recorder_params recorder_params;
+ memset(&recorder_params, 0, sizeof(recorder_params));
+ recorder_params.settings = &arg_parser->settings;
+ recorder_params.windowing = windowing;
+ recorder_params.capture_deps = capture_deps;
+ recorder_params.capture_sources = capture_sources;
+ recorder_params.audio_input_tracks = audio_input_tracks;
+ recorder_params.plugin_filepaths = plugin_arg->values;
+ recorder_params.num_plugin_filepaths = plugin_arg->num_values;
+#ifdef GSR_APP_AUDIO
+ recorder_params.pipewire_audio = &pipewire_audio;
+#endif
+
+ recorder_callbacks_context callbacks_context;
+ callbacks_context.recording_saved_script = arg_parser->settings.recording_saved_script;
+ callbacks_context.ipc = ipc;
+
+ gsr_recorder_callbacks callbacks;
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.replay_saved = replay_saved_callback;
+ callbacks.recording_started = recording_started_callback;
+ callbacks.recording_stopped = recording_stopped_callback;
+ callbacks.userdata = &callbacks_context;
+
+ int error = GSR_ERROR_OK;
+ recorder = gsr_recorder_create(&recorder_params, &callbacks, &error);
+ if(!recorder)
+ return error;
+
+ apply_pending_signals(recorder);
+ if(!atomic_load(&running))
+ gsr_recorder_stop(recorder);
+
+ gsr_ipc_handlers ipc_handlers;
+ memset(&ipc_handlers, 0, sizeof(ipc_handlers));
+ ipc_handlers.stop = ipc_stop_handler;
+ ipc_handlers.toggle_pause = ipc_toggle_pause_handler;
+ ipc_handlers.set_paused = ipc_set_paused_handler;
+ ipc_handlers.toggle_replay_recording = ipc_toggle_replay_recording_handler;
+ ipc_handlers.start_replay_recording = ipc_start_replay_recording_handler;
+ ipc_handlers.stop_replay_recording = ipc_stop_replay_recording_handler;
+ ipc_handlers.save_replay = ipc_save_replay_handler;
+ ipc_handlers.userdata = &arg_parser->settings;
+
+ int run_result = gsr_ipc_start(ipc, &ipc_handlers);
+ if(run_result == GSR_ERROR_OK)
+ run_result = gsr_recorder_run(recorder);
+
+ gsr_ipc_complete_request(ipc, GSR_IPC_DEFERRED_REQUEST_STOP, true, arg_parser->settings.is_replaying ? NULL : arg_parser->settings.filename);
+ gsr_ipc_stop(ipc);
+ gsr_recorder_destroy(recorder, true);
+ recorder = NULL;
+ return run_result;
+}
+
+static int run(args_parser *arg_parser) {
+ int exit_code = 0;
+
+ gsr_capture_sources capture_sources;
+ gsr_audio_input_tracks audio_input_tracks;
+ gsr_app_audio_names app_audio_names;
+ gsr_windowing windowing;
+ gsr_capture_deps capture_deps;
+ gsr_ipc ipc;
+ memset(&audio_input_tracks, 0, sizeof(audio_input_tracks));
+ memset(&app_audio_names, 0, sizeof(app_audio_names));
+ memset(&windowing, 0, sizeof(windowing));
+ memset(&ipc, 0, sizeof(ipc));
+ gsr_capture_deps_init(&capture_deps);
+
+ const Arg *ipc_arg = args_parser_get_arg(arg_parser, "-ipc");
+ assert(ipc_arg);
+
+ const int parse_capture_sources_result = gsr_capture_sources_parse(&capture_sources, arg_parser->settings.capture_source, arg_parser->settings.region_position, arg_parser->settings.region_size);
+ if(parse_capture_sources_result != GSR_ERROR_OK) {
+ exit_code = gsr_error_to_exit_code(parse_capture_sources_result);
+ goto done;
+ }
+
+ 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_code = 1;
+ goto done;
+ }
+
+ const int validate_args_result = validate_args_with_capture_sources(arg_parser, &capture_sources);
+ if(validate_args_result != GSR_ERROR_OK) {
+ exit_code = gsr_error_to_exit_code(validate_args_result);
+ goto done;
+ }
+
+ if(ipc_arg->num_values > 0 && gsr_ipc_init(&ipc, ipc_arg->values[0]) != GSR_ERROR_OK) {
+ exit_code = 1;
+ goto done;
+ }
+
+ const int parse_audio_inputs_result = parse_audio_inputs(arg_parser, &audio_input_tracks);
+ if(parse_audio_inputs_result != GSR_ERROR_OK) {
+ exit_code = gsr_error_to_exit_code(parse_audio_inputs_result);
+ goto done;
+ }
+
+ const bool uses_app_audio = gsr_audio_input_tracks_has_app_audio(&audio_input_tracks);
+#ifdef GSR_APP_AUDIO
+ if(uses_app_audio) {
+ const int app_audio_result = setup_app_audio(&app_audio_names);
+ if(app_audio_result != GSR_ERROR_OK) {
+ exit_code = gsr_error_to_exit_code(app_audio_result);
+ goto done;
+ }
+ }
+#else
+ if(uses_app_audio) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "application audio can't be recorded because GPU Screen Recorder is built without application audio support (-Dapp_audio option)");
+ exit_code = 2;
+ goto done;
+ }
+#endif
+
+ const int validate_app_audio_result = gsr_audio_input_tracks_validate_app_audio(&audio_input_tracks, &app_audio_names);
+ if(validate_app_audio_result != GSR_ERROR_OK) {
+ exit_code = gsr_error_to_exit_code(validate_app_audio_result);
+ goto done;
+ }
+
+ gsr_windowing_params windowing_params;
+ 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) {
+ exit_code = 1;
+ goto done;
+ }
+
+ 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();
+ }
+
+ if(video_codec_is_hdr(arg_parser->settings.video_codec)) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "portal capture option doesn't support hdr yet (PipeWire doesn't support hdr), the video will be tonemapped from hdr to sdr");
+ arg_parser->settings.video_codec = hdr_video_codec_to_sdr_video_codec(arg_parser->settings.video_codec);
+ }
+ }
+
+ if(gsr_windowing_load_egl(&windowing, &windowing_params) != GSR_ERROR_OK) {
+ exit_code = 1;
+ goto done;
+ }
+
+ gsr_shader_enable_debug_output(arg_parser->settings.gl_debug);
+#ifndef NDEBUG
+ gsr_shader_enable_debug_output(true);
+#endif
+
+ if(!args_parser_validate_with_gl_info(arg_parser, &windowing.egl)) {
+ exit_code = 1;
+ goto done;
+ }
+
+ if(!windowing.card_path_found) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "no /dev/dri/cardX device found. Make sure that you have at least one monitor connected or record a single window instead on X11 or record with the -w portal option");
+ exit_code = 2;
+ goto done;
+ }
+
+ gsr_capture_deps_init_cursor(&capture_deps, &windowing.egl, arg_parser->settings.record_cursor);
+
+ gsr_image_format image_format;
+ if(get_image_format_from_filename(arg_parser->settings.filename, &image_format)) {
+ if(audio_input_tracks.num_items > 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "can't record audio (-a) when taking a screenshot");
+ exit_code = 1;
+ goto done;
+ }
+
+ if(ipc_arg->num_values > 0)
+ gsr_log(GSR_LOG_LEVEL_WARNING, "option -ipc has no effect when taking a screenshot");
+
+ exit_code = gsr_error_to_exit_code(take_screenshot(arg_parser, &windowing, &capture_deps, &capture_sources, image_format));
+ } else {
+ exit_code = gsr_error_to_exit_code(record(arg_parser, &windowing, &capture_deps, &capture_sources, &audio_input_tracks, &ipc));
+ }
+
+ done:
+ gsr_ipc_deinit(&ipc);
+ gsr_capture_deps_deinit(&capture_deps);
+ gsr_windowing_deinit(&windowing);
+#ifdef GSR_APP_AUDIO
+ gsr_pipewire_audio_deinit(&pipewire_audio);
+#endif
+ gsr_app_audio_names_deinit(&app_audio_names);
+ gsr_audio_input_tracks_deinit(&audio_input_tracks);
+ gsr_capture_sources_deinit(&capture_sources);
+ return exit_code;
+}
+
+int main(int argc, char **argv) {
+ setlocale(LC_ALL, "C"); /* Sigh... stupid C */
+#ifdef __GLIBC__
+ mallopt(M_MMAP_THRESHOLD, 65536);
+#endif
+
+ install_signal_handlers();
+ set_environment_variables();
+ install_cuda_no_stable_perf_limit();
+
+ if(geteuid() == 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "don't run gpu-screen-recorder as the root user");
+ _exit(1);
+ }
+
+ args_handlers arg_handlers;
+ arg_handlers.version = version_command;
+ arg_handlers.info = info_command;
+ arg_handlers.list_audio_devices = list_audio_devices_command;
+ arg_handlers.list_application_audio = list_application_audio_command;
+ arg_handlers.list_v4l2_devices = list_v4l2_devices;
+ arg_handlers.list_capture_options = list_capture_options_command;
+ arg_handlers.list_monitors = list_monitors_command;
+
+ args_parser arg_parser;
+ int exit_code = 0;
+ int command_exit_code = 0;
+ switch(args_parser_parse(&arg_parser, argc, argv, &arg_handlers, NULL, &command_exit_code)) {
+ case ARGS_PARSE_RESULT_ERROR:
+ exit_code = 1;
+ break;
+ case ARGS_PARSE_RESULT_COMMAND_HANDLED:
+ exit_code = command_exit_code;
+ break;
+ case ARGS_PARSE_RESULT_OK: {
+ if(!arg_parser.settings.low_power) {
+ /* Forces low latency encoding mode. Use this environment variable until vaapi supports setting this as a parameter.
+ The downside of this is that it always uses maximum power, which is not ideal for replay mode that runs on system startup.
+ This option was added in mesa 24.1.4, released in july 17, 2024.
+ Seems like the performance issue is not in encoding, but rendering the frame.
+ Some frames end up taking 10 times longer. Seems to be an issue with amd gpu power management when letting the application sleep on the cpu side? */
+ setenv("AMD_DEBUG", "lowlatencyenc", true);
+ }
+
+ exit_code = run(&arg_parser);
+ break;
+ }
+ }
+
+ args_parser_deinit(&arg_parser);
+
+ /* We do an _exit here because cuda uses at_exit to do _something_ that causes the program to freeze,
+ but only on some nvidia driver versions on some gpus (RTX?), and _exit exits the program without calling
+ the at_exit registered functions.
+ Cuda (nvenc) is loaded in a separate process, but this still happens. */
+ _exit(exit_code);
+}