diff --git a/src/bin/e_exec.c b/src/bin/e_exec.c index d8b275332..54d87b129 100644 --- a/src/bin/e_exec.c +++ b/src/bin/e_exec.c @@ -2,3 +2,692 @@ * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 */ #include "e.h" + +#define MAX_OUTPUT_CHARACTERS 5000 + +/* TODO: + * - Clear e_exec_instances on shutdown + * - Clear e_exec_start_pending on shutdown + * - Create border add handler + */ + +typedef struct _E_Exec_Launch E_Exec_Launch; +typedef struct _E_Exec_Instance E_Exec_Instance; + +struct _E_Exec_Launch +{ + E_Zone *zone; + const char *launch_method; +}; + +struct _E_Exec_Instance +{ + Efreet_Desktop *desktop; + Ecore_Exe *exe; + int launch_id; + double launch_time; + Ecore_Timer *expire_timer; +}; + +struct _E_Config_Dialog_Data +{ + Efreet_Desktop *desktop; + char *exec; + + Ecore_Exe_Event_Del event; + Ecore_Exe_Event_Data *error; + Ecore_Exe_Event_Data *read; + + char *label; + char *exit; + char *signal; +}; + +static void _e_exec_cb_exec(void *data, Efreet_Desktop *desktop, char *exec, int remaining); +static int _e_exec_cb_expire_timer(void *data); +static int _e_exec_cb_exit(void *data, int type, void *event); + +static void _e_exec_error_dialog(Efreet_Desktop *desktop, const char *exec, Ecore_Exe_Event_Del *event, Ecore_Exe_Event_Data *error, Ecore_Exe_Event_Data *read); +static void _fill_data(E_Config_Dialog_Data *cfdata); +static void *_create_data(E_Config_Dialog *cfd); +static void _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata); +static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata); +static Evas_Object *_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata); +static Evas_Object *_dialog_scrolltext_create(Evas *evas, char *title, Ecore_Exe_Event_Data_Line *lines); +static void _dialog_save_cb(void *data, void *data2); + +static Evas_List *e_exec_start_pending = NULL; +static Evas_Hash *e_exec_instances = NULL; +static int startup_id = 0; + +static Ecore_Event_Handler *_e_exec_exit_handler = NULL; +static Ecore_Event_Handler *_e_exec_border_add_handler = NULL; + +EAPI int +e_exec_init(void) +{ + _e_exec_exit_handler = + ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _e_exec_cb_exit, NULL); +#if 0 + _e_exec_border_add_handler = + ecore_event_handler_add(E_EVENT_BORDER_ADD, _e_exec_cb_event_border_add, NULL); +#endif + return 1; +} + +EAPI int +e_exec_shutdown(void) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), "%i", startup_id); + e_util_env_set("E_STARTUP_ID", buf); + + if (_e_exec_exit_handler) ecore_event_handler_del(_e_exec_exit_handler); + if (_e_exec_border_add_handler) ecore_event_handler_del(_e_exec_border_add_handler); + return 1; +} + +EAPI int +e_exec(E_Zone *zone, Efreet_Desktop *desktop, const char *exec, + Ecore_List *files, const char *launch_method) +{ + E_Exec_Launch *launch; + + if ((!desktop) && (!exec)) return 0; + launch = E_NEW(E_Exec_Launch, 1); + if (!launch) return 0; + if (zone) + { + launch->zone = zone; + e_object_ref(E_OBJECT(launch->zone)); + } + if (launch_method) launch->launch_method = evas_stringshare_add(launch_method); + + if (desktop) + { + if (exec) + _e_exec_cb_exec(launch, NULL, strdup(exec), 0); + else + efreet_desktop_command_get(desktop, files, _e_exec_cb_exec, launch); + } + else + { + _e_exec_cb_exec(launch, NULL, strdup(exec), 0); + } + return 1; +} + +static void +_e_exec_cb_exec(void *data, Efreet_Desktop *desktop, char *exec, int remaining) +{ + E_Exec_Instance *inst = NULL; + E_Exec_Launch *launch; + Ecore_Exe *exe; + char *penv_display; + char buf[4096]; + + launch = data; + if (desktop) + { + inst = E_NEW(E_Exec_Instance, 1); + if (!inst) return; + } + + if (startup_id == 0) + { + const char *p; + p = getenv("E_STARTUP_ID"); + if (p) startup_id = atoi(p); + e_util_env_set("E_STARTUP_ID", NULL); + } + if (++startup_id < 1) startup_id = 1; + /* save previous env vars we need to save */ + penv_display = getenv("DISPLAY"); + if (penv_display) penv_display = strdup(penv_display); + if ((penv_display) && (launch->zone)) + { + const char *p1, *p2; + char buf2[32]; + int head; + + head = launch->zone->container->manager->num; + + /* set env vars */ + p1 = strrchr(penv_display, ':'); + p2 = strrchr(penv_display, '.'); + if ((p1) && (p2) && (p2 > p1)) /* "blah:x.y" */ + { + /* yes it could overflow... but who will overflow DISPLAY eh? why? to + * "exploit" your own applications running as you? + */ + strcpy(buf, penv_display); + buf[p2 - penv_display + 1] = 0; + snprintf(buf2, sizeof(buf2), "%i", head); + strcat(buf, buf2); + } + else if (p1) /* "blah:x */ + { + strcpy(buf, penv_display); + snprintf(buf2, sizeof(buf2), ".%i", head); + strcat(buf, buf2); + } + else + strcpy(buf, penv_display); + e_util_env_set("DISPLAY", buf); + } + snprintf(buf, sizeof(buf), "E_START|%i", startup_id); + e_util_env_set("DESKTOP_STARTUP_ID", buf); + + e_util_library_path_strip(); + exe = ecore_exe_pipe_run(exec, + ECORE_EXE_PIPE_AUTO | ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR | + ECORE_EXE_PIPE_READ_LINE_BUFFERED | ECORE_EXE_PIPE_ERROR_LINE_BUFFERED, + inst); + e_util_library_path_restore(); + if (penv_display) + { + e_util_env_set("DISPLAY", penv_display); + free(penv_display); + } + if (!exe) + { + E_FREE(inst); + e_util_dialog_show(_("Run Error"), + _("Enlightenment was unable to fork a child process:
" + "
" + "%s
"), + exec); + return; + } + /* reset env vars */ + if (launch->launch_method) e_exehist_add(launch->launch_method, exec); + free(exec); + /* 20 lines at start and end, 20x100 limit on bytes at each end. */ + ecore_exe_auto_limits_set(exe, 2000, 2000, 20, 20); + ecore_exe_tag_set(exe, "E/exec"); + + if (desktop) + { + Evas_List *l; + + inst->desktop = desktop; + inst->exe = exe; + inst->launch_id = startup_id; + inst->launch_time = ecore_time_get(); + inst->expire_timer = ecore_timer_add(10.0, _e_exec_cb_expire_timer, inst); + + l = evas_hash_find(e_exec_instances, desktop->orig_path); + l = evas_list_append(l, inst); + evas_hash_direct_add(e_exec_instances, desktop->orig_path, l); + e_exec_start_pending = evas_list_append(e_exec_start_pending, desktop); + } + else + ecore_exe_free(exe); + if (!remaining) + { + if (launch->launch_method) evas_stringshare_del(launch->launch_method); + if (launch->zone) e_object_unref(E_OBJECT(launch->zone)); + free(launch); + } +} + +static int +_e_exec_cb_expire_timer(void *data) +{ + E_Exec_Instance *inst; + + inst = data; + e_exec_start_pending = evas_list_remove(e_exec_start_pending, inst->desktop); + inst->expire_timer = NULL; + return 0; +} + +static int +_e_exec_cb_exit(void *data, int type, void *event) +{ + Evas_List *instances; + Ecore_Exe_Event_Del *ev; + E_Exec_Instance *inst; + + ev = event; + if (!ev->exe) return 1; + if (!(ecore_exe_tag_get(ev->exe) && + (!strcmp(ecore_exe_tag_get(ev->exe), "E/exec")))) return 1; + inst = ecore_exe_data_get(ev->exe); + if (!inst) return 1; + + /* /bin/sh uses this if cmd not found */ + if ((ev->exited) && + ((ev->exit_code == 127) || (ev->exit_code == 255))) + { + E_Dialog *dia; + + dia = e_dialog_new(e_container_current_get(e_manager_current_get()), + "E", "_e_exec_run_error_dialog"); + if (dia) + { + char buf[4096]; + + e_dialog_title_set(dia, _("Application run error")); + snprintf(buf, sizeof(buf), + _("Enlightenment was unable to run the application:
" + "
" + "%s
" + "
" + "The application failed to start."), + ecore_exe_cmd_get(ev->exe)); + e_dialog_text_set(dia, buf); + e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL); + e_dialog_button_focus_num(dia, 1); + e_win_centered_set(dia->win, 1); + e_dialog_show(dia); + } + } + /* Let's hope that everything returns this properly. */ + else if (!((ev->exited) && (ev->exit_code == EXIT_SUCCESS))) + { + /* Show the error dialog with details from the exe. */ + _e_exec_error_dialog(inst->desktop, ecore_exe_cmd_get(ev->exe), ev, + ecore_exe_event_data_get(ev->exe, ECORE_EXE_PIPE_ERROR), + ecore_exe_event_data_get(ev->exe, ECORE_EXE_PIPE_READ)); + } + instances = evas_hash_find(e_exec_instances, inst->desktop->orig_path); + instances = evas_list_remove(instances, inst); + evas_hash_direct_add(e_exec_instances, inst->desktop->orig_path, instances); + e_exec_start_pending = evas_list_remove(e_exec_start_pending, inst->desktop); + if (inst->expire_timer) ecore_timer_del(inst->expire_timer); + free(inst); + return 1; +} + +static void +_e_exec_error_dialog(Efreet_Desktop *desktop, const char *exec, Ecore_Exe_Event_Del *event, + Ecore_Exe_Event_Data *error, Ecore_Exe_Event_Data *read) +{ + E_Config_Dialog *cfd; + E_Config_Dialog_View *v; + E_Config_Dialog_Data *cfdata; + E_Container *con; + + v = E_NEW(E_Config_Dialog_View, 1); + if (!v) return; + cfdata = E_NEW(E_Config_Dialog_Data, 1); + if (!cfdata) + { + free(v); + return; + } + cfdata->desktop = desktop; + if (exec) cfdata->exec = strdup(exec); + cfdata->error = error; + cfdata->read = read; + cfdata->event = *event; + + v->create_cfdata = _create_data; + v->free_cfdata = _free_data; + v->basic.create_widgets = _basic_create_widgets; + v->advanced.create_widgets = _advanced_create_widgets; + + con = e_container_current_get(e_manager_current_get()); + /* Create The Dialog */ + cfd = e_config_dialog_new(con, _("Application Execution Error"), + "E", "_e_exec_error_exit_dialog", + NULL, 0, v, cfdata); +} + +static void +_fill_data(E_Config_Dialog_Data *cfdata) +{ + char buf[4096]; + + if (!cfdata->label) + { + snprintf(buf, sizeof(buf), _("%s stopped running unexpectedly."), cfdata->desktop->name); + cfdata->label = strdup(buf); + } + if ((cfdata->event.exited) && (!cfdata->exit)) + { + snprintf(buf, sizeof(buf), + _("An exit code of %i was returned from %s."), cfdata->event.exit_code, cfdata->exec); + cfdata->exit = strdup(buf); + } + if ((cfdata->event.signalled) && (!cfdata->signal)) + { + if (cfdata->event.exit_signal == SIGINT) + snprintf(buf, sizeof(buf), + _("%s was interrupted by an Interrupt Signal."), cfdata->desktop->exec); + else if (cfdata->event.exit_signal == SIGQUIT) + snprintf(buf, sizeof(buf), _("%s was interrupted by a Quit Signal."), + cfdata->exec); + else if (cfdata->event.exit_signal == SIGABRT) + snprintf(buf, sizeof(buf), + _("%s was interrupted by an Abort Signal."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGFPE) + snprintf(buf, sizeof(buf), + _("%s was interrupted by a Floating Point Error."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGKILL) + snprintf(buf, sizeof(buf), + _("%s was interrupted by an Uninterruptable Kill Signal."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGSEGV) + snprintf(buf, sizeof(buf), + _("%s was interrupted by a Segmentation Fault."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGPIPE) + snprintf(buf, sizeof(buf), + _("%s was interrupted by a Broken Pipe."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGTERM) + snprintf(buf, sizeof(buf), + _("%s was interrupted by a Termination Signal."), cfdata->exec); + else if (cfdata->event.exit_signal == SIGBUS) + snprintf(buf, sizeof(buf), + _("%s was interrupted by a Bus Error."), cfdata->exec); + else + snprintf(buf, sizeof(buf), + _("%s was interrupted by the signal number %i."), + cfdata->exec, cfdata->event.exit_signal); + cfdata->signal = strdup(buf); + /* FIXME: Add sigchld_info stuff + * cfdata->event.data + * siginfo_t + * { + * int si_signo; Signal number + * int si_errno; An errno value + * int si_code; Signal code + * pid_t si_pid; Sending process ID + * uid_t si_uid; Real user ID of sending process + * int si_status; Exit value or signal + * clock_t si_utime; User time consumed + * clock_t si_stime; System time consumed + * sigval_t si_value; Signal value + * int si_int; POSIX.1b signal + * void * si_ptr; POSIX.1b signal + * void * si_addr; Memory location which caused fault + * int si_band; Band event + * int si_fd; File descriptor + * } + */ + } +} + +static void * +_create_data(E_Config_Dialog *cfd) +{ + E_Config_Dialog_Data *cfdata; + + cfdata = cfd->data; + _fill_data(cfdata); + return cfdata; +} + +static void +_free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata) +{ + if (cfdata->error) ecore_exe_event_data_free(cfdata->error); + if (cfdata->read) ecore_exe_event_data_free(cfdata->read); + + E_FREE(cfdata->exec); + E_FREE(cfdata->signal); + E_FREE(cfdata->exit); + E_FREE(cfdata->label); + + free(cfdata); +} + +static Evas_Object * +_dialog_scrolltext_create(Evas *evas, char *title, Ecore_Exe_Event_Data_Line *lines) +{ + int i; + Evas_Object *obj, *os; + char *text; + char *trunc_note = _("***The remaining output has been truncated. Save the output to view.***\n"); + int tlen, max_lines; + + os = e_widget_framelist_add(evas, _(title), 0); + + obj = e_widget_textblock_add(evas); + + tlen = 0; + for (i = 0; lines[i].line != NULL; i++) + { + tlen += lines[i].size + 1; + /* When the program output is extraordinarily long, it can cause + * significant delays during text rendering. Limit to a fixed + * number of characters. */ + if (tlen > MAX_OUTPUT_CHARACTERS) + { + tlen -= lines[i].size + 1; + tlen += strlen(trunc_note); + break; + } + } + max_lines = i; + text = alloca(tlen + 1); + + if (text) + { + text[0] = 0; + for (i = 0; i < max_lines; i++) + { + strcat(text, lines[i].line); + strcat(text, "\n"); + } + + /* Append the warning about truncated output. */ + if (lines[max_lines].line != NULL) + strcat(text, trunc_note); + + e_widget_textblock_plain_set(obj, text); + } + e_widget_min_size_set(obj, 240, 120); + + e_widget_framelist_object_append(os, obj); + + return os; +} + +static Evas_Object * +_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata) +{ + char buf[4096]; + int error_length = 0; + Evas_Object *o, *ob, *os; + + _fill_data(cfdata); + + o = e_widget_list_add(evas, 0, 0); + + ob = e_widget_label_add(evas, cfdata->label); + e_widget_list_object_append(o, ob, 1, 1, 0.5); + + if (cfdata->error) + error_length = cfdata->error->size; + if (error_length) + { + os = _dialog_scrolltext_create(evas, _("Error Logs"), cfdata->error->lines); + e_widget_list_object_append(o, os, 1, 1, 0.5); + } + else + { + ob = e_widget_label_add(evas, _("There was no error message.")); + e_widget_list_object_append(o, ob, 1, 1, 0.5); + } + + ob = e_widget_button_add(evas, _("Save This Message"), "enlightenment/run", + _dialog_save_cb, NULL, cfdata); + e_widget_list_object_append(o, ob, 0, 0, 0.5); + + snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"), + e_user_homedir_get(), cfdata->desktop->name); + ob = e_widget_label_add(evas, buf); + e_widget_list_object_append(o, ob, 1, 1, 0.5); + + return o; +} + +static Evas_Object * +_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata) +{ + char buf[4096]; + int read_length = 0; + int error_length = 0; + Evas_Object *o, *of, *ob, *ot; + + _fill_data(cfdata); + + o = e_widget_list_add(evas, 0, 0); + ot = e_widget_table_add(evas, 0); + + ob = e_widget_label_add(evas, cfdata->label); + e_widget_list_object_append(o, ob, 1, 1, 0.5); + + if (cfdata->exit) + { + of = e_widget_framelist_add(evas, _("Error Information"), 0); + ob = e_widget_label_add(evas, _(cfdata->exit)); + e_widget_framelist_object_append(of, ob); + e_widget_list_object_append(o, of, 1, 1, 0.5); + } + + if (cfdata->signal) + { + of = e_widget_framelist_add(evas, _("Error Signal Information"), 0); + ob = e_widget_label_add(evas, _(cfdata->signal)); + e_widget_framelist_object_append(of, ob); + e_widget_list_object_append(o, of, 1, 1, 0.5); + } + + if (cfdata->read) + read_length = cfdata->read->size; + + if (read_length) + { + of = _dialog_scrolltext_create(evas, _("Output Data"), cfdata->read->lines); + /* FIXME: Add stdout "start". */ + /* FIXME: Add stdout "end". */ + } + else + { + of = e_widget_framelist_add(evas, _("Output Data"), 0); + ob = e_widget_label_add(evas, _("There was no output.")); + e_widget_framelist_object_append(of, ob); + } + e_widget_table_object_append(ot, of, 0, 0, 1, 1, 1, 1, 1, 1); + + if (cfdata->error) + error_length = cfdata->error->size; + if (error_length) + { + of = _dialog_scrolltext_create(evas, _("Error Logs"), cfdata->error->lines); + /* FIXME: Add stderr "start". */ + /* FIXME: Add stderr "end". */ + } + else + { + of = e_widget_framelist_add(evas, _("Error Logs"), 0); + ob = e_widget_label_add(evas, _("There was no error message.")); + e_widget_framelist_object_append(of, ob); + } + e_widget_table_object_append(ot, of, 1, 0, 1, 1, 1, 1, 1, 1); + + e_widget_list_object_append(o, ot, 1, 1, 0.5); + + ob = e_widget_button_add(evas, _("Save This Message"), "enlightenment/run", _dialog_save_cb, NULL, cfdata); + e_widget_list_object_append(o, ob, 0, 0, 0.5); + + snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"), + e_user_homedir_get(), cfdata->desktop->name); + ob = e_widget_label_add(evas, buf); + e_widget_list_object_append(o, ob, 1, 1, 0.5); + + return o; +} + +static void +_dialog_save_cb(void *data, void *data2) +{ + E_Config_Dialog_Data *cfdata; + FILE *f; + char *text; + char buf[1024]; + char buffer[4096]; + int read_length = 0; + int i, tlen; + + cfdata = data2; + + snprintf(buf, sizeof(buf), "%s/%s.log", e_user_homedir_get(), + e_util_filename_escape(cfdata->desktop->name)); + f = fopen(buf, "w"); + if (!f) return; + + if (cfdata->exit) + { + snprintf(buffer, sizeof(buffer), "Error Information:\n\t%s\n\n", cfdata->exit); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + if (cfdata->signal) + { + snprintf(buffer, sizeof(buffer), "Error Signal Information:\n\t%s\n\n", cfdata->signal); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + + if (cfdata->read) + read_length = cfdata->read->size; + + if (read_length) + { + tlen = 0; + for (i = 0; cfdata->read->lines[i].line != NULL; i++) + tlen += cfdata->read->lines[i].size + 2; + text = alloca(tlen + 1); + if (text) + { + text[0] = 0; + for (i = 0; cfdata->read->lines[i].line != NULL; i++) + { + strcat(text, "\t"); + strcat(text, cfdata->read->lines[i].line); + strcat(text, "\n"); + } + snprintf(buffer, sizeof(buffer), "Output Data:\n%s\n\n", text); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + } + else + { + snprintf(buffer, sizeof(buffer), "Output Data:\n\tThere was no output\n\n"); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + + /* Reusing this var */ + read_length = 0; + if (cfdata->error) + read_length = cfdata->error->size; + + if (read_length) + { + tlen = 0; + for (i = 0; cfdata->error->lines[i].line != NULL; i++) + tlen += cfdata->error->lines[i].size + 1; + text = alloca(tlen + 1); + if (text) + { + text[0] = 0; + for (i = 0; cfdata->error->lines[i].line != NULL; i++) + { + strcat(text, "\t"); + strcat(text, cfdata->error->lines[i].line); + strcat(text, "\n"); + } + snprintf(buffer, sizeof(buffer), "Error Logs:\n%s\n", text); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + } + else + { + snprintf(buffer, sizeof(buffer), "Error Logs:\n\tThere was no error message\n"); + fwrite(buffer, sizeof(char), strlen(buffer), f); + } + + fclose(f); +} diff --git a/src/bin/e_exec.h b/src/bin/e_exec.h index ab18f8522..75062c4de 100644 --- a/src/bin/e_exec.h +++ b/src/bin/e_exec.h @@ -7,5 +7,9 @@ #ifndef E_EXEC_H #define E_EXEC_H +EAPI int e_exec_init(void); +EAPI int e_exec_shutdown(void); +EAPI int e_exec(E_Zone *zone, Efreet_Desktop *desktop, const char *exec, Ecore_List *files, const char *launch_method); + #endif #endif diff --git a/src/bin/e_main.c b/src/bin/e_main.c index dac5c6fe6..5bd620d77 100644 --- a/src/bin/e_main.c +++ b/src/bin/e_main.c @@ -681,6 +681,14 @@ main(int argc, char **argv) _e_main_shutdown(-1); } _e_main_shutdown_push(e_app_shutdown); + TS("exec"); + /* init app system */ + if (!e_exec_init()) + { + e_error_message_show(_("Enlightenment cannot set up its exec system.")); + _e_main_shutdown(-1); + } + _e_main_shutdown_push(e_exec_shutdown); TS("remember"); /* do remember stuff */ if (!e_remember_init(after_restart ? E_STARTUP_RESTART: E_STARTUP_START))