diff options
Diffstat (limited to 'src/interface-gtk')
| -rw-r--r-- | src/interface-gtk/gtk-info-popup.c | 25 | ||||
| -rw-r--r-- | src/interface-gtk/gtk-info-popup.h | 2 | ||||
| -rw-r--r-- | src/interface-gtk/gtk-label.c | 46 | ||||
| -rw-r--r-- | src/interface-gtk/gtk-label.h | 6 | ||||
| -rw-r--r-- | src/interface-gtk/interface.c | 83 | ||||
| -rw-r--r-- | src/interface-gtk/view.c | 2 |
6 files changed, 119 insertions, 45 deletions
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c index aaa0a65..f2c8dc8 100644 --- a/src/interface-gtk/gtk-info-popup.c +++ b/src/interface-gtk/gtk-info-popup.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,8 @@ #include "gtk-label.h" #include "gtk-info-popup.h" +#define TECO_UNNAMED_FILE "(Unnamed)" + /* * FIXME: This is redundant with curses-info-popup.c. */ @@ -37,6 +39,7 @@ typedef struct { teco_stailq_entry_t entry; teco_popup_entry_type_t type; + /** entry name or empty string for the "(Unnamed)" buffer */ teco_string_t name; gboolean highlight; } teco_popup_entry_t; @@ -109,10 +112,10 @@ teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpoint GList *entry; for (entry = child_list; entry != NULL && !TECO_IS_GTK_LABEL(entry->data); entry = g_list_next(entry)); g_assert(entry != NULL); - const teco_string_t *str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data)); + teco_string_t str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data)); g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0, - str->data, (gulong)str->len); + str.data, (gulong)str.len); } static void @@ -249,6 +252,10 @@ teco_gtk_info_popup_new(void) GIcon * teco_gtk_info_popup_get_icon_for_path(const gchar *path, const gchar *fallback_name) { + if (!path || !*path) + /* "(Unnamed)" file */ + return g_icon_new_for_string(fallback_name, NULL); + GIcon *icon = NULL; g_autoptr(GFile) file = g_file_new_for_path(path); @@ -299,7 +306,7 @@ teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, static void teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, - const gchar *name, gssize len, gboolean highlight) + const gchar *name, gsize len, gboolean highlight) { g_return_if_fail(self != NULL); g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self)); @@ -318,12 +325,8 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic" : "folder"; - /* - * `name` is not guaranteed to be null-terminated. - */ - g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len); - - g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(path, fallback); + /* name comes from a teco_string_t and is guaranteed to be null-terminated */ + g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(name, fallback); if (icon) { gint width, height; gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); @@ -335,7 +338,7 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ } } - GtkWidget *label = teco_gtk_label_new(name, len); + GtkWidget *label = teco_gtk_label_new(name, len, TECO_UNNAMED_FILE); /* * Gtk v3.20 changed the CSS element names. * Adding a style class eases writing a portable fallback.css. diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h index ad79b84..3ce8e1f 100644 --- a/src/interface-gtk/gtk-info-popup.h +++ b/src/interface-gtk/gtk-info-popup.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c index e4b2823..6e05045 100644 --- a/src/interface-gtk/gtk-label.c +++ b/src/interface-gtk/gtk-label.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,7 @@ #include "gtk-label.h" -#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) +#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) struct _TecoGtkLabel { GtkLabel parent_instance; @@ -40,7 +40,10 @@ struct _TecoGtkLabel { PangoColor fg, bg; guint16 fg_alpha, bg_alpha; + /** text backing the label or empty string for fallback */ teco_string_t string; + /** fallback string to render if `string` is empty or NULL */ + const gchar *fallback; }; G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL) @@ -122,11 +125,21 @@ teco_gtk_label_class_init(TecoGtkLabelClass *klass) static void teco_gtk_label_init(TecoGtkLabel *self) {} +/** + * Create new TECO label widget. + * + * @param str String to render (can be NULL) + * @param len Length of str or negative value for null-terminated strings. + * @param fallback Null-terminated fallback string to render + * instead of empty strings. + * Must be a string constant with global lifetime or NULL. + */ GtkWidget * -teco_gtk_label_new(const gchar *str, gssize len) +teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback) { TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL)); + widget->fallback = fallback; teco_gtk_label_set_text(widget, str, len); return GTK_WIDGET(widget); @@ -251,27 +264,30 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len) teco_string_clear(&self->string); teco_string_init(&self->string, str, len < 0 ? strlen(str) : len); + teco_string_t string = self->string; + if (!string.len && self->fallback) { + string.data = (gchar *)self->fallback; + string.len = strlen(string.data); + } + g_autofree gchar *plaintext = NULL; + PangoAttrList *attribs = NULL; - if (self->string.len > 0) { - PangoAttrList *attribs = NULL; + teco_gtk_label_parse_string(string.data, string.len, + &self->fg, self->fg_alpha, + &self->bg, self->bg_alpha, + &attribs, &plaintext); - teco_gtk_label_parse_string(self->string.data, self->string.len, - &self->fg, self->fg_alpha, - &self->bg, self->bg_alpha, - &attribs, &plaintext); - - gtk_label_set_attributes(GTK_LABEL(self), attribs); - pango_attr_list_unref(attribs); - } + gtk_label_set_attributes(GTK_LABEL(self), attribs); + pango_attr_list_unref(attribs); gtk_label_set_text(GTK_LABEL(self), plaintext); } -const teco_string_t * +teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self) { - return &self->string; + return self->string; } /** diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h index 3cd4cb9..a84608a 100644 --- a/src/interface-gtk/gtk-label.h +++ b/src/interface-gtk/gtk-label.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,10 +24,10 @@ #define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type() G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel) -GtkWidget *teco_gtk_label_new(const gchar *str, gssize len); +GtkWidget *teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback); void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len); -const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self); +teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self); void teco_gtk_label_parse_string(const gchar *str, gssize len, PangoColor *fg, guint16 fg_alpha, diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index d7fc4c4..08ccf5d 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,14 +19,17 @@ #include "config.h" #endif +#include <stdlib.h> #include <string.h> #include <signal.h> +#include <unistd.h> #include <glib.h> #include <glib/gprintf.h> #include <glib/gstdio.h> #ifdef G_OS_UNIX +#include <unistd.h> #include <glib-unix.h> #endif @@ -78,7 +81,7 @@ static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny * static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED; static gchar teco_interface_get_ansi_key(GdkEventKey *event); -#define UNNAMED_FILE "(Unnamed)" +#define TECO_UNNAMED_FILE "(Unnamed)" #define USER_CSS_FILE ".teco_css" @@ -102,9 +105,10 @@ static struct { TECO_INFO_TYPE_BUFFER_DIRTY, TECO_INFO_TYPE_QREG } info_type; + /* current document's name or empty string for "(Unnamed)" buffer */ teco_string_t info_current; - gboolean no_csd; + gboolean no_csd, detach; gint xembed_id; GtkWidget *info_bar_widget; @@ -129,8 +133,48 @@ static struct { } teco_interface; void -teco_interface_init(void) +teco_interface_init(gint argc, gchar **argv) { +#ifdef G_OS_UNIX + if (teco_interface.detach && !g_getenv("__SCITECO_DETACHED")) { + /* + * NOTE: There is also daemon() on BSD/Linux, + * but the following should be more portable. + */ + pid_t pid = fork(); + g_assert(pid >= 0); + if (pid != 0) + /* parent process */ + exit(EXIT_SUCCESS); + + setsid(); + + if (isatty(0)) { + G_GNUC_UNUSED FILE *stdin_new = g_freopen("/dev/null", "r", stdin); + g_assert(stdin_new != NULL); + } + if (isatty(1)) { + G_GNUC_UNUSED FILE *stdout_new = g_freopen("/dev/null", "a+", stdout); + g_assert(stdout_new != NULL); + } + if (isatty(2)) { + G_GNUC_UNUSED FILE *stderr_new = g_freopen("/dev/null", "a+", stderr); + g_assert(stderr_new != NULL); + } + + /* + * gtk_get_option_group() already initialized GTK and even though the + * display is not yet opened, it's unsafe to continue. + * Instead, we re-exec already in the child process. + * We cannot easily remove --detach from argv, but still guard against + * recursive forks by using an environment variable. + */ + g_setenv("__SCITECO_DETACHED", "1", TRUE); + execv(argv[0], argv); + g_assert_not_reached(); + } +#endif + /* * gtk_init() is not necessary when using gtk_get_option_group(), * but this will open the default display. @@ -179,7 +223,7 @@ teco_interface_init(void) */ teco_interface.info_bar_widget = gtk_header_bar_new(); gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar"); - teco_interface.info_name_widget = teco_gtk_label_new("", 0); + teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0, NULL); gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget), @@ -265,7 +309,7 @@ teco_interface_init(void) gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar"); GtkWidget *message_bar_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget)); - teco_interface.message_widget = teco_gtk_label_new(NULL, 0); + teco_interface.message_widget = teco_gtk_label_new(NULL, 0, NULL); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget), "label"); @@ -340,6 +384,11 @@ teco_interface_get_options(void) G_OPTION_ARG_INT, &teco_interface.xembed_id, "Embed into an existing X11 Window.", "ID"}, #endif +#ifdef G_OS_UNIX + {"detach", 'd', G_OPTION_FLAG_IN_MAIN, + G_OPTION_ARG_NONE, &teco_interface.detach, + "Detach from controlling terminal (daemonize).", NULL}, +#endif {NULL} }; @@ -501,8 +550,13 @@ teco_interface_refresh_info(void) gtk_style_context_remove_class(style, "dirty"); g_auto(teco_string_t) info_current_temp; - teco_string_init(&info_current_temp, - teco_interface.info_current.data, teco_interface.info_current.len); + + if (!teco_interface.info_current.len) + teco_string_init(&info_current_temp, TECO_UNNAMED_FILE, strlen(TECO_UNNAMED_FILE)); + else + teco_string_init(&info_current_temp, + teco_interface.info_current.data, teco_interface.info_current.len); + if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY) teco_string_append_c(&info_current_temp, '*'); teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget), @@ -570,10 +624,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg) void teco_interface_info_update_buffer(const teco_buffer_t *buffer) { - const gchar *filename = buffer->filename ? : UNNAMED_FILE; - teco_string_clear(&teco_interface.info_current); - teco_string_init(&teco_interface.info_current, filename, strlen(filename)); + teco_string_init(&teco_interface.info_current, buffer->filename, + buffer->filename ? strlen(buffer->filename) : 0); teco_interface.info_type = buffer->state > TECO_BUFFER_CLEAN ? TECO_INFO_TYPE_BUFFER_DIRTY : TECO_INFO_TYPE_BUFFER; } @@ -1129,7 +1182,7 @@ teco_interface_event_loop(GError **error) if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg, &scitecoconfig.data, &scitecoconfig.len, NULL, error)) return FALSE; - if (teco_string_contains(&scitecoconfig, '\0')) { + if (teco_string_contains(scitecoconfig, '\0')) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Null-character not allowed in filenames"); return FALSE; @@ -1472,7 +1525,9 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data) { g_assert(len >= teco_interface.popup_prefix_len); - const teco_string_t insert = {str+teco_interface.popup_prefix_len, len-teco_interface.popup_prefix_len}; + /* str is an empty string for the "(Unnamed)" buffer */ + const teco_string_t insert = {str+teco_interface.popup_prefix_len, + len-teco_interface.popup_prefix_len}; teco_machine_t *machine = &teco_cmdline.machine.parent; const teco_view_t *last_view = teco_interface_current_view; @@ -1482,7 +1537,7 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint * A auto completion should never result in program termination. */ if (machine->current->insert_completion_cb && - !machine->current->insert_completion_cb(machine, &insert, NULL)) + !machine->current->insert_completion_cb(machine, insert, NULL)) return; teco_interface_popup_clear(); teco_cmdline_update(); diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c index 44e3988..3a18f33 100644 --- a/src/interface-gtk/view.c +++ b/src/interface-gtk/view.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2025 Robin Haberkorn + * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by |
