/*- * Copyright (C) 2005-2007 Nick Withers. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "downtime.h" #include <errno.h> #include <gtk/gtk.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> #include <sys/param.h> #include <sys/resource.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #ifdef BSD #include <sysexits.h> #endif #include <time.h> static void destroy_progress_callback(void ***args); static void destroy_progress(shutdown_t *shutdown, GtkWidget *progress_window); static void exit_and_abort(shutdown_t *shutdown); static void exit_no_abort(void); static void file_selected_callback(void ***); static void file_selected(user_specs_t *specs, GtkWidget *file_selection, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static void pause_shutdown_callback(void ***args); static void pause_shutdown(shutdown_t *shutdown, GtkWidget *progress_bar); static void save_current_user_specs_callback(void ***args); static void save_current_user_specs(char *config_path, GtkWindow *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static void save_prefs_callback(void ***args); static void save_prefs(user_specs_t *specs, GtkWidget *window, GtkWidget *main_window_on_top_box, GtkWidget *progress_window_on_top_box, GtkWidget *main_window); static void select_and_open_config_callback(void ***); static void select_and_open_config(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static void show_about(void); static void show_prefs_callback(void ***args); static void show_prefs(user_specs_t *specs, GtkWidget *main_window); static void show_progress_window(shutdown_t *shutdown, user_specs_t *specs); static shutdown_t * start_shutdown_with_current_specs_callback(void ***args); static shutdown_t * start_shutdown_with_current_specs(user_specs_t *specs, GtkWidget *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static void update_main(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static gboolean update_progress_callback(void ***args); static gboolean update_progress(shutdown_t *shutdown, GtkWidget *progress_bar); static user_specs_t * update_user_specs(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember); static void destroy_progress_callback(void ***args) { destroy_progress(*args[0], *args[1]); } static void destroy_progress(shutdown_t *shutdown, GtkWidget *progress_window) { if (shutdown->paused) gtk_main_quit(); else if (shutdown->pid) { GtkWidget *window, *vbox, *label, *hbox, *button; char *shutdown_pending_message; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_transient_for(GTK_WINDOW (window), GTK_WINDOW (progress_window)); gtk_window_set_title(GTK_WINDOW (window), "Abort active shutdown? - " PRGNAME); gtk_window_set_resizable(GTK_WINDOW (window), FALSE); gtk_window_set_position(GTK_WINDOW (window), GTK_WIN_POS_CENTER); gtk_container_set_border_width (GTK_CONTAINER (window), 10); g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); vbox = gtk_vbox_new(FALSE, 0); if ((pasprintf(&shutdown_pending_message, "There is a shutdown pending (PID %d), would you like to terminate it?", shutdown->pid)) == -1) show_error_dialog(EX_OSERR, "pasprintf() returned -1 in create_ask_kill_window(). Error: \"%s\" (%d)", strerror(errno), errno); label = gtk_label_new(shutdown_pending_message); gtk_box_pack_start(GTK_BOX (vbox), label, FALSE, FALSE, 5); gtk_widget_show(label); free(shutdown_pending_message); /* No longer required by the GTK, it stores the string itself */ hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, FALSE, 5); button = gtk_button_new_from_stock(GTK_STOCK_YES); g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (exit_and_abort), shutdown); gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5); gtk_widget_show(button); button = gtk_button_new_from_stock(GTK_STOCK_NO); g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (exit_no_abort), NULL); gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5); gtk_widget_show(button); button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), window); gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5); gtk_widget_show(button); gtk_container_add(GTK_CONTAINER (window), vbox); gtk_widget_show(hbox); gtk_widget_show(vbox); gtk_widget_show(window); gtk_main(); } else { show_warning_dialog("Shutdown PID not known, please terminate the relevant shutdown process manually, if so desired"); gtk_main_quit(); } } static void exit_and_abort(shutdown_t *shutdown) { if (kill(shutdown->pid, SIGTERM) != 0) /* shutdown->pid is checked to ensure it's > 0 in shutdown.c */ show_error_dialog(EX_OSERR, "Couldn't kill PID %d, error: \"%s\" (%d).\n\nPlease kill the shutdown(8) process manually.", shutdown->pid, strerror(errno), errno); exit(EXIT_SUCCESS); } static void exit_no_abort(void) { exit(EXIT_SUCCESS); } static void file_selected_callback(void ***args) { file_selected(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5]); } static void file_selected(user_specs_t *specs, GtkWidget *file_selection, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { char *config_path; config_path = (char *) gtk_file_selection_get_filename(GTK_FILE_SELECTION (file_selection)); if (load_user_specs(specs, config_path)) /* Not doing any checking of config_path here. If for any reason it's invalid, load_settings() should spit */ update_main(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember); else show_warning_dialog("Unable to open configuration file \"%s\". Error: \"%s\" (%d)", config_path, strerror(errno), errno); } static void pause_shutdown_callback(void ***args) { pause_shutdown(*args[0], *args[1]); } static void pause_shutdown(shutdown_t *shutdown, GtkWidget *progress_bar) { time_t current_time; current_time = time(NULL); if (!shutdown->paused) /* The shutdown isn't currently paused */ { if (kill(shutdown->pid, SIGTERM) != 0) /* We're not checking that the PID is valid here as this has already been done in start_shutdown(), which must have been successfully called if we're here */ { show_warning_dialog("Couldn't kill PID %d in order to pause the shutdown. Error: \"%s\" (%d)", shutdown->pid, strerror(errno), errno); } shutdown->timeout = max_minutes_to_shutdown(shutdown); shutdown->start_time -= current_time; /* shutdown->start_time now holds the start time relative to the current time (<= 0), rather than relative to the epoch */ shutdown->paused = TRUE; /* Indicate that a shutdown is paused. This'll stop the progress bar from updating */ gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), "(Paused)"); } else { user_specs_t specs; char *str_time_to_shutdown; shutdown->start_time += current_time; shutdown->shutdown_time = current_time + (shutdown->timeout * 60); specs.timeout = shutdown->timeout; specs.timeout_unit = MINUTES; specs.shutdown_type = shutdown->type; specs.remember = FALSE; if ((start_shutdown(&specs, shutdown)) == NULL) show_error_dialog(EX_OSERR, "Couldn't re-launch shutdown(8) following a pause. Error: \"%s\" (%d)", strerror(errno), errno); shutdown->paused = FALSE; /* Indicate that no shutdown is paused, allowing the progress bar to start updating again */ gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), str_time_to_shutdown = get_shutdown_time_left_string(shutdown)); free(str_time_to_shutdown); /* The GTK doesn't need this anymore, as it copies the text to its own memory area */ } } static void save_current_user_specs_callback(void ***args) { save_current_user_specs(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5]); } static void save_current_user_specs(char *config_path, GtkWindow *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { user_specs_t specs; if (update_user_specs(&specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember) == NULL) return; save_user_specs(&specs, config_path); } static void select_and_open_config_callback(void ***args) { select_and_open_config(*args[0], *args[1], *args[2], *args[3], *args[4]); } static void select_and_open_config(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { GtkWidget *file_selection; void **file_selected_callback_args[6]; file_selected_callback_args[0] = (void **) &specs; file_selected_callback_args[1] = (void **) &file_selection; file_selected_callback_args[2] = (void **) &timeout_field; file_selected_callback_args[3] = (void **) &timeout_unit_combo_box; file_selected_callback_args[4] = (void **) &shutdown_type_combo_box; file_selected_callback_args[5] = (void **) &toggle_remember; file_selection = gtk_file_selection_new("Select configuration file"); gtk_file_selection_set_filename(GTK_FILE_SELECTION (file_selection), specs->config_path == NULL ? CONFIG_FILE_NAME : specs->config_path); g_signal_connect(G_OBJECT (file_selection), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect(G_OBJECT (file_selection), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->ok_button), "clicked", G_CALLBACK (file_selected_callback), file_selected_callback_args); g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->ok_button), "clicked", G_CALLBACK (gtk_widget_destroy), file_selection); g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->cancel_button), "clicked", G_CALLBACK (gtk_widget_destroy), file_selection); gtk_widget_show(GTK_WIDGET (file_selection)); gtk_main(); } static void save_prefs_callback(void ***args) { save_prefs(*args[0], *args[1], *args[2], *args[3], *args[4]); } static void save_prefs(user_specs_t *specs, GtkWidget *window, GtkWidget *main_window_on_top_box, GtkWidget *progress_window_on_top_box, GtkWidget *main_window) { user_specs_t save_specs; init_user_specs(&save_specs); save_specs.main_window_always_on_top = specs->main_window_always_on_top = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (main_window_on_top_box)); save_specs.progress_window_always_on_top = specs->progress_window_always_on_top = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (progress_window_on_top_box)); gtk_window_set_keep_above(GTK_WINDOW(main_window), save_specs.main_window_always_on_top); if (!specs->config_path) show_stderr("No configuration file open, preferences will not be remembered"); else if (!load_user_specs(&save_specs, specs->config_path) && errno != ENOENT) /* If there ain't an existing configuration file, we'll just write a new 'un */ show_warning_dialog("Couldn't update preferences in configuration file \"%s\". Error: \"%s\" (%d)", specs->config_path, strerror(errno), errno); else save_user_specs(&save_specs, specs->config_path); gtk_widget_destroy(window); } static void show_about(void) { GtkWidget *about_dialog; char *authors[] = AUTHORS; about_dialog = gtk_about_dialog_new(); gtk_about_dialog_set_name(GTK_ABOUT_DIALOG (about_dialog), PRGNAME); gtk_about_dialog_set_version(GTK_ABOUT_DIALOG (about_dialog), VERSION); gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG (about_dialog), COPYRIGHT); gtk_about_dialog_set_website(GTK_ABOUT_DIALOG (about_dialog), CONTACT_WWW); gtk_about_dialog_set_license(GTK_ABOUT_DIALOG (about_dialog), LICENSE_LONG); gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG (about_dialog), (const gchar **) authors); g_signal_connect(GTK_ABOUT_DIALOG (about_dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_widget_show(about_dialog); } void show_main_window(user_specs_t *specs) { GtkAccelGroup *accel_group; GtkWidget *window, *timeout_field, *timeout_unit_combo_box, *shutdown_type_combo_box, *toggle_remember; GtkWidget *menu_bar, *menu, *menu_item, *vbox, *hbox, *label, *separator, *button, *ok_button; void **select_and_open_config_callback_args[5], **show_prefs_callback_args[2], **save_current_user_specs_callback_args[6], **start_shutdown_with_current_specs_callback_args[6]; select_and_open_config_callback_args[0] = (void **) &specs; select_and_open_config_callback_args[1] = (void **) &timeout_field; select_and_open_config_callback_args[2] = (void **) &timeout_unit_combo_box; select_and_open_config_callback_args[3] = (void **) &shutdown_type_combo_box; select_and_open_config_callback_args[4] = (void **) &toggle_remember; save_current_user_specs_callback_args[0] = (void **) &(specs->config_path); save_current_user_specs_callback_args[1] = (void **) &window; save_current_user_specs_callback_args[2] = (void **) &timeout_field; save_current_user_specs_callback_args[3] = (void **) &timeout_unit_combo_box; save_current_user_specs_callback_args[4] = (void **) &shutdown_type_combo_box; save_current_user_specs_callback_args[5] = (void **) &toggle_remember; show_prefs_callback_args[0] = (void **) &specs; show_prefs_callback_args[1] = (void **) &window; start_shutdown_with_current_specs_callback_args[0] = (void **) &specs; start_shutdown_with_current_specs_callback_args[1] = (void **) &window; start_shutdown_with_current_specs_callback_args[2] = (void **) &timeout_field; start_shutdown_with_current_specs_callback_args[3] = (void **) &timeout_unit_combo_box; start_shutdown_with_current_specs_callback_args[4] = (void **) &shutdown_type_combo_box; start_shutdown_with_current_specs_callback_args[5] = (void **) &toggle_remember; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); gtk_window_set_title(GTK_WINDOW(window), PRGNAME); gtk_window_set_resizable(GTK_WINDOW(window), FALSE); gtk_window_set_keep_above(GTK_WINDOW(window), specs->main_window_always_on_top); accel_group = gtk_accel_group_new(); gtk_window_add_accel_group(GTK_WINDOW (window), accel_group); vbox = gtk_vbox_new(FALSE, 0); menu_bar = gtk_menu_bar_new(); gtk_box_pack_start(GTK_BOX (vbox), menu_bar, FALSE, FALSE, 0); /* File menu */ menu = gtk_menu_new(); menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, accel_group); g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (select_and_open_config_callback), select_and_open_config_callback_args); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, accel_group); g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (save_current_user_specs_callback), save_current_user_specs_callback_args); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group); g_signal_connect(G_OBJECT (menu_item), "activate", G_CALLBACK (gtk_main_quit), NULL); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_menu_item_new_with_mnemonic("_File"); gtk_widget_show(menu_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu); gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item); /* Edit menu */ menu = gtk_menu_new(); menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, accel_group); g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (show_prefs_callback), show_prefs_callback_args); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_menu_item_new_with_mnemonic("_Edit"); gtk_widget_show(menu_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu); gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item); /* Help menu */ menu = gtk_menu_new(); menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, accel_group); g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (show_about), NULL); gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item); gtk_widget_show(menu_item); menu_item = gtk_menu_item_new_with_mnemonic("_Help"); gtk_widget_show(menu_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu); gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item); gtk_widget_show(menu_bar); /* * Timeout selection */ hbox = gtk_hbox_new(FALSE, 0); label = gtk_label_new("Timeout:"); gtk_box_pack_start(GTK_BOX (hbox), label, FALSE, FALSE, 5); gtk_widget_show(label); timeout_field = gtk_entry_new(); g_signal_connect_swapped(G_OBJECT (timeout_field), "activate", G_CALLBACK (start_shutdown_with_current_specs_callback), start_shutdown_with_current_specs_callback_args); gtk_box_pack_start(GTK_BOX (hbox), timeout_field, FALSE, FALSE, 5); gtk_widget_show(timeout_field); timeout_unit_combo_box = gtk_combo_box_new_text(); gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "minutes"); /* Be careful of order here - replicate in TIMEOUT_TYPES (in downtime.h) */ gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "hours"); gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "days"); gtk_combo_box_set_active(GTK_COMBO_BOX (timeout_unit_combo_box), (gint) specs->timeout_unit); gtk_box_pack_start(GTK_BOX (hbox), timeout_unit_combo_box, FALSE, FALSE, 5); gtk_widget_show(timeout_unit_combo_box); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); /* * Shutdown type selection */ hbox = gtk_hbox_new(FALSE, 0); label = gtk_label_new("Shutdown type:"); gtk_box_pack_start(GTK_BOX (hbox), label, FALSE, FALSE, 5); gtk_widget_show(label); shutdown_type_combo_box = gtk_combo_box_new_text(); gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Power-off"); /* Be careful of order here - replicate in SHUTDOWN_TYPES (in downtime.h) */ gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Restart"); gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Halt"); gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Single user mode"); gtk_combo_box_set_active(GTK_COMBO_BOX (shutdown_type_combo_box), (gint) specs->shutdown_type); gtk_box_pack_start(GTK_BOX (hbox), shutdown_type_combo_box, FALSE, TRUE, 5); gtk_widget_show(shutdown_type_combo_box); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); separator = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX (vbox), separator, FALSE, TRUE, 5); gtk_widget_show(separator); /* * "Remember these settings" */ toggle_remember = gtk_check_button_new_with_label("Remember these settings"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (toggle_remember), specs->remember); gtk_box_pack_start(GTK_BOX (vbox), toggle_remember, FALSE, TRUE, 5); gtk_widget_show(toggle_remember); separator = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX (vbox), separator, FALSE, TRUE, 5); gtk_widget_show(separator); /* * OK / Cancel buttons */ hbox = gtk_hbox_new(FALSE, 0); button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, TRUE, 5); gtk_widget_show(button); ok_button = gtk_button_new_from_stock(GTK_STOCK_OK); g_signal_connect_swapped(G_OBJECT (ok_button), "clicked", G_CALLBACK (start_shutdown_with_current_specs_callback), start_shutdown_with_current_specs_callback_args); gtk_box_pack_start(GTK_BOX (hbox), ok_button, FALSE, TRUE, 5); gtk_widget_show(ok_button); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER (window), vbox); gtk_widget_show(vbox); GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT); gtk_widget_grab_default(ok_button); update_main(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember); gtk_widget_show(GTK_WIDGET (window)); gtk_main(); } void show_error_dialog(int eval, const char *fmt, ...) { va_list ap; char *exp_fmt; GtkWidget *message_dialog; va_start(ap, fmt); if ((pvasprintf(&exp_fmt, fmt, ap)) == -1) { show_stderr("pvasprintf() returned -1 in show_error_dialog(). Error: \"%s\" (%d)", strerror(errno), errno); exit(EX_OSERR); } va_end(ap); show_stderr(exp_fmt); message_dialog = gtk_message_dialog_new(NULL, (GtkDialogFlags) NULL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, exp_fmt); gtk_window_set_title(GTK_WINDOW (message_dialog), "Error - " PRGNAME); gtk_dialog_run(GTK_DIALOG (message_dialog)); gtk_widget_destroy(message_dialog); exit(eval); } static void show_prefs_callback(void ***args) { show_prefs(*args[0], *args[1]); } static void show_prefs(user_specs_t *specs, GtkWidget *main_window) { GtkWidget *window, *main_window_on_top_box, *progress_window_on_top_box; GtkWidget *vbox, *hbox, *button_cancel, *button_ok; void **save_prefs_callback_args[5]; save_prefs_callback_args[0] = (void **) &specs; save_prefs_callback_args[1] = (void **) &window; save_prefs_callback_args[2] = (void **) &main_window_on_top_box; save_prefs_callback_args[3] = (void **) &progress_window_on_top_box; save_prefs_callback_args[4] = (void **) &main_window; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Preferences - " PRGNAME); gtk_window_set_resizable(GTK_WINDOW(window), FALSE); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); vbox = gtk_vbox_new(FALSE, 0); hbox = gtk_hbox_new(FALSE, 0); main_window_on_top_box = gtk_check_button_new_with_mnemonic("Main window always on _top"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (main_window_on_top_box), specs->main_window_always_on_top); gtk_box_pack_start(GTK_BOX (hbox), main_window_on_top_box, FALSE, TRUE, 5); gtk_widget_show(main_window_on_top_box); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); hbox = gtk_hbox_new(FALSE, 0); progress_window_on_top_box = gtk_check_button_new_with_mnemonic("_Progress window always on top"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (progress_window_on_top_box), specs->progress_window_always_on_top); gtk_box_pack_start(GTK_BOX (hbox), progress_window_on_top_box, FALSE, TRUE, 5); gtk_widget_show(progress_window_on_top_box); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); hbox = gtk_hbox_new(FALSE, 0); button_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL); button_ok = gtk_button_new_from_stock(GTK_STOCK_OK); gtk_box_pack_start(GTK_BOX (hbox), button_cancel, FALSE, TRUE, 5); gtk_box_pack_start(GTK_BOX (hbox), button_ok, FALSE, TRUE, 5); gtk_widget_show(button_ok); gtk_widget_show(button_cancel); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER (window), vbox); gtk_widget_show(vbox); g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect_swapped(G_OBJECT (button_cancel), "clicked", G_CALLBACK (gtk_widget_destroy), window); g_signal_connect_swapped(G_OBJECT (button_ok), "clicked", G_CALLBACK (save_prefs_callback), save_prefs_callback_args); GTK_WIDGET_SET_FLAGS(button_ok, GTK_CAN_DEFAULT); gtk_widget_grab_default(button_ok); gtk_widget_show(window); gtk_main(); } static void show_progress_window(shutdown_t *shutdown, user_specs_t *specs) { GtkWidget *window, *progress_bar, *vbox, *hbox, *button, *close_button; char *str_time_to_shutdown; void **destroy_progress_callback_args[2], **update_progress_callback_args[2], **pause_shutdown_callback_args[2]; destroy_progress_callback_args[0] = (void **) &shutdown; destroy_progress_callback_args[1] = (void **) &window; update_progress_callback_args[0] = (void **) &shutdown; update_progress_callback_args[1] = (void **) &progress_bar; pause_shutdown_callback_args[0] = (void **) &shutdown; pause_shutdown_callback_args[1] = (void **) &progress_bar; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect_swapped(G_OBJECT (window), "delete_event", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args); g_signal_connect_swapped(G_OBJECT (window), "destroy", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args); gtk_window_set_title(GTK_WINDOW (window), "Shutdown progress - " PRGNAME); gtk_window_set_resizable(GTK_WINDOW (window), FALSE); gtk_window_set_position(GTK_WINDOW (window), GTK_WIN_POS_CENTER); gtk_window_set_keep_above(GTK_WINDOW(window), specs->progress_window_always_on_top); gtk_container_set_border_width (GTK_CONTAINER (window), 10); vbox = gtk_vbox_new(FALSE, 0); progress_bar = gtk_progress_bar_new(); gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), (str_time_to_shutdown = get_shutdown_time_left_string(shutdown))); g_timeout_add((((shutdown->shutdown_time - shutdown->start_time) > 100) ? 1000 : (shutdown->shutdown_time - shutdown->start_time) * 10), (GSourceFunc) update_progress_callback, update_progress_callback_args); /* Read "((shutdown->shutdown_time - shutdown->start_time) > 100) ? 1000" as "(shutdown->shutdown_time - shutdown->start_time) * 10 > 1000". * 10 because the first part of the expression should be divided by 100 to get a percentage, then multiplied by 1000 to go from seconds to milliseconds */ free(str_time_to_shutdown); gtk_box_pack_start(GTK_BOX (vbox), progress_bar, FALSE, FALSE, 5); gtk_widget_show(progress_bar); hbox = gtk_hbox_new(FALSE, 0); button = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PAUSE); g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (pause_shutdown_callback), pause_shutdown_callback_args); if (!shutdown->pid) gtk_widget_set_sensitive(button, FALSE); /* Disable the pause button if we don't know the shutdown(8) process (if any)' PID */ gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5); gtk_widget_show(button); close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); g_signal_connect_swapped(G_OBJECT (close_button), "clicked", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args); gtk_box_pack_start(GTK_BOX (hbox), close_button, FALSE, FALSE, 5); gtk_widget_show(close_button); gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, FALSE, 5); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER (window), vbox); gtk_widget_show(vbox); gtk_widget_show(window); gtk_main(); } void show_stderr(const char *fmt, ...) { va_list ap; char *exp_fmt; time_t curr_time; struct tm *tm; va_start(ap, fmt); if ((pvasprintf(&exp_fmt, fmt, ap)) == -1) show_error_dialog(EX_OSERR, "pvasprintf() returned -1 in show_stderr(). Error: \"%s\" (%d)", strerror(errno), errno); va_end(ap); curr_time = time((time_t *) NULL); tm = localtime(&curr_time); fprintf(stderr, "%d-%.2d-%.2d %.2d:%.2d:%.2d %s: %s\n", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, PRGNAME, exp_fmt); } void show_warning_dialog(const char *fmt, ...) { va_list ap; char *exp_fmt; GtkWidget *message_dialog; va_start(ap, fmt); if ((pvasprintf(&exp_fmt, fmt, ap)) == -1) show_error_dialog(EX_OSERR, "pvasprintf() returned -1 in show_warning_dialog(). Error: \"%s\" (%d)", strerror(errno), errno); va_end(ap); show_stderr(exp_fmt); message_dialog = gtk_message_dialog_new(NULL, (GtkDialogFlags) NULL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, exp_fmt); gtk_window_set_title(GTK_WINDOW (message_dialog), "Warning - " PRGNAME); gtk_dialog_run(GTK_DIALOG (message_dialog)); gtk_widget_destroy(message_dialog); free(exp_fmt); } static shutdown_t * start_shutdown_with_current_specs_callback(void ***args) { return (start_shutdown_with_current_specs(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5])); } static shutdown_t * start_shutdown_with_current_specs(user_specs_t *specs, GtkWidget *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { shutdown_t *shutdown; if (update_user_specs(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember) == NULL) /* If we weren't able to successfully read the user specs... Note also that update_user_specs() deals with alerting the user about input dramas */ return (NULL); if (specs->remember) save_user_specs(specs, specs->config_path); if ((shutdown = start_shutdown(specs, NULL))) { gtk_widget_destroy(GTK_WIDGET (main_window)); show_progress_window(shutdown, specs); } return (shutdown); } static void update_main(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { char *timeout; if ((pasprintf(&timeout, "%lu", specs->timeout)) == -1) show_error_dialog(EX_OSERR, "pasprintf() returned -1 in update_main(). Error: \"%s\" (%d)", strerror(errno), errno); gtk_entry_set_text(GTK_ENTRY (timeout_field), timeout); free(timeout); gtk_combo_box_set_active(GTK_COMBO_BOX (timeout_unit_combo_box), (gint) specs->timeout_unit); gtk_combo_box_set_active(GTK_COMBO_BOX (shutdown_type_combo_box), (gint) specs->shutdown_type); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (toggle_remember), specs->remember); } static gboolean update_progress_callback(void ***args) { return (update_progress(*args[0], *args[1])); } static gboolean update_progress(shutdown_t *shutdown, GtkWidget *progress_bar) { double new_progress_percent; char *str_time_to_shutdown; if (shutdown->paused) /* If we're paused, we only need to update the progress bar text */ return (TRUE); /* Return TRUE so that the timer call to this function isn't canned */ if (!shutdown->in_progress) return (FALSE); if (shutdown->pid && kill(shutdown->pid, 0) != 0) /* Ensure that shutdown(8) is still running. shutdown->pid is checked to ensure it's > 0 in shutdown.c */ { int status; waitpid(shutdown->pid, &status, WNOHANG); if (WIFEXITED (status)) show_error_dialog(EX_UNAVAILABLE, "shutdown(8) (PID: %ld) exited unexpectedly with exit status %d (\"%s\")", shutdown->pid, WEXITSTATUS (status), strerror(WEXITSTATUS (status))); else show_error_dialog(EX_UNAVAILABLE, "shutdown(8) (PID: %ld) terminated unexpectedly", shutdown->pid); } new_progress_percent = difftime(shutdown->shutdown_time, shutdown->start_time) > 0 ? (100 * ((double) difftime(time(NULL), shutdown->start_time)) / (double) (difftime(shutdown->shutdown_time, shutdown->start_time))) : 0; if (new_progress_percent > 100) new_progress_percent = 100; else if (new_progress_percent < 0) new_progress_percent = 0; gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR (progress_bar), new_progress_percent / 100); gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), (str_time_to_shutdown = get_shutdown_time_left_string(shutdown))); free(str_time_to_shutdown); return (TRUE); } /* Updates the structure pointed to by specs with user-supplied specifications from the DownTime main dialogue. DO NOT call after the main window has been destroyed */ static user_specs_t * update_user_specs(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember) { char *nptr, *endptr; long int timeout; nptr = (char *) gtk_entry_get_text(GTK_ENTRY (timeout_field)); timeout = (unsigned long) strtol(nptr, &endptr, 10); if (!(*nptr != '\0' && *endptr == '\0')) { show_warning_dialog("An invalid timeout - \"%s\" - was specified", nptr); return (NULL); } specs->timeout = timeout; specs->timeout_unit = gtk_combo_box_get_active(GTK_COMBO_BOX (timeout_unit_combo_box)); specs->shutdown_type = gtk_combo_box_get_active(GTK_COMBO_BOX (shutdown_type_combo_box)); specs->remember = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (toggle_remember)); return (specs); }