/* 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 <stdarg.h>
#include <unistd.h>
#include <sys/wait.h>
#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;
}