mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
osc: implement urxvt’s “OSC 777;notify”
OSC 777 is URxvt’s generic escape to send commands to its perl
extensions. The first parameter is the name of the extension, followed
by its arguments.
OSC 777;notify is a, if not well established, at least a fairly well
known escape sequence to request a (desktop) notification. The syntax
is:
\e]777;notify;<title>;<body>\e\\
Neither title nor body is escaped in any way, meaning they should not
contain a ‘;’.
Foot will split title from body at the *first* ‘;’. Any remaining ‘;’
characters are treated as part of ‘body’.
Instead of adding built-in support for the freedesktop notification
specification (which would require us to link against at least dbus),
add a new config option to foot.ini: ‘notify’.
This option specifies the command to execute when a notification is
received. ‘${title}’ and ‘${body}’ can be used anywhere, in any
combination, and as many times as you want, in any of the command
arguments.
The default value is ‘notify-send -a foot -i foot ${title} ${body}’
This commit is contained in:
parent
3e25faeae7
commit
21cc68d49e
6 changed files with 184 additions and 12 deletions
|
|
@ -86,6 +86,9 @@ means foot can be PGO:d in e.g. sandboxed build scripts. See
|
|||
* `DECSET` escape to modify the `escape` key to send `\E[27;1;27~`
|
||||
instead of `\E`: `CSI ? 27127 h` enables the new behavior, `CSI ?
|
||||
27127 l` disables it (the default).
|
||||
* OSC 777;notify: desktop notifications. Use in combination with the
|
||||
new **notify** option in `foot.ini`
|
||||
(https://codeberg.org/dnkl/foot/issues/224).
|
||||
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
31
config.c
31
config.c
|
|
@ -566,6 +566,27 @@ parse_section_main(const char *key, const char *value, struct config *conf,
|
|||
mbstowcs(conf->word_delimiters, value, chars + 1);
|
||||
}
|
||||
|
||||
else if (strcmp(key, "notify") == 0) {
|
||||
free(conf->notify.raw_cmd);
|
||||
free(conf->notify.argv);
|
||||
|
||||
conf->notify.raw_cmd = NULL;
|
||||
conf->notify.argv = NULL;
|
||||
|
||||
char *raw_cmd = xstrdup(value);
|
||||
char **argv = NULL;
|
||||
|
||||
if (!tokenize_cmdline(raw_cmd, &argv)) {
|
||||
LOG_AND_NOTIFY_ERR(
|
||||
"%s:%d: [default]: notify: syntax error in command line",
|
||||
path, lineno);
|
||||
return false;
|
||||
}
|
||||
|
||||
conf->notify.raw_cmd = raw_cmd;
|
||||
conf->notify.argv = argv;
|
||||
}
|
||||
|
||||
else {
|
||||
LOG_AND_NOTIFY_ERR("%s:%u: [default]: %s: invalid key", path, lineno, key);
|
||||
return false;
|
||||
|
|
@ -1989,6 +2010,10 @@ config_load(struct config *conf, const char *conf_path,
|
|||
.server_socket_path = get_server_socket_path(),
|
||||
.presentation_timings = false,
|
||||
.hold_at_exit = false,
|
||||
.notify = {
|
||||
.raw_cmd = NULL,
|
||||
.argv = NULL,
|
||||
},
|
||||
|
||||
.tweak = {
|
||||
.fcft_filter = FCFT_SCALING_FILTER_LANCZOS3,
|
||||
|
|
@ -2004,6 +2029,10 @@ config_load(struct config *conf, const char *conf_path,
|
|||
.notifications = tll_init(),
|
||||
};
|
||||
|
||||
conf->notify.raw_cmd = xstrdup(
|
||||
"notify-send -a foot -i foot ${title} ${body}");
|
||||
tokenize_cmdline(conf->notify.raw_cmd, &conf->notify.argv);
|
||||
|
||||
tll_foreach(*initial_user_notifications, it)
|
||||
tll_push_back(conf->notifications, it->item);
|
||||
tll_free(*initial_user_notifications);
|
||||
|
|
@ -2070,6 +2099,8 @@ config_free(struct config conf)
|
|||
free(conf.app_id);
|
||||
free(conf.word_delimiters);
|
||||
free(conf.scrollback.indicator.text);
|
||||
free(conf.notify.raw_cmd);
|
||||
free(conf.notify.argv);
|
||||
for (size_t i = 0; i < ALEN(conf.fonts); i++) {
|
||||
tll_foreach(conf.fonts[i], it)
|
||||
config_font_destroy(&it->item);
|
||||
|
|
|
|||
5
config.h
5
config.h
|
|
@ -163,6 +163,11 @@ struct config {
|
|||
bool presentation_timings;
|
||||
bool hold_at_exit;
|
||||
|
||||
struct {
|
||||
char *raw_cmd;
|
||||
char **argv;
|
||||
} notify;
|
||||
|
||||
struct {
|
||||
enum fcft_scaling_filter fcft_filter;
|
||||
bool allow_overflowing_double_width_glyphs;
|
||||
|
|
|
|||
|
|
@ -145,6 +145,17 @@ in this order:
|
|||
text. Note that whitespace characters are _always_ word
|
||||
delimiters, regardless of this setting. Default: _,│`|:"'()[]{}<>_
|
||||
|
||||
*notify*
|
||||
Command to execute to display a notification. _${title}_ and
|
||||
_${body}_ will be replaced with the notification's actual _title_
|
||||
and _body_ (message content).
|
||||
|
||||
Applications can trigger notifications in the following ways:
|
||||
|
||||
- OSC 777: *\\e]777;notify;<title>;<body>\\e\\\\*
|
||||
|
||||
Default: _notify-send -a foot -i foot ${title} ${body}_.
|
||||
|
||||
|
||||
# SECTION: scrollback
|
||||
|
||||
|
|
|
|||
1
foot.ini
1
foot.ini
|
|
@ -16,6 +16,7 @@
|
|||
# bold-text-in-bright=no
|
||||
# bell=none
|
||||
# word-delimiters=,│`|:"'()[]{}<>
|
||||
# notify=notify-send -a foot -i foot ${title} ${body}
|
||||
|
||||
[scrollback]
|
||||
# lines=1000
|
||||
|
|
|
|||
145
osc.c
145
osc.c
|
|
@ -3,6 +3,10 @@
|
|||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define LOG_MODULE "osc"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
|
|
@ -12,6 +16,7 @@
|
|||
#include "grid.h"
|
||||
#include "render.h"
|
||||
#include "selection.h"
|
||||
#include "spawn.h"
|
||||
#include "terminal.h"
|
||||
#include "uri.h"
|
||||
#include "vt.h"
|
||||
|
|
@ -374,22 +379,122 @@ osc_set_pwd(struct terminal *term, char *string)
|
|||
free(host);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
osc_notify(struct terminal *term, char *string)
|
||||
{
|
||||
/*
|
||||
* The 'notify' perl extension
|
||||
* (https://pub.phyks.me/scripts/urxvt/notify) is very simple:
|
||||
*
|
||||
* #!/usr/bin/perl
|
||||
*
|
||||
* sub on_osc_seq_perl {
|
||||
* my ($term, $osc, $resp) = @_;
|
||||
* if ($osc =~ /^notify;(\S+);(.*)$/) {
|
||||
* system("notify-send '$1' '$2'");
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* As can be seen, the notification text is not encoded in any
|
||||
* way. The regex does a greedy match of the ';' separator. Thus,
|
||||
* any extra ';' will end up being part of the title. There's no
|
||||
* way to have a ';' in the message body.
|
||||
*
|
||||
* I've changed that behavior slightly in; we split the title from
|
||||
* body on the *first* ';', allowing us to have semicolons in the
|
||||
* message body, but *not* in the title.
|
||||
*/
|
||||
char *ctx = NULL;
|
||||
const char *cmd = strtok_r(string, ";", &ctx);
|
||||
const char *title = strtok_r(NULL, ";", &ctx);
|
||||
const char *msg = strtok_r(NULL, ";", &ctx);
|
||||
const char *title = strtok_r(string, ";", &ctx);
|
||||
const char *msg = strtok_r(NULL, "\x00", &ctx);
|
||||
|
||||
LOG_DBG("cmd: \"%s\", title: \"%s\", msg: \"%s\"",
|
||||
cmd, title, msg);
|
||||
LOG_DBG("notify: title=\"%s\", msg=\"%s\"", title, msg);
|
||||
|
||||
if (cmd == NULL || strcmp(cmd, "notify") != 0 || title == NULL || msg == NULL)
|
||||
/* TODO: move everything below to a separate function, to be able
|
||||
* to support multiple escape sequences */
|
||||
|
||||
if (title == NULL || msg == NULL)
|
||||
return;
|
||||
|
||||
if (term->conf->notify.argv == NULL)
|
||||
return;
|
||||
|
||||
size_t argv_size = 0;
|
||||
for (; term->conf->notify.argv[argv_size] != NULL; argv_size++)
|
||||
;
|
||||
|
||||
#define append(s, n) \
|
||||
do { \
|
||||
expanded = xrealloc(expanded, len + (n) + 1); \
|
||||
memcpy(&expanded[len], s, n); \
|
||||
len += n; \
|
||||
expanded[len] = '\0'; \
|
||||
} while (0)
|
||||
|
||||
char **argv = malloc((argv_size + 1) * sizeof(argv[0]));
|
||||
|
||||
/* Expand ${title} and ${body} */
|
||||
for (size_t i = 0; i < argv_size; i++) {
|
||||
size_t len = 0;
|
||||
char *expanded = NULL;
|
||||
|
||||
char *start = NULL;
|
||||
char *last_end = term->conf->notify.argv[i];
|
||||
|
||||
while ((start = strstr(last_end, "${")) != NULL) {
|
||||
/* Append everything from the last template's end to this
|
||||
* one's beginning */
|
||||
append(last_end, start - last_end);
|
||||
|
||||
/* Find end of template */
|
||||
start += 2;
|
||||
char *end = strstr(start, "}");
|
||||
|
||||
if (end == NULL) {
|
||||
/* Ensure final append() copies the unclosed '${' */
|
||||
last_end = start - 2;
|
||||
LOG_WARN("notify: unclosed template: %s", last_end);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Expand template */
|
||||
if (strncmp(start, "title", end - start) == 0)
|
||||
append(title, strlen(title));
|
||||
else if (strncmp(start, "body", end - start) == 0)
|
||||
append(msg, strlen(msg));
|
||||
else {
|
||||
/* Unrecognized template - append it as-is */
|
||||
start -= 2;
|
||||
append(start, end + 1 - start);
|
||||
LOG_WARN("notify: unrecognized template: %.*s",
|
||||
(int)(end + 1 - start), start);
|
||||
}
|
||||
|
||||
last_end = end + 1;;
|
||||
}
|
||||
|
||||
append(last_end, term->conf->notify.argv[i] + strlen(term->conf->notify.argv[i]) - last_end);
|
||||
argv[i] = expanded;
|
||||
}
|
||||
argv[argv_size] = NULL;
|
||||
|
||||
#undef append
|
||||
|
||||
LOG_DBG("notify command:");
|
||||
for (size_t i = 0; i < argv_size; i++)
|
||||
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
|
||||
|
||||
/* Redirect stdin to /dev/null, but ignore failure to open */
|
||||
int devnull = open("/dev/null", O_RDONLY);
|
||||
spawn(term->reaper, NULL, argv, devnull, -1, -1);
|
||||
|
||||
if (devnull >= 0)
|
||||
close(devnull);
|
||||
|
||||
for (size_t i = 0; i < argv_size; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
update_color_in_grids(struct terminal *term, uint32_t old_color,
|
||||
|
|
@ -673,11 +778,27 @@ osc_dispatch(struct terminal *term)
|
|||
osc_flash(term);
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case 777:
|
||||
osc_notify(term, string);
|
||||
case 777: {
|
||||
/*
|
||||
* OSC 777 is an URxvt generic escape used to send commands to
|
||||
* perl extensions. The generic syntax is: \E]777;<command>;<string>ST
|
||||
*
|
||||
* We only recognize the 'notify' command, which is, if not
|
||||
* well established, at least fairly well known.
|
||||
*/
|
||||
|
||||
char *param_brk = strchr(string, ';');
|
||||
if (param_brk == NULL) {
|
||||
UNHANDLED();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(string, "notify", param_brk - string) == 0)
|
||||
osc_notify(term, param_brk + 1);
|
||||
else
|
||||
UNHANDLED();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
default:
|
||||
UNHANDLED();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue