/* * cdebconf gtk plugin to display a terminal * * Copyright © 2008 Jérémy Bobbio * See debian/copyright for license. * */ #include #include #include #include #include #include extern char ** environ; #define DEFAULT_COMMAND_LINE "/bin/sh" /* Here's the plugin! */ int cdebconf_gtk_handler_terminal(struct frontend * fe, struct question * question, GtkWidget * question_box); struct terminal { struct frontend * fe; GtkWidget * goback_button; VteTerminal * terminal; char * command; char ** argv; char ** environ; }; static void destroy_terminal(struct terminal * terminal_data) { if (NULL != terminal_data->command) { g_free(terminal_data->command); } if (NULL != terminal_data->argv) { g_strfreev(terminal_data->argv); } if (NULL != terminal_data->environ) { g_strfreev(terminal_data->environ); } if (NULL != terminal_data->terminal) { g_object_unref(G_OBJECT(terminal_data->terminal)); } if (NULL != terminal_data->goback_button) { g_object_unref(G_OBJECT(terminal_data->goback_button)); } g_free(terminal_data); } static void cleanup(GtkWidget * widget, struct terminal * terminal_data) { destroy_terminal(terminal_data); } static void handle_child_exit(GtkWidget * button, struct terminal * terminal_data) { cdebconf_gtk_set_answer_ok(terminal_data->fe); } static struct terminal * init_terminal(struct frontend * fe, struct question * question) { struct terminal * terminal_data; if (NULL == (terminal_data = g_malloc0(sizeof (struct terminal)))) { g_critical("g_malloc0 failed."); return NULL; } terminal_data->fe = fe; return terminal_data; } static gboolean init_command(struct terminal * terminal_data, struct question * question) { const char * command_line; command_line = question_get_variable(question, "COMMAND_LINE"); if (NULL == command_line) { command_line = DEFAULT_COMMAND_LINE; } terminal_data->argv = g_strsplit_set( command_line, " \t\n" /* default IFS */, 4096 /* max number of arguments */ /* XXX: replace with the correct system macro */); if (NULL == terminal_data->argv || NULL == terminal_data->argv[0]) { g_critical("g_strsplit_set failed."); return FALSE; } terminal_data->command = g_strdup(terminal_data->argv[0]); if (NULL == terminal_data->command) { g_critical("g_strplit_set failed."); return FALSE; } return TRUE; } /* XXX: mostly copied from ui.c */ static GtkWidget * create_dialog_action_box(struct frontend * fe, GtkWidget * dialog) { GtkWidget * action_box; GtkWidget * cancel_button; GtkWidget * continue_button; char * label; /* check NULL! */ action_box = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(action_box), GTK_BUTTONBOX_END); label = cdebconf_gtk_get_text(fe, "debconf/button-cancel", "Cancel"); /* check NULL! */ cancel_button = gtk_button_new_with_label(label); g_free(label); gtk_widget_set_can_default(cancel_button, TRUE); g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked", G_CALLBACK(gtk_widget_destroy), dialog); g_signal_connect(G_OBJECT(cancel_button), "realize", G_CALLBACK(gtk_widget_grab_focus), NULL /* no data */); label = cdebconf_gtk_get_text(fe, "debconf/button-continue", "Continue"); continue_button = gtk_button_new_with_label(label); g_free(label); g_signal_connect_swapped(G_OBJECT(continue_button), "clicked", G_CALLBACK(cdebconf_gtk_set_answer_ok), fe); g_signal_connect_swapped(G_OBJECT(continue_button), "clicked", G_CALLBACK(gtk_widget_destroy), dialog); gtk_box_pack_start(GTK_BOX(action_box), cancel_button, TRUE /* expand */, TRUE /* fill */, DEFAULT_PADDING); gtk_box_pack_start(GTK_BOX(action_box), continue_button, TRUE /* expand */, TRUE /* fill */, DEFAULT_PADDING); gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_box), cancel_button, TRUE /* secondary */); return action_box; } /* XXX: copied from ui.c */ static GtkWidget * create_dialog_title_label(const gchar * title) { GtkWidget * label; gchar * markup; /* check NULL! */ label = gtk_label_new(NULL /* no text */); gtk_misc_set_alignment(GTK_MISC(label), 0 /* left aligned */, 0 /* top aligned */); markup = g_strdup_printf("%s", title); gtk_label_set_markup(GTK_LABEL(label), markup); g_free(markup); return label; } /* XXX: mostly copied from cdebconf_gtk_run_message_dialog() */ static gboolean run_confirm_dialog(struct terminal * terminal_data) { GtkWidget * parent; GtkWidget * dialog; GtkWidget * vbox; GtkWidget * label; GtkWidget * frame; gchar * text; parent = gtk_widget_get_ancestor(terminal_data->goback_button, GTK_TYPE_WINDOW); dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent)); gtk_window_set_modal(GTK_WINDOW(dialog), TRUE /* modal */); gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE /* not resizale */); gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); gtk_window_set_decorated(GTK_WINDOW(dialog), FALSE /* no decoration */); gtk_container_set_border_width(GTK_CONTAINER(dialog), 0 /* no border */); vbox = gtk_vbox_new(FALSE /* don't make children equal */, DEFAULT_PADDING); text = cdebconf_gtk_get_text( terminal_data->fe, "debconf/terminal/gtk/confirm-title", "Resume installation"); gtk_box_pack_start(GTK_BOX(vbox), create_dialog_title_label(text), FALSE /* don't expand */, FALSE /* don't fille */, 0 /* no padding */); g_free(text); text = cdebconf_gtk_get_text( terminal_data->fe, "debconf/terminal/gtk/confirm-message", "Choose \"Continue\" to really exit the shell and resume the " "installation; any processes still running in the shell will be " "aborted."); label = gtk_label_new(text); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); g_free(text); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE /* don't expand */, FALSE /* don't fill */, DEFAULT_PADDING); gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE /* don't expand */, FALSE /* don't fill */, 0 /* no padding */); gtk_box_pack_start(GTK_BOX(vbox), create_dialog_action_box(terminal_data->fe, dialog), FALSE /* don't expand */, FALSE /* don't fill */, 0 /* no padding */); cdebconf_gtk_center_widget(&vbox, DEFAULT_PADDING, DEFAULT_PADDING); frame = gtk_frame_new(NULL /* no label */); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); gtk_container_add(GTK_CONTAINER(frame), vbox); gtk_container_add(GTK_CONTAINER(dialog), frame); gtk_widget_show_all(dialog); return TRUE; } static GtkWidget * create_goback_button(struct terminal * terminal_data) { GtkWidget * button; char * label; label = cdebconf_gtk_get_text(terminal_data->fe, "debconf/button-goback", "Go Back"); /* XXX: check NULL! */ button = gtk_button_new_with_label(label); g_free(label); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(run_confirm_dialog), terminal_data); cdebconf_gtk_add_button(terminal_data->fe, button); return button; } static gboolean handle_goback_key(GtkWidget * widget, GdkEventKey * key, struct terminal * terminal_data) { if (GDK_KEY_Escape == key->keyval && (GDK_SHIFT_MASK | GDK_CONTROL_MASK) == (key->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) { run_confirm_dialog(terminal_data); return TRUE; } return FALSE; } static GtkWidget * create_widgets(struct terminal * terminal_data) { GtkWidget * hbox; VteTerminal * terminal; GtkWidget * scrollbar; GtkWidget * goback_button; GtkAdjustment * adjustment; PangoFontDescription *font_desc; goback_button = create_goback_button(terminal_data); if (NULL == goback_button) { g_critical("create_goback_button failed."); return NULL; } g_object_ref(G_OBJECT(goback_button)); terminal_data->goback_button = goback_button; g_setenv("VTE_BACKEND", "pango", TRUE /* overwrite */); if (NULL == (terminal = VTE_TERMINAL(vte_terminal_new()))) { g_critical("vte_terminal_new failed."); return NULL; } font_desc = pango_font_description_from_string("monospace"); vte_terminal_set_font(terminal, font_desc); g_signal_connect(terminal, "destroy", G_CALLBACK(cleanup), terminal_data); g_signal_connect(terminal, "child-exited", G_CALLBACK(handle_child_exit), terminal_data); g_signal_connect(terminal, "key_press_event", G_CALLBACK(handle_goback_key), terminal_data); g_signal_connect(terminal, "realize", G_CALLBACK(gtk_widget_grab_focus), NULL); g_object_ref(terminal); terminal_data->terminal = terminal; hbox = gtk_hbox_new(FALSE /* not homogeneous */, 0 /* no spacing */); if (NULL == hbox) { g_critical("gtk_hbox_new failed."); return NULL; } gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(terminal), TRUE /* expand */, TRUE /* fill */, 0 /* no padding */); #if VTE_CHECK_VERSION(0, 37, 0) adjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(terminal)); #else adjustment = vte_terminal_get_adjustment(terminal); #endif scrollbar = gtk_vscrollbar_new(adjustment); if (NULL == scrollbar) { g_critical("gtk_vscrollbar_new failed."); return NULL; } gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE /* don't expand */, FALSE /* don't fill */, 0 /* no padding */); return hbox; } static void set_nothing(struct question * question, void * dummy) { /* terminal questions do not put anything in the database */ return; } static gboolean prepare_environ(struct terminal * terminal_data) { guint orig_index; guint new_index; const char * src; terminal_data->environ = g_new0(char *, g_strv_length(environ) + 1); if (NULL == terminal_data->environ) { g_critical("g_malloc0 failed."); return FALSE; } new_index = 0; for (orig_index = 0; NULL != environ[orig_index]; orig_index++) { if (g_str_has_prefix(environ[orig_index], "DEBIAN_HAS_FRONTEND=")) { src = "DEBIAN_HAS_FRONTEND="; } else if (g_str_has_prefix(environ[orig_index], "DEBIAN_FRONTEND=")) { src = "DEBIAN_FRONTEND=newt"; } else { src = environ[orig_index]; } if (NULL == (terminal_data->environ[new_index] = g_strdup(src))) { g_critical("g_strdup failed."); return FALSE; } new_index++; } return TRUE; } static gboolean start_command(struct terminal * terminal_data) { gboolean ret = TRUE; pid_t pid; #if VTE_CHECK_VERSION(0, 37, 0) ret = vte_terminal_spawn_sync( terminal_data->terminal, VTE_PTY_NO_LASTLOG | VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP, "/", terminal_data->argv, terminal_data->environ, G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, NULL); #else ret = vte_terminal_fork_command_full( terminal_data->terminal, VTE_PTY_NO_LASTLOG | VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP, "/", terminal_data->argv, terminal_data->environ, G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL); #endif if (!ret) g_critical("vte_terminal_fork_command failed."); return ret; } int cdebconf_gtk_handler_terminal(struct frontend * fe, struct question * question, GtkWidget * question_box) { struct terminal * terminal_data; GtkWidget * widget; if (!IS_QUESTION_SINGLE(question)) { g_critical("entropy plugin does not work alongside other questions."); return DC_NOTOK; } if (NULL == (terminal_data = init_terminal(fe, question))) { g_critical("init_terminal failed."); return DC_NOTOK; } if (NULL == (widget = create_widgets(terminal_data))) { g_critical("create_widgets failed."); goto failed; } if (!init_command(terminal_data, question)) { g_critical("init_command failed."); goto failed; } if (!prepare_environ(terminal_data)) { g_critical("prepare_environ failed."); goto failed; } if (!start_command(terminal_data)) { g_critical("start_command failed."); goto failed; } cdebconf_gtk_add_common_layout(fe, question, question_box, widget); cdebconf_gtk_register_setter(fe, SETTER_FUNCTION(set_nothing), question, NULL); return DC_OK; failed: destroy_terminal(terminal_data); return DC_NOTOK; } /* vim: et sw=4 si */