diff --git a/config.c b/config.c index 77dc3a73..a1905eb1 100644 --- a/config.c +++ b/config.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -204,12 +205,20 @@ static const char *const url_binding_action_map[] = { [BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL] = "toggle-url-visible", }; +static const char *const msg_binding_action_map[] = { + [BIND_ACTION_CLOSE_MESSAGE_NONE] = NULL, + [BIND_ACTION_CLOSE_MESSAGE_CANCEL] = "cancel", + [BIND_ACTION_CLOSE_MESSAGE_ACCEPT] = "accept", +}; + static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT, "binding action map size mismatch"); static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT, "search binding action map size mismatch"); static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT, "URL binding action map size mismatch"); +static_assert(ALEN(msg_binding_action_map) == BIND_ACTION_MESSAGE_COUNT, + "Message binding action map size mismatch"); struct context { struct config *conf; @@ -964,6 +973,20 @@ parse_section_main(struct context *ctx) return true; } + else if (streq(key, "close-policy")) { + if (strcasecmp(value, "always") == 0) + conf->close_policy = CLOSE_ALWAYS; + else if (strcasecmp(value, "never") == 0) + conf->close_policy = CLOSE_NEVER; + else if (strcasecmp(value, "message-always") == 0) + conf->close_policy = CLOSE_MESSAGE_ALWAYS; + else if (strcasecmp(value, "message-demand") == 0) + conf->close_policy = CLOSE_MESSAGE_DEMAND; + else + return false; + return true; + } + else if (streq(key, "resize-delay-ms")) return value_to_uint16(ctx, 10, &conf->resize_delay_ms); @@ -1282,6 +1305,15 @@ parse_section_url(struct context *ctx) } } +static bool +parse_section_msg(struct context *ctx) +{ + const char *key = ctx->key; + LOG_CONTEXTUAL_ERR("not a valid option: %s", key); + return false; + +} + static bool parse_section_regex(struct context *ctx) { @@ -1416,6 +1448,20 @@ parse_color_theme(struct context *ctx, struct color_theme *theme) return true; } + else if (streq(key, "msg_style")) { + if (!value_to_two_colors( + ctx, + &theme->msg_style.fg, + &theme->msg_style.bg, + false)) + { + return false; + } + + theme->use_custom.msg_style = true; + return true; + } + else if (streq(key, "scrollback-indicator")) { if (!value_to_two_colors( ctx, @@ -2381,6 +2427,15 @@ parse_section_url_bindings(struct context *ctx) &ctx->conf->bindings.url); } +static bool +parse_section_msg_bindings(struct context *ctx) +{ + return parse_key_binding_section( + ctx, + BIND_ACTION_MESSAGE_COUNT, msg_binding_action_map, + &ctx->conf->bindings.msg); +} + static bool NOINLINE resolve_key_binding_collisions(struct config *conf, const char *section_name, const char *const action_map[], @@ -2927,6 +2982,7 @@ enum section { SECTION_DESKTOP_NOTIFICATIONS, SECTION_SCROLLBACK, SECTION_URL, + SECTION_MSG, SECTION_REGEX, SECTION_COLORS, SECTION_COLORS2, @@ -2936,6 +2992,7 @@ enum section { SECTION_KEY_BINDINGS, SECTION_SEARCH_BINDINGS, SECTION_URL_BINDINGS, + SECTION_MSG_BINDINGS, SECTION_MOUSE_BINDINGS, SECTION_TEXT_BINDINGS, SECTION_ENVIRONMENT, @@ -2958,6 +3015,7 @@ static const struct { [SECTION_DESKTOP_NOTIFICATIONS] = {&parse_section_desktop_notifications, "desktop-notifications"}, [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"}, [SECTION_URL] = {&parse_section_url, "url"}, + [SECTION_MSG] = {&parse_section_msg, "msg"}, [SECTION_REGEX] = {&parse_section_regex, "regex", true}, [SECTION_COLORS] = {&parse_section_colors, "colors"}, [SECTION_COLORS2] = {&parse_section_colors2, "colors2"}, @@ -2967,6 +3025,7 @@ static const struct { [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"}, [SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"}, + [SECTION_MSG_BINDINGS] = {&parse_section_msg_bindings, "msg-bindings"}, [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, [SECTION_TEXT_BINDINGS] = {&parse_section_text_bindings, "text-bindings"}, [SECTION_ENVIRONMENT] = {&parse_section_environment, "environment"}, @@ -3293,6 +3352,24 @@ add_default_url_bindings(struct config *conf) conf->bindings.url.arr = xmemdup(bindings, sizeof(bindings)); } +static void +add_default_msg_bindings(struct config *conf) +{ + const struct config_key_binding bindings[] = { + {BIND_ACTION_CLOSE_MESSAGE_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, + {BIND_ACTION_CLOSE_MESSAGE_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_g}}}, + {BIND_ACTION_CLOSE_MESSAGE_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_d}}}, + {BIND_ACTION_CLOSE_MESSAGE_CANCEL, m("none"), {{XKB_KEY_Escape}}}, + {BIND_ACTION_CLOSE_MESSAGE_CANCEL, m("none"), {{XKB_KEY_n}}}, + {BIND_ACTION_CLOSE_MESSAGE_ACCEPT, m("none"), {{XKB_KEY_q}}}, + {BIND_ACTION_CLOSE_MESSAGE_ACCEPT, m("none"), {{XKB_KEY_y}}}, + {BIND_ACTION_CLOSE_MESSAGE_ACCEPT, m("none"), {{XKB_KEY_Return}}}, + }; + + conf->bindings.msg.count = ALEN(bindings); + conf->bindings.msg.arr = xmemdup(bindings, sizeof(bindings)); +} + static void add_default_mouse_bindings(struct config *conf) { @@ -3353,6 +3430,7 @@ config_load(struct config *conf, const char *conf_path, .pad_x = 0, .pad_y = 0, .center_when = CENTER_MAXIMIZED_AND_FULLSCREEN, + .close_policy = CLOSE_ALWAYS, .resize_by_cells = true, .resize_keep_grid = true, .resize_delay_ms = 100, @@ -3420,6 +3498,7 @@ config_load(struct config *conf, const char *conf_path, .jump_label = false, .scrollback_indicator = false, .url = false, + .msg_style = false, }, }, .initial_color_theme = COLOR_THEME1, @@ -3567,6 +3646,7 @@ config_load(struct config *conf, const char *conf_path, add_default_key_bindings(conf); add_default_search_bindings(conf); add_default_url_bindings(conf); + add_default_msg_bindings(conf); add_default_mouse_bindings(conf); struct config_file conf_file = {.path = NULL, .fd = -1}; @@ -3628,6 +3708,8 @@ config_load(struct config *conf, const char *conf_path, xassert(conf->bindings.search.arr[i].action != BIND_ACTION_SEARCH_NONE); for (size_t i = 0; i < conf->bindings.url.count; i++) xassert(conf->bindings.url.arr[i].action != BIND_ACTION_URL_NONE); + for (size_t i = 0; i < conf->bindings.msg.count; i++) + xassert(conf->bindings.msg.arr[i].action != BIND_ACTION_CLOSE_MESSAGE_NONE); #endif free(conf_file.path); @@ -3706,6 +3788,9 @@ config_override_apply(struct config *conf, config_override_t *overrides, resolve_key_binding_collisions( conf, section_info[SECTION_URL_BINDINGS].name, url_binding_action_map, &conf->bindings.url, KEY_BINDING) && + resolve_key_binding_collisions( + conf, section_info[SECTION_MSG_BINDINGS].name, + msg_binding_action_map, &conf->bindings.msg, KEY_BINDING) && resolve_key_binding_collisions( conf, section_info[SECTION_MOUSE_BINDINGS].name, binding_action_map, &conf->bindings.mouse, MOUSE_BINDING); @@ -3827,6 +3912,7 @@ config_clone(const struct config *old) key_binding_list_clone(&conf->bindings.key, &old->bindings.key); key_binding_list_clone(&conf->bindings.search, &old->bindings.search); key_binding_list_clone(&conf->bindings.url, &old->bindings.url); + key_binding_list_clone(&conf->bindings.msg, &old->bindings.msg); key_binding_list_clone(&conf->bindings.mouse, &old->bindings.mouse); conf->env_vars.length = 0; @@ -3918,6 +4004,7 @@ config_free(struct config *conf) free_key_binding_list(&conf->bindings.key); free_key_binding_list(&conf->bindings.search); free_key_binding_list(&conf->bindings.url); + free_key_binding_list(&conf->bindings.msg); free_key_binding_list(&conf->bindings.mouse); tll_free_and_free(conf->mouse.selection_override_modifiers, free); diff --git a/config.h b/config.h index 315f7e24..2f4603cf 100644 --- a/config.h +++ b/config.h @@ -161,6 +161,11 @@ struct color_theme { uint32_t bg; } jump_label; + struct { + uint32_t fg; + uint32_t bg; + } msg_style; + struct { uint32_t fg; uint32_t bg; @@ -183,6 +188,7 @@ struct color_theme { bool jump_label:1; bool scrollback_indicator:1; bool url:1; + bool msg_style:1; bool search_box_no_match:1; bool search_box_match:1; uint8_t dim; @@ -209,6 +215,13 @@ enum center_when { CENTER_ALWAYS, }; +enum close_policy { + CLOSE_ALWAYS, + CLOSE_NEVER, + CLOSE_MESSAGE_ALWAYS, + CLOSE_MESSAGE_DEMAND, +}; + struct config { char *term; char *shell; @@ -227,6 +240,7 @@ struct config { unsigned pad_x; unsigned pad_y; enum center_when center_when; + enum close_policy close_policy; bool resize_by_cells; bool resize_keep_grid; @@ -357,6 +371,8 @@ struct config { /* While showing URL jump labels */ struct config_key_binding_list url; + + struct config_key_binding_list msg; } bindings; struct { diff --git a/csi.c b/csi.c index 437fd8bc..7b9bddd0 100644 --- a/csi.c +++ b/csi.c @@ -584,6 +584,7 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) else { term_ime_disable(term); term->ime_reenable_after_url_mode = false; + term->ime_reenable_after_msg_mode = false; } break; diff --git a/input.c b/input.c index fe90a7f0..54f00a40 100644 --- a/input.c +++ b/input.c @@ -38,6 +38,7 @@ #include "tokenize.h" #include "unicode-mode.h" #include "url-mode.h" +#include "message.h" #include "util.h" #include "vt.h" #include "xkbcommon-vmod.h" @@ -1632,7 +1633,17 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, xassert(bindings != NULL); if (pressed) { - if (term->unicode_mode.active) { + if (msgs_mode_is_active(term)) { + if (should_repeat) + start_repeater(seat, key); + + msgs_input( + seat, term, bindings, key, sym, mods, consumed, + raw_syms, raw_count, serial); + return; + } + + else if (term->unicode_mode.active) { unicode_mode_input(seat, term, sym); return; } diff --git a/key-binding.c b/key-binding.c index a2883ed5..19b48748 100644 --- a/key-binding.c +++ b/key-binding.c @@ -113,6 +113,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr, .key = tll_init(), .search = tll_init(), .url = tll_init(), + .msg = tll_init(), .mouse = tll_init(), }, .conf = it->item->conf, @@ -154,6 +155,7 @@ key_binding_new_for_conf(struct key_binding_manager *mgr, .key = tll_init(), .search = tll_init(), .url = tll_init(), + .msg = tll_init(), .mouse = tll_init(), }, .conf = conf, @@ -553,6 +555,17 @@ convert_url_bindings(struct key_set *set) } } +static void +convert_msg_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.msg.count; i++) { + const struct config_key_binding *binding = &conf->bindings.msg.arr[i]; + convert_key_binding(set, binding, &set->public.msg); + } +} + static void convert_mouse_binding(struct key_set *set, const struct config_key_binding *conf_binding) @@ -599,6 +612,7 @@ load_keymap(struct key_set *set) convert_key_bindings(set); convert_search_bindings(set); convert_url_bindings(set); + convert_msg_bindings(set); convert_mouse_bindings(set); set->public.selection_overrides = mods_to_mask( @@ -640,6 +654,7 @@ unload_keymap(struct key_set *set) key_bindings_destroy(&set->public.key); key_bindings_destroy(&set->public.search); key_bindings_destroy(&set->public.url); + key_bindings_destroy(&set->public.msg); key_bindings_destroy(&set->public.mouse); set->public.selection_overrides = 0; } diff --git a/key-binding.h b/key-binding.h index 5f0c1f1e..e0b7b3cd 100644 --- a/key-binding.h +++ b/key-binding.h @@ -110,6 +110,13 @@ enum bind_action_url { BIND_ACTION_URL_COUNT, }; +enum bind_action_close_message{ + BIND_ACTION_CLOSE_MESSAGE_NONE, + BIND_ACTION_CLOSE_MESSAGE_CANCEL, + BIND_ACTION_CLOSE_MESSAGE_ACCEPT, + BIND_ACTION_MESSAGE_COUNT, +}; + typedef tll(xkb_keycode_t) xkb_keycode_list_t; struct key_binding { @@ -142,6 +149,7 @@ struct key_binding_set { key_binding_list_t key; key_binding_list_t search; key_binding_list_t url; + key_binding_list_t msg; key_binding_list_t mouse; xkb_mod_mask_t selection_overrides; }; diff --git a/meson.build b/meson.build index 0b7bbc17..6a03a6b8 100644 --- a/meson.build +++ b/meson.build @@ -323,6 +323,7 @@ executable( 'tokenize.c', 'tokenize.h', 'unicode-mode.c', 'unicode-mode.h', 'url-mode.c', 'url-mode.h', + 'message.c', 'message.h', 'user-notification.c', 'user-notification.h', 'wayland.c', 'wayland.h', 'shm-formats.h', 'xkbcommon-vmod.h', diff --git a/message.c b/message.c new file mode 100644 index 00000000..d7702c44 --- /dev/null +++ b/message.c @@ -0,0 +1,185 @@ +#include "message.h" + + +#include +#include +#include +#include + +#include +#include + +#define LOG_MODULE "message" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "char32.h" +#include "key-binding.h" +#include "quirks.h" +#include "render.h" +#include "terminal.h" +#include "util.h" +#include "xmalloc.h" + + +static bool +execute_binding(struct seat *seat, struct terminal *term, + const struct key_binding *binding, uint32_t serial) +{ + const enum bind_action_close_message action = binding->action; + + switch (action) { + case BIND_ACTION_CLOSE_MESSAGE_NONE: + return false; + + case BIND_ACTION_CLOSE_MESSAGE_CANCEL: + msgs_reset(term); + return true; + + case BIND_ACTION_CLOSE_MESSAGE_ACCEPT: + term_shutdown(term); + return true; + + case BIND_ACTION_MESSAGE_COUNT: + return false; + } + return true; +} + +void +msgs_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, + xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, + const xkb_keysym_t *raw_syms, size_t raw_count, + uint32_t serial) +{ + + tll_foreach(bindings->msg, it) { + const struct key_binding *bind = &it->item; + if (bind->mods != mods || bind->mods == 0) + continue; + + for (size_t i = 0; i < raw_count; i++) { + if (bind->k.sym == raw_syms[i]) { + execute_binding(seat, term, bind, serial); + return; + } + } + } + + /* Match translated symbol */ + tll_foreach(bindings->msg, it) { + const struct key_binding *bind = &it->item; + + if (bind->k.sym == sym && + bind->mods == (mods & ~consumed)) + { + execute_binding(seat, term, bind, serial); + return; + } + + } + + /* Match raw key code */ + tll_foreach(bindings->msg, it) { + const struct key_binding *bind = &it->item; + if (bind->mods != mods || bind->mods == 0) + continue; + + /* Match raw key code */ + tll_foreach(bind->k.key_codes, code) { + if (code->item == key) { + execute_binding(seat, term, bind, serial); + return; + } + } + } + +} + +void +close_message(struct terminal *term) +{ + char *strs[] = {"Do you want to close this client?", "Confirm or cancel."}; + for (int i=0; i<2; i++){ + tll_push_back(term->msgs, ((struct msg){ + .id = (uint64_t)rand() << 32 | rand(), + .text = ambstoc32(strs[i]), + })); + } +} + +void +msgs_render(struct terminal *term) +{ + struct wl_window *win = term->window; + + if (tll_length(win->term->msgs) == 0) + return; + + if (term_ime_is_enabled(term)) { + term->ime_reenable_after_msg_mode = true; + term_ime_disable(term); + } + + /* Dirty the last cursor, to ensure it is erased */ + { + struct row *cursor_row = term->render.last_cursor.row; + if (cursor_row != NULL) { + struct cell *cell = &cursor_row->cells[term->render.last_cursor.col]; + cell->attrs.clean = 0; + cursor_row->dirty = true; + } + } + term->render.last_cursor.row = NULL; + + /* Clear scroll damage, to ensure we don't apply it twice (once on + * the snapshot:ed grid, and then later again on the real grid) */ + tll_free(term->grid->scroll_damage); + + term_damage_view(term); + + + xassert(tll_length(win->msgs) == 0); + tll_foreach(win->term->msgs, it) { + struct wl_msg msg = {.msg = &it->item}; + wayl_win_subsurface_new(win, &msg.surf, false); + + tll_push_back(win->msgs, msg); + } + + render_refresh_msgs(term); + render_refresh(term); +} + + +static void +msg_destroy(struct msg *msg) +{ + free(msg->text); +} + +void +msgs_reset(struct terminal *term) +{ + term->render.last_cursor.row = NULL; + + if (term->window != NULL) { + tll_foreach(term->window->msgs, it) { + wayl_win_subsurface_destroy(&it->item.surf); + tll_remove(term->window->msgs, it); + } + } + + tll_foreach(term->msgs, it) { + msg_destroy(&it->item); + tll_remove(term->msgs, it); + } + + /* Re-enable IME, if it was enabled before we entered URL-mode */ + if (term->ime_reenable_after_msg_mode) { + term->ime_reenable_after_msg_mode = false; + term_ime_enable(term); + } + + render_refresh(term); +} diff --git a/message.h b/message.h new file mode 100644 index 00000000..88d4f4fa --- /dev/null +++ b/message.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "config.h" +#include "key-binding.h" +#include "terminal.h" + +static inline bool msgs_mode_is_active(const struct terminal *term) +{ + return tll_length(term->msgs) > 0; +} + +void close_message(struct terminal *term); +void msgs_reset(struct terminal *term); +void msgs_render(struct terminal *term); + +void +msgs_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, + xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, + const xkb_keysym_t *raw_syms, size_t raw_count, + uint32_t serial); diff --git a/render.c b/render.c index 1c24bafa..d5064c84 100644 --- a/render.c +++ b/render.c @@ -12,6 +12,7 @@ #include #include "macros.h" +#include "terminal.h" #if HAS_INCLUDE() #include #define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0) @@ -42,6 +43,7 @@ #include "sixel.h" #include "srgb.h" #include "url-mode.h" +#include "message.h" #include "util.h" #include "xmalloc.h" @@ -1959,6 +1961,7 @@ render_overlay(struct terminal *term) const enum overlay_style style = term->is_searching ? OVERLAY_SEARCH : + msgs_mode_is_active(term) ? OVERLAY_MESSAGE : term->flash.active ? OVERLAY_FLASH : unicode_mode_active ? OVERLAY_UNICODE_MODE : OVERLAY_NONE; @@ -1978,6 +1981,7 @@ render_overlay(struct terminal *term) switch (style) { case OVERLAY_SEARCH: + case OVERLAY_MESSAGE: case OVERLAY_UNICODE_MODE: color = (pixman_color_t){0, 0, 0, 0x7fff}; break; @@ -4175,6 +4179,143 @@ render_urls(struct terminal *term) } } +static void +render_msgs(struct terminal *term) +{ + struct wl_window *win = term->window; + xassert(tll_length(win->msgs) > 0); + + const float scale = term->scale; + const int x_margin = (int)roundf(2 * scale); + const int y_margin = (int)roundf(1 * scale); + + struct { + const struct wl_msg *msg; + char32_t *text; + int x; + int y; + } info[tll_length(win->msgs)]; + + /* For shm_get_many() */ + int widths[tll_length(win->msgs)]; + int heights[tll_length(win->msgs)]; + + int render_count = 0; + int row = term->rows - 1 - tll_length(win->msgs); + const int max_width = term->width - term->margins.left - term->margins.right; + const int max_cols = max_width / term->cell_width; + + tll_foreach(win->msgs, it) { + const struct msg *msg = it->item.msg; + const char32_t *text = msg->text; + + struct wl_surface *surf = it->item.surf.surface.surf; + struct wl_subsurface *sub_surf = it->item.surf.sub; + + if (surf == NULL || sub_surf == NULL) + continue; + + if (row < 0) { + wl_surface_attach(surf, NULL, 0, 0); + wl_surface_commit(surf); + continue; + } + + + const size_t text_len = c32len(text); + char32_t label[text_len + 1]; + label[text_len] = U'\0'; + c32ncpy(label, text, text_len); + + + int col = max_cols - c32swidth(label, c32len(label)) - 1; + if (col < 0) + col = 0; + int x = col * term->cell_width; + int y = row * term->cell_height; + + /* Don't position it outside our window */ + if (x < -term->margins.left) + x = -term->margins.left; + if (y < -term->margins.top) + y = -term->margins.top; + + + int cols = 0; + + for (size_t i = 0; i <= c32len(label); i++) { + int _cols = c32swidth(label, i); + + if (_cols == (size_t)-1) + continue; + + if (_cols >= max_cols) { + if (i > 0) + label[i - 1] = U'…'; + label[i] = U'\0'; + cols = max_cols; + break; + } + cols = _cols; + } + + if (cols == 0) + continue; + + int width = x_margin + cols * term->cell_width + x_margin; + int height = y_margin + term->cell_height + y_margin; + + width = roundf(scale * ceilf(width / scale)); + height = roundf(scale * ceilf(height / scale)); + + info[render_count].msg = &it->item; + info[render_count].text = xc32dup(label); + info[render_count].x = x; + info[render_count].y = y; + + widths[render_count] = width; + heights[render_count] = height; + + row++; + render_count++; + } + + struct buffer_chain *chain = term->render.chains.msg; + struct buffer *bufs[render_count]; + shm_get_many(chain, render_count, widths, heights, bufs, false); + + render_overlay(term); + + uint32_t fg = term->conf->colors.use_custom.msg_style + ? term->conf->colors.msg_style.fg + : term->colors.table[0]; + uint32_t bg = term->conf->colors.use_custom.msg_style + ? term->conf->colors.msg_style.bg + : term->colors.table[3]; + + for (size_t i = 0; i < render_count; i++) { + const struct wayl_sub_surface *sub_surf = &info[i].msg->surf; + + const char32_t *label = info[i].text; + const int x = info[i].x; + const int y = info[i].y; + + xassert(sub_surf->surface.surf != NULL); + xassert(sub_surf->sub != NULL); + + wl_subsurface_set_position( + sub_surf->sub, + roundf((term->margins.left + x) / scale), + roundf((term->margins.top + y) / scale)); + + render_osd( + term, sub_surf, term->fonts[0], bufs[i], label, + fg, 0xffu << 24 | bg, x_margin); + + free(info[i].text); + } +} + static void render_update_title(struct terminal *term) { @@ -4205,11 +4346,13 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da bool csd = term->render.pending.csd; bool search = term->is_searching && term->render.pending.search; bool urls = urls_mode_is_active(term) > 0 && term->render.pending.urls; + bool msgs = msgs_mode_is_active(term) > 0 && term->render.pending.msgs; term->render.pending.grid = false; term->render.pending.csd = false; term->render.pending.search = false; term->render.pending.urls = false; + term->render.pending.msgs = false; struct grid *original_grid = term->grid; if (urls_mode_is_active(term)) { @@ -4229,6 +4372,9 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da if (urls) render_urls(term); + if (msgs) + render_msgs(term); + if ((grid && !term->delayed_render_timer.is_armed) || (csd | search | urls)) grid_render(term); @@ -4812,6 +4958,8 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) term->render.last_cursor.row = NULL; + render_refresh_msgs(term); + damage_view: /* Signal TIOCSWINSZ */ send_dimensions_to_client(term); @@ -5022,8 +5170,9 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) bool csd = term->render.refresh.csd; bool search = term->is_searching && term->render.refresh.search; bool urls = urls_mode_is_active(term) && term->render.refresh.urls; + bool msgs = msgs_mode_is_active(term) && term->render.refresh.msgs; - if (!(grid | csd | search | urls)) + if (!(grid | csd | search | urls | msgs)) continue; if (term->render.app_sync_updates.enabled && !(csd | search | urls)) @@ -5033,6 +5182,7 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) term->render.refresh.csd = false; term->render.refresh.search = false; term->render.refresh.urls = false; + term->render.refresh.msgs = false; if (term->window->frame_callback == NULL) { struct grid *original_grid = term->grid; @@ -5050,7 +5200,9 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) render_search_box(term); if (urls) render_urls(term); - if (grid | csd | search | urls) + if (msgs) + render_msgs(term); + if (grid | csd | search | urls | msgs) grid_render(term); tll_foreach(term->wl->seats, it) { @@ -5065,6 +5217,7 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data) term->render.pending.csd |= csd; term->render.pending.search |= search; term->render.pending.urls |= urls; + term->render.pending.msgs |= msgs; } } @@ -5195,6 +5348,13 @@ render_refresh_urls(struct terminal *term) term->render.refresh.urls = true; } +void +render_refresh_msgs(struct terminal *term) +{ + if (msgs_mode_is_active(term)) + term->render.refresh.msgs = true; +} + bool render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) diff --git a/render.h b/render.h index 81d2a905..a0019664 100644 --- a/render.h +++ b/render.h @@ -27,6 +27,7 @@ void render_refresh_csd(struct terminal *term); void render_refresh_search(struct terminal *term); void render_refresh_title(struct terminal *term); void render_refresh_urls(struct terminal *term); +void render_refresh_msgs(struct terminal *term); bool render_xcursor_set( struct seat *seat, struct terminal *term, enum cursor_shape shape); bool render_xcursor_is_valid(const struct seat *seat, const char *cursor); diff --git a/terminal.c b/terminal.c index 2e23f749..ec8ccff2 100644 --- a/terminal.c +++ b/terminal.c @@ -1361,6 +1361,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth), .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth), .url = shm_chain_new(wayl, false, 1, desired_bit_depth), + .msg = shm_chain_new(wayl, false, 1, desired_bit_depth), .csd = shm_chain_new(wayl, false, 1, desired_bit_depth), .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth), }, @@ -1905,6 +1906,7 @@ term_destroy(struct terminal *term) shm_chain_free(term->render.chains.scrollback_indicator); shm_chain_free(term->render.chains.render_timer); shm_chain_free(term->render.chains.url); + shm_chain_free(term->render.chains.msg); shm_chain_free(term->render.chains.csd); shm_chain_free(term->render.chains.overlay); pixman_region32_fini(&term->render.last_overlay_clip); diff --git a/terminal.h b/terminal.h index 88371b07..c1673e87 100644 --- a/terminal.h +++ b/terminal.h @@ -375,6 +375,7 @@ enum term_surface { enum overlay_style { OVERLAY_NONE, OVERLAY_SEARCH, + OVERLAY_MESSAGE, OVERLAY_FLASH, OVERLAY_UNICODE_MODE, }; @@ -394,6 +395,13 @@ struct url { }; typedef tll(struct url) url_list_t; +enum close_message_action {CLOSE_MESSAGE_ACCEPT}; +struct msg { + uint64_t id; + char32_t *text; +}; +typedef tll(struct msg) msg_list_t; + struct colors { uint32_t fg; @@ -652,6 +660,7 @@ struct terminal { struct buffer_chain *scrollback_indicator; struct buffer_chain *render_timer; struct buffer_chain *url; + struct buffer_chain *msg; struct buffer_chain *csd; struct buffer_chain *overlay; } chains; @@ -662,6 +671,7 @@ struct terminal { bool csd; bool search; bool urls; + bool msgs; } refresh; /* Scheduled for rendering, in the next frame callback */ @@ -670,6 +680,7 @@ struct terminal { bool csd; bool search; bool urls; + bool msgs; } pending; bool margins; /* Someone explicitly requested a refresh of the margins */ @@ -797,6 +808,9 @@ struct terminal { bool ime_reenable_after_url_mode; const struct config_spawn_template *url_launch; + msg_list_t msgs; + bool ime_reenable_after_msg_mode; + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED bool ime_enabled; #endif diff --git a/wayland.c b/wayland.c index 5f68ecf7..468e8cfb 100644 --- a/wayland.c +++ b/wayland.c @@ -34,6 +34,7 @@ #include "shm-formats.h" #include "util.h" #include "xmalloc.h" +#include "message.h" static void csd_reload_font(struct wl_window *win, float old_scale) @@ -958,8 +959,61 @@ xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { struct wl_window *win = data; struct terminal *term = win->term; + LOG_DBG("xdg-toplevel: close"); - term_shutdown(term); + + switch (term->conf->close_policy){ + case CLOSE_ALWAYS: + term_shutdown(term); + case CLOSE_NEVER: + break; + case CLOSE_MESSAGE_ALWAYS: + if (!msgs_mode_is_active(term)){ + close_message(term); + msgs_render(term); + } + break; + case CLOSE_MESSAGE_DEMAND: ; + /* ifs here check whether it's safe to close */ + pid_t fgpid = tcgetpgrp(term->ptmx); + + + /* No foreground process ( --hold mode) */ + if (fgpid < 0){ + term_shutdown(term); + break; + } + + + /* foreground process is the slave process which typcally is + * of some shell kind. Therefore shell integration helps to + * ensure that it's really a shell. + */ + + /* Shells or meant slave process should communicate with + * terminal whether they are ok to be therminated or not? + * for shells, checking if they have child processes or not + * should be enough. This probably can be done through + * shell integration, informing their jobs status. + */ + + // TODO multi line prompt_marker ??? + bool at_prompt = term->grid->cur_row->shell_integration.prompt_marker; + + if (fgpid == term->slave && at_prompt){ + // TODO make sure there's no background jobs + term_shutdown(term); + break; + } + + + /* by here, a close dialog should be necessary */ + if (!msgs_mode_is_active(term)) { + close_message(term); + msgs_render(term); + } + break; + } } #if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) @@ -2119,6 +2173,11 @@ wayl_win_destroy(struct wl_window *win) wl_surface_commit(it->item.surf.surface.surf); } + tll_foreach(win->msgs, it) { + wl_surface_attach(it->item.surf.surface.surf, NULL, 0, 0); + wl_surface_commit(it->item.surf.surface.surf); + } + /* CSD */ for (size_t i = 0; i < ALEN(win->csd.surface); i++) { if (win->csd.surface[i].surface.surf != NULL) { @@ -2142,6 +2201,11 @@ wayl_win_destroy(struct wl_window *win) tll_remove(win->urls, it); } + tll_foreach(win->msgs, it) { + wayl_win_subsurface_destroy(&it->item.surf); + tll_remove(win->msgs, it); + } + csd_destroy(win); wayl_win_subsurface_destroy(&win->search); wayl_win_subsurface_destroy(&win->scrollback_indicator); @@ -2153,6 +2217,7 @@ wayl_win_destroy(struct wl_window *win) shm_purge(term->render.chains.render_timer); shm_purge(term->render.chains.grid); shm_purge(term->render.chains.url); + shm_purge(term->render.chains.msg); shm_purge(term->render.chains.csd); tll_foreach(win->xdg_tokens, it) { diff --git a/wayland.h b/wayland.h index eb1c35a3..60056172 100644 --- a/wayland.h +++ b/wayland.h @@ -337,6 +337,11 @@ struct wl_url { struct wayl_sub_surface surf; }; +struct wl_msg { + const struct msg *msg; + struct wayl_sub_surface surf; +}; + enum csd_mode {CSD_UNKNOWN, CSD_NO, CSD_YES}; typedef void (*activation_token_cb_t)(const char *token, void *data); @@ -393,6 +398,7 @@ struct wl_window { tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */ tll(struct wl_url) urls; + tll(struct wl_msg) msgs; bool is_configured; bool is_fullscreen;