Merge branch 'master' into releases/1.18

This commit is contained in:
Daniel Eklöf 2024-08-14 11:59:23 +02:00
commit 54c1660a1e
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
16 changed files with 371 additions and 86 deletions

View file

@ -1,5 +1,6 @@
# Changelog # Changelog
* [Unreleased](#unreleased)
* [1.18.0](#1-18-0) * [1.18.0](#1-18-0)
* [1.17.2](#1-17-2) * [1.17.2](#1-17-2)
* [1.17.1](#1-17-1) * [1.17.1](#1-17-1)
@ -52,6 +53,36 @@
* [1.2.0](#1-2-0) * [1.2.0](#1-2-0)
## Unreleased
### Added
* OSC-99: support for the `s` parameter. Supported keywords are
`silent`, `system` and names from the freedesktop sound naming
specification.
* `${muted}` and `${sound-name}` added to the
`desktop-notifications.command` template.
### Changed
* CSD buttons now activate on mouse button **release**, rather than
press ([#1787][1787]).
### Deprecated
### Removed
### Fixed
* Regression: OSC-111 not handling alpha changes correctly, causing
visual glitches ([#1801][1801]).
[1801]: https://codeberg.org/dnkl/foot/issues/1801
### Security
### Contributors
## 1.18.0 ## 1.18.0
### Added ### Added

View file

@ -3,10 +3,8 @@
The fast, lightweight and minimalistic Wayland terminal emulator. The fast, lightweight and minimalistic Wayland terminal emulator.
[![CI status](https://ci.codeberg.org/api/badges/dnkl/foot/status.svg)](https://ci.codeberg.org/dnkl/foot) [![CI status](https://ci.codeberg.org/api/badges/dnkl/foot/status.svg)](https://ci.codeberg.org/dnkl/foot)
[![Pipeline status](https://gitlab.com/dnkl/foot/badges/master/pipeline.svg)](https://gitlab.com/dnkl/foot/commits/master)
[![builds.sr.ht status](https://builds.sr.ht/~dnkl/foot.svg)](https://builds.sr.ht/~dnkl/foot?)
[![Packaging status](https://repology.org/badge/vertical-allrepos/foot.svg)](https://repology.org/project/foot/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/foot.svg?columns=4)](https://repology.org/project/foot/versions)
## Index ## Index

View file

@ -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", 0); path = xstrjoin(xdg_config_home, "/foot/foot.ini");
else if (home_dir != NULL) else if (home_dir != NULL)
path = xstrjoin(home_dir, "/.config/foot/foot.ini", 0); path = xstrjoin(home_dir, "/.config/foot/foot.ini");
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", 0); path = xstrjoin(conf_dir, "/foot/foot.ini");
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);
@ -843,7 +843,7 @@ parse_section_main(struct context *ctx)
return false; return false;
} }
_include_path = xasprintf("%s/%s", home_dir, value + 2); _include_path = xstrjoin3(home_dir, "/", value + 2);
include_path = _include_path; include_path = _include_path;
} else } else
include_path = value; include_path = value;
@ -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", 0); return xstrjoin(xdg_runtime, "/foot.sock");
} }
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-argument} --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} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --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);

View file

@ -179,9 +179,10 @@ empty string to be set, but it must be quoted: *KEY=""*)
Default: _unset_ Default: _unset_
*box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates *box-drawings-uses-font-glyphs*
box/line drawing characters itself. The are several advantages to Boolean. When disabled, foot generates box/line drawing characters
doing this instead of using font glyphs: itself. The are several advantages to doing this instead of using
font glyphs:
- No antialiasing effects where e.g. line endpoints appear - No antialiasing effects where e.g. line endpoints appear
dimmed down, or blurred. dimmed down, or blurred.
@ -195,6 +196,13 @@ empty string to be set, but it must be quoted: *KEY=""*)
When enabled, box/line drawing characters are rendered using font When enabled, box/line drawing characters are rendered using font
glyphs. This may result in a more uniform look, in some use cases. glyphs. This may result in a more uniform look, in some use cases.
When disabled, foot will render the following Unicode codepoints
by itself:
- U+02500 - U+0259F
- U+02800 - U+028FF
- U+1Fb00 - U+1FB9B
Default: _no_. Default: _no_.
*dpi-aware* *dpi-aware*
@ -459,6 +467,17 @@ 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.
_${muted}_ is replaced by either *true* or *false*, depending
on whether the notification has requested all notification
sounds be muted. It is intended to set the *suppress-sound*
hint (with e.g. notify-send's *--hint* option).
_${sound-name}_ is replaced by sound-name requested by the
notification. This should be a name from the freedesktop sound
naming specification, but this is not something that foot
enforces. It is intended to set the *sound-name* hint (with
e.g. notify-send's *--hint* option).
_${action-argument}_ will be expanded to the _${action-argument}_ will be expanded to the
*command-action-argument* option, for each notification *command-action-argument* option, for each notification
action. There will always be at least one action, the action. There will always be at least one action, the
@ -568,6 +587,8 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
--urgency ${urgency}++ --urgency ${urgency}++
--expire-time ${expire-time}++ --expire-time ${expire-time}++
--hint STRING:image-path:${icon}++ --hint STRING:image-path:${icon}++
--hint BOOLEAN:suppress-sound:${muted}++
--hint STRING:sound-name:${sound-name}++
--replace-id ${replace-id}++ --replace-id ${replace-id}++
${action-argument}++ ${action-argument}++
--print-id++ --print-id++

View file

@ -47,7 +47,7 @@
# 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-argument} --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} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
# command-action-argument=--action ${action-name}=${action-label} # command-action-argument=--action ${action-name}=${action-label}
# close="" # close=""
# inhibit-when-focused=yes # inhibit-when-focused=yes

86
input.c
View file

@ -2053,6 +2053,25 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
} }
} }
static bool
pointer_is_on_button(const struct terminal *term, const struct seat *seat,
enum csd_surface csd_surface)
{
if (seat->mouse.x < 0)
return false;
if (seat->mouse.y < 0)
return false;
struct csd_data info = get_csd_data(term, csd_surface);
if (seat->mouse.x > info.width)
return false;
if (seat->mouse.y > info.height)
return false;
return true;
}
static void static void
wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
@ -2085,16 +2104,42 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
int x = wl_fixed_to_int(surface_x) * term->scale; int x = wl_fixed_to_int(surface_x) * term->scale;
int y = wl_fixed_to_int(surface_y) * term->scale; int y = wl_fixed_to_int(surface_y) * term->scale;
enum term_surface surf_kind = term->active_surface;
int button = 0;
bool send_to_client = false;
bool is_on_button = false;
/* If current surface is a button, check if pointer was on it
*before* the motion event */
switch (surf_kind) {
case TERM_SURF_BUTTON_MINIMIZE:
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE);
break;
case TERM_SURF_BUTTON_MAXIMIZE:
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE);
break;
case TERM_SURF_BUTTON_CLOSE:
is_on_button = pointer_is_on_button(term, seat, CSD_SURF_CLOSE);
break;
case TERM_SURF_NONE:
case TERM_SURF_GRID:
case TERM_SURF_TITLE:
case TERM_SURF_BORDER_LEFT:
case TERM_SURF_BORDER_RIGHT:
case TERM_SURF_BORDER_TOP:
case TERM_SURF_BORDER_BOTTOM:
break;
}
seat->pointer.hidden = false; seat->pointer.hidden = false;
seat->mouse.x = x; seat->mouse.x = x;
seat->mouse.y = y; seat->mouse.y = y;
term_xcursor_update_for_seat(term, seat); term_xcursor_update_for_seat(term, seat);
enum term_surface surf_kind = term->active_surface;
int button = 0;
bool send_to_client = false;
if (tll_length(seat->mouse.buttons) > 0) { if (tll_length(seat->mouse.buttons) > 0) {
const struct button_tracker *tracker = &tll_front(seat->mouse.buttons); const struct button_tracker *tracker = &tll_front(seat->mouse.buttons);
surf_kind = tracker->surf_kind; surf_kind = tracker->surf_kind;
@ -2104,9 +2149,21 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
switch (surf_kind) { switch (surf_kind) {
case TERM_SURF_NONE: case TERM_SURF_NONE:
break;
case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MINIMIZE:
if (pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE) != is_on_button)
render_refresh_csd(term);
break;
case TERM_SURF_BUTTON_MAXIMIZE: case TERM_SURF_BUTTON_MAXIMIZE:
if (pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE) != is_on_button)
render_refresh_csd(term);
break;
case TERM_SURF_BUTTON_CLOSE: case TERM_SURF_BUTTON_CLOSE:
if (pointer_is_on_button(term, seat, CSD_SURF_CLOSE) != is_on_button)
render_refresh_csd(term);
break; break;
case TERM_SURF_TITLE: case TERM_SURF_TITLE:
@ -2246,8 +2303,8 @@ fdm_csd_move(struct fdm *fdm, int fd, int events, void *data)
} }
static const struct key_binding * static const struct key_binding *
match_mouse_binding(const struct seat *seat, const struct terminal *term, match_mouse_binding(const struct seat *seat, const struct terminal *term,
int button) int button)
{ {
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) { if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
/* Seat has keyboard - use mouse bindings *with* modifiers */ /* Seat has keyboard - use mouse bindings *with* modifiers */
@ -2577,12 +2634,19 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
} }
case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MINIMIZE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) if (button == BTN_LEFT &&
pointer_is_on_button(term, seat, CSD_SURF_MINIMIZE) &&
state == WL_POINTER_BUTTON_STATE_RELEASED)
{
xdg_toplevel_set_minimized(term->window->xdg_toplevel); xdg_toplevel_set_minimized(term->window->xdg_toplevel);
}
break; break;
case TERM_SURF_BUTTON_MAXIMIZE: case TERM_SURF_BUTTON_MAXIMIZE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { if (button == BTN_LEFT &&
pointer_is_on_button(term, seat, CSD_SURF_MAXIMIZE) &&
state == WL_POINTER_BUTTON_STATE_RELEASED)
{
if (term->window->is_maximized) if (term->window->is_maximized)
xdg_toplevel_unset_maximized(term->window->xdg_toplevel); xdg_toplevel_unset_maximized(term->window->xdg_toplevel);
else else
@ -2591,8 +2655,12 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
break; break;
case TERM_SURF_BUTTON_CLOSE: case TERM_SURF_BUTTON_CLOSE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) if (button == BTN_LEFT &&
pointer_is_on_button(term, seat, CSD_SURF_CLOSE) &&
state == WL_POINTER_BUTTON_STATE_RELEASED)
{
term_shutdown(term); term_shutdown(term);
}
break; break;
case TERM_SURF_GRID: { case TERM_SURF_GRID: {

72
main.c
View file

@ -258,7 +258,7 @@ main(int argc, char *const *argv)
break; break;
case 't': case 't':
tll_push_back(overrides, xstrjoin("term=", optarg, 0)); tll_push_back(overrides, xstrjoin("term=", optarg));
break; break;
case 'L': case 'L':
@ -266,11 +266,11 @@ main(int argc, char *const *argv)
break; break;
case 'T': case 'T':
tll_push_back(overrides, xstrjoin("title=", optarg, 0)); tll_push_back(overrides, xstrjoin("title=", optarg));
break; break;
case 'a': case 'a':
tll_push_back(overrides, xstrjoin("app-id=", optarg, 0)); tll_push_back(overrides, xstrjoin("app-id=", optarg));
break; break;
case 'D': { case 'D': {
@ -284,7 +284,7 @@ main(int argc, char *const *argv)
} }
case 'f': { case 'f': {
char *font_override = xstrjoin("font=", optarg, 0); char *font_override = xstrjoin("font=", optarg);
tll_push_back(overrides, font_override); tll_push_back(overrides, font_override);
break; break;
} }
@ -425,19 +425,19 @@ main(int argc, char *const *argv)
* that does not exist on this system, then the above call may return * that does not exist on this system, then the above call may return
* NULL. We should just continue with the fallback method below. * NULL. We should just continue with the fallback method below.
*/ */
LOG_WARN("setlocale() failed"); LOG_ERR("setlocale() failed. The most common cause is that the "
locale = "C"; "configured locale is not available, or has been misspelled");
} }
LOG_INFO("locale: %s", locale); LOG_INFO("locale: %s", locale != NULL ? locale : "<invalid>");
bool bad_locale = !locale_is_utf8(); bool bad_locale = locale == NULL || !locale_is_utf8();
if (bad_locale) { if (bad_locale) {
static const char fallback_locales[][12] = { static const char fallback_locales[][12] = {
"C.UTF-8", "C.UTF-8",
"en_US.UTF-8", "en_US.UTF-8",
}; };
char *saved_locale = xstrdup(locale); char *saved_locale = locale != NULL ? xstrdup(locale) : NULL;
/* /*
* Try to force an UTF-8 locale. If we succeed, launch the * Try to force an UTF-8 locale. If we succeed, launch the
@ -448,13 +448,23 @@ main(int argc, char *const *argv)
const char *const fallback_locale = fallback_locales[i]; const char *const fallback_locale = fallback_locales[i];
if (setlocale(LC_CTYPE, fallback_locale) != NULL) { if (setlocale(LC_CTYPE, fallback_locale) != NULL) {
LOG_WARN("'%s' is not a UTF-8 locale, using '%s' instead", if (saved_locale != NULL) {
saved_locale, fallback_locale); LOG_WARN(
"'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale);
user_notification_add_fmt( user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING, &user_notifications, USER_NOTIFICATION_WARNING,
"'%s' is not a UTF-8 locale, using '%s' instead", "'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale); saved_locale, fallback_locale);
} else {
LOG_WARN(
"invalid locale, falling back to '%s'", fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"invalid locale, falling back to '%s'", fallback_locale);
}
bad_locale = false; bad_locale = false;
break; break;
@ -462,14 +472,22 @@ main(int argc, char *const *argv)
} }
if (bad_locale) { if (bad_locale) {
LOG_ERR( if (saved_locale != NULL) {
"'%s' is not a UTF-8 locale, and failed to find a fallback", LOG_ERR(
saved_locale); "'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale);
user_notification_add_fmt( user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR, &user_notifications, USER_NOTIFICATION_ERROR,
"'%s' is not a UTF-8 locale, and failed to find a fallback", "'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale); saved_locale);
} else {
LOG_ERR("invalid locale, and failed to find a fallback");
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"invalid locale, and failed to find a fallback");
}
} }
free(saved_locale); free(saved_locale);
} }
@ -658,15 +676,19 @@ out:
UNITTEST UNITTEST
{ {
char *s = xstrjoin("foo", "bar", 0); char *s = xstrjoin("foo", "bar");
xassert(streq(s, "foobar")); xassert(streq(s, "foobar"));
free(s); free(s);
s = xstrjoin("foo", "bar", ' '); s = xstrjoin3("foo", " ", "bar");
xassert(streq(s, "foo bar")); xassert(streq(s, "foo bar"));
free(s); free(s);
s = xstrjoin("foo", "bar", ','); s = xstrjoin3("foo", ",", "bar");
xassert(streq(s, "foo,bar")); xassert(streq(s, "foo,bar"));
free(s); free(s);
s = xstrjoin3("foo", "bar", "baz");
xassert(streq(s, "foobarbaz"));
free(s);
} }

View file

@ -12,12 +12,16 @@ is_debug_build = get_option('buildtype').startswith('debug')
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
if cc.has_function('memfd_create') if cc.has_function('memfd_create',
args: ['-D_GNU_SOURCE'],
prefix: '#include <sys/mman.h>')
add_project_arguments('-DMEMFD_CREATE', language: 'c') add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif endif
# Missing on DragonFly, FreeBSD < 14.1 # Missing on DragonFly, FreeBSD < 14.1
if cc.has_function('execvpe') if cc.has_function('execvpe',
args: ['-D_GNU_SOURCE'],
prefix: '#include <unistd.h>')
add_project_arguments('-DEXECVPE', language: 'c') add_project_arguments('-DEXECVPE', language: 'c')
endif endif

View file

@ -34,6 +34,7 @@ notify_free(struct terminal *term, struct notification *notif)
free(notif->icon_cache_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->sound_name);
free(notif->xdg_token); free(notif->xdg_token);
free(notif->stdout_data); free(notif->stdout_data);
@ -77,7 +78,7 @@ write_icon_file(const void *data, size_t data_sz, int *fd, char **filename,
LOG_DBG("wrote icon data to %s", name); LOG_DBG("wrote icon data to %s", name);
*filename = xstrdup(name); *filename = xstrdup(name);
*symbolic_name = xasprintf("file://%s", *filename); *symbolic_name = xstrjoin("file://", *filename);
return true; return true;
} }
@ -421,9 +422,12 @@ notify_notify(struct terminal *term, struct notification *notif)
? "normal" : "critical"; ? "normal" : "critical";
LOG_DBG("notify: title=\"%s\", body=\"%s\", app-id=\"%s\", category=\"%s\", " LOG_DBG("notify: title=\"%s\", body=\"%s\", app-id=\"%s\", category=\"%s\", "
"urgency=\"%s\", icon=\"%s\", expires=%d, replaces=%u (tracking: %s)", "urgency=\"%s\", icon=\"%s\", expires=%d, replaces=%u, muted=%s, "
"sound-name=%s (tracking: %s)",
title, body, app_id, notif->category, urgency_str, icon_name_or_path, title, body, app_id, notif->category, urgency_str, icon_name_or_path,
notif->expire_time, replaces_id, track_notification ? "yes" : "no"); notif->expire_time, replaces_id,
notif->muted ? "yes" : "no", notif->sound_name,
track_notification ? "yes" : "no");
xassert(title != NULL); xassert(title != NULL);
if (title == NULL) if (title == NULL)
@ -463,13 +467,16 @@ notify_notify(struct terminal *term, struct notification *notif)
} }
if (!spawn_expand_template( if (!spawn_expand_template(
&term->conf->desktop_notifications.command, 10, &term->conf->desktop_notifications.command, 12,
(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-argument"}, "urgency", "muted", "sound-name", "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,
notif->muted ? "true" : "false",
notif->sound_name != NULL ? notif->sound_name : "",
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 */
@ -542,6 +549,7 @@ notify_notify(struct terminal *term, struct notification *notif)
notif->icon_data = NULL; notif->icon_data = NULL;
notif->icon_data_sz = 0; notif->icon_data_sz = 0;
notif->icon_path = NULL; notif->icon_path = NULL;
notif->sound_name = NULL;
notif->icon_fd = -1; notif->icon_fd = -1;
notif->stdout_fd = -1; notif->stdout_fd = -1;
struct notification *new_notif = &tll_back(term->active_notifications); struct notification *new_notif = &tll_back(term->active_notifications);

View file

@ -52,6 +52,9 @@ struct notification {
bool report_activated; /* OSC-99: report notification activation to client */ bool report_activated; /* OSC-99: report notification activation to client */
bool report_closed; /* OSC-99: report notification closed to client */ bool report_closed; /* OSC-99: report notification closed to client */
bool muted; /* Explicitly mute the notification */
char *sound_name; /* Should be set to NULL if muted == true */
/* /*
* Used internally by notify * Used internally by notify
*/ */

107
osc.c
View file

@ -564,6 +564,33 @@ osc_notify(struct terminal *term, char *string)
}); });
} }
IGNORE_WARNING("-Wpedantic")
static bool
verify_kitty_id_is_valid(const char *id)
{
const size_t len = strlen(id);
for (size_t i = 0; i < len; i++) {
switch (id[i]) {
case 'a' ... 'z':
case 'A' ... 'Z':
case '0' ... '9':
case '_':
case '-':
case '+':
case '.':
break;
default:
return false;
}
}
return true;
}
UNIGNORE_WARNINGS
static void static void
kitty_notification(struct terminal *term, char *string) kitty_notification(struct terminal *term, char *string)
{ {
@ -582,6 +609,7 @@ kitty_notification(struct terminal *term, char *string)
char *icon_cache_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 *sound_name = NULL; /* The 's' parameter */
char *payload = NULL; char *payload = NULL;
bool focus = true; /* The 'a' parameter */ bool focus = true; /* The 'a' parameter */
@ -672,8 +700,11 @@ kitty_notification(struct terminal *term, char *string)
case 'i': case 'i':
/* id */ /* id */
free(id); if (verify_kitty_id_is_valid(value)) {
id = xstrdup(value); free(id);
id = xstrdup(value);
} else
LOG_WARN("OSC-99: ignoring invalid 'i' identifier");
break; break;
case 'p': case 'p':
@ -709,7 +740,7 @@ kitty_notification(struct terminal *term, char *string)
char reply[128]; char reply[128];
int n = xsnprintf( int n = xsnprintf(
reply, sizeof(reply), reply, sizeof(reply),
"\033]99;i=%s:p=?;p=%s:a=%s:o=%s:u=%s:c=1:w=1%s", "\033]99;i=%s:p=?;p=%s:a=%s:o=%s:u=%s:c=1:w=1:s=system,silent,error,warn,warning,info,question%s",
reply_id, p_caps, a_caps, when_caps, u_caps, terminator); reply_id, p_caps, a_caps, when_caps, u_caps, terminator);
xassert(n < sizeof(reply)); xassert(n < sizeof(reply));
@ -753,10 +784,15 @@ kitty_notification(struct terminal *term, char *string)
break; break;
} }
case 'f': case 'f': {
free(app_id); /* App-name */
app_id = base64_decode(value, NULL); char *decoded = base64_decode(value, NULL);
if (decoded != NULL) {
free(app_id);
app_id = decoded;
}
break; break;
}
case 't': { case 't': {
/* Type (category) */ /* Type (category) */
@ -767,7 +803,7 @@ kitty_notification(struct terminal *term, char *string)
else { else {
/* Append, comma separated */ /* Append, comma separated */
char *old_category = category; char *old_category = category;
category = xstrjoin(old_category, decoded, ','); category = xstrjoin3(old_category, ",", decoded);
free(decoded); free(decoded);
free(old_category); free(old_category);
} }
@ -775,6 +811,32 @@ kitty_notification(struct terminal *term, char *string)
break; break;
} }
case 's': {
/* Sound */
char *decoded = base64_decode(value, NULL);
if (decoded != NULL) {
free(sound_name);
sound_name = decoded;
const char *translated_name = NULL;
if (streq(decoded, "error"))
translated_name = "dialog-error";
else if (streq(decoded, "warn") || streq(decoded, "warning"))
translated_name = "dialog-warning";
else if (streq(decoded, "info"))
translated_name = "dialog-information";
else if (streq(decoded, "question"))
translated_name = "dialog-question";
if (translated_name != NULL) {
free(sound_name);
sound_name = xstrdup(translated_name);
}
}
break;
}
case 'g': case 'g':
/* graphical ID (see 'n' and 'p=icon') */ /* graphical ID (see 'n' and 'p=icon') */
free(icon_cache_id); free(icon_cache_id);
@ -910,12 +972,25 @@ kitty_notification(struct terminal *term, char *string)
category = NULL; /* Prevent double free */ category = NULL; /* Prevent double free */
} else { } else {
/* Append, comma separated */ /* Append, comma separated */
char *new_category = xstrjoin(notif->category, category, ','); char *new_category = xstrjoin3(notif->category, ",", category);
free(notif->category); free(notif->category);
notif->category = new_category; notif->category = new_category;
} }
} }
if (sound_name != NULL) {
notif->muted = streq(sound_name, "silent");
if (notif->muted || streq(sound_name, "system")) {
free(notif->sound_name);
notif->sound_name = NULL;
} else {
free(notif->sound_name);
notif->sound_name = sound_name;
sound_name = NULL; /* Prevent double free */
}
}
/* Handled chunked payload - append to existing metadata */ /* Handled chunked payload - append to existing metadata */
switch (payload_type) { switch (payload_type) {
case PAYLOAD_TITLE: case PAYLOAD_TITLE:
@ -929,7 +1004,7 @@ kitty_notification(struct terminal *term, char *string)
payload = NULL; payload = NULL;
} else { } else {
char *old = *ptr; char *old = *ptr;
*ptr = xstrjoin(old, payload, 0); *ptr = xstrjoin(old, payload);
free(old); free(old);
} }
break; break;
@ -1002,7 +1077,7 @@ kitty_notification(struct terminal *term, char *string)
alive_ids = xstrdup(item_id); alive_ids = xstrdup(item_id);
else { else {
char *old_alive_ids = alive_ids; char *old_alive_ids = alive_ids;
alive_ids = xstrjoin(old_alive_ids, item_id, ','); alive_ids = xstrjoin3(old_alive_ids, ",", item_id);
free(old_alive_ids); free(old_alive_ids);
} }
} }
@ -1037,6 +1112,7 @@ out:
free(symbolic_icon); free(symbolic_icon);
free(payload); free(payload);
free(category); free(category);
free(sound_name);
} }
void void
@ -1305,13 +1381,22 @@ osc_dispatch(struct terminal *term)
term_damage_color(term, COLOR_DEFAULT, 0); term_damage_color(term, COLOR_DEFAULT, 0);
break; break;
case 111: /* Reset default text background color */ case 111: { /* Reset default text background color */
LOG_DBG("resetting background color"); LOG_DBG("resetting background color");
bool alpha_changed = term->colors.alpha != term->conf->colors.alpha;
term->colors.bg = term->conf->colors.bg; term->colors.bg = term->conf->colors.bg;
term->colors.alpha = term->conf->colors.alpha; term->colors.alpha = term->conf->colors.alpha;
if (alpha_changed) {
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
}
term_damage_color(term, COLOR_DEFAULT, 0); term_damage_color(term, COLOR_DEFAULT, 0);
term_damage_margins(term); term_damage_margins(term);
break; break;
}
case 112: case 112:
LOG_DBG("resetting cursor color"); LOG_DBG("resetting cursor color");

View file

@ -2627,6 +2627,32 @@ render_csd_button_close(struct terminal *term, struct buffer *buf)
pixman_image_unref(src); pixman_image_unref(src);
} }
static bool
any_pointer_is_on_button(const struct terminal *term, enum csd_surface csd_surface)
{
if (unlikely(tll_length(term->wl->seats) == 0))
return false;
tll_foreach(term->wl->seats, it) {
const struct seat *seat = &it->item;
if (seat->mouse.x < 0)
continue;
if (seat->mouse.y < 0)
continue;
struct csd_data info = get_csd_data(term, csd_surface);
if (seat->mouse.x > info.width)
continue;
if (seat->mouse.y > info.height)
continue;
return true;
}
return false;
}
static void static void
render_csd_button(struct terminal *term, enum csd_surface surf_idx, render_csd_button(struct terminal *term, enum csd_surface surf_idx,
const struct csd_data *info, struct buffer *buf) const struct csd_data *info, struct buffer *buf)
@ -2650,21 +2676,24 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
_color = term->conf->colors.table[4]; /* blue */ _color = term->conf->colors.table[4]; /* blue */
is_set = term->conf->csd.color.minimize_set; is_set = term->conf->csd.color.minimize_set;
conf_color = &term->conf->csd.color.minimize; conf_color = &term->conf->csd.color.minimize;
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE; is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE &&
any_pointer_is_on_button(term, CSD_SURF_MINIMIZE);
break; break;
case CSD_SURF_MAXIMIZE: case CSD_SURF_MAXIMIZE:
_color = term->conf->colors.table[2]; /* green */ _color = term->conf->colors.table[2]; /* green */
is_set = term->conf->csd.color.maximize_set; is_set = term->conf->csd.color.maximize_set;
conf_color = &term->conf->csd.color.maximize; conf_color = &term->conf->csd.color.maximize;
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE; is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE &&
any_pointer_is_on_button(term, CSD_SURF_MAXIMIZE);
break; break;
case CSD_SURF_CLOSE: case CSD_SURF_CLOSE:
_color = term->conf->colors.table[1]; /* red */ _color = term->conf->colors.table[1]; /* red */
is_set = term->conf->csd.color.close_set; is_set = term->conf->csd.color.close_set;
conf_color = &term->conf->csd.color.quit; conf_color = &term->conf->csd.color.quit;
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE; is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE &&
any_pointer_is_on_button(term, CSD_SURF_CLOSE);
break; break;
default: default:

View file

@ -55,7 +55,7 @@ find_file_in_path(const char *file)
path != NULL; path != NULL;
path = strtok(NULL, ":")) path = strtok(NULL, ":"))
{ {
char *full = xasprintf("%s/%s", path, file); char *full = xstrjoin3(path, "/", file);
if (access(full, F_OK) == 0) { if (access(full, F_OK) == 0) {
free(path_list); free(path_list);
return full; return full;
@ -158,6 +158,7 @@ emit_one_notification(int fd, const struct user_notification *notif)
xassert(prefix != NULL); xassert(prefix != NULL);
if (write(fd, prefix, strlen(prefix)) < 0 || if (write(fd, prefix, strlen(prefix)) < 0 ||
write(fd, "foot: ", 6) < 0 ||
write(fd, notif->text, strlen(notif->text)) < 0 || write(fd, notif->text, strlen(notif->text)) < 0 ||
write(fd, postfix, strlen(postfix)) < 0) write(fd, postfix, strlen(postfix)) < 0)
{ {
@ -180,7 +181,8 @@ emit_one_notification(int fd, const struct user_notification *notif)
} }
static bool static bool
emit_notifications_of_kind(int fd, const user_notifications_t *notifications, emit_notifications_of_kind(int fd,
const user_notifications_t *notifications,
enum user_notification_kind kind) enum user_notification_kind kind)
{ {
tll_foreach(*notifications, it) { tll_foreach(*notifications, it) {
@ -329,7 +331,7 @@ add_to_env(struct environ *env, const char *name, const char *value)
if (env->envp == NULL) if (env->envp == NULL)
setenv(name, value, 1); setenv(name, value, 1);
else { else {
char *e = xasprintf("%s=%s", name, value); char *e = xstrjoin3(name, "=", value);
/* Search for existing variable. If found, replace it with the /* Search for existing variable. If found, replace it with the
new value */ new value */

View file

@ -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, 0); names[i][j] = xstrjoin(font->pattern, size);
} }
} }
@ -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" : "", 0), [1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""),
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : "", 0), [2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""),
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : "", 0), [3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""),
}; };
struct fcft_font *fonts[4]; struct fcft_font *fonts[4];
@ -1048,8 +1048,10 @@ reload_fonts(struct terminal *term, bool resize_grid)
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
if (tids[i] != 0) { if (tids[i] != 0) {
int ret; int ret;
thrd_join(tids[i], &ret); if (thrd_join(tids[i], &ret) != 0)
success = success && ret; success = false;
else
success = success && ret;
} else } else
success = false; success = false;
} }

View file

@ -141,6 +141,9 @@ main(int argc, const char *const *argv)
const char *key = strtok(key_value, "="); const char *key = strtok(key_value, "=");
const char *value = strtok(NULL, "="); const char *value = strtok(NULL, "=");
if (key == NULL)
continue;
#if 0 #if 0
assert((success && value != NULL) || assert((success && value != NULL) ||
(!success && value == NULL)); (!success && value == NULL));

View file

@ -25,16 +25,25 @@ xmemdup(const void *ptr, size_t size)
} }
static inline char * static inline char *
xstrjoin(const char *s1, const char *s2, char delim) xstrjoin(const char *s1, const char *s2)
{ {
size_t n1 = strlen(s1); size_t n1 = strlen(s1);
size_t n2 = delim > 0 ? 1 : 0; size_t n2 = strlen(s2);
size_t n3 = strlen(s2); char *joined = xmalloc(n1 + n2 + 1);
char *joined = xmalloc(n1 + n2 + n3 + 1);
memcpy(joined, s1, n1); memcpy(joined, s1, n1);
if (delim > 0) memcpy(joined + n1, s2, n2 + 1);
joined[n1] = delim; return joined;
memcpy(joined + n1 + n2, s2, n3 + 1); }
static inline char *
xstrjoin3(const char *s1, const char *s2, const char *s3)
{
size_t n1 = strlen(s1);
size_t n2 = strlen(s2);
size_t n3 = strlen(s3);
char *joined = xmalloc(n1 + n2 + n3 + 1);
memcpy(joined, s1, n1);
memcpy(joined + n1, s2, n2);
memcpy(joined + n1 + n2, s3, n3 + 1);
return joined; return joined;
} }