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:
Daniel Eklöf 2020-12-08 19:19:55 +01:00
parent 3e25faeae7
commit 21cc68d49e
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
6 changed files with 184 additions and 12 deletions

145
osc.c
View file

@ -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();