W tym krótkim tutorialu pokażę jak używać widget VTE
we własnych programach. Najpierw utworzymy w programie Glade
interfejs dla naszej przykładowej aplikacji a następnie w odpowiednie miejsce
layoutu podepniemy wyżej wspomniany widget. Pokażę jak załadować
shell do terminala i jak zabezpieczyć się przed opuszczeniem shella
przy pomocy komendy exit lub kombinacji klawiszy
Ctrl+D.
A więc do dzieła!
Zaczniemy od instalacji biblioteki developerskiej umożliwiającej wykorzystanie kontrolki VTE. W tym celu zainstaluj pakiet libvte-2.90-dev:
sudo apt-get install libvte-2.90-dev
Następnie otwórz program Glade i utwórz layout według podanego poniżej opisu.
Po wykonaniu powyższych kroków nasze okno powinno wyglądać jak na screenshocie poniżej:
Zapiszmy teraz nasz plik interfejsu jako main_window.ui i przejdźmy do tworzenia kodu naszej aplikacji. Zacznijmy od zainkludowania potrzebnych plików nagłówkowych:
#include <gtk/gtk.h>
#include <vte/vte.h>
#include <vte/reaper.h>
Plik gtk.h jest głównym plikiem nagłówkowym biblioteki gtk
i jest wymagany dla każdej aplikacji korzystającej z tej biblioteki.
Plik vte.h jest plikiem nagłówkowym definiującym kontrolkę
vte i pozwalającym używać ją w naszych aplikacjach.
Plik reaper.h pozwala przechwytywać zdarzenia zakończenia
wykonywania się procesu załadowanego do widgeta vte.
Zdefiniujmy teraz zmienne globalne i funkcje, które wykorzystamy w naszej aplikacji:
GtkBuilder *builder;
GtkWidget *mainWindow;
GtkWidget *vte;
gboolean loadGui();
void create_terminal();
void run_shell_in_terminal();
void vte_child_exited(VteReaper *vtereaper, gint arg1, gint arg2, gpointer user_data);
void on_execute_command_click(GtkWidget *widget, gpointer user_data);
Zmienna builder będzie przechowywała wskaźnik na obiekt
odpowiedzialny za wczytanie interfejsu aplikacji z utworzonego wcześniej pliku
main_window.ui.
Zmienna mainWindow jest wskaźnikiem na okno główne naszej aplikacji.
Natomiast zmienna vte będzie przechowywała wskaźnik na obiekt terminala VTE.
Funkcję główną możemy zdefiniować następująco:
gint main(gint argc, gchar *argv[])
{
gtk_init(&argc, &argv);
if (loadGui()) {
mainWindow = GTK_WIDGET(gtk_builder_get_object(builder, "mainWindow"));
GtkWidget *btnExecute = GTK_WIDGET(gtk_builder_get_object(builder, "btnExecute"));
if (mainWindow && btnExecute) {
g_signal_connect(G_OBJECT(mainWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(G_OBJECT(btnExecute), "clicked", G_CALLBACK(on_execute_command_click), NULL);
create_terminal();
gtk_widget_show_all(mainWindow);
gtk_main();
}
}
if (builder) {
g_object_unref(builder);
}
return 0;
}
Za pomocą funkcji gtk_init inicjalizujemy bibliotekę GTK.
Do funkcji tej przekazujemy zmienne argc i argv określające
parametry aplikacji przekazane w linii poleceń oraz ich ilość.
Następnie jeśli interfejs został poprawnie wczytany - pobieramy przy pomocy funkcji
gtk_builder_get_object wskaźniki na widgety okna głównego i przycisku "Wykonaj".
Jeśli udało się pobrać te wskaźniki podpinamy funkcję obsługi sygnału destroy
okna głównego, odpowiedzialną za zakończenie aplikacji (funkcja systemowa gtk_main_quit) oraz
funkcję obsługi sygnału clicked przycisku btnExecute -
zdefiniowana przez nas funkcja on_execute_command_click.
Następnie wywołujemy funkcję tworzącą terminal i pokazujemy okno główne na ekranie.
Na koniec uruchamiamy funkcję systemową gtk_main odpowiedzialną za przetwarzanie
w pętli wszystkich sygnałów i zdarzeń otrzymywanych przez aplikację.
Przed opuszczeniem funkcji głównej pamiętamy o zwolnieniu obiektu buildera przy pomocy funkcji
g_object_unref.
gboolean loadGui() {
GError *error;
builder = gtk_builder_new();
error = NULL;
gtk_builder_add_from_file(builder, "main_window.ui", &error);
if (error) {
g_print("Wystąpił błąd: %s\n", error->message);
g_error_free(error);
return FALSE;
}
return TRUE;
}
Funkcja loadGui odpowiada za utworzenie interfejsu aplikacji. Zwraca TRUE w przypadku sukcesu i FALSE w przypadku niepowodzenia. W funkcji tej najpierw tworzymy obiekt buildera przy pomocy funkcji gtk_builder_new a następnie wywołując funkcję gtk_builder_add_from_file próbujemy wczytać interfejs z pliku main_window.ui (z bieżącego katalogu roboczego). Jeśli wystąpił błąd, to wyświetlamy stosowny komunikat na standardowym wyjściu.
void run_shell_in_terminal() {
GError *error;
char *startterm[2] = {0, 0};
startterm[0] = vte_get_user_shell();
error = NULL;
vte_terminal_fork_command_full(VTE_TERMINAL(vte),
VTE_PTY_DEFAULT, // Tryb uruchomienia shella
NULL, // Katalog roboczy; ustawiony na NULL przyjmuje wartość bieżącego katalogu roboczego
startterm, // Polecenie do wykonania
NULL, // NULL lub lista zmiennych środowiskowych ustawianych w terminalu
(GSpawnFlags)(G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH), // Flagi dla tworzonego procesu
NULL, // Funkcja konfiguracyjna wywoływana przed wykonaniem funkcji exec
NULL, // Dane przekazywane do funkcji konfiguracyjnej
NULL, // NULL albo zmienna przechowująca PID potomka
&error // Zmienna przechowująca informację o błędzie
);
if (error) {
g_print("Wystąpił błąd: %s\n", error->message);
g_error_free(error);
}
g_free(startterm[0]);
}
Funkcja run_shell_in_terminal odpowiada za uruchomienie w terminalu bieżącego shella użytkownika. Pełną ścieżkę do tego shella otrzymujemy przy pomocy funkcji vte_get_user_shell i przechowujemy w tablicy startterm. Następnie uruchamiamy ten shell przy pomocy funkcji vte_terminal_fork_command_full. Poszczególne parametry tej funkcji opisałem w komentarzach przy wywołaniu.
void create_terminal() {
GtkWidget *frame_alignment = GTK_WIDGET(gtk_builder_get_object(builder, "alignment1"));
if (frame_alignment) {
vte = vte_terminal_new();
g_signal_connect(G_OBJECT(vte), "child-exited", G_CALLBACK(vte_child_exited), NULL);
gtk_container_add(GTK_CONTAINER(frame_alignment), vte);
run_shell_in_terminal();
}
}
Funkcja create_terminal odpowiada za utworzenie widgeta terminala VTE i dodanie go do załadowanego wcześniej interfejsu użytkownika. Najpierw pobieramy przy pomocy funkcji gtk_builder_get_object pobieramy widget-pojemnik, do którego dodamy obiekt terminala. Jest to widget alignment1. Następnie tworzymy terminal funkcją vte_terminal_new, podpinamu obsługę sygnału "child-exited" (wywoływany w momencie opuszczenia procesu shella) oraz dodajemy terminal do pojemnika. Na koniec uruchamiamy w terminalu shell użytkownika.
void vte_child_exited(VteReaper *vtereaper, gint arg1, gint arg2, gpointer user_data) {
// if user exits shell by pressing Ctrl+D or executing "exit" command run shell again
run_shell_in_terminal();
}
Funkcja vte_child_exited wywoływana po zamknięciu shella uruchamia go po prostu jeszcze raz. Dzięki temu blokujemy użytkownikowi możliwość całkowitego zamknięcia shella w terminalu.
void on_execute_command_click(GtkWidget *widget, gpointer user_data) {
GtkWidget *edtCommand = GTK_WIDGET(gtk_builder_get_object(builder, "edtCommand"));
if (edtCommand) {
const gchar *command = gtk_entry_get_text(GTK_ENTRY(edtCommand));
gchar *command_with_newline = g_strdup_printf("%s\r", command);
vte_terminal_feed_child(VTE_TERMINAL(vte), command_with_newline, -1);
g_free(command_with_newline);
}
}
Na koniec definiujemy funkcję obsługi kliknięcia przycisku. Funkcja ta najpierw pobiera wskaźnik na pole tekstowe z komendą do uruchomienia a następnie pobiera tę komendę przy pomocy funkcji gtk_entry_get_text, dokleja do niej znacznik wciśnięcia klawisza ENTER (\r) i wywołuje w terminalu (funkcja vte_terminal_feed_child). Na koniec oczywiście pamiętamy o zwolnieniu zasobów.
Kompletny kod aplikacji można pobrać poniżej lub na samej górze strony.