#include "rt-terminal.h" #include "rt-utils.h" #include G_DEFINE_TYPE(RtTerminal, rt_terminal, GTK_TYPE_BOX) enum { SIGNAL_TITLE_CHANGED, SIGNAL_BELL, SIGNAL_CHILD_EXITED, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0 }; static void rt_terminal_finalize(GObject *object) { RtTerminal *terminal = RT_TERMINAL(object); g_clear_object(&terminal->font_config); g_clear_object(&terminal->theme); g_clear_object(&terminal->pty); g_free(terminal->current_directory); g_free(terminal->window_title); G_OBJECT_CLASS(rt_terminal_parent_class)->finalize(object); } static void rt_terminal_class_init(RtTerminalClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = rt_terminal_finalize; signals[SIGNAL_TITLE_CHANGED] = g_signal_new( "title-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); signals[SIGNAL_BELL] = g_signal_new( "bell", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[SIGNAL_CHILD_EXITED] = g_signal_new( "child-exited", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); } static void on_window_title_changed(VteTerminal *vte, gpointer user_data) { RtTerminal *terminal = RT_TERMINAL(user_data); g_free(terminal->window_title); terminal->window_title = g_strdup(vte_terminal_get_window_title(vte)); g_signal_emit(terminal, signals[SIGNAL_TITLE_CHANGED], 0, terminal->window_title); } static void on_bell(VteTerminal *vte, gpointer user_data) { (void)vte; RtTerminal *terminal = RT_TERMINAL(user_data); g_signal_emit(terminal, signals[SIGNAL_BELL], 0); } static void on_child_exited(VteTerminal *vte, gint status, gpointer user_data) { (void)vte; RtTerminal *terminal = RT_TERMINAL(user_data); terminal->child_pid = 0; g_signal_emit(terminal, signals[SIGNAL_CHILD_EXITED], 0, status); } static void on_current_directory_uri_changed(VteTerminal *vte, gpointer user_data) { RtTerminal *terminal = RT_TERMINAL(user_data); const gchar *uri = vte_terminal_get_current_directory_uri(vte); if (uri) { g_free(terminal->current_directory); if (g_str_has_prefix(uri, "file://")) { terminal->current_directory = g_strdup(uri + 7); } else { terminal->current_directory = g_strdup(uri); } } } static void apply_font_rendering(RtTerminal *terminal) { PangoContext *context = gtk_widget_get_pango_context(GTK_WIDGET(terminal->vte)); cairo_font_options_t *options = cairo_font_options_create(); cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_SUBPIXEL); cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_FULL); cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_ON); cairo_font_options_set_subpixel_order(options, CAIRO_SUBPIXEL_ORDER_RGB); pango_cairo_context_set_font_options(context, options); cairo_font_options_destroy(options); pango_context_changed(context); } static void rt_terminal_init(RtTerminal *terminal) { terminal->vte = NULL; terminal->scrollbar = NULL; terminal->font_config = NULL; terminal->theme = NULL; terminal->pty = NULL; terminal->child_pid = 0; terminal->current_directory = NULL; terminal->window_title = NULL; terminal->config = NULL; gtk_orientable_set_orientation(GTK_ORIENTABLE(terminal), GTK_ORIENTATION_HORIZONTAL); terminal->vte = VTE_TERMINAL(vte_terminal_new()); gtk_widget_set_hexpand(GTK_WIDGET(terminal->vte), TRUE); gtk_widget_set_vexpand(GTK_WIDGET(terminal->vte), TRUE); GtkAdjustment *adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(terminal->vte)); terminal->scrollbar = GTK_SCROLLBAR(gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, adj)); gtk_box_append(GTK_BOX(terminal), GTK_WIDGET(terminal->vte)); gtk_box_append(GTK_BOX(terminal), GTK_WIDGET(terminal->scrollbar)); g_signal_connect(terminal->vte, "window-title-changed", G_CALLBACK(on_window_title_changed), terminal); g_signal_connect(terminal->vte, "bell", G_CALLBACK(on_bell), terminal); g_signal_connect(terminal->vte, "child-exited", G_CALLBACK(on_child_exited), terminal); g_signal_connect(terminal->vte, "current-directory-uri-changed", G_CALLBACK(on_current_directory_uri_changed), terminal); } RtTerminal* rt_terminal_new(RtConfig *config) { RtTerminal *terminal = g_object_new(RT_TYPE_TERMINAL, NULL); if (config) { terminal->config = config; terminal->font_config = rt_font_new(config->font_family, config->font_size); terminal->theme = rt_theme_load_by_name(config->theme_name); vte_terminal_set_scrollback_lines(terminal->vte, config->scrollback_lines); vte_terminal_set_scroll_on_output(terminal->vte, config->scroll_on_output); vte_terminal_set_scroll_on_keystroke(terminal->vte, config->scroll_on_keystroke); vte_terminal_set_bold_is_bright(terminal->vte, config->bold_is_bright); vte_terminal_set_audible_bell(terminal->vte, config->audible_bell); if (g_strcmp0(config->cursor_shape, "block") == 0) { vte_terminal_set_cursor_shape(terminal->vte, VTE_CURSOR_SHAPE_BLOCK); } else if (g_strcmp0(config->cursor_shape, "ibeam") == 0) { vte_terminal_set_cursor_shape(terminal->vte, VTE_CURSOR_SHAPE_IBEAM); } else if (g_strcmp0(config->cursor_shape, "underline") == 0) { vte_terminal_set_cursor_shape(terminal->vte, VTE_CURSOR_SHAPE_UNDERLINE); } if (config->cursor_blink) { vte_terminal_set_cursor_blink_mode(terminal->vte, VTE_CURSOR_BLINK_ON); } else { vte_terminal_set_cursor_blink_mode(terminal->vte, VTE_CURSOR_BLINK_OFF); } if (config->word_chars) { vte_terminal_set_word_char_exceptions(terminal->vte, config->word_chars); } PangoFontDescription *font_desc = rt_font_get_description(terminal->font_config); vte_terminal_set_font(terminal->vte, font_desc); rt_theme_apply_to_vte(terminal->theme, terminal->vte); } else { terminal->font_config = rt_font_new("Monospace", 11); terminal->theme = rt_theme_create_dracula(); PangoFontDescription *font_desc = rt_font_get_description(terminal->font_config); vte_terminal_set_font(terminal->vte, font_desc); rt_theme_apply_to_vte(terminal->theme, terminal->vte); vte_terminal_set_scrollback_lines(terminal->vte, 10000); } apply_font_rendering(terminal); return terminal; } static void on_spawn_callback(VteTerminal *vte, GPid pid, GError *error, gpointer user_data) { (void)vte; RtTerminal *terminal = RT_TERMINAL(user_data); if (error) { g_warning("Failed to spawn shell: %s", error->message); return; } terminal->child_pid = pid; } void rt_terminal_spawn_shell(RtTerminal *terminal, const gchar *working_dir) { g_return_if_fail(RT_IS_TERMINAL(terminal)); gchar *shell = NULL; if (terminal->config) { shell = rt_config_get_shell(terminal->config); } else { shell = rt_utils_get_shell(); } gchar **argv = rt_utils_get_shell_argv(shell); gchar **envp = NULL; if (terminal->config) { envp = rt_config_get_environment(terminal->config); } else { envp = g_get_environ(); } envp = g_environ_setenv(envp, "TERM", "xterm-256color", TRUE); envp = g_environ_setenv(envp, "COLORTERM", "truecolor", TRUE); const gchar *cwd = working_dir; if (!cwd || !*cwd) { cwd = g_get_home_dir(); } vte_terminal_spawn_async( terminal->vte, VTE_PTY_DEFAULT, cwd, argv, envp, G_SPAWN_SEARCH_PATH | G_SPAWN_FILE_AND_ARGV_ZERO, NULL, NULL, NULL, -1, NULL, on_spawn_callback, terminal ); g_free(shell); g_strfreev(argv); g_strfreev(envp); } void rt_terminal_apply_font(RtTerminal *terminal, const gchar *font_desc) { g_return_if_fail(RT_IS_TERMINAL(terminal)); if (terminal->font_config) { g_object_unref(terminal->font_config); } terminal->font_config = rt_font_from_string(font_desc); PangoFontDescription *desc = rt_font_get_description(terminal->font_config); vte_terminal_set_font(terminal->vte, desc); apply_font_rendering(terminal); } void rt_terminal_apply_theme(RtTerminal *terminal, RtTheme *theme) { g_return_if_fail(RT_IS_TERMINAL(terminal)); g_return_if_fail(RT_IS_THEME(theme)); if (terminal->theme) { g_object_unref(terminal->theme); } terminal->theme = g_object_ref(theme); rt_theme_apply_to_vte(terminal->theme, terminal->vte); } void rt_terminal_set_dpi_scale(RtTerminal *terminal, gdouble scale) { g_return_if_fail(RT_IS_TERMINAL(terminal)); if (terminal->font_config) { rt_font_set_dpi_scale(terminal->font_config, scale); PangoFontDescription *desc = rt_font_get_description(terminal->font_config); vte_terminal_set_font(terminal->vte, desc); apply_font_rendering(terminal); } } void rt_terminal_copy_clipboard(RtTerminal *terminal) { g_return_if_fail(RT_IS_TERMINAL(terminal)); vte_terminal_copy_clipboard_format(terminal->vte, VTE_FORMAT_TEXT); } void rt_terminal_paste_clipboard(RtTerminal *terminal) { g_return_if_fail(RT_IS_TERMINAL(terminal)); vte_terminal_paste_clipboard(terminal->vte); } void rt_terminal_reset(RtTerminal *terminal) { g_return_if_fail(RT_IS_TERMINAL(terminal)); vte_terminal_reset(terminal->vte, TRUE, TRUE); } void rt_terminal_feed_child(RtTerminal *terminal, const gchar *data, gsize length) { g_return_if_fail(RT_IS_TERMINAL(terminal)); if (data && length > 0) { vte_terminal_feed_child(terminal->vte, data, length); } } gchar* rt_terminal_get_text_range(RtTerminal *terminal, glong start, glong end) { g_return_val_if_fail(RT_IS_TERMINAL(terminal), NULL); (void)start; (void)end; return NULL; } VteTerminal* rt_terminal_get_vte(RtTerminal *terminal) { g_return_val_if_fail(RT_IS_TERMINAL(terminal), NULL); return terminal->vte; } const gchar* rt_terminal_get_title(RtTerminal *terminal) { g_return_val_if_fail(RT_IS_TERMINAL(terminal), NULL); return terminal->window_title; }