/*-
* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1