diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml
index 620ae83516..c70258459c 100644
--- a/man/org.freedesktop.hostname1.xml
+++ b/man/org.freedesktop.hostname1.xml
@@ -80,6 +80,8 @@ node /org/freedesktop/hostname1 {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OperatingSystemPrettyName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s OperatingSystemFancyName = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OperatingSystemCPEName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t OperatingSystemSupportEnd = ...;
@@ -170,6 +172,8 @@ node /org/freedesktop/hostname1 {
+
+
@@ -285,9 +289,11 @@ node /org/freedesktop/hostname1 {
KernelName, KernelRelease, and
KernelVersion expose the kernel name (e.g. Linux), release
(e.g. 5.0.0-11), and version (i.e. the build number, e.g. #11) as
- reported by uname2.
- OperatingSystemPrettyName, OperatingSystemCPEName, and
- HomeURL expose the PRETTY_NAME=, CPE_NAME= and
+ reported by uname2.
+
+ OperatingSystemPrettyName, OperatingSystemFancyName,
+ OperatingSystemCPEName, and HomeURL expose the
+ PRETTY_NAME=, FANCY_NAME=, CPE_NAME= and
HOME_URL= fields from
os-release5. The
purpose of those properties is to allow remote clients to access this information over D-Bus. Local
@@ -487,6 +493,7 @@ node /org/freedesktop/hostname1 {
ChassisAssetTag, OperatingSystemImageID,
OperatingSystemImageVersion, HardwareSKU, and
HardwareVersion were added in version 258.
+ OperatingSystemFancyName was added in version 260.
diff --git a/man/os-release.xml b/man/os-release.xml
index 1ab7e652b8..8b652d78f0 100644
--- a/man/os-release.xml
+++ b/man/os-release.xml
@@ -191,9 +191,34 @@
A pretty operating system name in a format suitable for presentation to the
user. May or may not contain a release code name or OS version of some kind, as suitable. If not
- set, a default of PRETTY_NAME="Linux" may be used
+ set, a default of PRETTY_NAME="Linux" may be used.
- Example: PRETTY_NAME="Fedora 17 (Beefy Miracle)".
+ Example: PRETTY_NAME="Fedora 17 (Beefy Miracle)"
+
+
+
+ FANCY_NAME=
+
+ Similar to PRETTY_NAME=, but may contain ANSI sequences and
+ fancy UTF-8 characters such as emojis. If defined this shall preferably used when displaying the OS
+ name on modern terminal emulators. If the terminal emulator does not support emojis,
+ PRETTY_NAME= shall be shown instead, possibly with
+ ANSI_COLOR= coloring. Use \033 to encode the ESC
+ character and \\ to encode the backslash character.
+
+
+
+ Unlike PRETTY_NAME= this field must not contain information that is
+ present in other fields, in particular the version (already specified in
+ VERSION=) or the codename (already specified in
+ VERSION_CODENAME=).
+
+ Example:
+ FANCY_NAME="🍅 \033[31mTomato\033[0;1mOS\033[0m"
+
+
diff --git a/src/core/main.c b/src/core/main.c
index 2676cd4c6b..0e5e201f2d 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -102,6 +102,7 @@
#include "umask-util.h"
#include "unit-name.h"
#include "user-util.h"
+#include "utf8.h"
#include "version.h"
#include "virt.h"
#include "watchdog.h"
@@ -1389,37 +1390,76 @@ static int enforce_syscall_archs(Set *archs) {
}
static int os_release_status(void) {
- _cleanup_free_ char *pretty_name = NULL, *name = NULL, *version = NULL,
- *ansi_color = NULL, *support_end = NULL;
+ _cleanup_free_ char *pretty_name = NULL, *fancy_name = NULL,
+ *name = NULL, *version = NULL, *ansi_color = NULL, *support_end = NULL, *codename = NULL;
int r;
r = parse_os_release(NULL,
- "PRETTY_NAME", &pretty_name,
- "NAME", &name,
- "VERSION", &version,
- "ANSI_COLOR", &ansi_color,
- "SUPPORT_END", &support_end);
+ "PRETTY_NAME", &pretty_name,
+ "FANCY_NAME", &fancy_name,
+ "NAME", &name,
+ "VERSION", &version,
+ "VERSION_CODENAME", &codename,
+ "ANSI_COLOR", &ansi_color,
+ "SUPPORT_END", &support_end);
if (r < 0)
return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read os-release file, ignoring: %m");
const char *label = os_release_pretty_name(pretty_name, name);
- const char *color = empty_to_null(ansi_color) ?: "1";
if (show_status_on(arg_show_status)) {
+ const char *color = empty_to_null(ansi_color) ?: "1";
+
+ /* The fancy name may contain emoji characters and ANSI sequences. Don't use it if our locale
+ * doesn't allow that, or ANSI sequences are off, or if it is empty. */
+
+ if (!isempty(fancy_name)) {
+ _cleanup_free_ char *unescaped = NULL;
+
+ /* Undo one level of C-style unescaping for this one */
+ ssize_t l = cunescape(fancy_name, /* flags= */ 0, &unescaped);
+ if (l < 0) {
+ log_debug_errno(l, "Failed to unescape FANCY_NAME=, ignoring: %m");
+ fancy_name = mfree(fancy_name);
+ } else {
+ free_and_replace(fancy_name, unescaped);
+
+ /* FANCY_NAME= does not contain version/codename info (unlike PRETTY_NAME=),
+ * but in this context it makes sense to show them if defined, hence append
+ * them here. */
+ if (version && !strextend(&fancy_name, " ", version))
+ return log_oom();
+
+ if (codename && !strextend(&fancy_name, " (", codename, ")"))
+ return log_oom();
+ }
+ }
+
+ if (isempty(fancy_name) ||
+ (!emoji_enabled() && !ascii_is_valid(fancy_name)) ||
+ !log_get_show_color())
+ fancy_name = mfree(fancy_name);
+
+ if (!fancy_name && log_get_show_color()) {
+ fancy_name = strjoin("\x1B[", color, "m", label);
+ if (!fancy_name)
+ return log_oom();
+ }
+
if (in_initrd()) {
if (log_get_show_color())
status_printf(NULL, 0,
- ANSI_HIGHLIGHT "Booting initrd of " ANSI_NORMAL "\x1B[%sm%s" ANSI_NORMAL ANSI_HIGHLIGHT "." ANSI_NORMAL,
- color, label);
+ ANSI_HIGHLIGHT "Booting initrd of " ANSI_NORMAL "%s" ANSI_NORMAL ANSI_HIGHLIGHT "." ANSI_NORMAL,
+ fancy_name);
else
status_printf(NULL, 0,
"Booting initrd of %s...", label);
} else {
if (log_get_show_color())
status_printf(NULL, 0,
- "\n" ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "\x1B[%sm%s" ANSI_NORMAL ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n",
- color, label);
+ "\n" ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "%s" ANSI_NORMAL ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n",
+ fancy_name);
else
status_printf(NULL, 0,
"\nWelcome to %s!\n",
diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c
index 227bcf4c50..ae1af41425 100644
--- a/src/hostname/hostnamectl.c
+++ b/src/hostname/hostnamectl.c
@@ -29,6 +29,7 @@
#include "runtime-scope.h"
#include "string-util.h"
#include "time-util.h"
+#include "utf8.h"
#include "verbs.h"
static bool arg_ask_password = true;
@@ -51,6 +52,7 @@ typedef struct StatusInfo {
const char *kernel_name;
const char *kernel_release;
const char *os_pretty_name;
+ const char *os_fancy_name;
const char *os_cpe_name;
usec_t os_support_end;
const char *os_image_id;
@@ -234,7 +236,14 @@ static int print_status_info(StatusInfo *i) {
return table_log_add_error(r);
}
- if (!isempty(i->os_pretty_name)) {
+ if (!isempty(i->os_fancy_name) && (emoji_enabled() || ascii_is_valid(i->os_fancy_name)) && colors_enabled()) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Operating System",
+ TABLE_STRING_WITH_ANSI, i->os_fancy_name,
+ TABLE_SET_URL, i->home_url);
+ if (r < 0)
+ return table_log_add_error(r);
+ } else if (!isempty(i->os_pretty_name)) {
r = table_add_many(table,
TABLE_FIELD, "Operating System",
TABLE_STRING, i->os_pretty_name,
@@ -425,6 +434,7 @@ static int show_all_names(sd_bus *bus) {
{ "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) },
{ "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) },
{ "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) },
+ { "OperatingSystemFancyName", "s", NULL, offsetof(StatusInfo, os_fancy_name) },
{ "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) },
{ "OperatingSystemSupportEnd", "t", NULL, offsetof(StatusInfo, os_support_end) },
{ "OperatingSystemImageID", "s", NULL, offsetof(StatusInfo, os_image_id) },
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index fbe1b2fa7a..d2461981af 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -22,6 +22,7 @@
#include "device-private.h"
#include "env-file.h"
#include "env-util.h"
+#include "escape.h"
#include "extract-word.h"
#include "fileio.h"
#include "hashmap.h"
@@ -75,6 +76,7 @@ typedef enum {
PROP_OS_SUPPORT_END,
PROP_OS_IMAGE_ID,
PROP_OS_IMAGE_VERSION,
+ PROP_OS_FANCY_NAME,
_PROP_MAX,
_PROP_INVALID = -EINVAL,
} HostProperty;
@@ -192,7 +194,7 @@ static void context_read_machine_info(Context *c) {
}
static void context_read_os_release(Context *c) {
- _cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL;
+ _cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL, *os_fancy_name = NULL, *os_ansi_color = NULL;
struct stat current_stat = {};
int r;
@@ -209,10 +211,13 @@ static void context_read_os_release(Context *c) {
(UINT64_C(1) << PROP_OS_HOME_URL) |
(UINT64_C(1) << PROP_OS_SUPPORT_END) |
(UINT64_C(1) << PROP_OS_IMAGE_ID) |
- (UINT64_C(1) << PROP_OS_IMAGE_VERSION));
+ (UINT64_C(1) << PROP_OS_IMAGE_VERSION) |
+ (UINT64_C(1) << PROP_OS_FANCY_NAME));
r = parse_os_release(NULL,
"PRETTY_NAME", &os_pretty_name,
+ "FANCY_NAME", &os_fancy_name,
+ "ANSI_COLOR", &os_ansi_color,
"NAME", &os_name,
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
"HOME_URL", &c->data[PROP_OS_HOME_URL],
@@ -225,6 +230,31 @@ static void context_read_os_release(Context *c) {
if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0)
log_oom();
+ if (!isempty(os_fancy_name)) {
+ _cleanup_free_ char *unescaped = NULL;
+
+ /* We undo one level of C escapes on this */
+ ssize_t l = cunescape(os_fancy_name, /* flags= */ 0, &unescaped);
+ if (l < 0) {
+ log_warning_errno(l, "Failed to unescape fancy OS name, ignoring: %m");
+ os_fancy_name = mfree(os_fancy_name);
+ } else
+ free_and_replace(os_fancy_name, unescaped);
+ }
+
+ if (isempty(os_fancy_name)) {
+ free(os_fancy_name); /* free if empty string */
+
+ if (isempty(os_ansi_color))
+ os_fancy_name = strdup(c->data[PROP_OS_PRETTY_NAME]);
+ else
+ os_fancy_name = strjoin("\x1B[", os_ansi_color, "m", c->data[PROP_OS_PRETTY_NAME]);
+ if (!os_fancy_name)
+ log_oom();
+ }
+
+ free_and_replace(c->data[PROP_OS_FANCY_NAME], os_fancy_name);
+
c->etc_os_release_stat = current_stat;
}
@@ -1705,6 +1735,7 @@ static int build_describe_response(Context *c, bool privileged, sd_json_variant
SD_JSON_BUILD_PAIR_STRING("KernelRelease", u.release),
SD_JSON_BUILD_PAIR_STRING("KernelVersion", u.version),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemPrettyName", c->data[PROP_OS_PRETTY_NAME]),
+ SD_JSON_BUILD_PAIR_STRING("OperatingSystemFancyName", c->data[PROP_OS_FANCY_NAME]),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemCPEName", c->data[PROP_OS_CPE_NAME]),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemHomeURL", c->data[PROP_OS_HOME_URL]),
JSON_BUILD_PAIR_FINITE_USEC("OperatingSystemSupportEnd", eol),
@@ -1781,6 +1812,7 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_PRETTY_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("OperatingSystemFancyName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_FANCY_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemCPEName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_CPE_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemSupportEnd", "t", property_get_os_support_end, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HomeURL", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_HOME_URL]), SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
index 9e366ce513..9f6c46afb7 100644
--- a/src/shared/format-table.c
+++ b/src/shared/format-table.c
@@ -283,6 +283,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
return 0;
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@@ -529,7 +530,7 @@ int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt
int r;
assert(t);
- assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
+ assert(IN_SET(dt, TABLE_STRING, TABLE_STRING_WITH_ANSI, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
va_start(ap, format);
r = vasprintf(&buffer, format, ap);
@@ -933,6 +934,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break;
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@@ -1393,6 +1395,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
switch (a->type) {
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_FIELD:
case TABLE_HEADER:
return strcmp(a->string, b->string);
@@ -1574,7 +1577,13 @@ static char* format_strv_width(char **strv, size_t column_width) {
return buf;
}
-static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
+static const char *table_data_format(
+ Table *t,
+ TableData *d,
+ bool avoid_uppercasing,
+ size_t column_width,
+ bool *have_soft) {
+
assert(d);
if (d->formatted &&
@@ -1587,6 +1596,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
return table_ersatz_string(t);
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@@ -2068,6 +2078,42 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
return d->formatted;
}
+static const char *table_data_format_strip_ansi(
+ Table *t,
+ TableData *d,
+ bool avoid_uppercasing,
+ size_t column_width,
+ bool *have_soft,
+ char **ret_buffer) {
+
+ /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
+
+ assert(ret_buffer);
+
+ const char *c;
+
+ c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
+ if (!c)
+ return NULL;
+
+ if (d->type != TABLE_STRING_WITH_ANSI) {
+ /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
+ * original string as-is */
+ *ret_buffer = NULL;
+ return c;
+ }
+
+ _cleanup_free_ char *s = strdup(c);
+ if (!s)
+ return NULL;
+
+ if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
+ return NULL;
+
+ *ret_buffer = TAKE_PTR(s);
+ return *ret_buffer;
+}
+
static int console_width_height(
const char *s,
size_t *ret_width,
@@ -2122,14 +2168,20 @@ static int table_data_requested_width_height(
size_t *ret_height,
bool *have_soft) {
- _cleanup_free_ char *truncated = NULL;
+ _cleanup_free_ char *truncated = NULL, *buffer = NULL;
bool truncation_applied = false;
size_t width, height;
+ bool soft = false;
const char *t;
int r;
- bool soft = false;
- t = table_data_format(table, d, false, available_width, &soft);
+ t = table_data_format_strip_ansi(
+ table,
+ d,
+ /* avoid_uppercasing= */ false,
+ available_width,
+ &soft,
+ &buffer);
if (!t)
return -ENOMEM;
@@ -2352,7 +2404,7 @@ int table_print(Table *t, FILE *f) {
if (r < 0)
return r;
if (r > 0) { /* Truncated because too many lines? */
- _cleanup_free_ char *last = NULL;
+ _cleanup_free_ char *last = NULL, *buffer = NULL;
const char *field;
/* If we are going to show only the first few lines of a cell that has
@@ -2360,9 +2412,13 @@ int table_print(Table *t, FILE *f) {
* ellipsis. Hence, let's figure out the last line, and account for its
* length plus ellipsis. */
- field = table_data_format(t, d, false,
- width ? width[j] : SIZE_MAX,
- &any_soft);
+ field = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width ? width[j] : SIZE_MAX,
+ &any_soft,
+ &buffer);
if (!field)
return -ENOMEM;
@@ -2550,7 +2606,7 @@ int table_print(Table *t, FILE *f) {
more_sublines = false;
for (size_t j = 0; j < display_columns; j++) {
- _cleanup_free_ char *buffer = NULL, *extracted = NULL;
+ _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
bool lines_truncated = false;
const char *field, *color = NULL, *underline = NULL;
TableData *d;
@@ -2558,7 +2614,21 @@ int table_print(Table *t, FILE *f) {
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
- field = table_data_format(t, d, false, width[j], NULL);
+ if (colors_enabled())
+ field = table_data_format(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width[j],
+ /* have_soft= */ NULL);
+ else
+ field = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width[j],
+ /* have_soft= */ NULL,
+ &stripped_ansi_buffer);
if (!field)
return -ENOMEM;
@@ -2665,7 +2735,8 @@ int table_print(Table *t, FILE *f) {
fputs(field, f);
- if (color || underline)
+ /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
+ if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
fputs(ANSI_NORMAL, f);
gap_color = d->rgap_color;
@@ -2920,6 +2991,18 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) {
SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
+ case TABLE_STRING_WITH_ANSI: {
+ _cleanup_free_ char *s = strdup(d->string);
+ if (!s)
+ return -ENOMEM;
+
+ /* We strip the ANSI data when outputting to JSON */
+ if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
+ return -ENOMEM;
+
+ return sd_json_variant_new_string(ret, s);
+ }
+
default:
return -EINVAL;
}
@@ -2963,7 +3046,7 @@ char* table_mangle_to_json_field_name(const char *str) {
}
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
- _cleanup_free_ char *mangled = NULL;
+ _cleanup_free_ char *mangled = NULL, *buffer = NULL;
const char *n;
assert(t);
@@ -2973,7 +3056,13 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
n = d->string;
else {
- n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
+ n = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ true,
+ /* column_width= */ SIZE_MAX,
+ /* have_soft= */ NULL,
+ &buffer);
if (!n)
return -ENOMEM;
}
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
index 2147b82317..56dd18fa66 100644
--- a/src/shared/format-table.h
+++ b/src/shared/format-table.h
@@ -10,6 +10,7 @@
typedef enum TableDataType {
TABLE_EMPTY,
TABLE_STRING,
+ TABLE_STRING_WITH_ANSI, /* like the above, but contains ANSI sequences/TABs. They will be stripped when outputing to JSON */
TABLE_HEADER, /* in regular mode: the cells in the first row, that carry the column names */
TABLE_FIELD, /* in vertical mode: the cells in the first column, that carry the field names */
TABLE_STRV,
diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c
index fb265b7511..e10f113046 100644
--- a/src/shared/varlink-io.systemd.Hostname.c
+++ b/src/shared/varlink-io.systemd.Hostname.c
@@ -20,7 +20,10 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_DEFINE_OUTPUT(KernelName, SD_VARLINK_STRING, 0),
SD_VARLINK_DEFINE_OUTPUT(KernelRelease, SD_VARLINK_STRING, 0),
SD_VARLINK_DEFINE_OUTPUT(KernelVersion, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("'Pretty' name of the OS. This is the primary OS identifier that is suitable for presentation to the user. Typically includes a version too, but doesn't have to."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("'Fancy' name of the OS; may contain non-ASCII Unicode chars, as well as basic ANSI sequences. This is similar to 'OperatingSystemPrettyName', but is preferably used on terminals that support ANSI sequences and full Unicode."),
+ SD_VARLINK_DEFINE_OUTPUT(OperatingSystemFancyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c
index ff886a3d5e..a2e142791f 100644
--- a/src/test/test-format-table.c
+++ b/src/test/test-format-table.c
@@ -4,6 +4,8 @@
#include
#include "alloc-util.h"
+#include "ansi-color.h"
+#include "env-util.h"
#include "format-table.h"
#include "json-util.h"
#include "terminal-util.h"
@@ -835,9 +837,80 @@ TEST(table_bps) {
"2500000000 2.3G 2.5Gbps\n");
}
+TEST(table_ansi) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+
+ ASSERT_NOT_NULL((table = table_new("foo", "bar", "baz", "kkk")));
+
+ ASSERT_OK(table_add_many(table,
+ TABLE_STRING, "hallo",
+ TABLE_STRING_WITH_ANSI, "knuerz" ANSI_HIGHLIGHT_RED "red" ANSI_HIGHLIGHT_GREEN "green",
+ TABLE_STRING_WITH_ANSI, "noansi",
+ TABLE_STRING_WITH_ANSI, ANSI_GREY "thisisgrey"));
+
+ unsigned saved_columns = columns();
+ bool saved_color = colors_enabled();
+ _cleanup_free_ char *saved_term = NULL;
+ const char *e = getenv("TERM");
+ if (e)
+ ASSERT_NOT_NULL((saved_term = strdup(e)));
+
+ ASSERT_OK_ERRNO(setenv("COLUMNS", "200", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "1", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("TERM", FALLBACK_TERM, /* overwrite= */ true));
+ reset_terminal_feature_caches();
+
+ _cleanup_free_ char *formatted = NULL;
+ ASSERT_OK(table_format(table, &formatted));
+
+ ASSERT_STREQ(formatted,
+ ANSI_ADD_UNDERLINE "FOO " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "BAR " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "BAZ " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "KKK " ANSI_NORMAL "\n"
+ "hallo knuerz" ANSI_HIGHLIGHT_RED "red" ANSI_HIGHLIGHT_GREEN "green" ANSI_NORMAL
+ " noansi" ANSI_NORMAL
+ " " ANSI_GREY "thisisgrey" ANSI_NORMAL "\n");
+
+ /* Validate that color is correctly stripped */
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "0", /* overwrite= */ true));
+ reset_terminal_feature_caches();
+
+ formatted = mfree(formatted);
+ ASSERT_OK(table_format(table, &formatted));
+
+ ASSERT_STREQ(formatted,
+ "FOO BAR BAZ KKK\n"
+ "hallo knuerzredgreen noansi thisisgrey\n");
+
+ ASSERT_OK(table_print(table, /* f= */ NULL));
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL, *jj = NULL;
+
+ ASSERT_OK(table_to_json(table, &j));
+
+ ASSERT_OK(sd_json_build(&jj,
+ SD_JSON_BUILD_ARRAY(
+ SD_JSON_BUILD_OBJECT(
+ SD_JSON_BUILD_PAIR_STRING("foo", "hallo"),
+ SD_JSON_BUILD_PAIR_STRING("bar", "knuerzredgreen"),
+ SD_JSON_BUILD_PAIR_STRING("baz", "noansi"),
+ SD_JSON_BUILD_PAIR_STRING("kkk", "thisisgrey")))));
+ ASSERT_TRUE(sd_json_variant_equal(j, jj));
+
+ ASSERT_OK(sd_json_variant_dump(j, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO, /* f= */ NULL, /* prefix= */ NULL));
+
+ ASSERT_OK(setenvf("COLUMNS", /* overwrite= */ true, "%u", saved_columns));
+ ASSERT_OK(setenvf("SYSTEMD_COLORS", /* overwrite= */ true, "%i", saved_color));
+ ASSERT_OK(set_unset_env("TERM", saved_term, /* overwrite= */ true));
+}
+
static int intro(void) {
- ASSERT_OK(setenv("SYSTEMD_COLORS", "0", 1));
- ASSERT_OK(setenv("COLUMNS", "40", 1));
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "0", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("COLUMNS", "40", /* overwrite= */ true));
return EXIT_SUCCESS;
}