/* $Id: run.c,v 1.17 2012/02/23 04:58:20 khorben Exp $ */ /* Copyright (c) 2006-2012 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Panel */ /* 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, version 3 of the License. * * 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 . */ #include #include #include #include #include #include #include #include #include #include "../config.h" #define _(string) gettext(string) /* constants */ #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef DATADIR # define DATADIR PREFIX "/share" #endif #ifndef LOCALEDIR # define LOCALEDIR DATADIR "/locale" #endif /* Run */ /* types */ typedef struct _Run { Config * config; gboolean terminal; GPid pid; /* current child */ guint source; /* widgets */ GtkWidget * window; GtkWidget * entry; } Run; /* constants */ #define RUN_CONFIG_FILE ".runrc" /* functions */ /* useful */ static int _run_error(Run * run, char const * message, int ret); static char * _run_get_config_filename(void); /* callbacks */ static gboolean _on_run_closex(gpointer data); static void _on_run_cancel(gpointer data); static void _on_run_choose_activate(GtkWidget * widget, gint arg1, gpointer data); static void _on_run_execute(gpointer data); static void _on_run_path_activate(gpointer data); static void _on_run_terminal_toggle(GtkWidget * widget, gpointer data); /* run_new */ static GtkWidget * _new_entry(Config * config); static Run * _run_new(void) { Run * run; GtkWindow * window; GtkWidget * vbox; GtkWidget * hbox; GtkWidget * widget; GtkSizeGroup * group; GtkFileFilter * filter; if((run = object_new(sizeof(*run))) == NULL) return NULL; run->config = config_new(); run->terminal = FALSE; run->pid = -1; run->source = 0; run->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); window = GTK_WINDOW(run->window); gtk_window_set_keep_above(window, TRUE); gtk_window_set_icon_name(window, GTK_STOCK_EXECUTE); gtk_window_set_position(window, GTK_WIN_POS_CENTER_ALWAYS); gtk_window_set_resizable(window, FALSE); gtk_window_set_skip_pager_hint(window, TRUE); gtk_window_set_title(window, _("Run program...")); g_signal_connect_swapped(G_OBJECT(window), "delete-event", G_CALLBACK( _on_run_closex), run); group = gtk_size_group_new(GTK_SIZE_GROUP_BOTH); vbox = gtk_vbox_new(FALSE, 0); hbox = gtk_hbox_new(FALSE, 0); widget = gtk_label_new(_("Command:")); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 4); run->entry = _new_entry(run->config); g_signal_connect_swapped(G_OBJECT(run->entry), "activate", G_CALLBACK( _on_run_path_activate), run); gtk_box_pack_start(GTK_BOX(hbox), run->entry, TRUE, TRUE, 4); widget = gtk_file_chooser_dialog_new(_("Run program..."), window, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("Executable files")); gtk_file_filter_add_mime_type(filter, "application/x-executable"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(widget), filter); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("Shell scripts")); gtk_file_filter_add_mime_type(filter, "application/x-shellscript"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("All files")); gtk_file_filter_add_pattern(filter, "*"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(widget), filter); g_signal_connect(G_OBJECT(widget), "response", G_CALLBACK( _on_run_choose_activate), run); widget = gtk_file_chooser_button_new_with_dialog(widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 4); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 4); widget = gtk_check_button_new_with_label(_("Run in a terminal")); g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK( _on_run_terminal_toggle), run); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0); hbox = gtk_hbox_new(FALSE, 0); widget = gtk_button_new_from_stock(GTK_STOCK_EXECUTE); g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK( _on_run_execute), run); gtk_size_group_add_widget(group, widget); gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, TRUE, 4); widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL); gtk_size_group_add_widget(group, widget); g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK( _on_run_cancel), run); gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 4); gtk_container_add(GTK_CONTAINER(run->window), vbox); gtk_widget_show_all(run->window); return run; } static GtkWidget * _new_entry(Config * config) { GtkWidget * entry; char * p; char const * q; GtkEntryCompletion * completion; GtkListStore * store; int i; char buf[10]; GtkTreeIter iter; entry = gtk_entry_new(); if(config == NULL) return entry; if((p = _run_get_config_filename()) == NULL) return entry; if(config_load(config, p) != 0) error_print("run"); free(p); completion = gtk_entry_completion_new(); gtk_entry_set_completion(GTK_ENTRY(entry), completion); g_object_unref(completion); store = gtk_list_store_new(1, G_TYPE_STRING); gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store)); g_object_unref(store); gtk_entry_completion_set_text_column(completion, 0); for(i = 0; i < 100; i++) { snprintf(buf, sizeof(buf), "%s%d", "command", i); if((q = config_get(config, "", buf)) == NULL) continue; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, q, -1); } return entry; } /* run_delete */ static void _run_delete(Run * run) { if(run->config != NULL) config_delete(run->config); object_delete(run); } /* useful */ /* run_error */ static int _run_error(Run * run, char const * message, int ret) { GtkWidget * dialog; GtkWindow * window = (run != NULL) ? GTK_WINDOW(run->window) : NULL; dialog = gtk_message_dialog_new(window, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", #endif message); gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ALWAYS); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_widget_show(dialog); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } /* run_get_config_filename */ static char * _run_get_config_filename(void) { char const * homedir; if((homedir = getenv("HOME")) == NULL) homedir = g_get_home_dir(); return string_new_append(homedir, "/", RUN_CONFIG_FILE, NULL); } /* callbacks */ /* on_run_closex */ static gboolean _on_run_closex(gpointer data) { Run * run = data; gtk_widget_hide(run->window); gtk_main_quit(); return FALSE; } /* on_run_cancel */ static void _on_run_cancel(gpointer data) { Run * run = data; gtk_widget_hide(run->window); gtk_main_quit(); } /* on_run_choose_activate */ static void _on_run_choose_activate(GtkWidget * widget, gint arg1, gpointer data) { Run * run = data; if(arg1 != GTK_RESPONSE_ACCEPT) return; gtk_entry_set_text(GTK_ENTRY(run->entry), gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(widget))); } /* on_run_execute */ static void _execute_watch(GPid pid, gint status, gpointer data); static void _execute_save_config(Run * run); static gboolean _execute_timeout(gpointer data); static void _on_run_execute(gpointer data) { Run * run = data; char const * path; char * argv_shell[] = { "/bin/sh", "run", "-c", NULL, NULL }; char * argv_xterm[] = { "xterm", "xterm", "-e", "sh", "-c", NULL, NULL }; char ** argv = argv_shell; GSpawnFlags flags = G_SPAWN_FILE_AND_ARGV_ZERO | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD; GError * error = NULL; path = gtk_entry_get_text(GTK_ENTRY(run->entry)); if((argv[3] = strdup(path)) == NULL) { _run_error(run, strerror(errno), 1); return; } if(run->terminal) { argv_xterm[5] = argv[3]; argv = argv_xterm; } if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, &run->pid, &error) == FALSE) { _run_error(run, error->message, 1); g_error_free(error); return; } gtk_widget_hide(run->window); g_child_watch_add(run->pid, _execute_watch, run); run->source = g_timeout_add(30000, _execute_timeout, run); } static void _execute_watch(GPid pid, gint status, gpointer data) { Run * run = data; char const * error; if(run->pid != pid) return; /* should not happen */ if(!WIFEXITED(status)) return; switch(WEXITSTATUS(status)) { case 127: error = _("Command not found"); break; case 126: error = _("Permission denied"); break; default: _execute_save_config(run); gtk_main_quit(); return; } if(run->source != 0) g_source_remove(run->source); run->source = 0; gtk_widget_show(run->window); _run_error(run, error, 0); } static void _execute_save_config(Run * run) { char const * p; int i; char buf[10]; char const * q; char * filename; if((filename = _run_get_config_filename()) == NULL) return; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif config_reset(run->config); if(config_load(run->config, filename) != 0) /* XXX this risks losing the configuration */ error_print("run"); p = gtk_entry_get_text(GTK_ENTRY(run->entry)); for(i = 0; i < 100; i++) { snprintf(buf, sizeof(buf), "%s%d", "command", i); q = config_get(run->config, NULL, buf); if(q == NULL || strcmp(p, q) != 0) continue; free(filename); /* the command is already known */ return; } for(i = 0; i < 100; i++) { snprintf(buf, sizeof(buf), "%s%d", "command", i); q = config_get(run->config, NULL, buf); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() config_set(config, %s, %s, %s)\n", __func__, "\"\"", buf, p); #endif /* FIXME this invalidates q and then p in the next iteration */ config_set(run->config, NULL, buf, p); if((p = q) == NULL) break; } if(config_save(run->config, filename) != 0) error_print("run"); free(filename); } static gboolean _execute_timeout(gpointer data) { Run * run = data; _execute_save_config(run); gtk_main_quit(); return FALSE; } /* on_run_path_activate */ static void _on_run_path_activate(gpointer data) { _on_run_execute(data); } /* on_run_terminal_toggle */ static void _on_run_terminal_toggle(GtkWidget * widget, gpointer data) { Run * run = data; run->terminal = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } /* usage */ static int _usage(void) { fputs(_("Usage: run\n"), stderr); return 1; } /* main */ int main(int argc, char * argv[]) { int o; Run * run; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); gtk_init(&argc, &argv); while((o = getopt(argc, argv, "")) != -1) switch(o) { default: return _usage(); } if(optind != argc) return _usage(); if((run = _run_new()) == NULL) return _run_error(NULL, error_get(), 2); gtk_main(); _run_delete(run); return 0; }