/*
* wmwrapper.c:
* a gtk wrapper that acts as a fallback to keep X running in
* the event of a window manager crash, non-zero exit status,
* or brief run time.
*
* Compiling:
* gcc wmwrapper.c -o wmwrapper `pkg-config --cflags gtk+-2.0` \
* `pkg-config --libs gtk+-2.0`
*
* Usage (~/.xinitrc):
* wmwrapper
* wmwrapper openbox
* wmwrapper fluxbox
*
* Example Configuration (~/.wmwrapperrc):
* [config]
* # specify a list of window managers to show in the dropdown
* wmlist=blackbox;fluxbox;openbox;wmaker;xterm
* # end X session if window manager exits with a status of 0,
* # false to always show wmwrapper dialog
* exit_on_zero=true
* # if exit_on_zero is true and the window manager exits with
* # status of 0, but runs less than safety_timeout seconds,
* # show dialog
* safety_timeout=5
* # if exit_on_zero is true and the window manager exits with
* # a status of 0, but this file exists, show dialog
* noexit_file=~/.wmwrapper_noexit
*
* Related:
* http://ordiluc.net/selectwm/
*
* Mike Hokenson
*/
#include
#include
#include
#include
#include
#include
/* configuration options */
static int safety_timeout = 5;
static int exit_on_zero = TRUE;
static gchar **wmlist, *noexit_file;
static GtkWidget *status_label;
void set_status(const gchar *text)
{
if (text) {
gchar *markup = g_strdup_printf("%s", text);
gtk_label_set_markup(GTK_LABEL(status_label), markup);
g_free(markup);
gtk_widget_show(status_label);
} else
gtk_widget_hide(status_label);
}
void execute(GtkWidget *widget, gpointer data)
{
GtkComboBox *combo = data;
GtkWidget *entry = GTK_BIN(combo)->child;
GtkWidget *window = GTK_WIDGET(combo)->parent->parent;
const gchar *command = gtk_entry_get_text(GTK_ENTRY(entry));
GError *err = NULL;
time_t start;
gint status;
set_status(NULL);
if (!*command) {
set_status("empty command");
return;
}
gtk_widget_hide(window);
while (gtk_events_pending())
gtk_main_iteration();
start = time(NULL);
/* capturing stdout/stderr would be nice, but it's making a zombie
of the wm until any and all applications it spawned exit, meaning
the wmwrapper dialog won't show up */
if (!g_spawn_command_line_sync(command, NULL, NULL, &status, &err)) {
set_status(err->message);
g_error_free(err);
} else {
if (exit_on_zero && status == 0) {
gint seconds = time(NULL) - start;
gchar *text = NULL;
if (safety_timeout && seconds < safety_timeout)
text = g_strdup_printf("\"%s\" finished in %d second(s), below the safety_timeout of %d second(s)", command, seconds, safety_timeout);
else if (g_file_test(noexit_file, G_FILE_TEST_IS_REGULAR))
text = g_strdup_printf("\"%s\" found", noexit_file);
else {
if (gtk_main_level() > 0)
gtk_main_quit();
else
exit(0);
}
if (text)
set_status(text);
g_free(text);
}
gtk_combo_box_prepend_text(combo, command);
}
gtk_widget_show(window);
gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
}
void destroy(GtkWidget *widget, gpointer data)
{
gtk_main_quit();
}
gchar *expand_tilde(const gchar *str)
{
if (!str || !str[0])
return g_strdup("");
if (strncmp(str, "~/", 2))
return g_strdup(str);
return g_build_filename(g_get_home_dir(), str + 2, NULL);
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *button;
GtkComboBox *combo;
GtkWidget *entry;
GString *command;
gchar *config;
GKeyFile *keyfile;
/* -- load configuration ------------------------------------------------- */
config = g_build_filename(g_get_home_dir(), ".wmwrapperrc", NULL);
keyfile = g_key_file_new();
if (g_file_test(config, G_FILE_TEST_IS_REGULAR)) {
GError *err = NULL;
if (!g_key_file_load_from_file(keyfile, config, G_KEY_FILE_NONE, &err)) {
g_critical("%s\n", err->message);
g_error_free(err);
}
if (g_key_file_has_key(keyfile, "config", "wmlist", NULL))
wmlist = g_key_file_get_string_list(keyfile, "config", "wmlist",
NULL, NULL);
if (g_key_file_has_key(keyfile, "config", "safety_timeout", NULL)) {
gint n = g_key_file_get_integer(keyfile, "config", "safety_timeout",
NULL);
if (n >= 0)
safety_timeout = n;
}
if (g_key_file_has_key(keyfile, "config", "exit_on_zero", NULL))
exit_on_zero = g_key_file_get_boolean(keyfile, "config", "exit_on_zero",
NULL);
if (g_key_file_has_key(keyfile, "config", "noexit_file", NULL)) {
gchar *s = g_key_file_get_string(keyfile, "config", "noexit_file", NULL);
noexit_file = expand_tilde(s);
g_free(s);
}
} else
noexit_file = g_build_filename(g_get_home_dir(), ".wmwrapper_noexit", NULL);
/* -- process any command line arguments --------------------------------- */
command = g_string_new(NULL);
if (argc > 1) {
gint i;
for (i = 1; i < argc; i++) {
if (command->len)
command = g_string_append(command, " ");
command = g_string_append(command, argv[i]);
}
}
/* -- create window ------------------------------------------------------ */
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_stick(GTK_WINDOW(window));
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_widget_set_size_request(window, 250, -1);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
vbox = gtk_vbox_new(FALSE, 3);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show(vbox);
combo = (GtkComboBox *) gtk_combo_box_entry_new_text();
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(combo), FALSE, FALSE, 0);
gtk_widget_show(GTK_WIDGET(combo));
status_label = gtk_label_new(NULL);
gtk_widget_set_size_request(status_label, 225, -1);
gtk_label_set_line_wrap(GTK_LABEL(status_label), TRUE);
gtk_label_set_justify(GTK_LABEL(status_label), GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(status_label), FALSE, FALSE, 0);
entry = GTK_BIN(combo)->child;
gtk_entry_set_text(GTK_ENTRY(entry), command->str);
gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
gtk_entry_set_activates_default(GTK_ENTRY(GTK_BIN(combo)->child), TRUE);
if (wmlist) {
gint i;
for (i = 0; wmlist[i]; i++)
gtk_combo_box_append_text(combo, wmlist[i]);
} else {
gtk_combo_box_append_text(combo, "fluxbox");
gtk_combo_box_append_text(combo, "openbox");
gtk_combo_box_append_text(combo, "wmaker");
gtk_combo_box_append_text(combo, "xterm");
}
hbox = gtk_hbox_new(FALSE, 2);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show(hbox);
gtk_box_set_homogeneous(GTK_BOX(hbox), TRUE);
button = gtk_button_new_from_stock(GTK_STOCK_EXECUTE);
gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
gtk_widget_show(button);
GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
gtk_window_set_default(GTK_WINDOW(window), button);
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(execute), combo);
button = gtk_button_new_from_stock(GTK_STOCK_QUIT);
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(destroy), NULL);
gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
gtk_widget_show(button);
if (command->len)
execute(NULL, combo);
else
gtk_widget_show(window);
gtk_main();
g_string_free(command, TRUE);
g_strfreev(wmlist);
g_free(noexit_file);
g_key_file_free(keyfile);
g_free(config);
exit(0);
}