/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2011 Red Hat, Inc. * Copyright (C) 2019 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * Authors: * Colin Walters * Marco Trevisan */ #include #include #include #include #ifdef GDK_WINDOWING_X11 #include #endif #include "gsm-fail-whale-dialog.h" #include "gsm-icon-names.h" struct _GsmFailWhaleDialog { GtkWindow parent; gboolean debug_mode; gboolean allow_logout; gboolean extensions; GdkMonitor *monitor; GdkRectangle geometry; }; G_DEFINE_TYPE (GsmFailWhaleDialog, gsm_fail_whale_dialog, GTK_TYPE_WINDOW); /* derived from tomboy */ static void _window_override_user_time (GsmFailWhaleDialog *window) { guint32 ev_time = gtk_get_current_event_time (); GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); #ifdef GDK_WINDOWING_X11 if (!GDK_IS_X11_WINDOW (gdk_window)) return; if (ev_time == 0) { gint ev_mask = gtk_widget_get_events (GTK_WIDGET (window)); if (!(ev_mask & GDK_PROPERTY_CHANGE_MASK)) { gtk_widget_add_events (GTK_WIDGET (window), GDK_PROPERTY_CHANGE_MASK); } /* * NOTE: Last resort for D-BUS or other non-interactive * openings. Causes roundtrip to server. Lame. */ ev_time = gdk_x11_get_server_time (gdk_window); } gdk_x11_window_set_user_time (gdk_window, ev_time); #endif } static void _window_move_resize_window (GsmFailWhaleDialog *window, gboolean move, gboolean resize) { if (window->debug_mode) return; g_debug ("Move and/or resize window x=%d y=%d w=%d h=%d", window->geometry.x, window->geometry.y, window->geometry.width, window->geometry.height); if (resize) { gtk_window_resize (GTK_WINDOW (window), window->geometry.width, window->geometry.height); } if (move) { gtk_window_move (GTK_WINDOW (window), window->geometry.x, window->geometry.y); } } static void update_geometry (GsmFailWhaleDialog *fail_dialog) { gdk_monitor_get_geometry (fail_dialog->monitor, &fail_dialog->geometry); } static void on_screen_size_changed (GdkScreen *screen, GsmFailWhaleDialog *fail_dialog) { gtk_widget_queue_resize (GTK_WIDGET (fail_dialog)); } static void gsm_fail_whale_dialog_realize (GtkWidget *widget) { if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize) { GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize (widget); } _window_override_user_time (GSM_FAIL_WHALE_DIALOG (widget)); update_geometry (GSM_FAIL_WHALE_DIALOG (widget)); _window_move_resize_window (GSM_FAIL_WHALE_DIALOG (widget), TRUE, TRUE); g_signal_connect (gtk_window_get_screen (GTK_WINDOW (widget)), "size_changed", G_CALLBACK (on_screen_size_changed), widget); } static void gsm_fail_whale_dialog_unrealize (GtkWidget *widget) { g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (widget)), on_screen_size_changed, widget); if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize) { GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize (widget); } } static void gsm_fail_whale_dialog_size_request (GtkWidget *widget, GtkRequisition *requisition) { GsmFailWhaleDialog *fail_dialog; GdkRectangle old_geometry; int position_changed = FALSE; int size_changed = FALSE; fail_dialog = GSM_FAIL_WHALE_DIALOG (widget); old_geometry = fail_dialog->geometry; update_geometry (fail_dialog); requisition->width = fail_dialog->geometry.width; requisition->height = fail_dialog->geometry.height; if (!gtk_widget_get_realized (widget)) { return; } if (old_geometry.width != fail_dialog->geometry.width || old_geometry.height != fail_dialog->geometry.height) { size_changed = TRUE; } if (old_geometry.x != fail_dialog->geometry.x || old_geometry.y != fail_dialog->geometry.y) { position_changed = TRUE; } _window_move_resize_window (fail_dialog, position_changed, size_changed); } static void gsm_fail_whale_dialog_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) { GtkRequisition requisition; gsm_fail_whale_dialog_size_request (widget, &requisition); *minimal_width = *natural_width = requisition.width; } static void gsm_fail_whale_dialog_get_preferred_width_for_height (GtkWidget *widget, gint for_height, gint *minimal_width, gint *natural_width) { GtkRequisition requisition; gsm_fail_whale_dialog_size_request (widget, &requisition); *minimal_width = *natural_width = requisition.width; } static void gsm_fail_whale_dialog_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) { GtkRequisition requisition; gsm_fail_whale_dialog_size_request (widget, &requisition); *minimal_height = *natural_height = requisition.height; } static void gsm_fail_whale_dialog_get_preferred_height_for_width (GtkWidget *widget, gint for_width, gint *minimal_height, gint *natural_height) { GtkRequisition requisition; gsm_fail_whale_dialog_size_request (widget, &requisition); *minimal_height = *natural_height = requisition.height; } static void gsm_fail_whale_dialog_class_init (GsmFailWhaleDialogClass *klass) { GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); widget_class->realize = gsm_fail_whale_dialog_realize; widget_class->unrealize = gsm_fail_whale_dialog_unrealize; widget_class->get_preferred_width = gsm_fail_whale_dialog_get_preferred_width; widget_class->get_preferred_height = gsm_fail_whale_dialog_get_preferred_height; widget_class->get_preferred_width_for_height = gsm_fail_whale_dialog_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = gsm_fail_whale_dialog_get_preferred_height_for_width; } static void on_logout_clicked (GtkWidget *button, GsmFailWhaleDialog *fail_dialog) { if (!fail_dialog->debug_mode) { g_spawn_command_line_async ("gnome-session-quit --force", NULL); } gtk_main_quit (); } static void setup_window (GsmFailWhaleDialog *fail_dialog) { GtkWidget *box; GtkWidget *image; GtkWidget *label; GtkWidget *message_label; GtkWidget *button_box; GtkWidget *button; GdkPixbuf *fail_icon; GdkDisplay *display; char *markup; int scale_factor; int i; gtk_window_set_title (GTK_WINDOW (fail_dialog), ""); gtk_window_set_icon_name (GTK_WINDOW (fail_dialog), GSM_ICON_COMPUTER_FAIL); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (fail_dialog), TRUE); gtk_window_set_keep_above (GTK_WINDOW (fail_dialog), TRUE); gtk_window_stick (GTK_WINDOW (fail_dialog)); gtk_window_set_position (GTK_WINDOW (fail_dialog), GTK_WIN_POS_CENTER_ALWAYS); /* only works if there is a window manager which is unlikely */ display = gtk_widget_get_display (GTK_WIDGET (fail_dialog)); for (i = 0; i < gdk_display_get_n_monitors (display); i++) { if (gdk_display_get_monitor (display, i) == fail_dialog->monitor) { GdkScreen *screen; screen = gtk_widget_get_screen (GTK_WIDGET (fail_dialog)); gtk_window_fullscreen_on_monitor (GTK_WINDOW (fail_dialog), screen, i); break; } } box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); gtk_widget_set_valign (box, GTK_ALIGN_CENTER); gtk_widget_show (box); gtk_container_add (GTK_CONTAINER (fail_dialog), box); scale_factor = gdk_monitor_get_scale_factor (fail_dialog->monitor); fail_icon = gtk_icon_theme_load_icon_for_scale (gtk_icon_theme_get_default (), GSM_ICON_COMPUTER_FAIL, 128, scale_factor, 0, NULL); if (fail_icon != NULL) { image = gtk_image_new_from_pixbuf (fail_icon); gtk_widget_show (image); gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); g_object_unref (fail_icon); } label = gtk_label_new (NULL); markup = g_strdup_printf ("%s", _("Oh no! Something has gone wrong.")); gtk_label_set_markup (GTK_LABEL (label), markup); g_free (markup); gtk_widget_show (label); gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); if (!fail_dialog->allow_logout) message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. Please contact a system administrator")); else if (fail_dialog->extensions) message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. All extensions have been disabled as a precaution.")); else message_label = gtk_label_new (_("A problem has occurred and the system can’t recover.\nPlease log out and try again.")); gtk_label_set_justify (GTK_LABEL (message_label), GTK_JUSTIFY_CENTER); gtk_label_set_line_wrap (GTK_LABEL (message_label), TRUE); gtk_widget_show (message_label); gtk_box_pack_start (GTK_BOX (box), message_label, FALSE, FALSE, 0); button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_set_border_width (GTK_CONTAINER (button_box), 20); gtk_widget_show (button_box); gtk_box_pack_end (GTK_BOX (box), button_box, FALSE, FALSE, 0); if (fail_dialog->allow_logout) { button = gtk_button_new_with_mnemonic (_("_Log Out")); gtk_widget_show (button); gtk_box_pack_end (GTK_BOX (button_box), button, FALSE, FALSE, 0); g_signal_connect (button, "clicked", G_CALLBACK (on_logout_clicked), fail_dialog); } } static void gsm_fail_whale_dialog_init (GsmFailWhaleDialog *fail_dialog) { } static gboolean debug_mode = FALSE; static gboolean allow_logout = FALSE; static gboolean extensions = FALSE; static GList *dialogs = NULL; static void create_fail_dialog (GdkMonitor *monitor) { GsmFailWhaleDialog *fail_dialog; fail_dialog = g_object_new (GSM_TYPE_FAIL_WHALE_DIALOG, NULL); fail_dialog->debug_mode = debug_mode; fail_dialog->allow_logout = allow_logout; fail_dialog->extensions = extensions; fail_dialog->monitor = monitor; setup_window (fail_dialog); g_signal_connect (fail_dialog, "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_widget_show (GTK_WIDGET (fail_dialog)); dialogs = g_list_prepend (dialogs, fail_dialog); } static void on_monitor_added (GdkDisplay *display, GdkMonitor *monitor) { create_fail_dialog (monitor); } static void on_monitor_removed (GdkDisplay *display, GdkMonitor *monitor) { GList *l; for (l = dialogs; l;) { GList *next = l->next; GsmFailWhaleDialog *fail_dialog = l->data; if (fail_dialog->monitor == monitor) { dialogs = g_list_delete_link (dialogs, l); gtk_widget_destroy (GTK_WIDGET (fail_dialog)); } l = next; } } int main (int argc, char *argv[]) { GOptionEntry entries[] = { { "debug", 0, 0, G_OPTION_ARG_NONE, &debug_mode, N_("Enable debugging code"), NULL }, { "allow-logout", 0, 0, G_OPTION_ARG_NONE, &allow_logout, N_("Allow logout"), NULL }, { "extensions", 0, 0, G_OPTION_ARG_NONE, &extensions, N_("Show extension warning"), NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL } }; GError *error = NULL; GdkDisplay *display; int i; bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); if (!gtk_init_with_args (&argc, &argv, " - fail whale", entries, GETTEXT_PACKAGE, &error)) { if (error != NULL) { g_warning ("%s", error->message); exit (1); } /* display server probably went away. Could be for legitimate reasons, could be for * unexpected reasons. If it went away unexpectantly, that's logged elsewhere, so * let's not add noise by logging here. */ return 0; } /* Force-off allow_logout when running inside GDM, this is needed * because the systemd service always passes --allow-logout */ if (g_strcmp0 (g_getenv ("RUNNING_UNDER_GDM"), "true") == 0) allow_logout = FALSE; display = gdk_display_get_default (); for (i = 0; i < gdk_display_get_n_monitors (display); i++) { create_fail_dialog (gdk_display_get_monitor (display, i)); } g_signal_connect (display, "monitor-added", G_CALLBACK (on_monitor_added), &dialogs); g_signal_connect (display, "monitor-removed", G_CALLBACK (on_monitor_removed), &dialogs); gtk_main (); return 0; }