aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp163
1 files changed, 133 insertions, 30 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 23d6f78..16b2a06 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -57,6 +57,7 @@ static GtkComboBoxText *framerate_mode_input_menu;
static GtkComboBoxText *stream_service_input_menu;
static GtkComboBoxText *record_container;
static GtkComboBoxText *replay_container;
+static GtkComboBoxText *custom_stream_container;
static GtkLabel *stream_key_label;
static GtkButton *record_file_chooser_button;
static GtkButton *replay_file_chooser_button;
@@ -71,7 +72,9 @@ static GtkButton *start_recording_button;
static GtkButton *pause_recording_button;
static GtkButton *start_replay_button;
static GtkButton *start_streaming_button;
-static GtkEntry *stream_id_entry;
+static GtkEntry *youtube_stream_id_entry;
+static GtkEntry *twitch_stream_id_entry;
+static GtkEntry *custom_stream_url_entry;
static GtkSpinButton *replay_time_entry;
static GtkButton *select_window_button;
static GtkWidget *audio_input_used_list;
@@ -94,6 +97,7 @@ static GtkWidget *overclock_button;
static GtkGrid *recording_bottom_panel_grid;
static GtkWidget *recording_record_time_label;
static GtkWidget *recording_record_icon;
+static GtkGrid *custom_stream_container_grid;
static GtkGrid *streaming_bottom_panel_grid;
static GtkWidget *streaming_record_time_label;
static GtkGrid *replay_bottom_panel_grid;
@@ -121,6 +125,10 @@ static bool wayland = false;
static bool flatpak = false;
static gsr_egl egl;
+static bool showing_notification = false;
+static double notification_timeout_seconds = 0.0;
+static double notification_start_seconds = 0.0;
+
struct AudioInput {
std::string name;
std::string description;
@@ -176,7 +184,8 @@ static const Container supported_containers[] = {
{ "mp4", "mp4" },
{ "flv", "flv" },
{ "matroska", "mkv" }, // TODO: Default to this on amd/intel, add (Recommended on AMD/Intel)
- { "mov", "mov" }
+ { "mov", "mov" },
+ { "mpegts", "ts" }
};
struct AudioRow {
@@ -613,7 +622,10 @@ static void save_configs() {
config.main_config.record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button));
config.streaming_config.streaming_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu));
- config.streaming_config.stream_key = gtk_entry_get_text(stream_id_entry);
+ config.streaming_config.youtube.stream_key = gtk_entry_get_text(youtube_stream_id_entry);
+ config.streaming_config.twitch.stream_key = gtk_entry_get_text(twitch_stream_id_entry);
+ config.streaming_config.custom.url = gtk_entry_get_text(custom_stream_url_entry);
+ config.streaming_config.custom.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(custom_stream_container));
if(!wayland) {
config.streaming_config.start_recording_hotkey.keysym = streaming_hotkey.keysym;
config.streaming_config.start_recording_hotkey.modifiers = streaming_hotkey.modkey_mask;
@@ -872,10 +884,19 @@ static bool try_card_has_valid_plane(const char *card_path) {
return false;
}
+static void string_copy(char *dst, const char *src, int len) {
+ int src_len = strlen(src);
+ int min_len = src_len;
+ if(len - 1 < min_len)
+ min_len = len - 1;
+ memcpy(dst, src, min_len);
+ dst[min_len] = '\0';
+}
+
/* output should be >= 128 bytes */
static bool gsr_get_valid_card_path(gsr_egl *egl, char *output) {
if(egl->dri_card_path) {
- strncpy(output, egl->dri_card_path, 127);
+ string_copy(output, egl->dri_card_path, 127);
return try_card_has_valid_plane(output);
}
@@ -893,6 +914,14 @@ static void show_notification(GtkApplication *app, const char *title, const char
g_notification_set_body(notification, body);
g_notification_set_priority(notification, priority);
g_application_send_notification(&app->parent, "gpu-screen-recorder", notification);
+
+ showing_notification = true;
+ if(priority < G_NOTIFICATION_PRIORITY_URGENT) {
+ notification_timeout_seconds = 2.0;
+ } else {
+ notification_timeout_seconds = 5.0;
+ }
+ notification_start_seconds = clock_get_monotonic_seconds();
}
static bool window_has_atom(Display *display, Window window, Atom atom) {
@@ -1382,7 +1411,6 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) {
void on_stream_key_icon_click(GtkWidget *widget, gpointer) {
gboolean visible = gtk_entry_get_visibility(GTK_ENTRY(widget));
-
gtk_entry_set_visibility(GTK_ENTRY(widget), !visible);
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, visible ? "view-reveal-symbolic" : "view-conceal-symbolic");
}
@@ -1809,7 +1837,6 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
save_configs();
- const char *stream_id_str = gtk_entry_get_text(stream_id_entry);
int fps = gtk_spin_button_get_value_as_int(fps_entry);
int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
@@ -1828,21 +1855,27 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
std::string fps_str = std::to_string(fps);
std::string stream_url;
+ const gchar *container_str = "flv";
const gchar *stream_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu));
if(strcmp(stream_service, "twitch") == 0) {
stream_url = "rtmp://live.twitch.tv/app/";
- stream_url += stream_id_str;
+ stream_url += gtk_entry_get_text(twitch_stream_id_entry);
} else if(strcmp(stream_service, "youtube") == 0) {
stream_url = "rtmp://a.rtmp.youtube.com/live2/";
- stream_url += stream_id_str;
+ stream_url += gtk_entry_get_text(youtube_stream_id_entry);
} else if(strcmp(stream_service, "custom") == 0) {
- stream_url = stream_id_str;
+ stream_url = gtk_entry_get_text(custom_stream_url_entry);
+ container_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(custom_stream_container));
if(stream_url.size() >= 7 && strncmp(stream_url.c_str(), "rtmp://", 7) == 0)
{}
else if(stream_url.size() >= 8 && strncmp(stream_url.c_str(), "rtmps://", 8) == 0)
{}
else if(stream_url.size() >= 6 && strncmp(stream_url.c_str(), "srt://", 6) == 0)
{}
+ else if(stream_url.size() >= 7 && strncmp(stream_url.c_str(), "http://", 7) == 0)
+ {}
+ else if(stream_url.size() >= 8 && strncmp(stream_url.c_str(), "https://", 8) == 0)
+ {}
else
stream_url = "rtmp://" + stream_url;
}
@@ -1858,7 +1891,7 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
snprintf(area, sizeof(area), "%dx%d", record_width, record_height);
std::vector<const char*> args = {
- "gpu-screen-recorder", "-w", window_str.c_str(), "-c", "flv", "-q", quality_input_str, "-k", video_codec_input_str, "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-cr", color_range_input_str, "-o", stream_url.c_str()
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str, "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-cr", color_range_input_str, "-o", stream_url.c_str()
};
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button)))
@@ -1936,8 +1969,26 @@ static void view_combo_box_change_callback(GtkComboBox *widget, gpointer userdat
static void stream_service_item_change_callback(GtkComboBox *widget, gpointer userdata) {
(void)userdata;
+
+ GtkEntry *stream_id_entries[3] = { youtube_stream_id_entry, twitch_stream_id_entry, custom_stream_url_entry };
+ for(int i = 0; i < 3; ++i) {
+ gtk_widget_set_visible(GTK_WIDGET(stream_id_entries[i]), false);
+ }
+
const gchar *selected_stream_service = gtk_combo_box_get_active_id(widget);
- gtk_label_set_text(stream_key_label, strcmp(selected_stream_service, "custom") == 0 ? "Url: " : "Stream key: ");
+ if(strcmp(selected_stream_service, "youtube") == 0) {
+ gtk_label_set_text(stream_key_label, "Stream key: ");
+ gtk_widget_set_visible(GTK_WIDGET(youtube_stream_id_entry), true);
+ gtk_widget_set_visible(GTK_WIDGET(custom_stream_container_grid), false);
+ } else if(strcmp(selected_stream_service, "twitch") == 0) {
+ gtk_label_set_text(stream_key_label, "Stream key: ");
+ gtk_widget_set_visible(GTK_WIDGET(twitch_stream_id_entry), true);
+ gtk_widget_set_visible(GTK_WIDGET(custom_stream_container_grid), false);
+ } else if(strcmp(selected_stream_service, "custom") == 0) {
+ gtk_label_set_text(stream_key_label, "Url: ");
+ gtk_widget_set_visible(GTK_WIDGET(custom_stream_url_entry), true);
+ gtk_widget_set_visible(GTK_WIDGET(custom_stream_container_grid), true);
+ }
}
static bool is_nv_fbc_installed() {
@@ -2484,7 +2535,6 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
audio_codec_input_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
gtk_combo_box_text_append(audio_codec_input_menu, "opus", "Opus (Recommended)");
gtk_combo_box_text_append(audio_codec_input_menu, "aac", "AAC");
- gtk_combo_box_text_append(audio_codec_input_menu, "flac", "FLAC");
gtk_widget_set_hexpand(GTK_WIDGET(audio_codec_input_menu), true);
gtk_grid_attach(audio_codec_grid, GTK_WIDGET(audio_codec_input_menu), 1, 0, 1, 1);
gtk_combo_box_set_active(GTK_COMBO_BOX(audio_codec_input_menu), 0);
@@ -2542,14 +2592,26 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_grid_set_column_spacing(start_button_grid, 10);
stream_button = GTK_BUTTON(gtk_button_new_with_label("Stream"));
+ GtkWidget *go_next_stream = gtk_image_new_from_icon_name("go-next", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(stream_button, go_next_stream);
+ gtk_button_set_always_show_image(stream_button, true);
+ gtk_button_set_image_position(stream_button, GTK_POS_RIGHT);
gtk_widget_set_hexpand(GTK_WIDGET(stream_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(stream_button), 0, 0, 1, 1);
record_button = GTK_BUTTON(gtk_button_new_with_label("Record"));
+ GtkWidget *go_next_record = gtk_image_new_from_icon_name("go-next", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(record_button, go_next_record);
+ gtk_button_set_always_show_image(record_button, true);
+ gtk_button_set_image_position(record_button, GTK_POS_RIGHT);
gtk_widget_set_hexpand(GTK_WIDGET(record_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(record_button), 1, 0, 1, 1);
replay_button = GTK_BUTTON(gtk_button_new_with_label("Replay"));
+ GtkWidget *go_next_replay = gtk_image_new_from_icon_name("go-next", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(replay_button, go_next_replay);
+ gtk_button_set_always_show_image(replay_button, true);
+ gtk_button_set_image_position(replay_button, GTK_POS_RIGHT);
gtk_widget_set_hexpand(GTK_WIDGET(replay_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(replay_button), 2, 0, 1, 1);
@@ -2561,7 +2623,7 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
}
static void add_wayland_global_hotkeys_ui(GtkGrid *grid, int &row, int width) {
- GtkWidget *label = gtk_label_new("Wayland don't support global hotkeys, use X11");
+ GtkWidget *label = gtk_label_new("Hotkeys not supported because Wayland doesn't support global hotkeys");
gtk_widget_set_hexpand(label, true);
gtk_grid_attach(grid, label, 0, row, width - 1, 1);
@@ -2575,7 +2637,7 @@ static void add_wayland_global_hotkeys_ui(GtkGrid *grid, int &row, int width) {
(void)userdata;
GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Wayland (desktop portal) doesn't support global hotkeys in any meaningful manner to most applications, including GPU Screen Recorder.\n"
- "If you want to use global hotkeys in GPU Screen Recorder then either use X11 or bind the following commands in your desktop environment settings to keys:\n"
+ "If you want to use global hotkeys in GPU Screen Recorder then either use X11 or bind the following commands in your Wayland desktop environment hotkey settings to keys:\n"
"Stop recording (saves video as well when not in replay mode):\n"
" killall -SIGINT gpu-screen-recorder\n"
"Save a replay:\n"
@@ -2683,10 +2745,14 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(replay_time_grid, GTK_WIDGET(replay_time_entry), 1, 0, 1, 1);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
-
gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, 5, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
+
replay_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
+ GtkWidget *go_previous = gtk_image_new_from_icon_name("go-previous", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(replay_back_button, go_previous);
+ gtk_button_set_always_show_image(replay_back_button, true);
+ gtk_button_set_image_position(replay_back_button, GTK_POS_LEFT);
gtk_widget_set_hexpand(GTK_WIDGET(replay_back_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(replay_back_button), 0, 0, 1, 1);
@@ -2808,6 +2874,10 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_set_column_spacing(start_button_grid, 10);
record_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
+ GtkWidget *go_previous = gtk_image_new_from_icon_name("go-previous", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(record_back_button, go_previous);
+ gtk_button_set_always_show_image(record_back_button, true);
+ gtk_button_set_image_position(record_back_button, GTK_POS_LEFT);
gtk_widget_set_hexpand(GTK_WIDGET(record_back_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(record_back_button), 0, 0, 1, 1);
@@ -2896,19 +2966,40 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(grid, GTK_WIDGET(stream_id_grid), 0, row++, 3, 1);
stream_key_label = GTK_LABEL(gtk_label_new("Stream key: "));
gtk_grid_attach(stream_id_grid, GTK_WIDGET(stream_key_label), 0, 0, 1, 1);
- stream_id_entry = GTK_ENTRY(gtk_entry_new());
- gtk_entry_set_visibility(stream_id_entry, FALSE);
- gtk_entry_set_input_purpose(stream_id_entry, GTK_INPUT_PURPOSE_PASSWORD);
- gtk_entry_set_icon_from_icon_name(stream_id_entry, GTK_ENTRY_ICON_SECONDARY, "view-reveal-symbolic");
- gtk_entry_set_icon_activatable(stream_id_entry, GTK_ENTRY_ICON_SECONDARY, true);
- g_signal_connect(stream_id_entry, "icon-press", G_CALLBACK(on_stream_key_icon_click), nullptr);
- gtk_widget_set_hexpand(GTK_WIDGET(stream_id_entry), true);
- gtk_grid_attach(stream_id_grid, GTK_WIDGET(stream_id_entry), 1, 0, 1, 1);
+
+ GtkEntry **stream_id_entries[3] = { &youtube_stream_id_entry, &twitch_stream_id_entry, &custom_stream_url_entry };
+ for(int i = 0; i < 3; ++i) {
+ *stream_id_entries[i] = GTK_ENTRY(gtk_entry_new());
+ gtk_entry_set_visibility(*stream_id_entries[i], FALSE);
+ gtk_entry_set_input_purpose(*stream_id_entries[i], GTK_INPUT_PURPOSE_PASSWORD);
+ gtk_entry_set_icon_from_icon_name(*stream_id_entries[i], GTK_ENTRY_ICON_SECONDARY, "view-reveal-symbolic");
+ gtk_entry_set_icon_activatable(*stream_id_entries[i], GTK_ENTRY_ICON_SECONDARY, true);
+ g_signal_connect(*stream_id_entries[i], "icon-press", G_CALLBACK(on_stream_key_icon_click), nullptr);
+ gtk_widget_set_hexpand(GTK_WIDGET(*stream_id_entries[i]), true);
+ gtk_grid_attach(stream_id_grid, GTK_WIDGET(*stream_id_entries[i]), 1, 0, 1, 1);
+ gtk_widget_set_visible(GTK_WIDGET(*stream_id_entries[i]), false);
+ }
+
+ custom_stream_container_grid = GTK_GRID(gtk_grid_new());
+ gtk_grid_attach(grid, GTK_WIDGET(custom_stream_container_grid), 0, row++, 3, 1);
+ gtk_grid_attach(custom_stream_container_grid, gtk_label_new("Container: "), 0, 0, 1, 1);
+ custom_stream_container = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
+ for(auto &supported_container : supported_containers) {
+ gtk_combo_box_text_append(custom_stream_container, supported_container.container_name, supported_container.file_extension);
+ }
+ gtk_widget_set_hexpand(GTK_WIDGET(custom_stream_container), true);
+ gtk_grid_attach(custom_stream_container_grid, GTK_WIDGET(custom_stream_container), 1, 0, 1, 1);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(custom_stream_container), 1);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, 3, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
+
stream_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
+ GtkWidget *go_previous = gtk_image_new_from_icon_name("go-previous", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(stream_back_button, go_previous);
+ gtk_button_set_always_show_image(stream_back_button, true);
+ gtk_button_set_image_position(stream_back_button, GTK_POS_LEFT);
gtk_widget_set_hexpand(GTK_WIDGET(stream_back_button), true);
gtk_grid_attach(start_button_grid, GTK_WIDGET(stream_back_button), 0, 0, 1, 1);
start_streaming_button = GTK_BUTTON(gtk_button_new_with_label("Start streaming"));
@@ -3002,7 +3093,20 @@ static void handle_record_timer() {
}
}
+static void handle_notification_timer(GtkApplication *app) {
+ if(!showing_notification)
+ return;
+
+ const double now = clock_get_monotonic_seconds();
+ if(now - notification_start_seconds >= notification_timeout_seconds) {
+ g_application_withdraw_notification(&app->parent, "gpu-screen-recorder");
+ showing_notification = false;
+ }
+}
+
static gboolean timer_timeout_handler(gpointer userdata) {
+ GtkApplication *app = (GtkApplication*)userdata;
+ handle_notification_timer(app);
handle_child_process_death(userdata);
handle_record_timer();
return G_SOURCE_CONTINUE;
@@ -3089,7 +3193,7 @@ static void load_config(const gpu_info &gpu_inf) {
if(!wayland && (config.main_config.codec == "hevc_hdr" || config.main_config.codec == "av1_hdr"))
config.main_config.codec = "auto";
- if(config.main_config.audio_codec != "opus" && config.main_config.audio_codec != "aac" && config.main_config.audio_codec != "flac")
+ if(config.main_config.audio_codec != "opus" && config.main_config.audio_codec != "aac")
config.main_config.audio_codec = "opus";
if(config.main_config.framerate_mode != "auto" && config.main_config.framerate_mode != "cfr" && config.main_config.framerate_mode != "vfr")
@@ -3098,9 +3202,6 @@ static void load_config(const gpu_info &gpu_inf) {
if(config.streaming_config.streaming_service != "twitch" && config.streaming_config.streaming_service != "youtube" && config.streaming_config.streaming_service != "custom")
config.streaming_config.streaming_service = "twitch";
- if(config.streaming_config.streaming_service == "custom")
- gtk_label_set_text(stream_key_label, "Url: ");
-
if(config.record_config.save_directory.empty() || !is_directory(config.record_config.save_directory.c_str()))
config.record_config.save_directory = get_videos_dir();
@@ -3137,7 +3238,10 @@ static void load_config(const gpu_info &gpu_inf) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(record_cursor_button), config.main_config.record_cursor);
gtk_combo_box_set_active_id(GTK_COMBO_BOX(stream_service_input_menu), config.streaming_config.streaming_service.c_str());
- gtk_entry_set_text(stream_id_entry, config.streaming_config.stream_key.c_str());
+ gtk_entry_set_text(youtube_stream_id_entry, config.streaming_config.youtube.stream_key.c_str());
+ gtk_entry_set_text(twitch_stream_id_entry, config.streaming_config.twitch.stream_key.c_str());
+ gtk_entry_set_text(custom_stream_url_entry, config.streaming_config.custom.url.c_str());
+ gtk_combo_box_set_active_id(GTK_COMBO_BOX(custom_stream_container), config.streaming_config.custom.container.c_str());
if(!wayland && streaming_hotkey_button && !config_empty) {
streaming_hotkey.keysym = config.streaming_config.start_recording_hotkey.keysym;
streaming_hotkey.modkey_mask = config.streaming_config.start_recording_hotkey.modifiers;
@@ -3182,6 +3286,7 @@ static void load_config(const gpu_info &gpu_inf) {
gtk_widget_set_visible(replay_save_hotkey.hotkey_active_label, false);
}
enable_stream_record_button_if_info_filled();
+ stream_service_item_change_callback(GTK_COMBO_BOX(stream_service_input_menu), nullptr);
if(!supported_video_codecs.h264 && !supported_video_codecs.hevc && gpu_inf.vendor != GPU_VENDOR_NVIDIA && config.main_config.codec != "av1") {
if(supported_video_codecs.av1) {
@@ -3291,8 +3396,6 @@ static const char* gpu_vendor_to_name(gpu_vendor vendor) {
static void activate(GtkApplication *app, gpointer) {
flatpak = is_inside_flatpak();
- Display *dpy = XOpenDisplay(NULL);
- wayland = !dpy || is_xwayland(dpy);
if(!wayland && !dpy) {
GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Neither X11 nor Wayland is running.");