mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
osc: kitty notifications: cleanup and update to latest version of spec
* Don't store a list of unfinished notifications. Use a single one. If
the notification ID of the 'current' notification doesn't match the
previous, unfinished one, the 'current' notification replaces the
previous one, instead of updating it.
* Update xstrjoin() to take an optional delimiter (for example ','),
and use that when joining categories and 'alive IDs'.
* Rename ${action-arg} to ${action-argument}
* Update handling of the 'n' parameter (symbolic icon name); the spec
allows it to be used multiple times, and the terminal is supposed to
pick the first one it can resolve. Foot can't resolve icons at all,
neither can 'notify-send' or 'fyi' (which is what foot typically
executes to display a notification); it's the notification daemon that
resolves icons.
The spec _could_ be interpreted to mean the terminal should lookup
.desktop files, and use the value of the 'Icon' key from the first
matching .desktop files. But foot doesn't read .desktop files, and I
don't intend to implement XDG directory scanning and parsing of
.desktop files just to figure out which icon to use.
Instead, use a simple heuristics; use the *shortest* symbolic
names. The idea is pretty simple: plain icon names are typically
shorter than .desktop file IDs.
This commit is contained in:
parent
18b87b2e20
commit
ea2f0e7c3f
12 changed files with 296 additions and 247 deletions
|
|
@ -75,8 +75,11 @@
|
||||||
* `desktop-notifications.command` option, replaces `notify`.
|
* `desktop-notifications.command` option, replaces `notify`.
|
||||||
* `desktop-notifications.inhibit-when-focused` option, replaces
|
* `desktop-notifications.inhibit-when-focused` option, replaces
|
||||||
`notify-focus-inhibit`.
|
`notify-focus-inhibit`.
|
||||||
* `${icon}`, `${urgency}`,`${action-name}` and `${action-label}` added
|
* `${icon}`, `${urgency}` and `${action-argument}` added
|
||||||
to the `desktop-notifications.command` template.
|
to the `desktop-notifications.command` template.
|
||||||
|
* `desktop-notifications.command-action-argument` option, defining how
|
||||||
|
`${action-argument}` (in `desktop-notifications.command`) should be
|
||||||
|
expanded.
|
||||||
* `desktop-notifications.close` option, defining what to execute when
|
* `desktop-notifications.close` option, defining what to execute when
|
||||||
an application wants to close an existing notification (via an
|
an application wants to close an existing notification (via an
|
||||||
OSC-99 escape sequence).
|
OSC-99 escape sequence).
|
||||||
|
|
|
||||||
12
config.c
12
config.c
|
|
@ -356,9 +356,9 @@ open_config(void)
|
||||||
|
|
||||||
/* First, check XDG_CONFIG_HOME (or .config, if unset) */
|
/* First, check XDG_CONFIG_HOME (or .config, if unset) */
|
||||||
if (xdg_config_home != NULL && xdg_config_home[0] != '\0')
|
if (xdg_config_home != NULL && xdg_config_home[0] != '\0')
|
||||||
path = xstrjoin(xdg_config_home, "/foot/foot.ini");
|
path = xstrjoin(xdg_config_home, "/foot/foot.ini", 0);
|
||||||
else if (home_dir != NULL)
|
else if (home_dir != NULL)
|
||||||
path = xstrjoin(home_dir, "/.config/foot/foot.ini");
|
path = xstrjoin(home_dir, "/.config/foot/foot.ini", 0);
|
||||||
|
|
||||||
if (path != NULL) {
|
if (path != NULL) {
|
||||||
LOG_DBG("checking for %s", path);
|
LOG_DBG("checking for %s", path);
|
||||||
|
|
@ -383,7 +383,7 @@ open_config(void)
|
||||||
conf_dir = strtok(NULL, ":"))
|
conf_dir = strtok(NULL, ":"))
|
||||||
{
|
{
|
||||||
free(path);
|
free(path);
|
||||||
path = xstrjoin(conf_dir, "/foot/foot.ini");
|
path = xstrjoin(conf_dir, "/foot/foot.ini", 0);
|
||||||
|
|
||||||
LOG_DBG("checking for %s", path);
|
LOG_DBG("checking for %s", path);
|
||||||
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||||
|
|
@ -1115,7 +1115,7 @@ parse_section_desktop_notifications(struct context *ctx)
|
||||||
if (streq(key, "command"))
|
if (streq(key, "command"))
|
||||||
return value_to_spawn_template(
|
return value_to_spawn_template(
|
||||||
ctx, &conf->desktop_notifications.command);
|
ctx, &conf->desktop_notifications.command);
|
||||||
else if (streq(key, "command-action-arg"))
|
else if (streq(key, "command-action-argument"))
|
||||||
return value_to_spawn_template(
|
return value_to_spawn_template(
|
||||||
ctx, &conf->desktop_notifications.command_action_arg);
|
ctx, &conf->desktop_notifications.command_action_arg);
|
||||||
else if (streq(key, "close"))
|
else if (streq(key, "close"))
|
||||||
|
|
@ -2931,7 +2931,7 @@ get_server_socket_path(void)
|
||||||
|
|
||||||
const char *wayland_display = getenv("WAYLAND_DISPLAY");
|
const char *wayland_display = getenv("WAYLAND_DISPLAY");
|
||||||
if (wayland_display == NULL) {
|
if (wayland_display == NULL) {
|
||||||
return xstrjoin(xdg_runtime, "/foot.sock");
|
return xstrjoin(xdg_runtime, "/foot.sock", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display);
|
return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display);
|
||||||
|
|
@ -3242,7 +3242,7 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers);
|
parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers);
|
||||||
|
|
||||||
tokenize_cmdline(
|
tokenize_cmdline(
|
||||||
"notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --replace-id ${replace-id} ${action-arg} --print-id -- ${title} ${body}",
|
"notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}",
|
||||||
&conf->desktop_notifications.command.argv.args);
|
&conf->desktop_notifications.command.argv.args);
|
||||||
tokenize_cmdline("--action ${action-name}=${action-label}", &conf->desktop_notifications.command_action_arg.argv.args);
|
tokenize_cmdline("--action ${action-name}=${action-label}", &conf->desktop_notifications.command_action_arg.argv.args);
|
||||||
tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args);
|
tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args);
|
||||||
|
|
|
||||||
|
|
@ -459,14 +459,15 @@ Note: do not set *TERM* here; use the *term* option in the main
|
||||||
below. Can be used together with e.g. notify-send's
|
below. Can be used together with e.g. notify-send's
|
||||||
*--replace-id* option.
|
*--replace-id* option.
|
||||||
|
|
||||||
_${action-arg}_ will be expanded to the *command-action-arg*
|
_${action-argument}_ will be expanded to the
|
||||||
option, for each notification action. There will always be at
|
*command-action-argument* option, for each notification
|
||||||
least one action, the "default" action. Foot uses this to
|
action. There will always be at least one action, the
|
||||||
enable window focusing, and reporting notification activation
|
"default" action. Foot uses this to enable window focusing,
|
||||||
to applications that requested such events.
|
and reporting notification activation to applications that
|
||||||
|
requested such events.
|
||||||
|
|
||||||
Applications can also define their own custom notification
|
Applications can also define their own custom notification
|
||||||
actions. See the *command-action-arg* option for details.
|
actions. See the *command-action-argument* option for details.
|
||||||
|
|
||||||
Ways to trigger notifications
|
Ways to trigger notifications
|
||||||
Applications can trigger notifications in the following ways:
|
Applications can trigger notifications in the following ways:
|
||||||
|
|
@ -496,7 +497,7 @@ Note: do not set *TERM* here; use the *term* option in the main
|
||||||
There are two parts to handle this. First, the notification
|
There are two parts to handle this. First, the notification
|
||||||
must define an action. For this purpose, foot will add a
|
must define an action. For this purpose, foot will add a
|
||||||
"default" action to the notification (see the
|
"default" action to the notification (see the
|
||||||
*command-action-arg* option).
|
*command-action-argument* option).
|
||||||
|
|
||||||
Second, foot needs to know when the notification is activated,
|
Second, foot needs to know when the notification is activated,
|
||||||
and it needs to get hold of the XDG activation token.
|
and it needs to get hold of the XDG activation token.
|
||||||
|
|
@ -524,18 +525,41 @@ xdgtoken=18179adf579a7a904ce73754964b1ec3
|
||||||
Foot recognizes the following things from the notification
|
Foot recognizes the following things from the notification
|
||||||
helper's stdout:
|
helper's stdout:
|
||||||
|
|
||||||
- _nnn_: integer in base 10, daemon assigned notification ID
|
- _id_: integer in base 10, daemon assigned notification ID
|
||||||
- *id=*_nnn_: same as plain _nnn_.
|
- *id=*_id_: same as plain _nnn_.
|
||||||
- *default*: the 'default' action was triggered
|
- *default*: the 'default' action was triggered
|
||||||
- *action=*_default_: same as _default_
|
- *action=*_default_: same as _default_
|
||||||
- *action=*_n_: application custom action _n_ triggered
|
- *action=*_n_: application custom action _n_ triggered
|
||||||
|
- _n_: integer in base 10, appearing after the ID; application
|
||||||
|
custom action _n_ triggered
|
||||||
- *xdgtoken=*_xyz_: XDG activation token.
|
- *xdgtoken=*_xyz_: XDG activation token.
|
||||||
|
|
||||||
Example:
|
Example #1:
|
||||||
17++
|
17++
|
||||||
action=default++
|
action=default++
|
||||||
xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
|
xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
|
||||||
|
|
||||||
|
Foot recognizes this as:
|
||||||
|
- notification has the daemon assigned ID 17
|
||||||
|
- the user triggered the default action
|
||||||
|
- the notification send an XDG activation token
|
||||||
|
|
||||||
|
Example #2:
|
||||||
|
17++
|
||||||
|
1
|
||||||
|
|
||||||
|
Foot recognizes this as:
|
||||||
|
- notification has the daemon assigned ID 17
|
||||||
|
- the user triggered the first custom action, "1"
|
||||||
|
|
||||||
|
Example #3:
|
||||||
|
id=17++
|
||||||
|
1
|
||||||
|
|
||||||
|
Foot recognizes this as:
|
||||||
|
- notification has the daemon assigned ID 17
|
||||||
|
- the user triggered the first custom action, "1
|
||||||
|
|
||||||
Default: _notify-send++
|
Default: _notify-send++
|
||||||
--wait++
|
--wait++
|
||||||
--app-name ${app-id}++
|
--app-name ${app-id}++
|
||||||
|
|
@ -545,11 +569,11 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
|
||||||
--expire-time ${expire-time}++
|
--expire-time ${expire-time}++
|
||||||
--hint STRING:image-path:${icon}++
|
--hint STRING:image-path:${icon}++
|
||||||
--replace-id ${replace-id}++
|
--replace-id ${replace-id}++
|
||||||
${action-arg}++
|
${action-argument}++
|
||||||
--print-id++
|
--print-id++
|
||||||
-- ${title} ${body}_.
|
-- ${title} ${body}_.
|
||||||
|
|
||||||
*command-action-arg*
|
*command-action-argument*
|
||||||
String to use with *command* to enable passing action/button names
|
String to use with *command* to enable passing action/button names
|
||||||
to the notification helper.
|
to the notification helper.
|
||||||
|
|
||||||
|
|
@ -561,32 +585,32 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
|
||||||
|
|
||||||
Furhermore, the OSC-99 notifications protocol allows applications
|
Furhermore, the OSC-99 notifications protocol allows applications
|
||||||
to define their own actions. Foot uses a combination of the
|
to define their own actions. Foot uses a combination of the
|
||||||
*command* option, and the *command-action-arg* option to pass the
|
*command* option, and the *command-action-argument* option to pass
|
||||||
names of the actions to the notification helper.
|
the names of the actions to the notification helper.
|
||||||
|
|
||||||
This option has the following template arguments:
|
This option has the following template arguments:
|
||||||
|
|
||||||
- _${action-name}_: the name of the action; *default* for the
|
- _${action-name}_: the name of the action; *default* for the
|
||||||
default action configured by foot, and _n_, where _n_ is an
|
default action configured by foot, and _n_, where _n_ is an
|
||||||
integer >= 1, for application defined actions.
|
integer >= 1, for application defined actions.
|
||||||
- _${action-label}_: *Click to activate* for the default action,
|
- _${action-label}_: *Activate* for the default action, and a
|
||||||
and a free-form string for application defined actions.
|
free-form string for application defined actions.
|
||||||
|
|
||||||
For each notification action (remember, there will always be at
|
For each notification action (remember, there will always be at
|
||||||
least one), *command-action-arg* will be expanded with the
|
least one), *command-action-argument* will be expanded with the
|
||||||
action's name and label.
|
action's name and label.
|
||||||
|
|
||||||
Then, _${action-arg}_ is expanded *command* to the full list of
|
Then, _${action-argument}_ is expanded *command* to the full list
|
||||||
actions.
|
of actions.
|
||||||
|
|
||||||
If *command-action-arg* is set to the empty string, no actions
|
If *command-action-argument* is set to the empty string, no
|
||||||
will be passed to *command*. That is, _${action-arg}_ will be
|
actions will be passed to *command*. That is, _${action-argument}_
|
||||||
replaced with the empty string.
|
will be replaced with the empty string.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
*command-action-arg=--action ${action-name}=${action-label}*
|
*command-action-argument=--action ${action-name}=${action-label}*++
|
||||||
*command=notify-send ${action-arg} ...*
|
*command=notify-send ${action-argument} ...*
|
||||||
|
|
||||||
Assume the application defined two custom actions: *OK* and
|
Assume the application defined two custom actions: *OK* and
|
||||||
*Cancel*.
|
*Cancel*.
|
||||||
|
|
|
||||||
4
foot.ini
4
foot.ini
|
|
@ -47,8 +47,8 @@
|
||||||
# command-focused=no
|
# command-focused=no
|
||||||
|
|
||||||
[desktop-notifications]
|
[desktop-notifications]
|
||||||
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --replace-id ${replace-id} ${action-arg} --print-id -- ${title} ${body}
|
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
|
||||||
# command-action-arg=--action ${action-name}=${action-label}
|
# command-action-argument=--action ${action-name}=${action-label}
|
||||||
# close=""
|
# close=""
|
||||||
# inhibit-when-focused=yes
|
# inhibit-when-focused=yes
|
||||||
|
|
||||||
|
|
|
||||||
23
main.c
23
main.c
|
|
@ -261,7 +261,7 @@ main(int argc, char *const *argv)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
tll_push_back(overrides, xstrjoin("term=", optarg));
|
tll_push_back(overrides, xstrjoin("term=", optarg, 0));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
|
|
@ -269,11 +269,11 @@ main(int argc, char *const *argv)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'T':
|
case 'T':
|
||||||
tll_push_back(overrides, xstrjoin("title=", optarg));
|
tll_push_back(overrides, xstrjoin("title=", optarg, 0));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
tll_push_back(overrides, xstrjoin("app-id=", optarg));
|
tll_push_back(overrides, xstrjoin("app-id=", optarg, 0));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D': {
|
case 'D': {
|
||||||
|
|
@ -287,7 +287,7 @@ main(int argc, char *const *argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'f': {
|
case 'f': {
|
||||||
char *font_override = xstrjoin("font=", optarg);
|
char *font_override = xstrjoin("font=", optarg, 0);
|
||||||
tll_push_back(overrides, font_override);
|
tll_push_back(overrides, font_override);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -658,3 +658,18 @@ out:
|
||||||
log_deinit();
|
log_deinit();
|
||||||
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
|
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UNITTEST
|
||||||
|
{
|
||||||
|
char *s = xstrjoin("foo", "bar", 0);
|
||||||
|
xassert(streq(s, "foobar"));
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = xstrjoin("foo", "bar", ' ');
|
||||||
|
xassert(streq(s, "foo bar"));
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
s = xstrjoin("foo", "bar", ',');
|
||||||
|
xassert(streq(s, "foo,bar"));
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
|
|
||||||
135
notify.c
135
notify.c
|
|
@ -23,13 +23,15 @@
|
||||||
void
|
void
|
||||||
notify_free(struct terminal *term, struct notification *notif)
|
notify_free(struct terminal *term, struct notification *notif)
|
||||||
{
|
{
|
||||||
fdm_del(term->fdm, notif->stdout_fd);
|
if (notif->pid > 0)
|
||||||
|
fdm_del(term->fdm, notif->stdout_fd);
|
||||||
|
|
||||||
free(notif->id);
|
free(notif->id);
|
||||||
free(notif->title);
|
free(notif->title);
|
||||||
free(notif->body);
|
free(notif->body);
|
||||||
free(notif->category);
|
free(notif->category);
|
||||||
free(notif->app_id);
|
free(notif->app_id);
|
||||||
free(notif->icon_id);
|
free(notif->icon_cache_id);
|
||||||
free(notif->icon_symbolic_name);
|
free(notif->icon_symbolic_name);
|
||||||
free(notif->icon_data);
|
free(notif->icon_data);
|
||||||
free(notif->xdg_token);
|
free(notif->xdg_token);
|
||||||
|
|
@ -44,6 +46,8 @@ notify_free(struct terminal *term, struct notification *notif)
|
||||||
if (notif->icon_fd >= 0)
|
if (notif->icon_fd >= 0)
|
||||||
close(notif->icon_fd);
|
close(notif->icon_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(notif, 0, sizeof(*notif));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
@ -119,9 +123,10 @@ consume_stdout(struct notification *notif, bool eof)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
uint32_t maybe_id = 0;
|
uint32_t maybe_id = 0;
|
||||||
|
uint32_t maybe_button_nr = 0;
|
||||||
|
|
||||||
/* Check for daemon assigned ID, either '123', or 'id=123' */
|
/* Check for daemon assigned ID, either '123', or 'id=123' */
|
||||||
if (to_integer(line, len, &maybe_id) ||
|
if ((notif->external_id == 0 && to_integer(line, len, &maybe_id)) ||
|
||||||
(len > 3 && memcmp(line, "id=", 3) == 0 &&
|
(len > 3 && memcmp(line, "id=", 3) == 0 &&
|
||||||
to_integer(&line[3], len - 3, &maybe_id)))
|
to_integer(&line[3], len - 3, &maybe_id)))
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +145,6 @@ consume_stdout(struct notification *notif, bool eof)
|
||||||
else if (len > 7 && memcmp(line, "action=", 7) == 0) {
|
else if (len > 7 && memcmp(line, "action=", 7) == 0) {
|
||||||
notif->activated = true;
|
notif->activated = true;
|
||||||
|
|
||||||
uint32_t maybe_button_nr;
|
|
||||||
if (to_integer(&line[7], len - 7, &maybe_button_nr)) {
|
if (to_integer(&line[7], len - 7, &maybe_button_nr)) {
|
||||||
notif->activated_button = maybe_button_nr;
|
notif->activated_button = maybe_button_nr;
|
||||||
LOG_DBG("custom action %u triggered", notif->activated_button);
|
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||||
|
|
@ -150,6 +154,18 @@ consume_stdout(struct notification *notif, bool eof)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (notif->external_id > 0 &&
|
||||||
|
to_integer(line, len, &maybe_button_nr) &&
|
||||||
|
maybe_button_nr > 0 &&
|
||||||
|
maybe_button_nr <= notif->button_count)
|
||||||
|
{
|
||||||
|
/* Single integer, appearing *after* the ID, and is within
|
||||||
|
the custom button/action range */
|
||||||
|
notif->activated = true;
|
||||||
|
notif->activated_button = maybe_button_nr;
|
||||||
|
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||||
|
}
|
||||||
|
|
||||||
/* Check for XDG activation token, 'xdgtoken=xyz' */
|
/* Check for XDG activation token, 'xdgtoken=xyz' */
|
||||||
else if (len > 9 && memcmp(line, "xdgtoken=", 9) == 0) {
|
else if (len > 9 && memcmp(line, "xdgtoken=", 9) == 0) {
|
||||||
notif->xdg_token = xstrndup(&line[9], len - 9);
|
notif->xdg_token = xstrndup(&line[9], len - 9);
|
||||||
|
|
@ -272,6 +288,31 @@ notif_done(struct reaper *reaper, pid_t pid, int status, void *data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
expand_action_to_argv(struct terminal *term, const char *name, const char *label,
|
||||||
|
size_t *argc, char ***argv)
|
||||||
|
{
|
||||||
|
char **expanded = NULL;
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
if (!spawn_expand_template(
|
||||||
|
&term->conf->desktop_notifications.command_action_arg, 2,
|
||||||
|
(const char *[]){"action-name", "action-label"},
|
||||||
|
(const char *[]){name, label},
|
||||||
|
&count, &expanded))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append to the "global" actions argv */
|
||||||
|
*argv = xrealloc(*argv, (*argc + count) * sizeof((*argv)[0]));
|
||||||
|
memcpy(&(*argv)[*argc], expanded, count * sizeof(expanded[0]));
|
||||||
|
*argc += count;
|
||||||
|
|
||||||
|
free(expanded);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
notify_notify(struct terminal *term, struct notification *notif)
|
notify_notify(struct terminal *term, struct notification *notif)
|
||||||
{
|
{
|
||||||
|
|
@ -309,11 +350,11 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
/* Icon: symbolic name if present, otherwise a filename */
|
/* Icon: symbolic name if present, otherwise a filename */
|
||||||
const char *icon_name_or_path = "";
|
const char *icon_name_or_path = "";
|
||||||
|
|
||||||
if (notif->icon_id != NULL) {
|
if (notif->icon_cache_id != NULL) {
|
||||||
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||||
const struct notification_icon *icon = &term->notification_icons[i];
|
const struct notification_icon *icon = &term->notification_icons[i];
|
||||||
|
|
||||||
if (icon->id != NULL && streq(icon->id, notif->icon_id)) {
|
if (icon->id != NULL && streq(icon->id, notif->icon_cache_id)) {
|
||||||
/* For now, we set the symbolic name to 'file:///path'
|
/* For now, we set the symbolic name to 'file:///path'
|
||||||
* when using a file based icon. */
|
* when using a file based icon. */
|
||||||
xassert(icon->symbolic_name != NULL);
|
xassert(icon->symbolic_name != NULL);
|
||||||
|
|
@ -397,59 +438,27 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
xsnprintf(expire_time, sizeof(expire_time), "%d", notif->expire_time);
|
xsnprintf(expire_time, sizeof(expire_time), "%d", notif->expire_time);
|
||||||
|
|
||||||
if (term->conf->desktop_notifications.command_action_arg.argv.args) {
|
if (term->conf->desktop_notifications.command_action_arg.argv.args) {
|
||||||
struct action {
|
if (!expand_action_to_argv(
|
||||||
const char *name;
|
term, "default", "Activate", &action_argc, &action_argv))
|
||||||
const char *label;
|
{
|
||||||
};
|
return false;
|
||||||
|
|
||||||
tll(struct action) actions = tll_init();
|
|
||||||
tll_push_back(actions, ((struct action){"default", "Click to activate"}));
|
|
||||||
|
|
||||||
tll_foreach(notif->actions, it) {
|
|
||||||
tll_push_back(actions, ((struct action){NULL, it->item}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t action_idx = 0;
|
size_t action_idx = 1;
|
||||||
tll_foreach(actions, it) {
|
tll_foreach(notif->actions, it) {
|
||||||
const char *name = it->item.name;
|
|
||||||
const char *label = it->item.label;
|
|
||||||
|
|
||||||
/*
|
/* Custom actions use a numerical name, starting at 1 */
|
||||||
* Custom actions (buttons) start at 1.
|
char name[16];
|
||||||
*
|
xsnprintf(name, sizeof(name), "%zu", action_idx++);
|
||||||
* We always insert our own default action first, causing
|
|
||||||
* all custom actions to start at index 1 in our list.
|
|
||||||
*/
|
|
||||||
char numerical_name[16];
|
|
||||||
xsnprintf(numerical_name, sizeof(numerical_name), "%zu", action_idx);
|
|
||||||
|
|
||||||
if (name == NULL)
|
if (!expand_action_to_argv(
|
||||||
name = numerical_name;
|
term, name, it->item, &action_argc, &action_argv))
|
||||||
|
|
||||||
char **expanded = NULL;
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
if (!spawn_expand_template(
|
|
||||||
&term->conf->desktop_notifications.command_action_arg, 2,
|
|
||||||
(const char *[]){"action-name", "action-label"},
|
|
||||||
(const char *[]){name, label},
|
|
||||||
&count, &expanded))
|
|
||||||
{
|
{
|
||||||
|
for (size_t i = 0; i < action_argc; i++)
|
||||||
|
free(action_argv[i]);
|
||||||
|
free(action_argv);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append to the "global" actions argv */
|
|
||||||
action_argv = xrealloc(
|
|
||||||
action_argv, (action_argc + count) * sizeof(action_argv[0]));
|
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++)
|
|
||||||
action_argv[action_argc + i] = expanded[i];
|
|
||||||
|
|
||||||
action_argc += count;
|
|
||||||
|
|
||||||
free(expanded);
|
|
||||||
action_idx++;
|
|
||||||
tll_remove(actions, it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -457,33 +466,35 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
&term->conf->desktop_notifications.command, 10,
|
&term->conf->desktop_notifications.command, 10,
|
||||||
(const char *[]){
|
(const char *[]){
|
||||||
"app-id", "window-title", "icon", "title", "body", "category",
|
"app-id", "window-title", "icon", "title", "body", "category",
|
||||||
"urgency", "expire-time", "replace-id", "action-arg"},
|
"urgency", "expire-time", "replace-id", "action-argument"},
|
||||||
(const char *[]){
|
(const char *[]){
|
||||||
app_id, term->window_title, icon_name_or_path, title, body,
|
app_id, term->window_title, icon_name_or_path, title, body,
|
||||||
notif->category != NULL ? notif->category : "", urgency_str,
|
notif->category != NULL ? notif->category : "", urgency_str,
|
||||||
expire_time, replaces_id_str,
|
expire_time, replaces_id_str,
|
||||||
|
|
||||||
/* Custom expansion below, since we need to expand to multiple arguments */
|
/* Custom expansion below, since we need to expand to multiple arguments */
|
||||||
"${action-arg}"},
|
"${action-argument}"},
|
||||||
&argc, &argv))
|
&argc, &argv))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Post-process the expanded argv, and patch in all the --action
|
||||||
|
arguments we expanded earlier */
|
||||||
for (size_t i = 0; i < argc; i++) {
|
for (size_t i = 0; i < argc; i++) {
|
||||||
if (!streq(argv[i], "${action-arg}"))
|
if (!streq(argv[i], "${action-argument}"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (action_argc == 0) {
|
if (action_argc == 0) {
|
||||||
free(argv[i]);
|
free(argv[i]);
|
||||||
|
|
||||||
/* Remove ${command-arg}, but include terminating NULL */
|
/* Remove ${command-argument}, but include terminating NULL */
|
||||||
memmove(&argv[i], &argv[i + 1], (argc - i) * sizeof(argv[0]));
|
memmove(&argv[i], &argv[i + 1], (argc - i) * sizeof(argv[0]));
|
||||||
argc--;
|
argc--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the "${action-arg}" entry, add all actions argument
|
/* Remove the "${action-argument}" entry, add all actions argument
|
||||||
from earlier, but include terminating NULL */
|
from earlier, but include terminating NULL */
|
||||||
argv = xrealloc(argv, (argc + action_argc) * sizeof(argv[0]));
|
argv = xrealloc(argv, (argc + action_argc) * sizeof(argv[0]));
|
||||||
|
|
||||||
|
|
@ -492,7 +503,7 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
&argv[i + 1],
|
&argv[i + 1],
|
||||||
(argc - i) * sizeof(argv[0])); /* Include terminating NULL */
|
(argc - i) * sizeof(argv[0])); /* Include terminating NULL */
|
||||||
|
|
||||||
free(argv[i]); /* Free xstrdup("${action-arg}"); */
|
free(argv[i]); /* Free xstrdup("${action-argument}"); */
|
||||||
|
|
||||||
/* Insert the action arguments */
|
/* Insert the action arguments */
|
||||||
for (size_t j = 0; j < action_argc; j++) {
|
for (size_t j = 0; j < action_argc; j++) {
|
||||||
|
|
@ -501,7 +512,7 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
}
|
}
|
||||||
|
|
||||||
argc += action_argc;
|
argc += action_argc;
|
||||||
argc--; /* The ${action-arg} option has been removed */
|
argc--; /* The ${action-argument} option has been removed */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -518,12 +529,15 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
/* Non-fatal */
|
/* Non-fatal */
|
||||||
} else {
|
} else {
|
||||||
tll_push_back(term->active_notifications, *notif);
|
tll_push_back(term->active_notifications, *notif);
|
||||||
|
|
||||||
|
/* We've taken over ownership of all data; clear, so that
|
||||||
|
notify_free() doesn't double free */
|
||||||
notif->id = NULL;
|
notif->id = NULL;
|
||||||
notif->title = NULL;
|
notif->title = NULL;
|
||||||
notif->body = NULL;
|
notif->body = NULL;
|
||||||
notif->category = NULL;
|
notif->category = NULL;
|
||||||
notif->app_id = NULL;
|
notif->app_id = NULL;
|
||||||
notif->icon_id = NULL;
|
notif->icon_cache_id = NULL;
|
||||||
notif->icon_symbolic_name = NULL;
|
notif->icon_symbolic_name = NULL;
|
||||||
notif->icon_data = NULL;
|
notif->icon_data = NULL;
|
||||||
notif->icon_data_sz = 0;
|
notif->icon_data_sz = 0;
|
||||||
|
|
@ -533,6 +547,7 @@ notify_notify(struct terminal *term, struct notification *notif)
|
||||||
struct notification *new_notif = &tll_back(term->active_notifications);
|
struct notification *new_notif = &tll_back(term->active_notifications);
|
||||||
|
|
||||||
/* We don't need these anymore. They'll be free:d by the caller */
|
/* We don't need these anymore. They'll be free:d by the caller */
|
||||||
|
new_notif->button_count = tll_length(notif->actions);
|
||||||
memset(&new_notif->actions, 0, sizeof(new_notif->actions));
|
memset(&new_notif->actions, 0, sizeof(new_notif->actions));
|
||||||
notif = new_notif;
|
notif = new_notif;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
notify.h
27
notify.h
|
|
@ -29,26 +29,28 @@ struct notification {
|
||||||
/*
|
/*
|
||||||
* Set by caller of notify_notify()
|
* Set by caller of notify_notify()
|
||||||
*/
|
*/
|
||||||
char *id;
|
char *id; /* Internal notification ID */
|
||||||
char *title;
|
|
||||||
|
char *app_id; /* Custom app-id, overrides the terminal's app-id if set */
|
||||||
|
char *title; /* Required */
|
||||||
char *body;
|
char *body;
|
||||||
char *category;
|
char *category;
|
||||||
|
|
||||||
char *app_id; /* Custm app-id, overrides the terminal's app-id */
|
|
||||||
char *icon_id;
|
|
||||||
char *icon_symbolic_name;
|
|
||||||
uint8_t *icon_data;
|
|
||||||
size_t icon_data_sz;
|
|
||||||
|
|
||||||
enum notify_when when;
|
enum notify_when when;
|
||||||
enum notify_urgency urgency;
|
enum notify_urgency urgency;
|
||||||
int32_t expire_time;
|
int32_t expire_time;
|
||||||
|
|
||||||
tll(char *) actions;
|
tll(char *) actions;
|
||||||
|
|
||||||
bool focus;
|
char *icon_cache_id;
|
||||||
bool may_be_programatically_closed;
|
char *icon_symbolic_name;
|
||||||
bool report_activated;
|
uint8_t *icon_data;
|
||||||
bool report_closed;
|
size_t icon_data_sz;
|
||||||
|
|
||||||
|
bool focus; /* Focus the foot window when notification is activated */
|
||||||
|
bool may_be_programatically_closed; /* OSC-99: notification may be programatically closed by the client */
|
||||||
|
bool report_activated; /* OSC-99: report notification activation to client */
|
||||||
|
bool report_closed; /* OSC-99: report notification closed to client */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used internally by notify
|
* Used internally by notify
|
||||||
|
|
@ -56,6 +58,7 @@ struct notification {
|
||||||
|
|
||||||
uint32_t external_id; /* Daemon assigned notification ID */
|
uint32_t external_id; /* Daemon assigned notification ID */
|
||||||
bool activated; /* User 'activated' the notification */
|
bool activated; /* User 'activated' the notification */
|
||||||
|
uint32_t button_count; /* Number of buttons (custom actions) in notification */
|
||||||
uint32_t activated_button; /* User activated one of the custom actions */
|
uint32_t activated_button; /* User activated one of the custom actions */
|
||||||
char *xdg_token; /* XDG activation token, from daemon */
|
char *xdg_token; /* XDG activation token, from daemon */
|
||||||
|
|
||||||
|
|
|
||||||
220
osc.c
220
osc.c
|
|
@ -579,7 +579,7 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
|
|
||||||
char *id = NULL; /* The 'i' parameter */
|
char *id = NULL; /* The 'i' parameter */
|
||||||
char *app_id = NULL; /* The 'f' parameter */
|
char *app_id = NULL; /* The 'f' parameter */
|
||||||
char *icon_id = NULL; /* The 'g' parameter */
|
char *icon_cache_id = NULL; /* The 'g' parameter */
|
||||||
char *symbolic_icon = NULL; /* The 'n' parameter */
|
char *symbolic_icon = NULL; /* The 'n' parameter */
|
||||||
char *category = NULL; /* The 't' parameter */
|
char *category = NULL; /* The 't' parameter */
|
||||||
char *payload = NULL;
|
char *payload = NULL;
|
||||||
|
|
@ -693,18 +693,24 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
else if (streq(value, "?")) {
|
else if (streq(value, "?")) {
|
||||||
/* Query capabilities */
|
/* Query capabilities */
|
||||||
|
|
||||||
char when_str[64];
|
const char *reply_id = id != NULL ? id : "0";
|
||||||
strcpy(when_str, "unfocused");
|
|
||||||
|
const char *p_caps = "title,body,?,close,alive,icon,buttons";
|
||||||
|
const char *a_caps = "focus,report";
|
||||||
|
const char *u_caps = "0,1,2";
|
||||||
|
|
||||||
|
char when_caps[64];
|
||||||
|
strcpy(when_caps, "unfocused");
|
||||||
if (!term->conf->desktop_notifications.inhibit_when_focused)
|
if (!term->conf->desktop_notifications.inhibit_when_focused)
|
||||||
strcat(when_str, ",always");
|
strcat(when_caps, ",always");
|
||||||
|
|
||||||
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
|
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
|
||||||
|
|
||||||
char reply[128];
|
char reply[128];
|
||||||
int n = xsnprintf(
|
int n = xsnprintf(
|
||||||
reply, sizeof(reply),
|
reply, sizeof(reply),
|
||||||
"\033]99;i=%s:p=?;p=title,body,?,close,alive,icon,buttons:a=focus,report:o=%s:u=0,1,2:c=1:w=1%s",
|
"\033]99;i=%s:p=?;p=%s:a=%s:o=%s:u=%s:c=1:w=1%s",
|
||||||
id != NULL ? id : "0", when_str, terminator);
|
reply_id, p_caps, a_caps, when_caps, u_caps, terminator);
|
||||||
|
|
||||||
xassert(n < sizeof(reply));
|
xassert(n < sizeof(reply));
|
||||||
term_to_slave(term, reply, n);
|
term_to_slave(term, reply, n);
|
||||||
|
|
@ -759,60 +765,79 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
if (category == NULL)
|
if (category == NULL)
|
||||||
category = decoded;
|
category = decoded;
|
||||||
else {
|
else {
|
||||||
const size_t old_len = strlen(category);
|
|
||||||
const size_t new_len = strlen(decoded);
|
|
||||||
|
|
||||||
/* Append, comma separated */
|
/* Append, comma separated */
|
||||||
category = xrealloc(category, old_len + 1 + new_len + 1);
|
char *old_category = category;
|
||||||
category[old_len] = ',';
|
category = xstrjoin(old_category, decoded, ',');
|
||||||
memcpy(&category[old_len + 1], decoded, new_len);
|
|
||||||
category[old_len + 1 + new_len] = '\0';
|
|
||||||
free(decoded);
|
free(decoded);
|
||||||
|
free(old_category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
/* graphical ID */
|
/* graphical ID (see 'n' and 'p=icon') */
|
||||||
free(icon_id);
|
free(icon_cache_id);
|
||||||
icon_id = xstrdup(value);
|
icon_cache_id = xstrdup(value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n':
|
case 'n': {
|
||||||
/* Symbolic icon name, used with 'g' */
|
/* Symbolic icon name, may used with 'g' */
|
||||||
free(symbolic_icon);
|
|
||||||
symbolic_icon = base64_decode(value, NULL);
|
|
||||||
|
|
||||||
/* Translate OSC-99 "special" names */
|
/*
|
||||||
if (symbolic_icon != NULL) {
|
* Sigh, protocol says 'n' can be used multiple times, and
|
||||||
const char *translated_name = NULL;
|
* that the terminal picks the first one that it can
|
||||||
|
* resolve.
|
||||||
|
*
|
||||||
|
* We can't resolve any icons at all. So, enter
|
||||||
|
* heuristics... let's pick the *shortest* symbolic
|
||||||
|
* name. The idea is that icon *names* are typically
|
||||||
|
* shorter than .desktop names, and macOS bundle
|
||||||
|
* identifiers.
|
||||||
|
*/
|
||||||
|
char *maybe_new_symbolic_icon = base64_decode(value, NULL);
|
||||||
|
if (maybe_new_symbolic_icon == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
if (streq(symbolic_icon, "error"))
|
if (symbolic_icon == NULL ||
|
||||||
translated_name = "dialog-error";
|
strlen(maybe_new_symbolic_icon) < strlen(symbolic_icon))
|
||||||
else if (streq(symbolic_icon, "warn") ||
|
{
|
||||||
streq(symbolic_icon, "warning"))
|
free(symbolic_icon);
|
||||||
translated_name = "dialog-warning";
|
symbolic_icon = maybe_new_symbolic_icon;
|
||||||
else if (streq(symbolic_icon, "info"))
|
|
||||||
translated_name = "dialog-information";
|
|
||||||
else if (streq(symbolic_icon, "question"))
|
|
||||||
translated_name = "dialog-question";
|
|
||||||
else if (streq(symbolic_icon, "help"))
|
|
||||||
translated_name = "system-help";
|
|
||||||
else if (streq(symbolic_icon, "file-manager"))
|
|
||||||
translated_name = "system-file-manager";
|
|
||||||
else if (streq(symbolic_icon, "system-monitor"))
|
|
||||||
translated_name = "utilities-system-monitor";
|
|
||||||
else if (streq(symbolic_icon, "text-editor"))
|
|
||||||
translated_name = "text-editor";
|
|
||||||
|
|
||||||
if (translated_name != NULL) {
|
/* Translate OSC-99 "special" names */
|
||||||
free(symbolic_icon);
|
if (symbolic_icon != NULL) {
|
||||||
symbolic_icon = xstrdup(translated_name);
|
const char *translated_name = NULL;
|
||||||
|
|
||||||
|
if (streq(symbolic_icon, "error"))
|
||||||
|
translated_name = "dialog-error";
|
||||||
|
else if (streq(symbolic_icon, "warn") ||
|
||||||
|
streq(symbolic_icon, "warning"))
|
||||||
|
translated_name = "dialog-warning";
|
||||||
|
else if (streq(symbolic_icon, "info"))
|
||||||
|
translated_name = "dialog-information";
|
||||||
|
else if (streq(symbolic_icon, "question"))
|
||||||
|
translated_name = "dialog-question";
|
||||||
|
else if (streq(symbolic_icon, "help"))
|
||||||
|
translated_name = "system-help";
|
||||||
|
else if (streq(symbolic_icon, "file-manager"))
|
||||||
|
translated_name = "system-file-manager";
|
||||||
|
else if (streq(symbolic_icon, "system-monitor"))
|
||||||
|
translated_name = "utilities-system-monitor";
|
||||||
|
else if (streq(symbolic_icon, "text-editor"))
|
||||||
|
translated_name = "text-editor";
|
||||||
|
|
||||||
|
if (translated_name != NULL) {
|
||||||
|
free(symbolic_icon);
|
||||||
|
symbolic_icon = xstrdup(translated_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
free(maybe_new_symbolic_icon);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (base64) {
|
if (base64) {
|
||||||
|
|
@ -824,42 +849,28 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
payload_size = strlen(payload);
|
payload_size = strlen(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search for an existing (d=0) notification to update */
|
/* Append metadata to previous notification chunk */
|
||||||
struct notification *notif = NULL;
|
struct notification *notif = &term->kitty_notification;
|
||||||
tll_foreach(term->kitty_notifications, it) {
|
|
||||||
if ((id == NULL && it->item.id == NULL) ||
|
if (!((id == NULL && notif->id == NULL) ||
|
||||||
(id != NULL && it->item.id != NULL && streq(it->item.id, id)))
|
(id != NULL && notif->id != NULL && streq(id, notif->id))) ||
|
||||||
{
|
!notif->may_be_programatically_closed) /* Free:d notification has this as false... */
|
||||||
/* Found existing notification */
|
{
|
||||||
notif = &it->item;
|
/* ID mismatch, ignore previous notification state */
|
||||||
break;
|
notify_free(term, notif);
|
||||||
}
|
|
||||||
|
notif->id = id;
|
||||||
|
notif->when = when;
|
||||||
|
notif->urgency = urgency;
|
||||||
|
notif->expire_time = expire_time;
|
||||||
|
notif->focus = focus;
|
||||||
|
notif->may_be_programatically_closed = true;
|
||||||
|
notif->report_activated = report_activated;
|
||||||
|
notif->report_closed = report_closed;
|
||||||
|
|
||||||
|
id = NULL; /* Prevent double free */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notif == NULL) {
|
|
||||||
tll_push_front(term->kitty_notifications, ((struct notification){
|
|
||||||
.id = id,
|
|
||||||
.when = when,
|
|
||||||
.urgency = urgency,
|
|
||||||
.expire_time = expire_time,
|
|
||||||
.actions = tll_init(),
|
|
||||||
.focus = focus,
|
|
||||||
.may_be_programatically_closed = true,
|
|
||||||
.report_activated = report_activated,
|
|
||||||
.report_closed = report_closed,
|
|
||||||
.stdout_fd = -1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
id = NULL; /* Prevent double free */
|
|
||||||
notif = &tll_front(term->kitty_notifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notif->pid > 0) {
|
|
||||||
/* Notification has already been completed, ignore new metadata */
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update notification metadata */
|
|
||||||
if (have_a) {
|
if (have_a) {
|
||||||
notif->focus = focus;
|
notif->focus = focus;
|
||||||
notif->report_activated = report_activated;
|
notif->report_activated = report_activated;
|
||||||
|
|
@ -875,10 +886,10 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
if (have_w)
|
if (have_w)
|
||||||
notif->expire_time = expire_time;
|
notif->expire_time = expire_time;
|
||||||
|
|
||||||
if (icon_id != NULL) {
|
if (icon_cache_id != NULL) {
|
||||||
free(notif->icon_id);
|
free(notif->icon_cache_id);
|
||||||
notif->icon_id = icon_id;
|
notif->icon_cache_id = icon_cache_id;
|
||||||
icon_id = NULL; /* Prevent double free */
|
icon_cache_id = NULL; /* Prevent double free */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (symbolic_icon != NULL) {
|
if (symbolic_icon != NULL) {
|
||||||
|
|
@ -898,15 +909,10 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
notif->category = category;
|
notif->category = category;
|
||||||
category = NULL; /* Prevent double free */
|
category = NULL; /* Prevent double free */
|
||||||
} else {
|
} else {
|
||||||
const size_t old_len = strlen(notif->category);
|
|
||||||
const size_t new_len = strlen(category);
|
|
||||||
|
|
||||||
/* Append, comma separated */
|
/* Append, comma separated */
|
||||||
notif->category =
|
char *new_category = xstrjoin(notif->category, category, ',');
|
||||||
xrealloc(notif->category, old_len + 1 + new_len + 1);
|
free(notif->category);
|
||||||
notif->category[old_len] = ',';
|
notif->category = new_category;
|
||||||
memcpy(¬if->category[old_len + 1], category, new_len);
|
|
||||||
notif->category[old_len + 1 + new_len] = '\0';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -923,7 +929,7 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
payload = NULL;
|
payload = NULL;
|
||||||
} else {
|
} else {
|
||||||
char *old = *ptr;
|
char *old = *ptr;
|
||||||
*ptr = xstrjoin(old, payload);
|
*ptr = xstrjoin(old, payload, 0);
|
||||||
free(old);
|
free(old);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -964,11 +970,11 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
/* Update icon cache, if necessary */
|
/* Update icon cache, if necessary */
|
||||||
if (notif->icon_id != NULL &&
|
if (notif->icon_cache_id != NULL &&
|
||||||
(notif->icon_symbolic_name != NULL || notif->icon_data != NULL))
|
(notif->icon_symbolic_name != NULL || notif->icon_data != NULL))
|
||||||
{
|
{
|
||||||
notify_icon_del(term, notif->icon_id);
|
notify_icon_del(term, notif->icon_cache_id);
|
||||||
notify_icon_add(term, notif->icon_id,
|
notify_icon_add(term, notif->icon_cache_id,
|
||||||
notif->icon_symbolic_name,
|
notif->icon_symbolic_name,
|
||||||
notif->icon_data, notif->icon_data_sz);
|
notif->icon_data, notif->icon_data_sz);
|
||||||
|
|
||||||
|
|
@ -985,27 +991,19 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
notify_close(term, notif->id);
|
notify_close(term, notif->id);
|
||||||
} else if (payload_type == PAYLOAD_ALIVE) {
|
} else if (payload_type == PAYLOAD_ALIVE) {
|
||||||
char *alive_ids = NULL;
|
char *alive_ids = NULL;
|
||||||
size_t alive_ids_len = 0;
|
|
||||||
|
|
||||||
tll_foreach(term->active_notifications, it) {
|
tll_foreach(term->active_notifications, it) {
|
||||||
/* TODO: check with kitty: use "0" for all
|
/* TODO: check with kitty: use "0" for all
|
||||||
notifications with no ID? */
|
notifications with no ID? */
|
||||||
|
|
||||||
const char *item_id = it->item.id != NULL ? it->item.id : "0";
|
const char *item_id = it->item.id != NULL ? it->item.id : "0";
|
||||||
const size_t id_len = strlen(item_id);
|
|
||||||
|
|
||||||
if (alive_ids == NULL) {
|
if (alive_ids == NULL)
|
||||||
alive_ids = xstrdup(item_id);
|
alive_ids = xstrdup(item_id);
|
||||||
alive_ids_len = id_len;
|
else {
|
||||||
} else {
|
char *old_alive_ids = alive_ids;
|
||||||
alive_ids = xrealloc(alive_ids, alive_ids_len + 1 + id_len + 1);
|
alive_ids = xstrjoin(old_alive_ids, item_id, ',');
|
||||||
|
free(old_alive_ids);
|
||||||
/* Append ",<id>" */
|
|
||||||
alive_ids[alive_ids_len] = ',';
|
|
||||||
memcpy(&alive_ids[alive_ids_len + 1], item_id, id_len);
|
|
||||||
|
|
||||||
alive_ids_len += 1 + id_len;
|
|
||||||
alive_ids[alive_ids_len] = '\0';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1029,19 +1027,13 @@ kitty_notification(struct terminal *term, char *string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tll_foreach(term->kitty_notifications, it) {
|
notify_free(term, notif);
|
||||||
if (&it->item == notif) {
|
|
||||||
notify_free(term, &it->item);
|
|
||||||
tll_remove(term->kitty_notifications, it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
free(id);
|
free(id);
|
||||||
free(app_id);
|
free(app_id);
|
||||||
free(icon_id);
|
free(icon_cache_id);
|
||||||
free(symbolic_icon);
|
free(symbolic_icon);
|
||||||
free(payload);
|
free(payload);
|
||||||
free(category);
|
free(category);
|
||||||
|
|
|
||||||
21
terminal.c
21
terminal.c
|
|
@ -994,7 +994,7 @@ reload_fonts(struct terminal *term, bool resize_grid)
|
||||||
snprintf(size, sizeof(size), ":size=%.2f",
|
snprintf(size, sizeof(size), ":size=%.2f",
|
||||||
term->font_sizes[i][j].pt_size * scale);
|
term->font_sizes[i][j].pt_size * scale);
|
||||||
|
|
||||||
names[i][j] = xstrjoin(font->pattern, size);
|
names[i][j] = xstrjoin(font->pattern, size, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1021,9 +1021,9 @@ reload_fonts(struct terminal *term, bool resize_grid)
|
||||||
|
|
||||||
char *attrs[4] = {
|
char *attrs[4] = {
|
||||||
[0] = dpi, /* Takes ownership */
|
[0] = dpi, /* Takes ownership */
|
||||||
[1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""),
|
[1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : "", 0),
|
||||||
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""),
|
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : "", 0),
|
||||||
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""),
|
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : "", 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fcft_font *fonts[4];
|
struct fcft_font *fonts[4];
|
||||||
|
|
@ -1313,7 +1313,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
||||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
.ime_enabled = true,
|
.ime_enabled = true,
|
||||||
#endif
|
#endif
|
||||||
.kitty_notifications = tll_init(),
|
|
||||||
.active_notifications = tll_init(),
|
.active_notifications = tll_init(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1823,11 +1822,7 @@ term_destroy(struct terminal *term)
|
||||||
tll_remove(term->ptmx_paste_buffers, it);
|
tll_remove(term->ptmx_paste_buffers, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
tll_foreach(term->kitty_notifications, it) {
|
notify_free(term, &term->kitty_notification);
|
||||||
notify_free(term, &it->item);
|
|
||||||
tll_remove(term->kitty_notifications, it);
|
|
||||||
}
|
|
||||||
|
|
||||||
tll_foreach(term->active_notifications, it) {
|
tll_foreach(term->active_notifications, it) {
|
||||||
notify_free(term, &it->item);
|
notify_free(term, &it->item);
|
||||||
tll_remove(term->active_notifications, it);
|
tll_remove(term->active_notifications, it);
|
||||||
|
|
@ -2041,11 +2036,7 @@ term_reset(struct terminal *term, bool hard)
|
||||||
tll_remove(term->alt.sixel_images, it);
|
tll_remove(term->alt.sixel_images, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
tll_foreach(term->kitty_notifications, it) {
|
notify_free(term, &term->kitty_notification);
|
||||||
notify_free(term, &it->item);
|
|
||||||
tll_remove(term->kitty_notifications, it);
|
|
||||||
}
|
|
||||||
|
|
||||||
tll_foreach(term->active_notifications, it) {
|
tll_foreach(term->active_notifications, it) {
|
||||||
notify_free(term, &it->item);
|
notify_free(term, &it->item);
|
||||||
tll_remove(term->active_notifications, it);
|
tll_remove(term->active_notifications, it);
|
||||||
|
|
|
||||||
|
|
@ -799,9 +799,11 @@ struct terminal {
|
||||||
void *cb_data;
|
void *cb_data;
|
||||||
} shutdown;
|
} shutdown;
|
||||||
|
|
||||||
/* Notifications that either haven't been sent yet, or have been
|
/* State, to handle chunked notifications */
|
||||||
sent but not yet dismissed */
|
struct notification kitty_notification;
|
||||||
tll(struct notification) kitty_notifications;
|
|
||||||
|
/* Currently active notifications, from foot's perspective (their
|
||||||
|
notification helper processes are still running) */
|
||||||
tll(struct notification) active_notifications;
|
tll(struct notification) active_notifications;
|
||||||
struct notification_icon notification_icons[32];
|
struct notification_icon notification_icons[32];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -580,7 +580,7 @@ test_section_desktop_notifications(void)
|
||||||
|
|
||||||
test_boolean(&ctx, &parse_section_desktop_notifications, "inhibit-when-focused", &conf.desktop_notifications.inhibit_when_focused);
|
test_boolean(&ctx, &parse_section_desktop_notifications, "inhibit-when-focused", &conf.desktop_notifications.inhibit_when_focused);
|
||||||
test_spawn_template(&ctx, &parse_section_desktop_notifications, "command", &conf.desktop_notifications.command);
|
test_spawn_template(&ctx, &parse_section_desktop_notifications, "command", &conf.desktop_notifications.command);
|
||||||
test_spawn_template(&ctx, &parse_section_desktop_notifications, "command-action-arg", &conf.desktop_notifications.command_action_arg);
|
test_spawn_template(&ctx, &parse_section_desktop_notifications, "command-action-argument", &conf.desktop_notifications.command_action_arg);
|
||||||
test_spawn_template(&ctx, &parse_section_desktop_notifications, "close", &conf.desktop_notifications.close);
|
test_spawn_template(&ctx, &parse_section_desktop_notifications, "close", &conf.desktop_notifications.close);
|
||||||
|
|
||||||
config_free(&conf);
|
config_free(&conf);
|
||||||
|
|
|
||||||
12
xmalloc.h
12
xmalloc.h
|
|
@ -25,12 +25,16 @@ xmemdup(const void *ptr, size_t size)
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline char *
|
static inline char *
|
||||||
xstrjoin(const char *s1, const char *s2)
|
xstrjoin(const char *s1, const char *s2, char delim)
|
||||||
{
|
{
|
||||||
size_t n1 = strlen(s1);
|
size_t n1 = strlen(s1);
|
||||||
size_t n2 = strlen(s2);
|
size_t n2 = delim > 0 ? 1 : 0;
|
||||||
char *joined = xmalloc(n1 + n2 + 1);
|
size_t n3 = strlen(s2);
|
||||||
|
|
||||||
|
char *joined = xmalloc(n1 + n2 + n3 + 1);
|
||||||
memcpy(joined, s1, n1);
|
memcpy(joined, s1, n1);
|
||||||
memcpy(joined + n1, s2, n2 + 1);
|
if (delim > 0)
|
||||||
|
joined[n1] = delim;
|
||||||
|
memcpy(joined + n1 + n2, s2, n3 + 1);
|
||||||
return joined;
|
return joined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue