/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2013 Red Hat, Inc. */ /** * SECTION:nmt-newt-utils * @short_description: Utility functions */ #include "libnm-client-aux-extern/nm-default-client.h" #include "nmt-newt-utils.h" #include #include #include #include "libnm-glib-aux/nm-io-utils.h" static void nmt_newt_dialog_g_log_handler(const char * log_domain, GLogLevelFlags log_level, const char * message, gpointer user_data) { const char * level_name; char * full_message; int screen_width, screen_height; newtComponent text, ok, form; newtGrid grid; g_assert(!(log_level & G_LOG_FLAG_RECURSION)); if (log_level & G_LOG_LEVEL_DEBUG) return; switch (log_level & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_ERROR: level_name = "ERROR"; break; case G_LOG_LEVEL_CRITICAL: level_name = "CRITICAL"; break; case G_LOG_LEVEL_WARNING: level_name = "WARNING"; break; case G_LOG_LEVEL_MESSAGE: level_name = "Message"; break; default: level_name = NULL; } full_message = g_strdup_printf("%s%s%s%s%s", log_domain ?: "", log_domain && level_name ? " " : "", level_name ?: "", log_domain || level_name ? ": " : "", message); /* newtWinMessage() wraps the window too narrowly by default, so * we don't want to use that. But we intentionally avoid using any * NmtNewt classes, to avoid possible error recursion. */ newtGetScreenSize(&screen_width, &screen_height); text = newtTextboxReflowed(-1, -1, full_message, MAX(70, screen_width - 10), 0, 0, 0); g_free(full_message); ok = newtButton(-1, -1, "OK"); grid = newtCreateGrid(1, 2); newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, 0, 0, 0, 0, 0, 0); newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, ok, 0, 1, 0, 0, NEWT_ANCHOR_RIGHT, 0); newtGridWrappedWindow(grid, (char *) (level_name ?: "")); newtGridFree(grid, TRUE); form = newtForm(NULL, NULL, 0); newtFormAddComponents(form, text, ok, NULL); newtRunForm(form); newtFormDestroy(form); newtPopWindow(); } static void nmt_newt_basic_g_log_handler(const char * log_domain, GLogLevelFlags log_level, const char * message, gpointer user_data) { newtSuspend(); g_log_default_handler(log_domain, log_level, message, NULL); newtResume(); } static void nmt_newt_suspend_callback(gpointer user_data) { newtSuspend(); kill(getpid(), SIGTSTP); newtResume(); } static void _newtSetColor(int colorset, const char *fg, const char *bg) { newtSetColor(colorset, (char *) fg, (char *) bg); } /** * nmt_newt_parse_colors: * @s: buffer with color settings * @is_newt: boolean indicating if buffer s * contains NEWT (true) or NMT (false) color setting * * Parses content of buffer s and sets color accordingly * with newtSetColor() */ static void nmt_newt_parse_colors(const char *s, bool is_newt) { gs_free const char **lines = NULL; size_t i; lines = nm_utils_strsplit_set(s, ";:\n\r\t "); if (!lines) return; for (i = 0; lines[i]; i++) { const char *name; const char *fg; const char *bg; char * parsed_s; parsed_s = (char *) lines[i]; name = parsed_s; if (!(parsed_s = strchr(parsed_s, '=')) || !*parsed_s) continue; *parsed_s = '\0'; fg = ++parsed_s; if (!(parsed_s = strchr(parsed_s, ',')) || !*parsed_s) continue; *parsed_s = '\0'; bg = ++parsed_s; if (is_newt) { if (nm_streq(name, "checkbox")) _newtSetColor(NEWT_COLORSET_CHECKBOX, fg, bg); } else { if (nm_streq(name, "badLabel")) { _newtSetColor(NMT_NEWT_COLORSET_BAD_LABEL, fg, bg); } else if (nm_streq(name, "plainLabel")) { _newtSetColor(NMT_NEWT_COLORSET_PLAIN_LABEL, fg, bg); } else if (nm_streq(name, "disabledButton")) { _newtSetColor(NMT_NEWT_COLORSET_DISABLED_BUTTON, fg, bg); } else if (nm_streq(name, "textboxWithBackground")) { _newtSetColor(NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND, fg, bg); } } } } /** * nmt_newt_nit_colors: * @is_newt: boolean indicating if the function is looking for NEWT or NMT env var * * Looks for enviroment variables for aditional * color set up in nmtui */ static void nmt_newt_init_colors(gboolean is_newt) { const char * colors; gs_free char *file_content = NULL; colors = getenv(is_newt ? "NEWT_COLORS" : "NMT_NEWT_COLORS"); if (!colors) { const char *file_name; file_name = getenv(is_newt ? "NEWT_COLORS_FILE" : "NMT_NEWT_COLORS_FILE"); if (file_name && file_name[0] != '\0' && (nm_utils_file_get_contents(-1, file_name, 16384, NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, &file_content, NULL, NULL, NULL))) { colors = file_content; } } nmt_newt_parse_colors(colors, is_newt); } /** libnm-client-aux-extern * nmt_newt_init: * * Wrapper for newtInit() that also does some nmt-newt-internal setup. * This should be called once, before any other nmt-newt functions. */ void nmt_newt_init(void) { newtInit(); newtCls(); newtSetColor(NEWT_COLORSET_CHECKBOX, "black", "lightgray"); newtSetColor(NMT_NEWT_COLORSET_BAD_LABEL, "red", "lightgray"); newtSetColor(NMT_NEWT_COLORSET_PLAIN_LABEL, "black", "lightgray"); newtSetColor(NMT_NEWT_COLORSET_DISABLED_BUTTON, "blue", "lightgray"); newtSetColor(NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND, "black", "white"); nmt_newt_init_colors(TRUE); nmt_newt_init_colors(FALSE); if (g_getenv("NMTUI_DEBUG")) g_log_set_default_handler(nmt_newt_dialog_g_log_handler, NULL); else g_log_set_default_handler(nmt_newt_basic_g_log_handler, NULL); newtSetSuspendCallback(nmt_newt_suspend_callback, NULL); } /** * nmt_newt_finished: * * Wrapper for newtFinished(). Should be called at the end of the program. */ void nmt_newt_finished(void) { newtFinished(); g_log_set_default_handler(g_log_default_handler, NULL); } /** * nmt_newt_message_dialog: * @message: a printf()-style message format * @...: arguments * * Displays the given message in a dialog box with a single "OK" * button, and returns after the user clicks "OK". */ void nmt_newt_message_dialog(const char *message, ...) { va_list ap; char * msg, *msg_lc, *ok_lc; va_start(ap, message); msg = g_strdup_vprintf(message, ap); va_end(ap); msg_lc = nmt_newt_locale_from_utf8(msg); ok_lc = nmt_newt_locale_from_utf8(_("OK")); newtWinMessage(NULL, ok_lc, "%s", msg_lc); g_free(ok_lc); g_free(msg_lc); g_free(msg); } /** * nmt_newt_choice_dialog: * @button1: the label for the first button * @button2: the label for the second button * @message: a printf()-style message format * @...: arguments * * Displays the given message in a dialog box with two buttons with * the indicated labels, and waits for the user to click one. * * Returns: which button was clicked: 0 for @button1 or 1 for @button2 */ int nmt_newt_choice_dialog(const char *button1, const char *button2, const char *message, ...) { va_list ap; char * msg, *msg_lc, *button1_lc, *button2_lc; int choice; va_start(ap, message); msg = g_strdup_vprintf(message, ap); va_end(ap); msg_lc = nmt_newt_locale_from_utf8(msg); button1_lc = nmt_newt_locale_from_utf8(button1); button2_lc = nmt_newt_locale_from_utf8(button2); choice = newtWinChoice(NULL, button1_lc, button2_lc, "%s", msg_lc); g_free(button1_lc); g_free(button2_lc); g_free(msg_lc); g_free(msg); return choice; } /** * nmt_newt_locale_to_utf8: * @str_lc: a string in the user's locale encoding * * Convenience wrapper around g_locale_to_utf8(). * * Note that libnewt works in terms of the user's locale character * set, NOT UTF-8, so all strings received from libnewt must be * converted back to UTF-8 before being returned to the caller or used * in other APIs. * * Returns: @str_lc, converted to UTF-8. */ char * nmt_newt_locale_to_utf8(const char *str_lc) { char *str_utf8; str_utf8 = g_locale_to_utf8(str_lc, -1, NULL, NULL, NULL); if (!str_utf8) str_utf8 = g_strdup(""); return str_utf8; } /** * nmt_newt_locale_from_utf8: * @str_utf8: a UTF-8 string * * Convenience wrapper around g_locale_from_utf8(). * * Note that libnewt works in terms of the user's locale character * set, NOT UTF-8, so all strings from nmt-newt must be converted to * locale encoding before being passed to libnewt. * * Returns: @str_utf8, converted to the user's locale encoding. */ char * nmt_newt_locale_from_utf8(const char *str_utf8) { char *str_lc; str_lc = g_locale_from_utf8(str_utf8, -1, NULL, NULL, NULL); if (!str_lc) str_lc = g_strdup(""); return str_lc; } /** * nmt_newt_text_width * @str: a UTF-8 string * * Computes the width (in terminal columns) of @str. * * Returns: the width of @str */ int nmt_newt_text_width(const char *str) { int width; gunichar ch; for (width = 0; *str; str = g_utf8_next_char(str)) { ch = g_utf8_get_char(str); /* Based on _vte_iso2022_unichar_width */ if (G_LIKELY(ch < 0x80)) width += 1; else if (G_UNLIKELY(g_unichar_iszerowidth(ch))) width += 0; else if (G_UNLIKELY(g_unichar_iswide(ch))) width += 2; else width += 1; } return width; } /** * nmt_newt_edit_string: * @data: data to edit * * libnewt does not have a multi-line editable text component, so * nmt-newt provides this function instead, which will open the user's * editor to edit a file containing the given @data (ensuring that the * current screen state is saved before starting the editor and * restored after it returns). * * Returns: the edited data, or %NULL if an error occurred. */ char * nmt_newt_edit_string(const char *data) { gssize len, nwrote; char * filename, *argv[3]; GError *error = NULL; int fd, status; char * new_data = NULL; fd = g_file_open_tmp("XXXXXX.json", &filename, &error); if (fd == -1) { nmt_newt_message_dialog(_("Could not create temporary file: %s"), error->message); g_error_free(error); return NULL; } len = data ? strlen(data) : 0; while (len) { do nwrote = write(fd, data, len); while (nwrote == -1 && errno == EINTR); len -= nwrote; data += nwrote; } nm_close(fd); argv[0] = (char *) g_getenv("VISUAL"); if (!argv[0]) argv[0] = (char *) g_getenv("EDITOR"); if (!argv[0]) argv[0] = (char *) "vi"; argv[1] = filename; argv[2] = NULL; newtSuspend(); g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN, NULL, NULL, NULL, NULL, &status, &error); newtResume(); if (error) { nmt_newt_message_dialog(_("Could not create temporary file: %s"), error->message); g_error_free(error); goto done; } if (!g_spawn_check_exit_status(status, &error)) { nmt_newt_message_dialog(_("Editor failed: %s"), error->message); g_error_free(error); goto done; } if (!g_file_get_contents(filename, &new_data, NULL, &error)) { nmt_newt_message_dialog(_("Could not re-read file: %s"), error->message); g_error_free(error); goto done; } done: unlink(filename); g_free(filename); return new_data; }