diff --git a/man/systemd.xml b/man/systemd.xml
index 679851fc90..84aa43723c 100644
--- a/man/systemd.xml
+++ b/man/systemd.xml
@@ -855,6 +855,15 @@
+
+ $SYSTEMD_URLIFY
+
+ The value must be a boolean. Controls whether clickable links should be generated in the output
+ for terminal emulators supporting this. This can be specified to override the decision that
+ systemd makes based on $TERM and other conditions.
+
+
+
$LISTEN_PID
$LISTEN_FDS
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index f0405e3c3a..3166145b33 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -8,21 +8,22 @@
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
#include
+#include
#include
#include
@@ -34,6 +35,7 @@
#include "io-util.h"
#include "log.h"
#include "macro.h"
+#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
@@ -1269,3 +1271,94 @@ int vt_reset_keyboard(int fd) {
return 0;
}
+
+static bool urlify_enabled(void) {
+ static int cached_urlify_enabled = -1;
+
+ /* Unfortunately 'less' doesn't support links like this yet ðŸ˜, hence let's disable this as long as there's a
+ * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe
+ * to assume that a link-enabled 'less' version has hit most installations. */
+
+ if (cached_urlify_enabled < 0) {
+ int val;
+
+ val = getenv_bool("SYSTEMD_URLIFY");
+ if (val >= 0)
+ cached_urlify_enabled = val;
+ else
+ cached_urlify_enabled = colors_enabled() && !pager_have();
+ }
+
+ return cached_urlify_enabled;
+}
+
+int terminal_urlify(const char *url, const char *text, char **ret) {
+ char *n;
+
+ assert(url);
+
+ /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See
+ * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
+
+ if (isempty(text))
+ text = url;
+
+ if (urlify_enabled())
+ n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
+ else
+ n = strdup(text);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+}
+
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
+ _cleanup_free_ char *absolute = NULL;
+ struct utsname u;
+ const char *url;
+ int r;
+
+ assert(path);
+
+ /* Much like terminal_urlify() above, but takes a file system path as input, and turns it into a properl
+ * file:// URL first. */
+
+ if (isempty(path))
+ return -EINVAL;
+
+ if (isempty(text))
+ text = path;
+
+ if (!urlify_enabled()) {
+ char *n;
+
+ n = strdup(text);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+ }
+
+ if (uname(&u) < 0)
+ return -errno;
+
+ if (!path_is_absolute(path)) {
+ r = path_make_absolute_cwd(path, &absolute);
+ if (r < 0)
+ return r;
+
+ path = absolute;
+ }
+
+ /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
+ * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
+ * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
+ * careful with validating the strings either. */
+
+ url = strjoina("file://", u.nodename, path);
+
+ return terminal_urlify(url, text, ret);
+}
diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h
index d60956455b..ad6ee338ed 100644
--- a/src/basic/terminal-util.h
+++ b/src/basic/terminal-util.h
@@ -156,3 +156,6 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode);
int vt_default_utf8(void);
int vt_reset_keyboard(int fd);
+
+int terminal_urlify(const char *url, const char *text, char **ret);
+int terminal_urlify_path(const char *path, const char *text, char **ret);
diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c
index cebc7742bf..2cc9324ff3 100644
--- a/src/hostname/hostnamectl.c
+++ b/src/hostname/hostnamectl.c
@@ -20,6 +20,7 @@
#include "bus-util.h"
#include "hostname-util.h"
#include "spawn-polkit-agent.h"
+#include "terminal-util.h"
#include "util.h"
#include "verbs.h"
@@ -44,6 +45,7 @@ typedef struct StatusInfo {
const char *os_cpe_name;
const char *virtualization;
const char *architecture;
+ const char *home_url;
} StatusInfo;
static void print_status_info(StatusInfo *i) {
@@ -87,8 +89,17 @@ static void print_status_info(StatusInfo *i) {
if (!isempty(i->virtualization))
printf(" Virtualization: %s\n", i->virtualization);
- if (!isempty(i->os_pretty_name))
- printf(" Operating System: %s\n", i->os_pretty_name);
+ if (!isempty(i->os_pretty_name)) {
+ _cleanup_free_ char *formatted = NULL;
+ const char *t = i->os_pretty_name;
+
+ if (i->home_url) {
+ if (terminal_urlify(i->home_url, i->os_pretty_name, &formatted) >= 0)
+ t = formatted;
+ }
+
+ printf(" Operating System: %s\n", t);
+ }
if (!isempty(i->os_cpe_name))
printf(" CPE OS Name: %s\n", i->os_cpe_name);
@@ -141,6 +152,7 @@ static int show_all_names(sd_bus *bus, sd_bus_error *error) {
{ "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) },
{ "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) },
{ "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) },
+ { "HomeURL", "s", NULL, offsetof(StatusInfo, home_url) },
{}
};
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index c73390cfdf..614f93af73 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -39,6 +39,7 @@ enum {
PROP_KERNEL_VERSION,
PROP_OS_PRETTY_NAME,
PROP_OS_CPE_NAME,
+ PROP_HOME_URL,
_PROP_MAX
};
@@ -100,6 +101,7 @@ static int context_read_data(Context *c) {
r = parse_env_file("/etc/os-release", NEWLINE,
"PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
+ "HOME_URL", &c->data[PROP_HOME_URL],
NULL);
if (r == -ENOENT)
r = parse_env_file("/usr/lib/os-release", NEWLINE,
@@ -640,6 +642,7 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HomeURL", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOME_URL, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 388eee47a1..bcf5babb42 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -3954,11 +3954,11 @@ static void print_status_info(
UnitStatusInfo *i,
bool *ellipsized) {
- ExecStatusInfo *p;
+ char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1, since2[FORMAT_TIMESTAMP_MAX], *s2;
const char *active_on, *active_off, *on, *off, *ss;
+ _cleanup_free_ char *formatted_path = NULL;
+ ExecStatusInfo *p;
usec_t timestamp;
- char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
- char since2[FORMAT_TIMESTAMP_MAX], *s2;
const char *path;
char **t, **t2;
int r;
@@ -3993,7 +3993,9 @@ static void print_status_info(
} else
on = off = "";
- path = i->source_path ? i->source_path : i->fragment_path;
+ path = i->source_path ?: i->fragment_path;
+ if (path && terminal_urlify_path(path, NULL, &formatted_path) >= 0)
+ path = formatted_path;
if (i->load_error != 0)
printf(" Loaded: %s%s%s (Reason: %s)\n",
@@ -4021,6 +4023,9 @@ static void print_status_info(
char ** dropin;
STRV_FOREACH(dropin, i->dropin_paths) {
+ _cleanup_free_ char *dropin_formatted = NULL;
+ const char *df;
+
if (!dir || last) {
printf(dir ? " " :
" Drop-In: ");
@@ -4040,7 +4045,12 @@ static void print_status_info(
last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir));
- printf("%s%s", basename(*dropin), last ? "\n" : ", ");
+ if (terminal_urlify_path(*dropin, basename(*dropin), &dropin_formatted) >= 0)
+ df = dropin_formatted;
+ else
+ df = *dropin;
+
+ printf("%s%s", df, last ? "\n" : ", ");
}
}
@@ -4143,8 +4153,17 @@ static void print_status_info(
if (i->what)
printf(" What: %s\n", i->what);
- STRV_FOREACH(t, i->documentation)
- printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t);
+ STRV_FOREACH(t, i->documentation) {
+ _cleanup_free_ char *formatted = NULL;
+ const char *q;
+
+ if (terminal_urlify(*t, NULL, &formatted) >= 0)
+ q = formatted;
+ else
+ q = *t;
+
+ printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", q);
+ }
STRV_FOREACH_PAIR(t, t2, i->listen)
printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t);
diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c
index 0a240280ea..c83dfa9dcd 100644
--- a/src/test/test-terminal-util.c
+++ b/src/test/test-terminal-util.c
@@ -9,6 +9,7 @@
#include
#include
+#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "log.h"
@@ -63,12 +64,25 @@ static void test_read_one_char(void) {
unlink(name);
}
+static void test_terminal_urlify(void) {
+ _cleanup_free_ char *formatted = NULL;
+
+ assert_se(terminal_urlify("https://www.freedesktop.org/wiki/Software/systemd/", "systemd homepage", &formatted) >= 0);
+ printf("Hey, considere visiting the %s right now! It is very good!\n", formatted);
+
+ formatted = mfree(formatted);
+
+ assert_se(terminal_urlify_path("/etc/fstab", "this link to your /etc/fstab", &formatted) >= 0);
+ printf("Or click on %s to have a look at it!\n", formatted);
+}
+
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
test_default_term_for_tty();
test_read_one_char();
+ test_terminal_urlify();
return 0;
}