diff --git a/Makefile b/Makefile index fb66345..d64644f 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ dist: $(PACKAGE)-$(VERSION)/include/project.conf \ $(PACKAGE)-$(VERSION)/src/panel.c \ $(PACKAGE)-$(VERSION)/src/main.c \ + $(PACKAGE)-$(VERSION)/src/run.c \ $(PACKAGE)-$(VERSION)/src/Makefile \ $(PACKAGE)-$(VERSION)/src/common.h \ $(PACKAGE)-$(VERSION)/src/project.conf \ diff --git a/src/Makefile b/src/Makefile index 42035a1..8451cdd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ SUBDIRS = applets -TARGETS = panel +TARGETS = panel run PREFIX = /usr/local DESTDIR = BINDIR = $(PREFIX)/bin @@ -8,7 +8,8 @@ CPPFLAGSF= -I ../include CPPFLAGS= CFLAGSF = -W `pkg-config gtk+-2.0 --cflags` CFLAGS = -Wall -g -O2 -pedantic -LDFLAGSF= `pkg-config gtk+-2.0 --libs` -L$(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib -lSystem +LDFLAGSF= `pkg-config gtk+-2.0 --libs` -lSystem +LDFLAGS = -L$(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib RM = rm -f LN = ln -f MKDIR = mkdir -p @@ -27,28 +28,41 @@ panel_LDFLAGS = $(LDFLAGSF) $(LDFLAGS) panel: $(panel_OBJS) $(CC) -o panel $(panel_OBJS) $(panel_LDFLAGS) +run_OBJS = run.o +run_CFLAGS = $(CPPFLAGSF) $(CPPFLAGS) $(CFLAGSF) $(CFLAGS) +run_LDFLAGS = $(LDFLAGSF) $(LDFLAGS) + +run: $(run_OBJS) + $(CC) -o run $(run_OBJS) $(run_LDFLAGS) + panel.o: panel.c common.h ../include/Panel.h $(CC) $(panel_CFLAGS) -c panel.c main.o: main.c common.h $(CC) $(panel_CFLAGS) -c main.c +run.o: run.c + $(CC) $(run_CFLAGS) -c run.c + clean: @for i in $(SUBDIRS); do (cd $$i && $(MAKE) clean) || exit; done - $(RM) $(panel_OBJS) + $(RM) $(panel_OBJS) $(run_OBJS) distclean: @for i in $(SUBDIRS); do (cd $$i && $(MAKE) distclean) || exit; done - $(RM) $(panel_OBJS) + $(RM) $(panel_OBJS) $(run_OBJS) $(RM) $(TARGETS) install: all @for i in $(SUBDIRS); do (cd $$i && $(MAKE) install) || exit; done $(MKDIR) $(DESTDIR)$(BINDIR) $(INSTALL) -m 0755 panel $(DESTDIR)$(BINDIR)/panel + $(MKDIR) $(DESTDIR)$(BINDIR) + $(INSTALL) -m 0755 run $(DESTDIR)$(BINDIR)/run uninstall: @for i in $(SUBDIRS); do (cd $$i && $(MAKE) uninstall) || exit; done $(RM) $(DESTDIR)$(BINDIR)/panel + $(RM) $(DESTDIR)$(BINDIR)/run .PHONY: all subdirs clean distclean install uninstall diff --git a/src/project.conf b/src/project.conf index 0579671..60a502b 100644 --- a/src/project.conf +++ b/src/project.conf @@ -1,9 +1,10 @@ subdirs=applets -targets=panel +targets=panel,run cppflags_force=-I ../include cflags_force=-W `pkg-config gtk+-2.0 --cflags` cflags=-Wall -g -O2 -pedantic -ldflags_force=`pkg-config gtk+-2.0 --libs` -L$(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib -lSystem +ldflags_force=`pkg-config gtk+-2.0 --libs` -lSystem +ldflags=-L$(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib dist=Makefile,common.h [panel] @@ -18,3 +19,8 @@ depends=common.h [panel.c] depends=common.h,../include/Panel.h + +[run] +type=binary +sources=run.c +install=$(BINDIR) diff --git a/src/run.c b/src/run.c new file mode 100644 index 0000000..d0c4d1c --- /dev/null +++ b/src/run.c @@ -0,0 +1,368 @@ +/* $Id$ */ +/* Copyright (c) 2009 Pierre Pronchery */ +/* This file is part of DeforaOS Desktop Accessories */ +/* Accessories is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + * + * Accessories 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 + * Accessories; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA */ + + + +#include +#include +#include +#include +#include +#include +#include + + +/* run */ +/* types */ +typedef struct _Run +{ + Config * config; + gboolean terminal; + + /* widgets */ + GtkWidget * window; + GtkWidget * entry; + + /* internal */ + pid_t pid; /* current child */ +} Run; + + +/* constants */ +#define RUN_CONFIG_FILE ".runrc" + + +/* functions */ +/* useful */ +static int _run_error(char const * message, int ret); +static char * _run_get_config_filename(void); + +/* callbacks */ +static gboolean _on_run_closex(GtkWidget * widget, GdkEvent * event, + gpointer data); +static void _on_run_cancel(GtkWidget * widget, gpointer data); +static void _on_run_choose_activate(GtkWidget * widget, gint arg1, + gpointer data); +static void _on_run_execute(GtkWidget * widget, gpointer data); +static void _on_run_path_activate(GtkWidget * widget, 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; + + if((run = object_new(sizeof(*run))) == NULL) + return NULL; + run->config = config_new(); + run->terminal = FALSE; + 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(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(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); + 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(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(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; + config_load(config, p); + 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) + break; + 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(char const * message, int ret) +{ + GtkWidget * dialog; + + dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, "%s", 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; + size_t len; + char * filename; + + if((homedir = getenv("HOME")) == NULL) + homedir = g_get_home_dir(); + len = strlen(homedir) + 1 + sizeof(RUN_CONFIG_FILE); + if((filename = malloc(len)) == NULL) + return NULL; + snprintf(filename, len, "%s/%s", homedir, RUN_CONFIG_FILE); + return filename; +} + + +/* callbacks */ +/* on_run_closex */ +static gboolean _on_run_closex(GtkWidget * widget, GdkEvent * event, + gpointer data) +{ + Run * run = data; + + gtk_widget_hide(run->window); + gtk_main_quit(); + return FALSE; +} + + +/* on_run_cancel */ +static void _on_run_cancel(GtkWidget * widget, 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_parent(GtkWidget * window, pid_t pid); */ +static gboolean _execute_idle(gpointer data); +static void _idle_save_config(Run * run); + +static void _on_run_execute(GtkWidget * widget, gpointer data) +{ + Run * run = data; + char const * path; + + path = gtk_entry_get_text(GTK_ENTRY(run->entry)); + if((run->pid = fork()) == -1) /* handle error */ + { + gtk_widget_hide(run->window); + _run_error(strerror(errno), 0); + gtk_widget_show(run->window); + } + else if(run->pid != 0) /* parent process */ + { + gtk_widget_hide(run->window); + g_idle_add(_execute_idle, run); + } + else /* child process */ + { + if(run->terminal == TRUE) + execlp("xterm", "xterm", "-e", "sh", "-c", path, NULL); + else + execlp("/bin/sh", "run", "-c", path, NULL); + _exit(127); /* an error occured */ + } +} + +static gboolean _execute_idle(gpointer data) +{ + Run * run = data; + pid_t p; + int status; + + if((p = waitpid(run->pid, &status, 0)) == -1) /* XXX blocks Gtk+ */ + { + if(errno == ECHILD) /* should not happen */ + { + _run_error(strerror(errno), 0); + gtk_main_quit(); + return FALSE; + } + if(errno == EINTR) + return TRUE; + _run_error(strerror(errno), 0); + gtk_widget_show(run->window); + return FALSE; + } + if(WIFEXITED(status)) + { + if(WEXITSTATUS(status) == 127) /* XXX may mean anything... */ + { + _run_error("Child exited with error code 127", 0); + gtk_widget_show(run->window); + return FALSE; + } + _idle_save_config(run); + gtk_main_quit(); + } + return FALSE; +} + +static void _idle_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 + 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, "", buf); +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s() config_set(config, "", %s, %s)\n", + __func__, buf, p); +#endif + config_set(run->config, "", buf, p); + if((p = q) == NULL) + break; + } + config_save(run->config, filename); + free(filename); +} + + +/* on_run_path_activate */ +static void _on_run_path_activate(GtkWidget * widget, gpointer data) +{ + _on_run_execute(widget, 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)); +} + + +/* main */ +int main(int argc, char * argv[]) +{ + Run * run; + + gtk_init(&argc, &argv); + if((run = _run_new()) == NULL) + return _run_error(error_get(), 2); + gtk_main(); + _run_delete(run); + return 0; +}