From 84aaa7968c2adf2e4176a6673a6b6010ab7d0a3f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 19 Jun 2024 16:41:06 -0700 Subject: [PATCH] Backport changes from Fedora 40 Resolves: RHEL-42425 Resolves: RHEL-41136 --- .gitignore | 1 + 0001-a11y-implement-GtkAccessibleText.patch | 1324 ++++++++ ...otification-and-shell-precmd-preexec.patch | 533 ++++ sources | 2 +- vte291-cntnr-precmd-preexec-scroll.patch | 2727 ----------------- vte291.spec | 11 +- 6 files changed, 1868 insertions(+), 2730 deletions(-) create mode 100644 0001-a11y-implement-GtkAccessibleText.patch create mode 100644 0001-add-notification-and-shell-precmd-preexec.patch delete mode 100644 vte291-cntnr-precmd-preexec-scroll.patch diff --git a/.gitignore b/.gitignore index 1bf0ace..8cfe832 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,4 @@ /vte-0.74.1.tar.xz /vte-0.74.2.tar.xz /vte-0.76.2.tar.xz +/vte-0.76.3.tar.xz diff --git a/0001-a11y-implement-GtkAccessibleText.patch b/0001-a11y-implement-GtkAccessibleText.patch new file mode 100644 index 0000000..4361311 --- /dev/null +++ b/0001-a11y-implement-GtkAccessibleText.patch @@ -0,0 +1,1324 @@ +From aa096f3c96d8f4a736be7fdb48103daffd332296 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Mon, 4 Mar 2024 14:09:53 -0800 +Subject: [PATCH] a11y: implement GtkAccessibleText + +This is an initial implementaiton of GtkAccessibleText which was added +to GTK for 4.14. It attempts to implement things in a very similar +fashion to the previous code for GTK 3 although considerable effort was +made to simplify and improve readability as to how it works. + +Currently, this supports reading back what you type and what has changed +on screen. It is not yet 1:1 what the GTK 3 a11y implementation did +because ATK was doing many other things (including proxying keyboard +keys) to the other side of the a11y bus. That appears to improve +readback by screen readers in the form of "backspace" and what character +was deleted. + +I expect things to get closer to 1:1 but that work is going to have to +be done inside of GTK itself first and should not require much if +anything here. + +A new VteTerminal:enable-a11y feature flag property has been added +because I'm concerned about enabling this by default until the a11y bus +learns to be more lazy. Currently there is no way to "do nothing" until +a peer (e.g. screenreader) is interested in the contents. + +Ideally, we would have a short-circuit like is currently implemented +by checking vte_terminal_get_enable_a11y() to avoid any sort of +contents calculation when there are no a11y observers. + +It also allows disabling the GTK 3 a11y implementation just to keep +some symmetry between the APIs. + +Currently, this does not implement "text-scrolled" like the GTK 3 +implementation does as I'm not sure yet if there is a benefit. +--- + src/meson.build | 13 +- + src/vte.cc | 11 + + src/vte/vteterminal.h | 7 + + src/vteaccess-gtk4.cc | 874 ++++++++++++++++++++++++++++++++++++++++++ + src/vteaccess-gtk4.h | 25 ++ + src/vteaccess.cc | 21 + + src/vtegtk.cc | 97 ++++- + src/vtegtk.hh | 1 + + src/vteinternal.hh | 8 + + 10 files changed, 1053 insertions(+), 10 deletions(-) + create mode 100644 src/vteaccess-gtk4.cc + create mode 100644 src/vteaccess-gtk4.h + +diff --git a/src/meson.build b/src/meson.build +index 3f89f492..6d1b4b2b 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -18,11 +18,16 @@ subdir('vte') + + src_inc = include_directories('.') + +-a11y_sources = files( ++a11y_gtk3_sources = files( + 'vteaccess.cc', + 'vteaccess.h', + ) + ++a11y_gtk4_sources = files( ++ 'vteaccess-gtk4.cc', ++ 'vteaccess-gtk4.h', ++) ++ + debug_sources = files( + 'debug.cc', + 'debug.h', +@@ -300,7 +305,7 @@ if get_option('gtk3') + libvte_gtk3_public_deps = libvte_common_public_deps + [gtk3_dep,] + + if get_option('a11y') +- libvte_gtk3_sources += a11y_sources ++ libvte_gtk3_sources += a11y_gtk3_sources + endif + + libvte_gtk3 = shared_library( +@@ -349,6 +354,10 @@ if get_option('gtk4') + libvte_gtk4_deps = libvte_common_deps + [gtk4_dep,] + libvte_gtk4_public_deps = libvte_common_public_deps + [gtk4_dep,] + ++ if get_option('a11y') ++ libvte_gtk4_sources += a11y_gtk4_sources ++ endif ++ + libvte_gtk4 = shared_library( + vte_gtk4_api_name, + sources: libvte_gtk4_sources, +diff --git a/src/vte.cc b/src/vte.cc +index a8a0e22c..4d9bf411 100644 +--- a/src/vte.cc ++++ b/src/vte.cc +@@ -10132,6 +10132,17 @@ Terminal::set_text_blink_mode(TextBlinkMode setting) + return true; + } + ++bool ++Terminal::set_enable_a11y(bool setting) ++{ ++ if (setting == m_enable_a11y) ++ return false; ++ ++ m_enable_a11y = setting; ++ ++ return true; ++} ++ + bool + Terminal::set_enable_bidi(bool setting) + { +diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h +index 9c2e2dae..7f4bf6df 100644 +--- a/src/vte/vteterminal.h ++++ b/src/vte/vteterminal.h +@@ -388,6 +388,13 @@ _VTE_PUBLIC + void vte_terminal_set_delete_binding(VteTerminal *terminal, + VteEraseBinding binding) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); + ++/* Accessibility */ ++_VTE_PUBLIC ++void vte_terminal_set_enable_a11y(VteTerminal *terminal, ++ gboolean enable_a11y) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); ++_VTE_PUBLIC ++gboolean vte_terminal_get_enable_a11y(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); ++ + /* BiDi and shaping */ + _VTE_PUBLIC + void vte_terminal_set_enable_bidi(VteTerminal *terminal, +diff --git a/src/vteaccess-gtk4.cc b/src/vteaccess-gtk4.cc +new file mode 100644 +index 00000000..f42e7578 +--- /dev/null ++++ b/src/vteaccess-gtk4.cc +@@ -0,0 +1,874 @@ ++/* ++ * Copyright © 2024 Christian Hergert ++ * Copyright © 2002,2003 Red Hat, Inc. ++ * ++ * This library is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as published ++ * by the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this library. If not, see . ++ */ ++ ++#include "config.h" ++ ++#include "vteinternal.hh" ++#include "vteaccess-gtk4.h" ++ ++#define GDK_ARRAY_NAME char_positions ++#define GDK_ARRAY_TYPE_NAME CharPositions ++#define GDK_ARRAY_ELEMENT_TYPE int ++#define GDK_ARRAY_BY_VALUE 1 ++#define GDK_ARRAY_PREALLOC 8 ++#define GDK_ARRAY_NO_MEMSET ++#include "gdkarrayimpl.c" ++ ++typedef struct _VteAccessibleTextContents ++{ ++ /* A GdkArrayImpl of attributes per byte */ ++ VteCharAttrList attrs; ++ ++ /* The byte position within the UTF-8 string where each visible ++ * character starts. ++ */ ++ CharPositions characters; ++ ++ /* The character position within the UTF-8 string where each line ++ * break occurs. To get byte offset, use @characters. ++ */ ++ CharPositions linebreaks; ++ ++ /* The UTF-8 string encoded as bytes so that we may reference it ++ * using GBytes for "substrings". However @string does include a ++ * trailing NUL byte in it's size so that the a11y infrastructure ++ * may elide some string copies. ++ */ ++ GBytes *string; ++ ++ /* Number of bytes in @string excluding trailing NUL. */ ++ gsize n_bytes; ++ ++ /* Number of unicode characters in @string. */ ++ gsize n_chars; ++ ++ /* The character position (not bytes) of the caret in @string */ ++ gsize caret; ++ ++ /* Cached column/row of the caret updated each time we are notified ++ * of the caret having moved. We cache this so that we can elide ++ * extraneous notifications after snapshoting. We will update the ++ * carent position synchronously when notified so @caret may always ++ * be relied upon as correct. ++ */ ++ long cached_caret_column; ++ long cached_caret_row; ++} VteAccessibleTextContents; ++ ++typedef struct _VteAccessibleText ++{ ++ VteTerminal *terminal; ++ VteAccessibleTextContents contents[2]; ++ guint contents_flip : 1; ++} VteAccessibleText; ++ ++static inline gboolean ++_pango_color_equal(const PangoColor *a, ++ const PangoColor *b) ++{ ++ return a->red == b->red && ++ a->green == b->green && ++ a->blue == b->blue; ++} ++ ++static void ++vte_accessible_text_contents_init (VteAccessibleTextContents *contents) ++{ ++ vte_char_attr_list_init (&contents->attrs); ++ char_positions_init (&contents->characters); ++ char_positions_init (&contents->linebreaks); ++ contents->string = nullptr; ++ contents->n_bytes = 0; ++ contents->n_chars = 0; ++ contents->caret = 0; ++ contents->cached_caret_row = 0; ++ contents->cached_caret_column = 0; ++} ++ ++static void ++vte_accessible_text_contents_clear (VteAccessibleTextContents *contents) ++{ ++ vte_char_attr_list_clear (&contents->attrs); ++ char_positions_clear (&contents->characters); ++ char_positions_clear (&contents->linebreaks); ++ g_clear_pointer (&contents->string, g_bytes_unref); ++ contents->n_bytes = 0; ++ contents->n_chars = 0; ++ contents->caret = 0; ++ contents->cached_caret_row = 0; ++ contents->cached_caret_column = 0; ++} ++ ++static void ++vte_accessible_text_contents_reset (VteAccessibleTextContents *contents) ++{ ++ vte_char_attr_list_set_size (&contents->attrs, 0); ++ char_positions_set_size (&contents->characters, 0); ++ char_positions_set_size (&contents->linebreaks, 0); ++ g_clear_pointer (&contents->string, g_bytes_unref); ++ contents->n_bytes = 0; ++ contents->n_chars = 0; ++ contents->caret = 0; ++ contents->cached_caret_row = 0; ++ contents->cached_caret_column = 0; ++} ++ ++static const char * ++vte_accessible_text_contents_get_string (VteAccessibleTextContents *contents, ++ gsize *len) ++{ ++ const char *ret; ++ ++ if (contents->string == nullptr || g_bytes_get_size (contents->string) == 0) { ++ *len = 0; ++ return ""; ++ } ++ ++ ret = (const char *)g_bytes_get_data (contents->string, len); ++ ++ if (*len > 0) { ++ (*len)--; ++ } ++ ++ return ret; ++} ++ ++static int ++vte_accessible_text_contents_offset_from_xy (VteAccessibleTextContents *contents, ++ int x, ++ int y) ++{ ++ int offset; ++ int linebreak; ++ int next_linebreak; ++ ++ if (y >= int(char_positions_get_size (&contents->linebreaks))) { ++ y = int(char_positions_get_size (&contents->linebreaks)) - 1; ++ if (y < 0) { ++ return 0; ++ } ++ } ++ ++ linebreak = *char_positions_index (&contents->linebreaks, y); ++ if (y + 1 == int(char_positions_get_size (&contents->linebreaks))) { ++ next_linebreak = int(char_positions_get_size (&contents->characters)); ++ } else { ++ next_linebreak = *char_positions_index (&contents->linebreaks, y + 1); ++ } ++ ++ offset = linebreak + x; ++ if (offset >= next_linebreak) { ++ offset = next_linebreak - 1; ++ } ++ ++ return offset; ++} ++ ++static gunichar ++vte_accessible_text_contents_get_char_at (VteAccessibleTextContents *contents, ++ guint offset) ++{ ++ const char *str; ++ ++ if (contents->string == nullptr) ++ return 0; ++ ++ if (offset >= contents->n_chars) ++ return 0; ++ ++ g_assert (offset < char_positions_get_size (&contents->characters)); ++ ++ str = (const char *)g_bytes_get_data (contents->string, nullptr); ++ str += *char_positions_index (&contents->characters, offset); ++ ++ return g_utf8_get_char (str); ++ ++} ++ ++static GBytes * ++_g_string_free_to_bytes_with_nul (GString *str) ++{ ++ /* g_string_free_to_bytes() will have a trailing-NUL but not include it ++ * in the size of the GBytes. We want the size included in our GBytes ++ * so that GtkAccessibleText may avoid some copies. ++ */ ++ gsize len = str->len + 1; ++ return g_bytes_new_take (g_string_free (str, FALSE), len); ++} ++ ++static inline gsize ++vte_accessible_text_contents_find_caret (VteAccessibleTextContents *contents, ++ long ccol, ++ long crow) ++{ ++ g_assert (contents != nullptr); ++ ++ /* Get the offsets to the beginnings of each line. */ ++ gsize caret = 0; ++ for (gsize i = 0; i < char_positions_get_size (&contents->characters); i++) { ++ /* Get the attributes for the current cell. */ ++ int offset = *char_positions_index (&contents->characters, i); ++ const struct _VteCharAttributes *attrs = vte_char_attr_list_get (&contents->attrs, offset); ++ ++ /* If this cell is "before" the cursor, move the caret to be "here". */ ++ if ((attrs->row < crow) || ++ ((attrs->row == crow) && (attrs->column < ccol))) { ++ caret = i + 1; ++ } ++ } ++ ++ return caret; ++} ++ ++static void ++vte_accessible_text_contents_snapshot (VteAccessibleTextContents *contents, ++ VteTerminal *terminal) ++{ ++ auto impl = _vte_terminal_get_impl (terminal); ++ GString *gstr = g_string_new (nullptr); ++ ++ try { ++ impl->get_text_displayed_a11y (gstr, &contents->attrs); ++ } catch (...) { ++ g_string_truncate (gstr, 0); ++ } ++ ++ if (vte_char_attr_list_get_size (&contents->attrs) >= G_MAXINT) { ++ g_string_truncate (gstr, 0); ++ return; ++ } ++ ++ /* Get the offsets to the beginnings of each character. */ ++ int i = 0; ++ const char *next = gstr->str; ++ int n_attrs = int(vte_char_attr_list_get_size (&contents->attrs)); ++ while (i < n_attrs) { ++ char_positions_append (&contents->characters, &i); ++ next = g_utf8_next_char (next); ++ if (next != nullptr) { ++ i = next - gstr->str; ++ continue; ++ } ++ break; ++ } ++ ++ /* Find offsets for the beginning of lines. */ ++ gsize n_chars = char_positions_get_size (&contents->characters); ++ int row; ++ for (i = 0, row = 0; i < int(n_chars); i++) { ++ /* Get the attributes for the current cell. */ ++ int offset = *char_positions_index (&contents->characters, i); ++ const struct _VteCharAttributes *attrs = vte_char_attr_list_get (&contents->attrs, offset); ++ ++ /* If this character is on a row different from the row ++ * the character we looked at previously was on, then ++ * it's a new line and we need to keep track of where ++ * it is. */ ++ if ((i == 0) || (attrs->row != row)) { ++ _vte_debug_print (VTE_DEBUG_ALLY, ++ "Row %d/%ld begins at %d.\n", ++ int(char_positions_get_size (&contents->linebreaks)), ++ attrs->row, i); ++ char_positions_append (&contents->linebreaks, &i); ++ } ++ ++ row = attrs->row; ++ } ++ ++ /* Add the final line break. */ ++ char_positions_append (&contents->linebreaks, &i); ++ ++ /* Update the caret position. */ ++ long ccol, crow; ++ vte_terminal_get_cursor_position (terminal, &ccol, &crow); ++ _vte_debug_print (VTE_DEBUG_ALLY, "Cursor at (%ld, " "%ld).\n", ccol, crow); ++ gsize caret = vte_accessible_text_contents_find_caret (contents, ccol, crow); ++ ++ contents->n_bytes = gstr->len; ++ contents->n_chars = n_chars; ++ contents->string = _g_string_free_to_bytes_with_nul (gstr); ++ contents->caret = caret; ++ contents->cached_caret_column = ccol; ++ contents->cached_caret_row = crow; ++ ++ _vte_debug_print (VTE_DEBUG_ALLY, ++ "Refreshed accessibility snapshot, " ++ "%ld cells, %ld characters.\n", ++ long(vte_char_attr_list_get_size(&contents->attrs)), ++ long(char_positions_get_size (&contents->characters))); ++} ++ ++static GBytes * ++vte_accessible_text_contents_slice (VteAccessibleTextContents *contents, ++ guint start, ++ guint end) ++{ ++ static const char empty[] = {0}; ++ guint start_offset; ++ guint end_offset; ++ ++ g_assert (contents != nullptr); ++ ++ if (contents->string == nullptr) ++ return g_bytes_new_static (empty, sizeof empty); ++ ++ if (start > contents->n_chars) ++ start = contents->n_chars; ++ ++ if (end > contents->n_chars) ++ end = contents->n_chars; ++ ++ if (end < start) ++ std::swap (end, start); ++ ++ g_assert (start <= char_positions_get_size (&contents->characters)); ++ g_assert (end <= char_positions_get_size (&contents->characters)); ++ ++ if (start == char_positions_get_size (&contents->characters)) ++ start_offset = g_bytes_get_size (contents->string); ++ else ++ start_offset = *char_positions_index (&contents->characters, start); ++ ++ if (end == char_positions_get_size (&contents->characters)) ++ end_offset = g_bytes_get_size (contents->string); ++ else ++ end_offset = *char_positions_index (&contents->characters, end); ++ ++ g_assert (start_offset <= end_offset); ++ ++ if (start_offset == end_offset) ++ return g_bytes_new_static (empty, sizeof empty); ++ ++ return g_bytes_new_from_bytes (contents->string, start_offset, end_offset - start_offset); ++} ++ ++static void ++vte_accessible_text_free (VteAccessibleText *state) ++{ ++ vte_accessible_text_contents_clear (&state->contents[0]); ++ vte_accessible_text_contents_clear (&state->contents[1]); ++ state->terminal = nullptr; ++ g_free (state); ++} ++ ++static VteAccessibleText * ++vte_accessible_text_get (VteTerminal *terminal) ++{ ++ return (VteAccessibleText *)g_object_get_data (G_OBJECT (terminal), "VTE_ACCESSIBLE_TEXT"); ++} ++ ++static GBytes * ++vte_accessible_text_get_contents (GtkAccessibleText *accessible, ++ guint start, ++ guint end) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state = vte_accessible_text_get (terminal); ++ VteAccessibleTextContents *contents = nullptr; ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ contents = &state->contents[state->contents_flip]; ++ ++ return vte_accessible_text_contents_slice (contents, start, end); ++} ++ ++static GBytes * ++vte_accessible_text_get_contents_at (GtkAccessibleText *accessible, ++ guint offset, ++ GtkAccessibleTextGranularity granularity, ++ guint *start, ++ guint *end) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state = vte_accessible_text_get (terminal); ++ VteAccessibleTextContents *contents; ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ auto impl = _vte_terminal_get_impl (terminal); ++ ++ contents = &state->contents[state->contents_flip]; ++ ++ if (contents->string == nullptr) { ++ return nullptr; ++ } ++ ++ if (offset > contents->n_chars) { ++ offset = contents->n_chars; ++ } ++ ++ switch (granularity) { ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_CHARACTER: { ++ *start = offset; ++ *end = offset + 1; ++ return vte_accessible_text_contents_slice (contents, offset, offset + 1); ++ } ++ ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_LINE: { ++ guint char_offset = *char_positions_index (&contents->characters, offset); ++ guint line; ++ ++ for (line = 0; ++ line < char_positions_get_size (&contents->linebreaks); ++ line++) { ++ guint line_offset = *char_positions_index (&contents->linebreaks, line); ++ ++ if (line_offset > char_offset) { ++ line--; ++ break; ++ } ++ } ++ ++ _vte_debug_print (VTE_DEBUG_ALLY, ++ "Character %u is on line %u.\n", ++ offset, line); ++ ++ *start = *char_positions_index (&contents->linebreaks, line); ++ if (line + 1 < char_positions_get_size (&contents->linebreaks)) ++ *end = *char_positions_index (&contents->linebreaks, line + 1); ++ else ++ *end = contents->n_chars; ++ ++ return vte_accessible_text_contents_slice (contents, *start, *end); ++ } ++ ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_WORD: { ++ gunichar ch = vte_accessible_text_contents_get_char_at (contents, offset); ++ ++ if (ch == 0 || !impl->is_word_char (ch)) ++ break; ++ ++ *start = offset; ++ *end = offset; ++ ++ while (*start > 0 && ++ (ch = vte_accessible_text_contents_get_char_at (contents, *start - 1)) && ++ impl->is_word_char (ch)) { ++ (*start)--; ++ } ++ ++ while (*end < contents->n_chars && ++ (ch = vte_accessible_text_contents_get_char_at (contents, *end + 1)) && ++ impl->is_word_char (ch)) { ++ (*end)++; ++ } ++ ++ return vte_accessible_text_contents_slice (contents, *start, *end); ++ } ++ ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_SENTENCE: ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_PARAGRAPH: ++ default: ++ break; ++ } ++ ++ return nullptr; ++} ++ ++static guint ++vte_accessible_text_get_caret_position (GtkAccessibleText *accessible) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state = vte_accessible_text_get (terminal); ++ ++ g_assert (VTE_IS_TERMINAL (accessible)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ return state->contents[state->contents_flip].caret; ++} ++ ++static gboolean ++vte_accessible_text_get_selection (GtkAccessibleText *accessible, ++ gsize *n_ranges, ++ GtkAccessibleTextRange **ranges) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state = vte_accessible_text_get (terminal); ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (ranges != nullptr); ++ ++ *n_ranges = 0; ++ *ranges = nullptr; ++ ++ try { ++ auto impl = _vte_terminal_get_impl (terminal); ++ VteAccessibleTextContents *contents = &state->contents[state->contents_flip]; ++ GtkAccessibleTextRange range; ++ ++ if (impl->m_selection_resolved.empty() || ++ impl->m_selection[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] == nullptr) ++ return FALSE; ++ ++ auto start_column = impl->m_selection_resolved.start_column(); ++ auto start_row = impl->m_selection_resolved.start_row(); ++ auto end_column = impl->m_selection_resolved.end_column(); ++ auto end_row = impl->m_selection_resolved.end_row(); ++ ++ auto start_offset = vte_accessible_text_contents_offset_from_xy (contents, start_column, start_row); ++ auto end_offset = vte_accessible_text_contents_offset_from_xy (contents, end_column, end_row); ++ ++ range.start = gsize(start_offset); ++ range.length = gsize(end_offset - start_offset); ++ ++ *n_ranges = 1; ++ *ranges = (GtkAccessibleTextRange *)g_memdup2 (&range, sizeof range); ++ ++ return TRUE; ++ } catch (...) { } ++ ++ return FALSE; ++} ++ ++static gboolean ++vte_accessible_text_get_attributes (GtkAccessibleText *accessible, ++ guint offset, ++ gsize *n_ranges, ++ GtkAccessibleTextRange **ranges, ++ char ***attribute_names, ++ char ***attribute_values) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state = vte_accessible_text_get (terminal); ++ VteAccessibleTextContents *contents; ++ struct _VteCharAttributes cur_attr; ++ struct _VteCharAttributes attr; ++ GtkAccessibleTextRange range; ++ struct { ++ const char *name; ++ const char *value; ++ } attrs[4]; ++ char fg_color[16]; ++ char bg_color[16]; ++ guint n_attrs = 0; ++ guint start = 0; ++ guint end = 0; ++ guint i; ++ ++ g_assert (VTE_IS_TERMINAL (accessible)); ++ g_assert (ranges != nullptr); ++ g_assert (attribute_names != nullptr); ++ g_assert (attribute_values != nullptr); ++ ++ contents = &state->contents[state->contents_flip]; ++ ++ *n_ranges = 0; ++ *ranges = nullptr; ++ *attribute_names = nullptr; ++ *attribute_values = nullptr; ++ ++ attr = *vte_char_attr_list_get (&contents->attrs, offset); ++ start = 0; ++ for (i = offset; i--;) { ++ cur_attr = *vte_char_attr_list_get (&contents->attrs, i); ++ if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || ++ !_pango_color_equal (&cur_attr.back, &attr.back) || ++ cur_attr.underline != attr.underline || ++ cur_attr.strikethrough != attr.strikethrough) { ++ start = i + 1; ++ break; ++ } ++ } ++ end = vte_char_attr_list_get_size (&contents->attrs) - 1; ++ for (i = offset + 1; i < vte_char_attr_list_get_size (&contents->attrs); i++) { ++ cur_attr = *vte_char_attr_list_get (&contents->attrs, i); ++ if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || ++ !_pango_color_equal (&cur_attr.back, &attr.back) || ++ cur_attr.underline != attr.underline || ++ cur_attr.strikethrough != attr.strikethrough) { ++ end = i - 1; ++ break; ++ } ++ } ++ ++ range.start = start; ++ range.length = end - start; ++ ++ if (range.length == 0) ++ return FALSE; ++ ++ if (attr.underline) { ++ attrs[n_attrs].name = "underline"; ++ attrs[n_attrs].value = "true"; ++ n_attrs++; ++ } ++ ++ if (attr.strikethrough) { ++ attrs[n_attrs].name = "strikethrough"; ++ attrs[n_attrs].value = "true"; ++ n_attrs++; ++ } ++ ++ g_snprintf (fg_color, sizeof fg_color, "%u,%u,%u", ++ attr.fore.red, attr.fore.green, attr.fore.blue); ++ attrs[n_attrs].name = "fg-color"; ++ attrs[n_attrs].value = fg_color; ++ n_attrs++; ++ ++ g_snprintf (bg_color, sizeof bg_color, "%u,%u,%u", ++ attr.back.red, attr.back.green, attr.back.blue); ++ attrs[n_attrs].name = "bg-color"; ++ attrs[n_attrs].value = bg_color; ++ n_attrs++; ++ ++ *attribute_names = g_new0 (char *, n_attrs + 1); ++ *attribute_values = g_new0 (char *, n_attrs + 1); ++ *n_ranges = 1; ++ *ranges = (GtkAccessibleTextRange *)g_memdup2 (&range, sizeof range); ++ ++ for (i = 0; i < n_attrs; i++) { ++ (*attribute_names)[i] = g_strdup (attrs[i].name); ++ (*attribute_values)[i] = g_strdup (attrs[i].value); ++ } ++ ++ return TRUE; ++} ++ ++void ++_vte_accessible_text_iface_init (GtkAccessibleTextInterface *iface) ++{ ++ iface->get_attributes = vte_accessible_text_get_attributes; ++ iface->get_caret_position = vte_accessible_text_get_caret_position; ++ iface->get_contents = vte_accessible_text_get_contents; ++ iface->get_contents_at = vte_accessible_text_get_contents_at; ++ iface->get_selection = vte_accessible_text_get_selection; ++} ++ ++static void ++vte_accessible_text_contents_changed (VteTerminal *terminal, ++ VteAccessibleText *state) ++{ ++ VteAccessibleTextContents *next = nullptr; ++ VteAccessibleTextContents *prev = nullptr; ++ const char *nextstr; ++ const char *prevstr; ++ gsize prevlen; ++ gsize nextlen; ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ ++ prev = &state->contents[state->contents_flip]; ++ next = &state->contents[!state->contents_flip]; ++ ++ /* Get a new snapshot of contents so that we can compare this to the ++ * previous contents. That way we can discover if it was a backspace ++ * that occurred or if it's more than that. ++ * ++ * We do not filp state->contents_flip immediately so that we can ++ * allow the AT context the ability to access the current contents ++ * on DELETE operations. ++ */ ++ vte_accessible_text_contents_reset (next); ++ vte_accessible_text_contents_snapshot (next, state->terminal); ++ ++ nextstr = vte_accessible_text_contents_get_string (next, &nextlen); ++ prevstr = vte_accessible_text_contents_get_string (prev, &prevlen); ++ ++ vte_assert_cmpint (char_positions_get_size (&prev->characters), ==, prev->n_chars); ++ vte_assert_cmpint (char_positions_get_size (&next->characters), ==, next->n_chars); ++ ++ /* NOTE: ++ * ++ * The code below is based upon what vteaccess.cc did for GTK 3. ++ * It does not do any sort of appropriate diffing to try to handle ++ * scrolling correctly. That would be a good idea to implement in ++ * the longer term. ++ * ++ * It just looks for a long prefix match, and then a long suffix ++ * match and attempts to diff what is between those to end points. ++ */ ++ ++ const char *prevc = prevstr; ++ const char *nextc = nextstr; ++ gsize offset = 0; ++ ++ /* Find the beginning of changes */ ++ while ((offset < prev->n_chars) && (offset < next->n_chars)) { ++ gunichar prevch = g_utf8_get_char (prevc); ++ gunichar nextch = g_utf8_get_char (nextc); ++ ++ if (prevch != nextch) { ++ break; ++ } ++ ++ offset++; ++ ++ prevc = g_utf8_next_char (prevc); ++ nextc = g_utf8_next_char (nextc); ++ } ++ ++ /* Find the end of changes */ ++ gsize next_end = next->n_chars; ++ gsize prev_end = prev->n_chars; ++ ++ prevc = prevstr + prevlen; ++ nextc = nextstr + nextlen; ++ ++ while ((next_end > offset) && (prev_end > offset)) { ++ prevc = g_utf8_prev_char (prevc); ++ nextc = g_utf8_prev_char (nextc); ++ ++ gunichar prevch = g_utf8_get_char (prevc); ++ gunichar nextch = g_utf8_get_char (nextc); ++ ++ if (prevch != nextch) { ++ break; ++ } ++ ++ next_end--; ++ prev_end--; ++ } ++ ++ if (offset < prev_end) { ++ gtk_accessible_text_update_contents (GTK_ACCESSIBLE_TEXT (terminal), ++ GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_REMOVE, ++ offset, prev_end); ++ } ++ ++ state->contents_flip = !state->contents_flip; ++ ++ if (offset < next_end) { ++ gtk_accessible_text_update_contents (GTK_ACCESSIBLE_TEXT (terminal), ++ GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_INSERT, ++ offset, next_end); ++ } ++ ++ if (prev->caret != next->caret) { ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); ++ } ++} ++ ++static void ++vte_accessible_text_cursor_moved (VteTerminal *terminal, ++ VteAccessibleText *state) ++{ ++ VteAccessibleTextContents *contents = nullptr; ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ ++ contents = &state->contents[state->contents_flip]; ++ ++ long ccol, crow; ++ vte_terminal_get_cursor_position (terminal, &ccol, &crow); ++ if (ccol == contents->cached_caret_column && crow == contents->cached_caret_row) { ++ return; ++ } ++ ++ _vte_debug_print (VTE_DEBUG_ALLY, "Cursor at (%ld, " "%ld).\n", ccol, crow); ++ ++ contents->cached_caret_column = ccol; ++ contents->cached_caret_row = crow; ++ contents->caret = vte_accessible_text_contents_find_caret (contents, ccol, crow); ++ ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); ++} ++ ++static void ++vte_accessible_text_window_title_changed (VteTerminal *terminal, ++ VteAccessibleText *state) ++{ ++ const char *window_title; ++ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ ++ window_title = vte_terminal_get_window_title (terminal); ++ ++ gtk_accessible_update_property (GTK_ACCESSIBLE (terminal), ++ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, window_title ? window_title : "", ++ GTK_ACCESSIBLE_VALUE_UNDEFINED); ++} ++ ++static void ++vte_accessible_text_selection_changed (VteTerminal *terminal, ++ VteAccessibleText *state) ++{ ++ g_assert (VTE_IS_TERMINAL (terminal)); ++ g_assert (state != nullptr); ++ g_assert (state->terminal == terminal); ++ ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); ++ gtk_accessible_text_update_selection_bound (GTK_ACCESSIBLE_TEXT (terminal)); ++} ++ ++void ++_vte_accessible_text_init (GtkAccessibleText *accessible) ++{ ++ VteTerminal *terminal = VTE_TERMINAL (accessible); ++ VteAccessibleText *state; ++ ++ state = g_new0 (VteAccessibleText, 1); ++ state->terminal = terminal; ++ ++ vte_accessible_text_contents_init (&state->contents[0]); ++ vte_accessible_text_contents_init (&state->contents[1]); ++ ++ g_object_set_data_full (G_OBJECT (terminal), ++ "VTE_ACCESSIBLE_TEXT", ++ state, ++ (GDestroyNotify)vte_accessible_text_free); ++ ++ g_signal_connect (terminal, ++ "contents-changed", ++ G_CALLBACK (vte_accessible_text_contents_changed), ++ state); ++ g_signal_connect (terminal, ++ "cursor-moved", ++ G_CALLBACK (vte_accessible_text_cursor_moved), ++ state); ++ g_signal_connect (terminal, ++ "selection-changed", ++ G_CALLBACK (vte_accessible_text_selection_changed), ++ state); ++ g_signal_connect (terminal, ++ "window-title-changed", ++ G_CALLBACK (vte_accessible_text_window_title_changed), ++ state); ++ ++ const char *window_title = vte_terminal_get_window_title (terminal); ++ ++ gtk_accessible_update_property (GTK_ACCESSIBLE (accessible), ++ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, window_title ? window_title : "", ++ GTK_ACCESSIBLE_PROPERTY_HAS_POPUP, TRUE, ++ GTK_ACCESSIBLE_PROPERTY_LABEL, "Terminal", ++ GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, TRUE, ++ GTK_ACCESSIBLE_VALUE_UNDEFINED); ++} +diff --git a/src/vteaccess-gtk4.h b/src/vteaccess-gtk4.h +new file mode 100644 +index 00000000..37b09c7b +--- /dev/null ++++ b/src/vteaccess-gtk4.h +@@ -0,0 +1,25 @@ ++/* ++ * Copyright © 2024 Christian Hergert ++ * ++ * This library is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as published ++ * by the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this library. If not, see . ++ */ ++ ++#include ++ ++G_BEGIN_DECLS ++ ++void _vte_accessible_text_iface_init (GtkAccessibleTextInterface *iface); ++void _vte_accessible_text_init (GtkAccessibleText *accessible); ++ ++G_END_DECLS +diff --git a/src/vteaccess.cc b/src/vteaccess.cc +index fff918dc..c1c1a4c0 100644 +--- a/src/vteaccess.cc ++++ b/src/vteaccess.cc +@@ -386,6 +386,12 @@ _vte_terminal_accessible_text_modified(VteTerminalAccessible* accessible) + glong offset, caret_offset, olen, clen; + gint old_snapshot_caret; + ++ auto widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)); ++ auto terminal = VTE_TERMINAL(widget); ++ ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ + old_snapshot_caret = priv->snapshot_caret; + priv->snapshot_contents_invalid = TRUE; + vte_terminal_accessible_update_private_data_if_needed(accessible, +@@ -541,6 +547,9 @@ _vte_terminal_accessible_text_scrolled(VteTerminalAccessible* accessible, + auto widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)); + auto terminal = VTE_TERMINAL(widget); + ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ + row_count = vte_terminal_get_row_count(terminal); + if (((howmuch < 0) && (howmuch <= -row_count)) || + ((howmuch > 0) && (howmuch >= row_count))) { +@@ -793,6 +802,9 @@ vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data) + VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; + VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); + ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ + _vte_debug_print(VTE_DEBUG_ALLY, + "Invalidating accessibility cursor.\n"); + priv->snapshot_caret_invalid = TRUE; +@@ -807,6 +819,9 @@ vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data) + { + VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; + ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ + atk_object_set_description(ATK_OBJECT(accessible), vte_terminal_get_window_title(terminal)); + } + +@@ -820,6 +835,9 @@ vte_terminal_accessible_visibility_notify(VteTerminal *terminal, + GtkWidget *widget; + gboolean visible; + ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return FALSE; ++ + visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED; + /* The VISIBLE state indicates that this widget is "visible". */ + atk_object_notify_state_change(ATK_OBJECT(accessible), +@@ -851,6 +869,9 @@ vte_terminal_accessible_selection_changed (VteTerminal *terminal, + { + VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; + ++ if (!vte_terminal_get_enable_a11y (terminal)) ++ return; ++ + g_signal_emit_by_name (accessible, "text_selection_changed"); + } + +diff --git a/src/vtegtk.cc b/src/vtegtk.cc +index c713a95a..6ece18e9 100644 +--- a/src/vtegtk.cc ++++ b/src/vtegtk.cc +@@ -65,9 +65,11 @@ + #include + + #if WITH_A11Y +-#if VTE_GTK == 3 +-#include "vteaccess.h" +-#endif /* VTE_GTK == 3 */ ++# if VTE_GTK == 3 ++# include "vteaccess.h" ++# elif VTE_GTK == 4 ++# include "vteaccess-gtk4.h" ++# endif + #endif /* WITH_A11Y */ + + #if WITH_ICU +@@ -155,6 +157,14 @@ private: + std::shared_ptr m_widget; + }; + ++#if defined(WITH_A11Y) && VTE_GTK == 4 ++# define VTE_IMPLEMENT_ACCESSIBLE \ ++ G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE_TEXT, \ ++ _vte_accessible_text_iface_init) ++#else ++# define VTE_IMPLEMENT_ACCESSIBLE ++#endif ++ + #if VTE_DEBUG + G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, + { +@@ -163,6 +173,7 @@ G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, + } + g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate)); + G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr) ++ VTE_IMPLEMENT_ACCESSIBLE + if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) { + g_printerr("vte_terminal_get_type()\n"); + }) +@@ -173,7 +184,8 @@ G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, + g_type_add_instance_private(g_define_type_id, sizeof(VteTerminalPrivate)); + } + g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate)); +- G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr)) ++ G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr) ++ VTE_IMPLEMENT_ACCESSIBLE) + #endif + + static inline auto +@@ -901,6 +913,10 @@ try + gtk_widget_set_has_window(&terminal->widget, FALSE); + #endif + ++#if defined(WITH_A11Y) && VTE_GTK == 4 ++ _vte_accessible_text_init (GTK_ACCESSIBLE_TEXT (terminal)); ++#endif ++ + place = vte_terminal_get_instance_private(terminal); + new (place) VteTerminalPrivate{terminal}; + } +@@ -1017,6 +1033,9 @@ try + case PROP_DELETE_BINDING: + g_value_set_enum (value, widget->delete_binding()); + break; ++ case PROP_ENABLE_A11Y: ++ g_value_set_boolean (value, vte_terminal_get_enable_a11y (terminal)); ++ break; + case PROP_ENABLE_BIDI: + g_value_set_boolean (value, vte_terminal_get_enable_bidi (terminal)); + break; +@@ -1172,6 +1191,9 @@ try + case PROP_DELETE_BINDING: + vte_terminal_set_delete_binding (terminal, (VteEraseBinding)g_value_get_enum (value)); + break; ++ case PROP_ENABLE_A11Y: ++ vte_terminal_set_enable_a11y (terminal, g_value_get_boolean (value)); ++ break; + case PROP_ENABLE_BIDI: + vte_terminal_set_enable_bidi (terminal, g_value_get_boolean (value)); + break; +@@ -2286,6 +2308,22 @@ vte_terminal_class_init(VteTerminalClass *klass) + VTE_ERASE_AUTO, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); + ++ /** ++ * VteTerminal:enable-a11y: ++ * ++ * Controls whether or not a11y is enabled for the widget. ++ * ++ * Since: 0.78 ++ */ ++ pspecs[PROP_ENABLE_A11Y] = ++ g_param_spec_boolean ("enable-a11y", NULL, NULL, ++#if VTE_GTK == 3 ++ TRUE, ++#else ++ FALSE, ++#endif ++ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); ++ + /** + * VteTerminal:enable-bidi: + * +@@ -2712,10 +2750,12 @@ vte_terminal_class_init(VteTerminalClass *klass) + err.assert_no_error(); + #endif + +-#if VTE_GTK == 3 + #if WITH_A11Y ++#if VTE_GTK == 3 + /* a11y */ + gtk_widget_class_set_accessible_type(widget_class, VTE_TYPE_TERMINAL_ACCESSIBLE); ++#elif VTE_GTK == 4 ++ gtk_widget_class_set_accessible_role(widget_class, GTK_ACCESSIBLE_ROLE_TERMINAL); + #endif + #endif + } +@@ -5709,6 +5749,53 @@ catch (...) + vte::log_exception(); + } + ++/** ++ * vte_terminal_get_enable_a11y: ++ * @terminal: a #VteTerminal ++ * ++ * Checks whether the terminal communicates with a11y backends ++ * ++ * Returns: %TRUE if a11y is enabled, %FALSE if not ++ * ++ * Since: 0.78 ++ */ ++gboolean ++vte_terminal_get_enable_a11y(VteTerminal *terminal) noexcept ++try ++{ ++ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), false); ++ return IMPL(terminal)->m_enable_a11y; ++} ++catch (...) ++{ ++ vte::log_exception(); ++ return false; ++} ++ ++/** ++ * vte_terminal_set_enable_a11y: ++ * @terminal: a #VteTerminal ++ * @enable_a11y: %TRUE to enable a11y support ++ * ++ * Controls whether or not the terminal will communicate with a11y backends. ++ * ++ * Since: 0.78 ++ */ ++void ++vte_terminal_set_enable_a11y(VteTerminal *terminal, ++ gboolean enable_a11y) noexcept ++try ++{ ++ g_return_if_fail(VTE_IS_TERMINAL(terminal)); ++ ++ if (IMPL(terminal)->set_enable_a11y(enable_a11y != FALSE)) ++ g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ENABLE_A11Y]); ++} ++catch (...) ++{ ++ vte::log_exception(); ++} ++ + /** + * vte_terminal_get_enable_bidi: + * @terminal: a #VteTerminal +diff --git a/src/vtegtk.hh b/src/vtegtk.hh +index 566c8508..87259680 100644 +--- a/src/vtegtk.hh ++++ b/src/vtegtk.hh +@@ -80,6 +80,7 @@ enum { + PROP_CURRENT_DIRECTORY_URI, + PROP_CURRENT_FILE_URI, + PROP_DELETE_BINDING, ++ PROP_ENABLE_A11Y, + PROP_ENABLE_BIDI, + PROP_ENABLE_FALLBACK_SCROLLING, + PROP_ENABLE_SHAPING, +diff --git a/src/vteinternal.hh b/src/vteinternal.hh +index 07a9e993..6e2c2e7f 100644 +--- a/src/vteinternal.hh ++++ b/src/vteinternal.hh +@@ -799,6 +799,13 @@ public: + const char *m_hyperlink_hover_uri; /* data is owned by the ring */ + long m_hyperlink_auto_id{0}; + ++ /* Accessibility support */ ++#if VTE_GTK == 3 ++ bool m_enable_a11y{true}; ++#elif VTE_GTK == 4 ++ bool m_enable_a11y{false}; ++#endif ++ + /* RingView and friends */ + vte::base::RingView m_ringview; + bool m_enable_bidi{true}; +@@ -1528,6 +1535,7 @@ public: + bool set_cursor_style(CursorStyle style); + bool set_delete_binding(EraseMode binding); + auto delete_binding() const noexcept { return m_delete_binding; } ++ bool set_enable_a11y(bool setting); + bool set_enable_bidi(bool setting); + bool set_enable_shaping(bool setting); + bool set_encoding(char const* codeset, +-- +2.43.1 + diff --git a/0001-add-notification-and-shell-precmd-preexec.patch b/0001-add-notification-and-shell-precmd-preexec.patch new file mode 100644 index 0000000..ffc02b2 --- /dev/null +++ b/0001-add-notification-and-shell-precmd-preexec.patch @@ -0,0 +1,533 @@ +From f31af265a19a406cd193a82b96dff1dd2e4595b4 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Mon, 4 Mar 2024 14:03:38 -0800 +Subject: [PATCH] add notification and shell precmd/preexec + +This is a bit simpler to manage as a single patch rather than a series of +patches which incrementally update things. + +This alters some of the original patches so that we don't need to have +such careful integration with the vtable of the class as that isn't used. + +You can always connect to the signal rather than the vtable default func. +--- + src/marshal.list | 1 + + src/vte.cc | 28 +++++++++ + src/vte.sh.in | 7 ++- + src/vte/vteterminal.h | 4 ++ + src/vtegtk.cc | 131 ++++++++++++++++++++++++++++++++++++++++++ + src/vtegtk.hh | 5 ++ + src/vteinternal.hh | 26 +++++++++ + src/vteseq.cc | 123 ++++++++++++++++++++++++++++++++++++++- + 8 files changed, 322 insertions(+), 3 deletions(-) + +diff --git a/src/marshal.list b/src/marshal.list +index 241128c3..f9b3818f 100644 +--- a/src/marshal.list ++++ b/src/marshal.list +@@ -1,3 +1,4 @@ + VOID:STRING,BOXED + VOID:STRING,UINT ++VOID:STRING,STRING + VOID:UINT,UINT +diff --git a/src/vte.cc b/src/vte.cc +index 2cba7369..a8a0e22c 100644 +--- a/src/vte.cc ++++ b/src/vte.cc +@@ -10771,6 +10771,34 @@ Terminal::emit_pending_signals() + + emit_adjustment_changed(); + ++ if (m_pending_changes & vte::to_integral(PendingChanges::NOTIFICATION)) { ++ _vte_debug_print (VTE_DEBUG_SIGNALS, ++ "Emitting `notification-received'.\n"); ++ g_signal_emit(freezer.get(), signals[SIGNAL_NOTIFICATION_RECEIVED], 0, ++ m_notification_summary.c_str(), ++ m_notification_body.c_str()); ++ } ++ ++ if (m_pending_changes & vte::to_integral(PendingChanges::SHELL_PREEXEC)) { ++ _vte_debug_print (VTE_DEBUG_SIGNALS, ++ "Emitting `shell-preexec'.\n"); ++ g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PREEXEC], 0); ++ } ++ ++ if (m_pending_changes & vte::to_integral(PendingChanges::SHELL_PRECMD)) { ++ _vte_debug_print (VTE_DEBUG_SIGNALS, ++ "Emitting `shell-precmd'.\n"); ++ g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PRECMD], 0); ++ } ++ ++ if (m_pending_changes & vte::to_integral(PendingChanges::CONTAINERS)) { ++ _vte_debug_print(VTE_DEBUG_SIGNALS, ++ "Notifying `current-container-name' and `current-container-runtime'.\n"); ++ ++ g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_CONTAINER_NAME]); ++ g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_CONTAINER_RUNTIME]); ++ } ++ + if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) { + if (m_window_title != m_window_title_pending) { + m_window_title.swap(m_window_title_pending); +diff --git a/src/vte.sh.in b/src/vte.sh.in +index 2328a9ec..93f45ea8 100644 +--- a/src/vte.sh.in ++++ b/src/vte.sh.in +@@ -28,6 +28,12 @@ case "$TERM" in + *) return 0 ;; + esac + ++__vte_shell_precmd() { ++ local command=$(HISTTIMEFORMAT= history 1 | sed 's/^ *[0-9]\+ *//') ++ command="${command//;/ }" ++ printf '\033]777;notify;Command completed;%s\033\\\033]777;precmd\033\\' "${command}" ++} ++ + __vte_osc7 () { + printf "\033]7;file://%s%s\033\\" "${HOSTNAME}" "$(@libexecdir@/vte-urlencode-cwd)" + } +@@ -37,6 +43,7 @@ __vte_prompt_command() { + [ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/} + pwd="${pwd//[[:cntrl:]]}" + printf "\033]0;%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${pwd}" ++ __vte_shell_precmd + __vte_osc7 + } + +@@ -49,9 +56,12 @@ if [[ -n "${BASH_VERSION:-}" ]]; then + # use the __vte_prompt_command function which also sets the title. + + if [[ "$(declare -p PROMPT_COMMAND 2>&1)" =~ "declare -a" ]]; then ++ PROMPT_COMMAND+=(__vte_shell_precmd) + PROMPT_COMMAND+=(__vte_osc7) ++ PS0=$(printf "\033]777;preexec\033\\") + else + PROMPT_COMMAND="__vte_prompt_command" ++ PS0=$(printf "\033]777;preexec\033\\") + fi + + # Shell integration +diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h +index a9e1e494..9c2e2dae 100644 +--- a/src/vte/vteterminal.h ++++ b/src/vte/vteterminal.h +@@ -559,6 +559,10 @@ glong vte_terminal_get_column_count(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VT + _VTE_PUBLIC + const char *vte_terminal_get_window_title(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); + _VTE_PUBLIC ++const char *vte_terminal_get_current_container_name(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); ++_VTE_PUBLIC ++const char *vte_terminal_get_current_container_runtime(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); ++_VTE_PUBLIC + const char *vte_terminal_get_current_directory_uri(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); + _VTE_PUBLIC + const char *vte_terminal_get_current_file_uri(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); +diff --git a/src/vtegtk.cc b/src/vtegtk.cc +index 92eb6881..c713a95a 100644 +--- a/src/vtegtk.cc ++++ b/src/vtegtk.cc +@@ -999,6 +999,12 @@ try + case PROP_CURSOR_BLINK_MODE: + g_value_set_enum (value, vte_terminal_get_cursor_blink_mode (terminal)); + break; ++ case PROP_CURRENT_CONTAINER_NAME: ++ g_value_set_string (value, vte_terminal_get_current_container_name (terminal)); ++ break; ++ case PROP_CURRENT_CONTAINER_RUNTIME: ++ g_value_set_string (value, vte_terminal_get_current_container_runtime (terminal)); ++ break; + case PROP_CURRENT_DIRECTORY_URI: + g_value_set_string (value, vte_terminal_get_current_directory_uri (terminal)); + break; +@@ -1434,6 +1440,60 @@ vte_terminal_class_init(VteTerminalClass *klass) + G_OBJECT_CLASS_TYPE(klass), + g_cclosure_marshal_VOID__INTv); + ++ /** ++ * VteTerminal::notification-received: ++ * @vteterminal: the object which received the signal ++ * @summary: The summary ++ * @body: (allow-none): Extra optional text ++ * ++ * Emitted when a process running in the terminal wants to ++ * send a notification to the desktop environment. ++ */ ++ signals[SIGNAL_NOTIFICATION_RECEIVED] = ++ g_signal_new(I_("notification-received"), ++ G_OBJECT_CLASS_TYPE(klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, ++ NULL, ++ _vte_marshal_VOID__STRING_STRING, ++ G_TYPE_NONE, ++ 2, G_TYPE_STRING, G_TYPE_STRING); ++ ++ /** ++ * VteTerminal::shell-precmd: ++ * @vteterminal: the object which received the signal ++ * ++ * Emitted right before an interactive shell shows a ++ * first-level prompt. ++ */ ++ signals[SIGNAL_SHELL_PRECMD] = ++ g_signal_new(I_("shell-precmd"), ++ G_OBJECT_CLASS_TYPE(klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, ++ NULL, ++ g_cclosure_marshal_VOID__VOID, ++ G_TYPE_NONE, 0); ++ ++ /** ++ * VteTerminal::shell-preexec: ++ * @vteterminal: the object which received the signal ++ * ++ * Emitted when the interactive shell has read in a complete ++ * command and is about to execute it. ++ */ ++ signals[SIGNAL_SHELL_PREEXEC] = ++ g_signal_new(I_("shell-preexec"), ++ G_OBJECT_CLASS_TYPE(klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, ++ NULL, ++ g_cclosure_marshal_VOID__VOID, ++ G_TYPE_NONE, 0); ++ + /** + * VteTerminal::window-title-changed: + * @vteterminal: the object which received the signal +@@ -2487,6 +2547,27 @@ vte_terminal_class_init(VteTerminalClass *klass) + NULL, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); + ++ /** ++ * VteTerminal:current-container-name: ++ * ++ * The name of the current container, or %NULL if unset. ++ */ ++ pspecs[PROP_CURRENT_CONTAINER_NAME] = ++ g_param_spec_string ("current-container-name", NULL, NULL, ++ NULL, ++ (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); ++ ++ /** ++ * VteTerminal:current-container-runtime: ++ * ++ * The name of the runtime toolset used to set up the current ++ * container, or %NULL if unset. ++ */ ++ pspecs[PROP_CURRENT_CONTAINER_RUNTIME] = ++ g_param_spec_string ("current-container-runtime", NULL, NULL, ++ NULL, ++ (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); ++ + /** + * VteTerminal:current-directory-uri: + * +@@ -5419,6 +5500,56 @@ catch (...) + return -1; + } + ++/** ++ * vte_terminal_get_current_container_name: ++ * @terminal: a #VteTerminal ++ * ++ * Returns: (nullable) (transfer none): the name of the current ++ * container, or %NULL ++ */ ++const char * ++vte_terminal_get_current_container_name(VteTerminal *terminal) noexcept ++try ++{ ++ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL); ++ auto impl = IMPL(terminal); ++ if (impl->m_containers.empty()) ++ return NULL; ++ ++ const VteContainer &container = impl->m_containers.top(); ++ return container.m_name.c_str(); ++} ++catch (...) ++{ ++ vte::log_exception(); ++ return NULL; ++} ++ ++/** ++ * vte_terminal_get_current_container_runtime: ++ * @terminal: a #VteTerminal ++ * ++ * Returns: (nullable) (transfer none): the name of the runtime ++ * toolset used to set up the current container, or %NULL ++ */ ++const char * ++vte_terminal_get_current_container_runtime(VteTerminal *terminal) noexcept ++try ++{ ++ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL); ++ auto impl = IMPL(terminal); ++ if (impl->m_containers.empty()) ++ return NULL; ++ ++ const VteContainer &container = impl->m_containers.top(); ++ return container.m_runtime.c_str(); ++} ++catch (...) ++{ ++ vte::log_exception(); ++ return NULL; ++} ++ + /** + * vte_terminal_get_current_directory_uri: + * @terminal: a #VteTerminal +diff --git a/src/vtegtk.hh b/src/vtegtk.hh +index 1d1383af..566c8508 100644 +--- a/src/vtegtk.hh ++++ b/src/vtegtk.hh +@@ -53,6 +53,9 @@ enum { + SIGNAL_RESTORE_WINDOW, + SIGNAL_SELECTION_CHANGED, + SIGNAL_SETUP_CONTEXT_MENU, ++ SIGNAL_SHELL_PRECMD, ++ SIGNAL_SHELL_PREEXEC, ++ SIGNAL_NOTIFICATION_RECEIVED, + SIGNAL_WINDOW_TITLE_CHANGED, + LAST_SIGNAL + }; +@@ -72,6 +75,8 @@ enum { + PROP_CONTEXT_MENU, + PROP_CURSOR_BLINK_MODE, + PROP_CURSOR_SHAPE, ++ PROP_CURRENT_CONTAINER_NAME, ++ PROP_CURRENT_CONTAINER_RUNTIME, + PROP_CURRENT_DIRECTORY_URI, + PROP_CURRENT_FILE_URI, + PROP_DELETE_BINDING, +diff --git a/src/vteinternal.hh b/src/vteinternal.hh +index ed57ad16..07a9e993 100644 +--- a/src/vteinternal.hh ++++ b/src/vteinternal.hh +@@ -63,6 +63,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -121,6 +122,18 @@ typedef enum _VteCharacterReplacement { + VTE_CHARACTER_REPLACEMENT_LINE_DRAWING + } VteCharacterReplacement; + ++struct VteContainer { ++public: ++ VteContainer(const std::string &name, const std::string &runtime) : ++ m_name{name}, ++ m_runtime{runtime} ++ { ++ } ++ ++ std::string m_name; ++ std::string m_runtime; ++}; ++ + typedef struct _VtePaletteColor { + struct { + vte::color::rgb color; +@@ -710,6 +723,12 @@ public: + gboolean m_cursor_moved_pending; + gboolean m_contents_changed_pending; + ++ /* desktop notification */ ++ std::stack m_containers; ++ ++ std::string m_notification_summary; ++ std::string m_notification_body; ++ + std::string m_window_title{}; + std::string m_current_directory_uri{}; + std::string m_current_file_uri{}; +@@ -723,6 +742,10 @@ public: + TITLE = 1u << 0, + CWD = 1u << 1, + CWF = 1u << 2, ++ NOTIFICATION = 1u << 4, ++ SHELL_PREEXEC = 1u << 5, ++ SHELL_PRECMD = 1u << 6, ++ CONTAINERS = 1u << 7, + }; + unsigned m_pending_changes{0}; + +@@ -1654,6 +1677,9 @@ public: + int osc) noexcept; + + /* OSC handlers */ ++ void handle_urxvt_extension(vte::parser::Sequence const& seq, ++ vte::parser::StringTokeniser::const_iterator& token, ++ vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept; + void set_color(vte::parser::Sequence const& seq, + vte::parser::StringTokeniser::const_iterator& token, + vte::parser::StringTokeniser::const_iterator const& endtoken, +diff --git a/src/vteseq.cc b/src/vteseq.cc +index 904837e1..26f7b0d6 100644 +--- a/src/vteseq.cc ++++ b/src/vteseq.cc +@@ -39,6 +39,9 @@ + #define ST_C0 _VTE_CAP_ST + + #include ++#include ++#include ++#include + + using namespace std::literals; + +@@ -1276,6 +1279,121 @@ Terminal::erase_in_line(vte::parser::Sequence const& seq) + m_text_deleted_flag = TRUE; + } + ++void ++Terminal::handle_urxvt_extension(vte::parser::Sequence const& seq, ++ vte::parser::StringTokeniser::const_iterator& token, ++ vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept ++{ ++ if (token == endtoken) ++ return; ++ ++ if (*token == "container") { ++ ++token; ++ ++ if (token == endtoken) ++ return; ++ ++ const std::string sub_command = *token; ++ ++token; ++ ++ if (sub_command == "pop") { ++ if (token == endtoken) ++ return; ++ ++ ++token; ++ ++ if (token == endtoken) ++ return; ++ ++ ++token; ++ ++ if (token == endtoken) { ++ if (!m_containers.empty()) { ++ m_containers.pop(); ++ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); ++ } ++ ++ return; ++ } ++ ++ const std::string uid_token = *token; ++ ++token; ++ ++ const uid_t uid = getuid(); ++ const std::string uid_str = std::to_string(uid); ++ ++ if (uid_token == uid_str) { ++ if (!m_containers.empty()) { ++ m_containers.pop(); ++ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); ++ } ++ ++ return; ++ } ++ ++ return; ++ } else if (sub_command == "push") { ++ if (token == endtoken) ++ return; ++ ++ const std::string name = *token; ++ ++token; ++ ++ if (token == endtoken) ++ return; ++ ++ const std::string runtime = *token; ++ ++token; ++ ++ if (token == endtoken) { ++ m_containers.emplace(name, runtime); ++ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); ++ return; ++ } ++ ++ const std::string uid_token = *token; ++ ++token; ++ ++ const uid_t uid = getuid(); ++ const std::string uid_str = std::to_string(uid); ++ ++ if (uid_token == uid_str) { ++ m_containers.emplace(name, runtime); ++ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); ++ return; ++ } ++ ++ return; ++ } ++ ++ return; ++ } ++ ++ if (*token == "notify") { ++ ++token; ++ ++ if (token == endtoken) ++ return; ++ ++ m_notification_summary = *token; ++ m_notification_body.clear(); ++ m_pending_changes |= vte::to_integral(PendingChanges::NOTIFICATION); ++ ++token; ++ ++ if (token == endtoken) ++ return; ++ ++ m_notification_body = *token; ++ return; ++ } ++ ++ if (*token == "precmd") { ++ m_pending_changes |= vte::to_integral(PendingChanges::SHELL_PRECMD); ++ } else if (*token == "preexec") { ++ m_pending_changes |= vte::to_integral(PendingChanges::SHELL_PREEXEC); ++ } ++} ++ + bool + Terminal::get_osc_color_index(int osc, + int value, +@@ -6596,6 +6714,10 @@ Terminal::OSC(vte::parser::Sequence const& seq) + reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_ESCAPE); + break; + ++ case VTE_OSC_URXVT_EXTENSION: ++ handle_urxvt_extension(seq, it, cend); ++ break; ++ + case VTE_OSC_XTERM_SET_ICON_TITLE: + case VTE_OSC_XTERM_SET_XPROPERTY: + case VTE_OSC_XTERM_SET_COLOR_MOUSE_CURSOR_FG: +@@ -6636,7 +6758,6 @@ Terminal::OSC(vte::parser::Sequence const& seq) + case VTE_OSC_URXVT_SET_FONT_BOLD_ITALIC: + case VTE_OSC_URXVT_VIEW_UP: + case VTE_OSC_URXVT_VIEW_DOWN: +- case VTE_OSC_URXVT_EXTENSION: + case VTE_OSC_YF_RQGWR: + default: + break; +-- +2.43.1 + diff --git a/sources b/sources index 1f4ac86..d612df3 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (vte-0.76.2.tar.xz) = 206e2706c926972d4f389cf0418b840345ba6eb0336a3cae8b0d604f773b8b86746ef4986c94cfd6b0716449351ecb51a62f3ba08885b9cc8c8a8580e75f7fbf +SHA512 (vte-0.76.3.tar.xz) = 59cf3241f59b7ce795098814a04816d150330e4464a2438c974ac03cfd6aa05e7e037121a6a21929d6b12eb17fb1a4bf48c936604f0e0b770e3f125adb5a4c50 diff --git a/vte291-cntnr-precmd-preexec-scroll.patch b/vte291-cntnr-precmd-preexec-scroll.patch deleted file mode 100644 index 32ac71e..0000000 --- a/vte291-cntnr-precmd-preexec-scroll.patch +++ /dev/null @@ -1,2727 +0,0 @@ -From aace6f9a1f217be6496a3df0833bf1cd9841bf24 Mon Sep 17 00:00:00 2001 -From: Christian Hergert -Date: Tue, 2 Apr 2024 11:25:40 +0100 -Subject: [PATCH 01/12] a11y: implement GtkAccessibleText - -This is an initial implementaiton of GtkAccessibleText which was added -to GTK for 4.14. It attempts to implement things in a very similar -fashion to the previous code for GTK 3 although considerable effort was -made to simplify and improve readability as to how it works. - -Currently, this supports reading back what you type and what has changed -on screen. It is not yet 1:1 what the GTK 3 a11y implementation did -because ATK was doing many other things (including proxying keyboard -keys) to the other side of the a11y bus. That appears to improve -readback by screen readers in the form of "backspace" and what character -was deleted. - -I expect things to get closer to 1:1 but that work is going to have to -be done inside of GTK itself first and should not require much if -anything here. - -A new VteTerminal:enable-a11y feature flag property has been added -because I'm concerned about enabling this by default until the a11y bus -learns to be more lazy. Currently there is no way to "do nothing" until -a peer (e.g. screenreader) is interested in the contents. - -Ideally, we would have a short-circuit like is currently implemented by -checking vte_terminal_get_enable_a11y() to avoid any sort of contents -calculation when there are no a11y observers. - -It also allows disabling the GTK 3 a11y implementation just to keep some -symmetry between the APIs. - -Currently, this does not implement "text-scrolled" like the GTK 3 -implementation does as I'm not sure yet if there is a benefit. ---- - src/meson.build | 13 +- - src/vte.cc | 11 + - src/vte/vteterminal.h | 7 + - src/vteaccess-gtk4.cc | 874 ++++++++++++++++++++++++++++++++++++++++++ - src/vteaccess-gtk4.h | 25 ++ - src/vteaccess.cc | 21 + - src/vtegtk.cc | 97 ++++- - src/vtegtk.hh | 1 + - src/vteinternal.hh | 8 + - 9 files changed, 1050 insertions(+), 7 deletions(-) - create mode 100644 src/vteaccess-gtk4.cc - create mode 100644 src/vteaccess-gtk4.h - -diff --git a/src/meson.build b/src/meson.build -index 3f89f492..6d1b4b2b 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -18,11 +18,16 @@ subdir('vte') - - src_inc = include_directories('.') - --a11y_sources = files( -+a11y_gtk3_sources = files( - 'vteaccess.cc', - 'vteaccess.h', - ) - -+a11y_gtk4_sources = files( -+ 'vteaccess-gtk4.cc', -+ 'vteaccess-gtk4.h', -+) -+ - debug_sources = files( - 'debug.cc', - 'debug.h', -@@ -300,7 +305,7 @@ if get_option('gtk3') - libvte_gtk3_public_deps = libvte_common_public_deps + [gtk3_dep,] - - if get_option('a11y') -- libvte_gtk3_sources += a11y_sources -+ libvte_gtk3_sources += a11y_gtk3_sources - endif - - libvte_gtk3 = shared_library( -@@ -349,6 +354,10 @@ if get_option('gtk4') - libvte_gtk4_deps = libvte_common_deps + [gtk4_dep,] - libvte_gtk4_public_deps = libvte_common_public_deps + [gtk4_dep,] - -+ if get_option('a11y') -+ libvte_gtk4_sources += a11y_gtk4_sources -+ endif -+ - libvte_gtk4 = shared_library( - vte_gtk4_api_name, - sources: libvte_gtk4_sources, -diff --git a/src/vte.cc b/src/vte.cc -index b3bcc0a6..6f5ed505 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10134,6 +10134,17 @@ Terminal::set_text_blink_mode(TextBlinkMode setting) - return true; - } - -+bool -+Terminal::set_enable_a11y(bool setting) -+{ -+ if (setting == m_enable_a11y) -+ return false; -+ -+ m_enable_a11y = setting; -+ -+ return true; -+} -+ - bool - Terminal::set_enable_bidi(bool setting) - { -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index a9e1e494..33360909 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -388,6 +388,13 @@ _VTE_PUBLIC - void vte_terminal_set_delete_binding(VteTerminal *terminal, - VteEraseBinding binding) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); - -+/* Accessibility */ -+_VTE_PUBLIC -+void vte_terminal_set_enable_a11y(VteTerminal *terminal, -+ gboolean enable_a11y) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+_VTE_PUBLIC -+gboolean vte_terminal_get_enable_a11y(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+ - /* BiDi and shaping */ - _VTE_PUBLIC - void vte_terminal_set_enable_bidi(VteTerminal *terminal, -diff --git a/src/vteaccess-gtk4.cc b/src/vteaccess-gtk4.cc -new file mode 100644 -index 00000000..f42e7578 ---- /dev/null -+++ b/src/vteaccess-gtk4.cc -@@ -0,0 +1,874 @@ -+/* -+ * Copyright © 2024 Christian Hergert -+ * Copyright © 2002,2003 Red Hat, Inc. -+ * -+ * This library is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU Lesser General Public License as published -+ * by the Free Software Foundation, either version 3 of the License, or -+ * (at your option) any later version. -+ * -+ * This library is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public License -+ * along with this library. If not, see . -+ */ -+ -+#include "config.h" -+ -+#include "vteinternal.hh" -+#include "vteaccess-gtk4.h" -+ -+#define GDK_ARRAY_NAME char_positions -+#define GDK_ARRAY_TYPE_NAME CharPositions -+#define GDK_ARRAY_ELEMENT_TYPE int -+#define GDK_ARRAY_BY_VALUE 1 -+#define GDK_ARRAY_PREALLOC 8 -+#define GDK_ARRAY_NO_MEMSET -+#include "gdkarrayimpl.c" -+ -+typedef struct _VteAccessibleTextContents -+{ -+ /* A GdkArrayImpl of attributes per byte */ -+ VteCharAttrList attrs; -+ -+ /* The byte position within the UTF-8 string where each visible -+ * character starts. -+ */ -+ CharPositions characters; -+ -+ /* The character position within the UTF-8 string where each line -+ * break occurs. To get byte offset, use @characters. -+ */ -+ CharPositions linebreaks; -+ -+ /* The UTF-8 string encoded as bytes so that we may reference it -+ * using GBytes for "substrings". However @string does include a -+ * trailing NUL byte in it's size so that the a11y infrastructure -+ * may elide some string copies. -+ */ -+ GBytes *string; -+ -+ /* Number of bytes in @string excluding trailing NUL. */ -+ gsize n_bytes; -+ -+ /* Number of unicode characters in @string. */ -+ gsize n_chars; -+ -+ /* The character position (not bytes) of the caret in @string */ -+ gsize caret; -+ -+ /* Cached column/row of the caret updated each time we are notified -+ * of the caret having moved. We cache this so that we can elide -+ * extraneous notifications after snapshoting. We will update the -+ * carent position synchronously when notified so @caret may always -+ * be relied upon as correct. -+ */ -+ long cached_caret_column; -+ long cached_caret_row; -+} VteAccessibleTextContents; -+ -+typedef struct _VteAccessibleText -+{ -+ VteTerminal *terminal; -+ VteAccessibleTextContents contents[2]; -+ guint contents_flip : 1; -+} VteAccessibleText; -+ -+static inline gboolean -+_pango_color_equal(const PangoColor *a, -+ const PangoColor *b) -+{ -+ return a->red == b->red && -+ a->green == b->green && -+ a->blue == b->blue; -+} -+ -+static void -+vte_accessible_text_contents_init (VteAccessibleTextContents *contents) -+{ -+ vte_char_attr_list_init (&contents->attrs); -+ char_positions_init (&contents->characters); -+ char_positions_init (&contents->linebreaks); -+ contents->string = nullptr; -+ contents->n_bytes = 0; -+ contents->n_chars = 0; -+ contents->caret = 0; -+ contents->cached_caret_row = 0; -+ contents->cached_caret_column = 0; -+} -+ -+static void -+vte_accessible_text_contents_clear (VteAccessibleTextContents *contents) -+{ -+ vte_char_attr_list_clear (&contents->attrs); -+ char_positions_clear (&contents->characters); -+ char_positions_clear (&contents->linebreaks); -+ g_clear_pointer (&contents->string, g_bytes_unref); -+ contents->n_bytes = 0; -+ contents->n_chars = 0; -+ contents->caret = 0; -+ contents->cached_caret_row = 0; -+ contents->cached_caret_column = 0; -+} -+ -+static void -+vte_accessible_text_contents_reset (VteAccessibleTextContents *contents) -+{ -+ vte_char_attr_list_set_size (&contents->attrs, 0); -+ char_positions_set_size (&contents->characters, 0); -+ char_positions_set_size (&contents->linebreaks, 0); -+ g_clear_pointer (&contents->string, g_bytes_unref); -+ contents->n_bytes = 0; -+ contents->n_chars = 0; -+ contents->caret = 0; -+ contents->cached_caret_row = 0; -+ contents->cached_caret_column = 0; -+} -+ -+static const char * -+vte_accessible_text_contents_get_string (VteAccessibleTextContents *contents, -+ gsize *len) -+{ -+ const char *ret; -+ -+ if (contents->string == nullptr || g_bytes_get_size (contents->string) == 0) { -+ *len = 0; -+ return ""; -+ } -+ -+ ret = (const char *)g_bytes_get_data (contents->string, len); -+ -+ if (*len > 0) { -+ (*len)--; -+ } -+ -+ return ret; -+} -+ -+static int -+vte_accessible_text_contents_offset_from_xy (VteAccessibleTextContents *contents, -+ int x, -+ int y) -+{ -+ int offset; -+ int linebreak; -+ int next_linebreak; -+ -+ if (y >= int(char_positions_get_size (&contents->linebreaks))) { -+ y = int(char_positions_get_size (&contents->linebreaks)) - 1; -+ if (y < 0) { -+ return 0; -+ } -+ } -+ -+ linebreak = *char_positions_index (&contents->linebreaks, y); -+ if (y + 1 == int(char_positions_get_size (&contents->linebreaks))) { -+ next_linebreak = int(char_positions_get_size (&contents->characters)); -+ } else { -+ next_linebreak = *char_positions_index (&contents->linebreaks, y + 1); -+ } -+ -+ offset = linebreak + x; -+ if (offset >= next_linebreak) { -+ offset = next_linebreak - 1; -+ } -+ -+ return offset; -+} -+ -+static gunichar -+vte_accessible_text_contents_get_char_at (VteAccessibleTextContents *contents, -+ guint offset) -+{ -+ const char *str; -+ -+ if (contents->string == nullptr) -+ return 0; -+ -+ if (offset >= contents->n_chars) -+ return 0; -+ -+ g_assert (offset < char_positions_get_size (&contents->characters)); -+ -+ str = (const char *)g_bytes_get_data (contents->string, nullptr); -+ str += *char_positions_index (&contents->characters, offset); -+ -+ return g_utf8_get_char (str); -+ -+} -+ -+static GBytes * -+_g_string_free_to_bytes_with_nul (GString *str) -+{ -+ /* g_string_free_to_bytes() will have a trailing-NUL but not include it -+ * in the size of the GBytes. We want the size included in our GBytes -+ * so that GtkAccessibleText may avoid some copies. -+ */ -+ gsize len = str->len + 1; -+ return g_bytes_new_take (g_string_free (str, FALSE), len); -+} -+ -+static inline gsize -+vte_accessible_text_contents_find_caret (VteAccessibleTextContents *contents, -+ long ccol, -+ long crow) -+{ -+ g_assert (contents != nullptr); -+ -+ /* Get the offsets to the beginnings of each line. */ -+ gsize caret = 0; -+ for (gsize i = 0; i < char_positions_get_size (&contents->characters); i++) { -+ /* Get the attributes for the current cell. */ -+ int offset = *char_positions_index (&contents->characters, i); -+ const struct _VteCharAttributes *attrs = vte_char_attr_list_get (&contents->attrs, offset); -+ -+ /* If this cell is "before" the cursor, move the caret to be "here". */ -+ if ((attrs->row < crow) || -+ ((attrs->row == crow) && (attrs->column < ccol))) { -+ caret = i + 1; -+ } -+ } -+ -+ return caret; -+} -+ -+static void -+vte_accessible_text_contents_snapshot (VteAccessibleTextContents *contents, -+ VteTerminal *terminal) -+{ -+ auto impl = _vte_terminal_get_impl (terminal); -+ GString *gstr = g_string_new (nullptr); -+ -+ try { -+ impl->get_text_displayed_a11y (gstr, &contents->attrs); -+ } catch (...) { -+ g_string_truncate (gstr, 0); -+ } -+ -+ if (vte_char_attr_list_get_size (&contents->attrs) >= G_MAXINT) { -+ g_string_truncate (gstr, 0); -+ return; -+ } -+ -+ /* Get the offsets to the beginnings of each character. */ -+ int i = 0; -+ const char *next = gstr->str; -+ int n_attrs = int(vte_char_attr_list_get_size (&contents->attrs)); -+ while (i < n_attrs) { -+ char_positions_append (&contents->characters, &i); -+ next = g_utf8_next_char (next); -+ if (next != nullptr) { -+ i = next - gstr->str; -+ continue; -+ } -+ break; -+ } -+ -+ /* Find offsets for the beginning of lines. */ -+ gsize n_chars = char_positions_get_size (&contents->characters); -+ int row; -+ for (i = 0, row = 0; i < int(n_chars); i++) { -+ /* Get the attributes for the current cell. */ -+ int offset = *char_positions_index (&contents->characters, i); -+ const struct _VteCharAttributes *attrs = vte_char_attr_list_get (&contents->attrs, offset); -+ -+ /* If this character is on a row different from the row -+ * the character we looked at previously was on, then -+ * it's a new line and we need to keep track of where -+ * it is. */ -+ if ((i == 0) || (attrs->row != row)) { -+ _vte_debug_print (VTE_DEBUG_ALLY, -+ "Row %d/%ld begins at %d.\n", -+ int(char_positions_get_size (&contents->linebreaks)), -+ attrs->row, i); -+ char_positions_append (&contents->linebreaks, &i); -+ } -+ -+ row = attrs->row; -+ } -+ -+ /* Add the final line break. */ -+ char_positions_append (&contents->linebreaks, &i); -+ -+ /* Update the caret position. */ -+ long ccol, crow; -+ vte_terminal_get_cursor_position (terminal, &ccol, &crow); -+ _vte_debug_print (VTE_DEBUG_ALLY, "Cursor at (%ld, " "%ld).\n", ccol, crow); -+ gsize caret = vte_accessible_text_contents_find_caret (contents, ccol, crow); -+ -+ contents->n_bytes = gstr->len; -+ contents->n_chars = n_chars; -+ contents->string = _g_string_free_to_bytes_with_nul (gstr); -+ contents->caret = caret; -+ contents->cached_caret_column = ccol; -+ contents->cached_caret_row = crow; -+ -+ _vte_debug_print (VTE_DEBUG_ALLY, -+ "Refreshed accessibility snapshot, " -+ "%ld cells, %ld characters.\n", -+ long(vte_char_attr_list_get_size(&contents->attrs)), -+ long(char_positions_get_size (&contents->characters))); -+} -+ -+static GBytes * -+vte_accessible_text_contents_slice (VteAccessibleTextContents *contents, -+ guint start, -+ guint end) -+{ -+ static const char empty[] = {0}; -+ guint start_offset; -+ guint end_offset; -+ -+ g_assert (contents != nullptr); -+ -+ if (contents->string == nullptr) -+ return g_bytes_new_static (empty, sizeof empty); -+ -+ if (start > contents->n_chars) -+ start = contents->n_chars; -+ -+ if (end > contents->n_chars) -+ end = contents->n_chars; -+ -+ if (end < start) -+ std::swap (end, start); -+ -+ g_assert (start <= char_positions_get_size (&contents->characters)); -+ g_assert (end <= char_positions_get_size (&contents->characters)); -+ -+ if (start == char_positions_get_size (&contents->characters)) -+ start_offset = g_bytes_get_size (contents->string); -+ else -+ start_offset = *char_positions_index (&contents->characters, start); -+ -+ if (end == char_positions_get_size (&contents->characters)) -+ end_offset = g_bytes_get_size (contents->string); -+ else -+ end_offset = *char_positions_index (&contents->characters, end); -+ -+ g_assert (start_offset <= end_offset); -+ -+ if (start_offset == end_offset) -+ return g_bytes_new_static (empty, sizeof empty); -+ -+ return g_bytes_new_from_bytes (contents->string, start_offset, end_offset - start_offset); -+} -+ -+static void -+vte_accessible_text_free (VteAccessibleText *state) -+{ -+ vte_accessible_text_contents_clear (&state->contents[0]); -+ vte_accessible_text_contents_clear (&state->contents[1]); -+ state->terminal = nullptr; -+ g_free (state); -+} -+ -+static VteAccessibleText * -+vte_accessible_text_get (VteTerminal *terminal) -+{ -+ return (VteAccessibleText *)g_object_get_data (G_OBJECT (terminal), "VTE_ACCESSIBLE_TEXT"); -+} -+ -+static GBytes * -+vte_accessible_text_get_contents (GtkAccessibleText *accessible, -+ guint start, -+ guint end) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state = vte_accessible_text_get (terminal); -+ VteAccessibleTextContents *contents = nullptr; -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ contents = &state->contents[state->contents_flip]; -+ -+ return vte_accessible_text_contents_slice (contents, start, end); -+} -+ -+static GBytes * -+vte_accessible_text_get_contents_at (GtkAccessibleText *accessible, -+ guint offset, -+ GtkAccessibleTextGranularity granularity, -+ guint *start, -+ guint *end) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state = vte_accessible_text_get (terminal); -+ VteAccessibleTextContents *contents; -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ auto impl = _vte_terminal_get_impl (terminal); -+ -+ contents = &state->contents[state->contents_flip]; -+ -+ if (contents->string == nullptr) { -+ return nullptr; -+ } -+ -+ if (offset > contents->n_chars) { -+ offset = contents->n_chars; -+ } -+ -+ switch (granularity) { -+ case GTK_ACCESSIBLE_TEXT_GRANULARITY_CHARACTER: { -+ *start = offset; -+ *end = offset + 1; -+ return vte_accessible_text_contents_slice (contents, offset, offset + 1); -+ } -+ -+ case GTK_ACCESSIBLE_TEXT_GRANULARITY_LINE: { -+ guint char_offset = *char_positions_index (&contents->characters, offset); -+ guint line; -+ -+ for (line = 0; -+ line < char_positions_get_size (&contents->linebreaks); -+ line++) { -+ guint line_offset = *char_positions_index (&contents->linebreaks, line); -+ -+ if (line_offset > char_offset) { -+ line--; -+ break; -+ } -+ } -+ -+ _vte_debug_print (VTE_DEBUG_ALLY, -+ "Character %u is on line %u.\n", -+ offset, line); -+ -+ *start = *char_positions_index (&contents->linebreaks, line); -+ if (line + 1 < char_positions_get_size (&contents->linebreaks)) -+ *end = *char_positions_index (&contents->linebreaks, line + 1); -+ else -+ *end = contents->n_chars; -+ -+ return vte_accessible_text_contents_slice (contents, *start, *end); -+ } -+ -+ case GTK_ACCESSIBLE_TEXT_GRANULARITY_WORD: { -+ gunichar ch = vte_accessible_text_contents_get_char_at (contents, offset); -+ -+ if (ch == 0 || !impl->is_word_char (ch)) -+ break; -+ -+ *start = offset; -+ *end = offset; -+ -+ while (*start > 0 && -+ (ch = vte_accessible_text_contents_get_char_at (contents, *start - 1)) && -+ impl->is_word_char (ch)) { -+ (*start)--; -+ } -+ -+ while (*end < contents->n_chars && -+ (ch = vte_accessible_text_contents_get_char_at (contents, *end + 1)) && -+ impl->is_word_char (ch)) { -+ (*end)++; -+ } -+ -+ return vte_accessible_text_contents_slice (contents, *start, *end); -+ } -+ -+ case GTK_ACCESSIBLE_TEXT_GRANULARITY_SENTENCE: -+ case GTK_ACCESSIBLE_TEXT_GRANULARITY_PARAGRAPH: -+ default: -+ break; -+ } -+ -+ return nullptr; -+} -+ -+static guint -+vte_accessible_text_get_caret_position (GtkAccessibleText *accessible) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state = vte_accessible_text_get (terminal); -+ -+ g_assert (VTE_IS_TERMINAL (accessible)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ return state->contents[state->contents_flip].caret; -+} -+ -+static gboolean -+vte_accessible_text_get_selection (GtkAccessibleText *accessible, -+ gsize *n_ranges, -+ GtkAccessibleTextRange **ranges) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state = vte_accessible_text_get (terminal); -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (ranges != nullptr); -+ -+ *n_ranges = 0; -+ *ranges = nullptr; -+ -+ try { -+ auto impl = _vte_terminal_get_impl (terminal); -+ VteAccessibleTextContents *contents = &state->contents[state->contents_flip]; -+ GtkAccessibleTextRange range; -+ -+ if (impl->m_selection_resolved.empty() || -+ impl->m_selection[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] == nullptr) -+ return FALSE; -+ -+ auto start_column = impl->m_selection_resolved.start_column(); -+ auto start_row = impl->m_selection_resolved.start_row(); -+ auto end_column = impl->m_selection_resolved.end_column(); -+ auto end_row = impl->m_selection_resolved.end_row(); -+ -+ auto start_offset = vte_accessible_text_contents_offset_from_xy (contents, start_column, start_row); -+ auto end_offset = vte_accessible_text_contents_offset_from_xy (contents, end_column, end_row); -+ -+ range.start = gsize(start_offset); -+ range.length = gsize(end_offset - start_offset); -+ -+ *n_ranges = 1; -+ *ranges = (GtkAccessibleTextRange *)g_memdup2 (&range, sizeof range); -+ -+ return TRUE; -+ } catch (...) { } -+ -+ return FALSE; -+} -+ -+static gboolean -+vte_accessible_text_get_attributes (GtkAccessibleText *accessible, -+ guint offset, -+ gsize *n_ranges, -+ GtkAccessibleTextRange **ranges, -+ char ***attribute_names, -+ char ***attribute_values) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state = vte_accessible_text_get (terminal); -+ VteAccessibleTextContents *contents; -+ struct _VteCharAttributes cur_attr; -+ struct _VteCharAttributes attr; -+ GtkAccessibleTextRange range; -+ struct { -+ const char *name; -+ const char *value; -+ } attrs[4]; -+ char fg_color[16]; -+ char bg_color[16]; -+ guint n_attrs = 0; -+ guint start = 0; -+ guint end = 0; -+ guint i; -+ -+ g_assert (VTE_IS_TERMINAL (accessible)); -+ g_assert (ranges != nullptr); -+ g_assert (attribute_names != nullptr); -+ g_assert (attribute_values != nullptr); -+ -+ contents = &state->contents[state->contents_flip]; -+ -+ *n_ranges = 0; -+ *ranges = nullptr; -+ *attribute_names = nullptr; -+ *attribute_values = nullptr; -+ -+ attr = *vte_char_attr_list_get (&contents->attrs, offset); -+ start = 0; -+ for (i = offset; i--;) { -+ cur_attr = *vte_char_attr_list_get (&contents->attrs, i); -+ if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || -+ !_pango_color_equal (&cur_attr.back, &attr.back) || -+ cur_attr.underline != attr.underline || -+ cur_attr.strikethrough != attr.strikethrough) { -+ start = i + 1; -+ break; -+ } -+ } -+ end = vte_char_attr_list_get_size (&contents->attrs) - 1; -+ for (i = offset + 1; i < vte_char_attr_list_get_size (&contents->attrs); i++) { -+ cur_attr = *vte_char_attr_list_get (&contents->attrs, i); -+ if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || -+ !_pango_color_equal (&cur_attr.back, &attr.back) || -+ cur_attr.underline != attr.underline || -+ cur_attr.strikethrough != attr.strikethrough) { -+ end = i - 1; -+ break; -+ } -+ } -+ -+ range.start = start; -+ range.length = end - start; -+ -+ if (range.length == 0) -+ return FALSE; -+ -+ if (attr.underline) { -+ attrs[n_attrs].name = "underline"; -+ attrs[n_attrs].value = "true"; -+ n_attrs++; -+ } -+ -+ if (attr.strikethrough) { -+ attrs[n_attrs].name = "strikethrough"; -+ attrs[n_attrs].value = "true"; -+ n_attrs++; -+ } -+ -+ g_snprintf (fg_color, sizeof fg_color, "%u,%u,%u", -+ attr.fore.red, attr.fore.green, attr.fore.blue); -+ attrs[n_attrs].name = "fg-color"; -+ attrs[n_attrs].value = fg_color; -+ n_attrs++; -+ -+ g_snprintf (bg_color, sizeof bg_color, "%u,%u,%u", -+ attr.back.red, attr.back.green, attr.back.blue); -+ attrs[n_attrs].name = "bg-color"; -+ attrs[n_attrs].value = bg_color; -+ n_attrs++; -+ -+ *attribute_names = g_new0 (char *, n_attrs + 1); -+ *attribute_values = g_new0 (char *, n_attrs + 1); -+ *n_ranges = 1; -+ *ranges = (GtkAccessibleTextRange *)g_memdup2 (&range, sizeof range); -+ -+ for (i = 0; i < n_attrs; i++) { -+ (*attribute_names)[i] = g_strdup (attrs[i].name); -+ (*attribute_values)[i] = g_strdup (attrs[i].value); -+ } -+ -+ return TRUE; -+} -+ -+void -+_vte_accessible_text_iface_init (GtkAccessibleTextInterface *iface) -+{ -+ iface->get_attributes = vte_accessible_text_get_attributes; -+ iface->get_caret_position = vte_accessible_text_get_caret_position; -+ iface->get_contents = vte_accessible_text_get_contents; -+ iface->get_contents_at = vte_accessible_text_get_contents_at; -+ iface->get_selection = vte_accessible_text_get_selection; -+} -+ -+static void -+vte_accessible_text_contents_changed (VteTerminal *terminal, -+ VteAccessibleText *state) -+{ -+ VteAccessibleTextContents *next = nullptr; -+ VteAccessibleTextContents *prev = nullptr; -+ const char *nextstr; -+ const char *prevstr; -+ gsize prevlen; -+ gsize nextlen; -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ -+ prev = &state->contents[state->contents_flip]; -+ next = &state->contents[!state->contents_flip]; -+ -+ /* Get a new snapshot of contents so that we can compare this to the -+ * previous contents. That way we can discover if it was a backspace -+ * that occurred or if it's more than that. -+ * -+ * We do not filp state->contents_flip immediately so that we can -+ * allow the AT context the ability to access the current contents -+ * on DELETE operations. -+ */ -+ vte_accessible_text_contents_reset (next); -+ vte_accessible_text_contents_snapshot (next, state->terminal); -+ -+ nextstr = vte_accessible_text_contents_get_string (next, &nextlen); -+ prevstr = vte_accessible_text_contents_get_string (prev, &prevlen); -+ -+ vte_assert_cmpint (char_positions_get_size (&prev->characters), ==, prev->n_chars); -+ vte_assert_cmpint (char_positions_get_size (&next->characters), ==, next->n_chars); -+ -+ /* NOTE: -+ * -+ * The code below is based upon what vteaccess.cc did for GTK 3. -+ * It does not do any sort of appropriate diffing to try to handle -+ * scrolling correctly. That would be a good idea to implement in -+ * the longer term. -+ * -+ * It just looks for a long prefix match, and then a long suffix -+ * match and attempts to diff what is between those to end points. -+ */ -+ -+ const char *prevc = prevstr; -+ const char *nextc = nextstr; -+ gsize offset = 0; -+ -+ /* Find the beginning of changes */ -+ while ((offset < prev->n_chars) && (offset < next->n_chars)) { -+ gunichar prevch = g_utf8_get_char (prevc); -+ gunichar nextch = g_utf8_get_char (nextc); -+ -+ if (prevch != nextch) { -+ break; -+ } -+ -+ offset++; -+ -+ prevc = g_utf8_next_char (prevc); -+ nextc = g_utf8_next_char (nextc); -+ } -+ -+ /* Find the end of changes */ -+ gsize next_end = next->n_chars; -+ gsize prev_end = prev->n_chars; -+ -+ prevc = prevstr + prevlen; -+ nextc = nextstr + nextlen; -+ -+ while ((next_end > offset) && (prev_end > offset)) { -+ prevc = g_utf8_prev_char (prevc); -+ nextc = g_utf8_prev_char (nextc); -+ -+ gunichar prevch = g_utf8_get_char (prevc); -+ gunichar nextch = g_utf8_get_char (nextc); -+ -+ if (prevch != nextch) { -+ break; -+ } -+ -+ next_end--; -+ prev_end--; -+ } -+ -+ if (offset < prev_end) { -+ gtk_accessible_text_update_contents (GTK_ACCESSIBLE_TEXT (terminal), -+ GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_REMOVE, -+ offset, prev_end); -+ } -+ -+ state->contents_flip = !state->contents_flip; -+ -+ if (offset < next_end) { -+ gtk_accessible_text_update_contents (GTK_ACCESSIBLE_TEXT (terminal), -+ GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_INSERT, -+ offset, next_end); -+ } -+ -+ if (prev->caret != next->caret) { -+ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); -+ } -+} -+ -+static void -+vte_accessible_text_cursor_moved (VteTerminal *terminal, -+ VteAccessibleText *state) -+{ -+ VteAccessibleTextContents *contents = nullptr; -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ -+ contents = &state->contents[state->contents_flip]; -+ -+ long ccol, crow; -+ vte_terminal_get_cursor_position (terminal, &ccol, &crow); -+ if (ccol == contents->cached_caret_column && crow == contents->cached_caret_row) { -+ return; -+ } -+ -+ _vte_debug_print (VTE_DEBUG_ALLY, "Cursor at (%ld, " "%ld).\n", ccol, crow); -+ -+ contents->cached_caret_column = ccol; -+ contents->cached_caret_row = crow; -+ contents->caret = vte_accessible_text_contents_find_caret (contents, ccol, crow); -+ -+ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); -+} -+ -+static void -+vte_accessible_text_window_title_changed (VteTerminal *terminal, -+ VteAccessibleText *state) -+{ -+ const char *window_title; -+ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ -+ window_title = vte_terminal_get_window_title (terminal); -+ -+ gtk_accessible_update_property (GTK_ACCESSIBLE (terminal), -+ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, window_title ? window_title : "", -+ GTK_ACCESSIBLE_VALUE_UNDEFINED); -+} -+ -+static void -+vte_accessible_text_selection_changed (VteTerminal *terminal, -+ VteAccessibleText *state) -+{ -+ g_assert (VTE_IS_TERMINAL (terminal)); -+ g_assert (state != nullptr); -+ g_assert (state->terminal == terminal); -+ -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ -+ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (terminal)); -+ gtk_accessible_text_update_selection_bound (GTK_ACCESSIBLE_TEXT (terminal)); -+} -+ -+void -+_vte_accessible_text_init (GtkAccessibleText *accessible) -+{ -+ VteTerminal *terminal = VTE_TERMINAL (accessible); -+ VteAccessibleText *state; -+ -+ state = g_new0 (VteAccessibleText, 1); -+ state->terminal = terminal; -+ -+ vte_accessible_text_contents_init (&state->contents[0]); -+ vte_accessible_text_contents_init (&state->contents[1]); -+ -+ g_object_set_data_full (G_OBJECT (terminal), -+ "VTE_ACCESSIBLE_TEXT", -+ state, -+ (GDestroyNotify)vte_accessible_text_free); -+ -+ g_signal_connect (terminal, -+ "contents-changed", -+ G_CALLBACK (vte_accessible_text_contents_changed), -+ state); -+ g_signal_connect (terminal, -+ "cursor-moved", -+ G_CALLBACK (vte_accessible_text_cursor_moved), -+ state); -+ g_signal_connect (terminal, -+ "selection-changed", -+ G_CALLBACK (vte_accessible_text_selection_changed), -+ state); -+ g_signal_connect (terminal, -+ "window-title-changed", -+ G_CALLBACK (vte_accessible_text_window_title_changed), -+ state); -+ -+ const char *window_title = vte_terminal_get_window_title (terminal); -+ -+ gtk_accessible_update_property (GTK_ACCESSIBLE (accessible), -+ GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, window_title ? window_title : "", -+ GTK_ACCESSIBLE_PROPERTY_HAS_POPUP, TRUE, -+ GTK_ACCESSIBLE_PROPERTY_LABEL, "Terminal", -+ GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, TRUE, -+ GTK_ACCESSIBLE_VALUE_UNDEFINED); -+} -diff --git a/src/vteaccess-gtk4.h b/src/vteaccess-gtk4.h -new file mode 100644 -index 00000000..37b09c7b ---- /dev/null -+++ b/src/vteaccess-gtk4.h -@@ -0,0 +1,25 @@ -+/* -+ * Copyright © 2024 Christian Hergert -+ * -+ * This library is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU Lesser General Public License as published -+ * by the Free Software Foundation, either version 3 of the License, or -+ * (at your option) any later version. -+ * -+ * This library is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public License -+ * along with this library. If not, see . -+ */ -+ -+#include -+ -+G_BEGIN_DECLS -+ -+void _vte_accessible_text_iface_init (GtkAccessibleTextInterface *iface); -+void _vte_accessible_text_init (GtkAccessibleText *accessible); -+ -+G_END_DECLS -diff --git a/src/vteaccess.cc b/src/vteaccess.cc -index fff918dc..c1c1a4c0 100644 ---- a/src/vteaccess.cc -+++ b/src/vteaccess.cc -@@ -386,6 +386,12 @@ _vte_terminal_accessible_text_modified(VteTerminalAccessible* accessible) - glong offset, caret_offset, olen, clen; - gint old_snapshot_caret; - -+ auto widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)); -+ auto terminal = VTE_TERMINAL(widget); -+ -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ - old_snapshot_caret = priv->snapshot_caret; - priv->snapshot_contents_invalid = TRUE; - vte_terminal_accessible_update_private_data_if_needed(accessible, -@@ -541,6 +547,9 @@ _vte_terminal_accessible_text_scrolled(VteTerminalAccessible* accessible, - auto widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)); - auto terminal = VTE_TERMINAL(widget); - -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ - row_count = vte_terminal_get_row_count(terminal); - if (((howmuch < 0) && (howmuch <= -row_count)) || - ((howmuch > 0) && (howmuch >= row_count))) { -@@ -793,6 +802,9 @@ vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data) - VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; - VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); - -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ - _vte_debug_print(VTE_DEBUG_ALLY, - "Invalidating accessibility cursor.\n"); - priv->snapshot_caret_invalid = TRUE; -@@ -807,6 +819,9 @@ vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data) - { - VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; - -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ - atk_object_set_description(ATK_OBJECT(accessible), vte_terminal_get_window_title(terminal)); - } - -@@ -820,6 +835,9 @@ vte_terminal_accessible_visibility_notify(VteTerminal *terminal, - GtkWidget *widget; - gboolean visible; - -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return FALSE; -+ - visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED; - /* The VISIBLE state indicates that this widget is "visible". */ - atk_object_notify_state_change(ATK_OBJECT(accessible), -@@ -851,6 +869,9 @@ vte_terminal_accessible_selection_changed (VteTerminal *terminal, - { - VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; - -+ if (!vte_terminal_get_enable_a11y (terminal)) -+ return; -+ - g_signal_emit_by_name (accessible, "text_selection_changed"); - } - -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index 92eb6881..082f3b95 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -65,9 +65,11 @@ - #include - - #if WITH_A11Y --#if VTE_GTK == 3 --#include "vteaccess.h" --#endif /* VTE_GTK == 3 */ -+# if VTE_GTK == 3 -+# include "vteaccess.h" -+# elif VTE_GTK == 4 -+# include "vteaccess-gtk4.h" -+# endif - #endif /* WITH_A11Y */ - - #if WITH_ICU -@@ -155,6 +157,14 @@ private: - std::shared_ptr m_widget; - }; - -+#if defined(WITH_A11Y) && VTE_GTK == 4 -+# define VTE_IMPLEMENT_ACCESSIBLE \ -+ G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE_TEXT, \ -+ _vte_accessible_text_iface_init) -+#else -+# define VTE_IMPLEMENT_ACCESSIBLE -+#endif -+ - #if VTE_DEBUG - G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, - { -@@ -163,6 +173,7 @@ G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, - } - g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate)); - G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr) -+ VTE_IMPLEMENT_ACCESSIBLE - if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) { - g_printerr("vte_terminal_get_type()\n"); - }) -@@ -173,7 +184,8 @@ G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET, - g_type_add_instance_private(g_define_type_id, sizeof(VteTerminalPrivate)); - } - g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate)); -- G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr)) -+ G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, nullptr) -+ VTE_IMPLEMENT_ACCESSIBLE) - #endif - - static inline auto -@@ -901,6 +913,10 @@ try - gtk_widget_set_has_window(&terminal->widget, FALSE); - #endif - -+#if defined(WITH_A11Y) && VTE_GTK == 4 -+ _vte_accessible_text_init (GTK_ACCESSIBLE_TEXT (terminal)); -+#endif -+ - place = vte_terminal_get_instance_private(terminal); - new (place) VteTerminalPrivate{terminal}; - } -@@ -1011,6 +1027,9 @@ try - case PROP_DELETE_BINDING: - g_value_set_enum (value, widget->delete_binding()); - break; -+ case PROP_ENABLE_A11Y: -+ g_value_set_boolean (value, vte_terminal_get_enable_a11y (terminal)); -+ break; - case PROP_ENABLE_BIDI: - g_value_set_boolean (value, vte_terminal_get_enable_bidi (terminal)); - break; -@@ -1166,6 +1185,9 @@ try - case PROP_DELETE_BINDING: - vte_terminal_set_delete_binding (terminal, (VteEraseBinding)g_value_get_enum (value)); - break; -+ case PROP_ENABLE_A11Y: -+ vte_terminal_set_enable_a11y (terminal, g_value_get_boolean (value)); -+ break; - case PROP_ENABLE_BIDI: - vte_terminal_set_enable_bidi (terminal, g_value_get_boolean (value)); - break; -@@ -2226,6 +2248,22 @@ vte_terminal_class_init(VteTerminalClass *klass) - VTE_ERASE_AUTO, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); - -+ /** -+ * VteTerminal:enable-a11y: -+ * -+ * Controls whether or not a11y is enabled for the widget. -+ * -+ * Since: 0.78 -+ */ -+ pspecs[PROP_ENABLE_A11Y] = -+ g_param_spec_boolean ("enable-a11y", NULL, NULL, -+#if VTE_GTK == 3 -+ TRUE, -+#else -+ FALSE, -+#endif -+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); -+ - /** - * VteTerminal:enable-bidi: - * -@@ -2631,10 +2669,12 @@ vte_terminal_class_init(VteTerminalClass *klass) - err.assert_no_error(); - #endif - --#if VTE_GTK == 3 - #if WITH_A11Y -+#if VTE_GTK == 3 - /* a11y */ - gtk_widget_class_set_accessible_type(widget_class, VTE_TYPE_TERMINAL_ACCESSIBLE); -+#elif VTE_GTK == 4 -+ gtk_widget_class_set_accessible_role(widget_class, GTK_ACCESSIBLE_ROLE_TERMINAL); - #endif - #endif - } -@@ -5578,6 +5618,53 @@ catch (...) - vte::log_exception(); - } - -+/** -+ * vte_terminal_get_enable_a11y: -+ * @terminal: a #VteTerminal -+ * -+ * Checks whether the terminal communicates with a11y backends -+ * -+ * Returns: %TRUE if a11y is enabled, %FALSE if not -+ * -+ * Since: 0.78 -+ */ -+gboolean -+vte_terminal_get_enable_a11y(VteTerminal *terminal) noexcept -+try -+{ -+ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), false); -+ return IMPL(terminal)->m_enable_a11y; -+} -+catch (...) -+{ -+ vte::log_exception(); -+ return false; -+} -+ -+/** -+ * vte_terminal_set_enable_a11y: -+ * @terminal: a #VteTerminal -+ * @enable_a11y: %TRUE to enable a11y support -+ * -+ * Controls whether or not the terminal will communicate with a11y backends. -+ * -+ * Since: 0.78 -+ */ -+void -+vte_terminal_set_enable_a11y(VteTerminal *terminal, -+ gboolean enable_a11y) noexcept -+try -+{ -+ g_return_if_fail(VTE_IS_TERMINAL(terminal)); -+ -+ if (IMPL(terminal)->set_enable_a11y(enable_a11y != FALSE)) -+ g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ENABLE_A11Y]); -+} -+catch (...) -+{ -+ vte::log_exception(); -+} -+ - /** - * vte_terminal_get_enable_bidi: - * @terminal: a #VteTerminal -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index 1d1383af..a73957a0 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -75,6 +75,7 @@ enum { - PROP_CURRENT_DIRECTORY_URI, - PROP_CURRENT_FILE_URI, - PROP_DELETE_BINDING, -+ PROP_ENABLE_A11Y, - PROP_ENABLE_BIDI, - PROP_ENABLE_FALLBACK_SCROLLING, - PROP_ENABLE_SHAPING, -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index ed57ad16..4f8ade65 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -776,6 +776,13 @@ public: - const char *m_hyperlink_hover_uri; /* data is owned by the ring */ - long m_hyperlink_auto_id{0}; - -+ /* Accessibility support */ -+#if VTE_GTK == 3 -+ bool m_enable_a11y{true}; -+#elif VTE_GTK == 4 -+ bool m_enable_a11y{false}; -+#endif -+ - /* RingView and friends */ - vte::base::RingView m_ringview; - bool m_enable_bidi{true}; -@@ -1505,6 +1512,7 @@ public: - bool set_cursor_style(CursorStyle style); - bool set_delete_binding(EraseMode binding); - auto delete_binding() const noexcept { return m_delete_binding; } -+ bool set_enable_a11y(bool setting); - bool set_enable_bidi(bool setting); - bool set_enable_shaping(bool setting); - bool set_encoding(char const* codeset, --- -2.44.0 - - -From 64ea71aef8878971fbca1c5f97b38be77a848eeb Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Wed, 7 Jan 2015 16:01:00 +0100 -Subject: [PATCH 02/12] Add sequences and signals for desktop notification - -Add sequences - OSC 777 ; notify ; SUMMARY ; BODY BEL - OSC 777 ; notify ; SUMMARY BEL - OSC 777 ; notify ; SUMMARY ; BODY ST - OSC 777 ; notify ; SUMMARY ST - -that let terminal applications send a notification to the desktop -environment. - -Based on Enlightenment's Terminology: -https://phab.enlightenment.org/T1765 - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 ---- - src/marshal.list | 1 + - src/vte.cc | 10 ++++++++++ - src/vte/vteterminal.h | 4 +++- - src/vtegtk.cc | 25 +++++++++++++++++++++++++ - src/vtegtk.hh | 3 +++ - src/vteinternal.hh | 14 ++++++++++++++ - src/vteseq.cc | 37 +++++++++++++++++++++++++++++++++++++ - 7 files changed, 93 insertions(+), 1 deletion(-) - -diff --git a/src/marshal.list b/src/marshal.list -index 241128c3..4412cf3d 100644 ---- a/src/marshal.list -+++ b/src/marshal.list -@@ -1,3 +1,4 @@ - VOID:STRING,BOXED -+VOID:STRING,STRING - VOID:STRING,UINT - VOID:UINT,UINT -diff --git a/src/vte.cc b/src/vte.cc -index 6f5ed505..dd24a147 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10784,6 +10784,16 @@ Terminal::emit_pending_signals() - - emit_adjustment_changed(); - -+#if _VTE_GTK == 3 -+ if (m_pending_changes & vte::to_integral(PendingChanges::NOTIFICATION)) { -+ _vte_debug_print (VTE_DEBUG_SIGNALS, -+ "Emitting `notification-received'.\n"); -+ g_signal_emit(freezer.get(), signals[SIGNAL_NOTIFICATION_RECEIVED], 0, -+ m_notification_summary.c_str(), -+ m_notification_body.c_str()); -+ } -+#endif -+ - if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) { - if (m_window_title != m_window_title_pending) { - m_window_title.swap(m_window_title_pending); -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index 33360909..36a32881 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -114,9 +114,11 @@ struct _VteTerminalClass { - void (*bell)(VteTerminal* terminal); - - #if _VTE_GTK == 3 -+ void (*notification_received)(VteTerminal* terminal, const gchar *summary, const gchar *body); -+ - /* Compatibility padding due to fedora patches intruding on our ABI */ - /*< private >*/ -- gpointer _extra_padding[3]; -+ gpointer _extra_padding[2]; - #endif /* _VTE_GTK == 3 */ - - void (*setup_context_menu)(VteTerminal* terminal, -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index 082f3b95..e253a379 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -1373,6 +1373,9 @@ vte_terminal_class_init(VteTerminalClass *klass) - klass->child_exited = NULL; - klass->encoding_changed = NULL; - klass->char_size_changed = NULL; -+#if _VTE_GTK == 3 -+ klass->notification_received = NULL; -+#endif - klass->window_title_changed = NULL; - klass->icon_title_changed = NULL; - klass->selection_changed = NULL; -@@ -1456,6 +1459,28 @@ vte_terminal_class_init(VteTerminalClass *klass) - G_OBJECT_CLASS_TYPE(klass), - g_cclosure_marshal_VOID__INTv); - -+#if _VTE_GTK == 3 -+ /** -+ * VteTerminal::notification-received: -+ * @vteterminal: the object which received the signal -+ * @summary: The summary -+ * @body: (allow-none): Extra optional text -+ * -+ * Emitted when a process running in the terminal wants to -+ * send a notification to the desktop environment. -+ */ -+ signals[SIGNAL_NOTIFICATION_RECEIVED] = -+ g_signal_new(I_("notification-received"), -+ G_OBJECT_CLASS_TYPE(klass), -+ G_SIGNAL_RUN_LAST, -+ G_STRUCT_OFFSET(VteTerminalClass, notification_received), -+ NULL, -+ NULL, -+ _vte_marshal_VOID__STRING_STRING, -+ G_TYPE_NONE, -+ 2, G_TYPE_STRING, G_TYPE_STRING); -+#endif -+ - /** - * VteTerminal::window-title-changed: - * @vteterminal: the object which received the signal -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index a73957a0..bd49ed9b 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -53,6 +53,9 @@ enum { - SIGNAL_RESTORE_WINDOW, - SIGNAL_SELECTION_CHANGED, - SIGNAL_SETUP_CONTEXT_MENU, -+#if _VTE_GTK == 3 -+ SIGNAL_NOTIFICATION_RECEIVED, -+#endif - SIGNAL_WINDOW_TITLE_CHANGED, - LAST_SIGNAL - }; -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index 4f8ade65..d7c82654 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -710,6 +710,12 @@ public: - gboolean m_cursor_moved_pending; - gboolean m_contents_changed_pending; - -+#if _VTE_GTK == 3 -+ /* desktop notification */ -+ std::string m_notification_summary; -+ std::string m_notification_body; -+#endif -+ - std::string m_window_title{}; - std::string m_current_directory_uri{}; - std::string m_current_file_uri{}; -@@ -723,6 +729,9 @@ public: - TITLE = 1u << 0, - CWD = 1u << 1, - CWF = 1u << 2, -+#if _VTE_GTK == 3 -+ NOTIFICATION = 1u << 3, -+#endif - }; - unsigned m_pending_changes{0}; - -@@ -1662,6 +1671,11 @@ public: - int osc) noexcept; - - /* OSC handlers */ -+#if _VTE_GTK == 3 -+ void handle_urxvt_extension(vte::parser::Sequence const& seq, -+ vte::parser::StringTokeniser::const_iterator& token, -+ vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept; -+#endif - void set_color(vte::parser::Sequence const& seq, - vte::parser::StringTokeniser::const_iterator& token, - vte::parser::StringTokeniser::const_iterator const& endtoken, -diff --git a/src/vteseq.cc b/src/vteseq.cc -index 904837e1..7702c477 100644 ---- a/src/vteseq.cc -+++ b/src/vteseq.cc -@@ -1276,6 +1276,35 @@ Terminal::erase_in_line(vte::parser::Sequence const& seq) - m_text_deleted_flag = TRUE; - } - -+#if _VTE_GTK == 3 -+void -+Terminal::handle_urxvt_extension(vte::parser::Sequence const& seq, -+ vte::parser::StringTokeniser::const_iterator& token, -+ vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept -+{ -+ if (token == endtoken) -+ return; -+ -+ if (*token == "notify") { -+ ++token; -+ -+ if (token == endtoken) -+ return; -+ -+ m_notification_summary = *token; -+ m_notification_body.clear(); -+ m_pending_changes |= vte::to_integral(PendingChanges::NOTIFICATION); -+ ++token; -+ -+ if (token == endtoken) -+ return; -+ -+ m_notification_body = *token; -+ return; -+ } -+} -+#endif -+ - bool - Terminal::get_osc_color_index(int osc, - int value, -@@ -6596,6 +6625,12 @@ Terminal::OSC(vte::parser::Sequence const& seq) - reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_ESCAPE); - break; - -+#if _VTE_GTK == 3 -+ case VTE_OSC_URXVT_EXTENSION: -+ handle_urxvt_extension(seq, it, cend); -+ break; -+#endif -+ - case VTE_OSC_XTERM_SET_ICON_TITLE: - case VTE_OSC_XTERM_SET_XPROPERTY: - case VTE_OSC_XTERM_SET_COLOR_MOUSE_CURSOR_FG: -@@ -6636,7 +6671,9 @@ Terminal::OSC(vte::parser::Sequence const& seq) - case VTE_OSC_URXVT_SET_FONT_BOLD_ITALIC: - case VTE_OSC_URXVT_VIEW_UP: - case VTE_OSC_URXVT_VIEW_DOWN: -+#if _VTE_GTK != 3 - case VTE_OSC_URXVT_EXTENSION: -+#endif - case VTE_OSC_YF_RQGWR: - default: - break; --- -2.44.0 - - -From d129d56171c4059a8622a63e7a27ee9306590377 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Thu, 29 Jan 2015 13:09:17 +0100 -Subject: [PATCH 03/12] vte.sh: Emit OSC 777 from PROMPT_COMMAND - -For some reason, the three consecutive backslashes break the parsing. -As Christian Persch suggested, replacing the double quotes with -singles fixes it. - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 ---- - src/vte.sh.in | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/src/vte.sh.in b/src/vte.sh.in -index 2328a9ec..157bd29c 100644 ---- a/src/vte.sh.in -+++ b/src/vte.sh.in -@@ -33,10 +33,12 @@ __vte_osc7 () { - } - - __vte_prompt_command() { -+ local command=$(HISTTIMEFORMAT= history 1 | sed 's/^ *[0-9]\+ *//') -+ command="${command//;/ }" - local pwd='~' - [ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/} - pwd="${pwd//[[:cntrl:]]}" -- printf "\033]0;%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${pwd}" -+ printf '\033]777;notify;Command completed;%s\033\\\033]0;%s@%s:%s\033\\' "${command}" "${USER}" "${HOSTNAME%%.*}" "${pwd}" - __vte_osc7 - } - --- -2.44.0 - - -From da46de7cb882f7682d3605f374730ddccd9dfa0d Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Thu, 22 Jan 2015 16:37:10 +0100 -Subject: [PATCH 04/12] Test the notification-received signal - ---- - src/app/app.cc | 11 ++++++++++- - 1 file changed, 10 insertions(+), 1 deletion(-) - -diff --git a/src/app/app.cc b/src/app/app.cc -index e4e7b978..875022e9 100644 ---- a/src/app/app.cc -+++ b/src/app/app.cc -@@ -2943,6 +2943,14 @@ window_window_title_changed_cb(VteTerminal* terminal, - gtk_window_set_title(GTK_WINDOW(window), title && title[0] ? title : "Terminal"); - } - -+static void -+notification_received_cb(VteTerminal *terminal, -+ const gchar *summary, -+ const gchar *body) -+{ -+ g_print("[%s]: %s\n", summary, body); -+} -+ - static void - window_lower_window_cb(VteTerminal* terminal, - VteappWindow* window) -@@ -3234,8 +3242,9 @@ vteapp_window_constructed(GObject *object) - if (options.object_notifications) - g_signal_connect(window->terminal, "notify", G_CALLBACK(window_notify_cb), window); - -- /* Settings */ - #if VTE_GTK == 3 -+ g_signal_connect(window->terminal, "notification-received", G_CALLBACK(notification_received_cb), NULL); -+ - if (!options.double_buffer) { - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; - gtk_widget_set_double_buffered(GTK_WIDGET(window->terminal), false); --- -2.44.0 - - -From 6a61519759cc00a7e8e2469eb4bebcfe39307215 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Fri, 13 May 2016 17:53:54 +0200 -Subject: [PATCH 05/12] Add a property to configure the scroll speed - -By default, it is set to zero which gives the current behaviour of -moving the buffer by a function of the number of visible rows. - -https://bugzilla.redhat.com/show_bug.cgi?id=1103380 ---- - src/vte.cc | 25 ++++++++++++++++++ - src/vte/vteterminal.h | 6 +++++ - src/vtegtk.cc | 59 +++++++++++++++++++++++++++++++++++++++++++ - src/vtegtk.hh | 3 +++ - src/vteinternal.hh | 6 +++++ - 5 files changed, 99 insertions(+) - -diff --git a/src/vte.cc b/src/vte.cc -index dd24a147..24deaead 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10033,6 +10033,9 @@ vte_cairo_get_clip_region(cairo_t *cr) - bool - Terminal::widget_mouse_scroll(vte::platform::ScrollEvent const& event) - { -+#if _VTE_GTK == 3 -+ gdouble scroll_speed; -+#endif - gdouble v; - gint cnt, i; - int button; -@@ -10068,7 +10071,17 @@ Terminal::widget_mouse_scroll(vte::platform::ScrollEvent const& event) - return true; - } - -+#if _VTE_GTK == 3 -+ if (m_scroll_speed == 0) { -+ scroll_speed = ceil (m_row_count /* page increment */ / 10.); -+ } else { -+ scroll_speed = m_scroll_speed; -+ } -+ -+ v = MAX (1., scroll_speed); -+#else - v = MAX (1., ceil (m_row_count /* page increment */ / 10.)); -+#endif - _vte_debug_print(VTE_DEBUG_EVENTS, - "Scroll speed is %d lines per non-smooth scroll unit\n", - (int) v); -@@ -10399,6 +10412,18 @@ Terminal::decscusr_cursor_shape() const noexcept - } - } - -+#if _VTE_GTK == 3 -+bool -+Terminal::set_scroll_speed(unsigned int scroll_speed) -+{ -+ if (scroll_speed == m_scroll_speed) -+ return false; -+ -+ m_scroll_speed = scroll_speed; -+ return true; -+} -+#endif -+ - bool - Terminal::set_scrollback_lines(long lines) - { -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index 36a32881..ec56a0ce 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -343,6 +343,12 @@ void vte_terminal_set_cursor_shape(VteTerminal *terminal, - _VTE_PUBLIC - VteCursorShape vte_terminal_get_cursor_shape(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); - -+#if _VTE_GTK == 3 -+_VTE_PUBLIC -+void vte_terminal_set_scroll_speed(VteTerminal *terminal, -+ guint scroll_speed) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+#endif -+ - /* Set the number of scrollback lines, above or at an internal minimum. */ - _VTE_PUBLIC - void vte_terminal_set_scrollback_lines(VteTerminal *terminal, -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index e253a379..a22a7049 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -1072,6 +1072,11 @@ try - case PROP_REWRAP_ON_RESIZE: - g_value_set_boolean (value, vte_terminal_get_rewrap_on_resize (terminal)); - break; -+#if _VTE_GTK == 3 -+ case PROP_SCROLL_SPEED: -+ g_value_set_uint (value, impl->m_scroll_speed); -+ break; -+#endif - case PROP_SCROLLBACK_LINES: - g_value_set_uint (value, vte_terminal_get_scrollback_lines(terminal)); - break; -@@ -1225,6 +1230,11 @@ try - case PROP_REWRAP_ON_RESIZE: - vte_terminal_set_rewrap_on_resize (terminal, g_value_get_boolean (value)); - break; -+#if _VTE_GTK == 3 -+ case PROP_SCROLL_SPEED: -+ vte_terminal_set_scroll_speed (terminal, g_value_get_uint (value)); -+ break; -+#endif - case PROP_SCROLLBACK_LINES: - vte_terminal_set_scrollback_lines (terminal, g_value_get_uint (value)); - break; -@@ -2444,6 +2454,23 @@ vte_terminal_class_init(VteTerminalClass *klass) - TRUE, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); - -+#if _VTE_GTK == 3 -+ /** -+ * VteTerminal:scroll-speed: -+ * -+ * The number of lines by which the buffer is moved when -+ * scrolling with a mouse wheel on top of the terminal -+ * Setting it to zero will cause the buffer to be moved by an -+ * amount depending on the number of visible rows the widget -+ * can display. -+ */ -+ pspecs[PROP_SCROLL_SPEED] = -+ g_param_spec_uint ("scroll-speed", NULL, NULL, -+ 0, G_MAXUINT, -+ 0, -+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); -+#endif -+ - /** - * VteTerminal:scrollback-lines: - * -@@ -6503,6 +6530,38 @@ catch (...) - return -1; - } - -+#if _VTE_GTK == 3 -+/** -+ * vte_terminal_set_scroll_speed: -+ * @terminal: a #VteTerminal -+ * @scroll_speed: move the buffer by this number of lines while scrolling -+ * -+ * Sets the number of lines by which the buffer is moved when -+ * scrolling with a mouse wheel. Setting it to zero will cause the -+ * buffer to be moved by an amount depending on the number of visible -+ * rows the widget can display. -+ */ -+void -+vte_terminal_set_scroll_speed(VteTerminal *terminal, -+ guint scroll_speed) noexcept -+try -+{ -+ g_return_if_fail(VTE_IS_TERMINAL(terminal)); -+ -+ GObject *object = G_OBJECT(terminal); -+ g_object_freeze_notify(object); -+ -+ if (IMPL(terminal)->set_scroll_speed(scroll_speed)) -+ g_object_notify_by_pspec(object, pspecs[PROP_SCROLL_SPEED]); -+ -+ g_object_thaw_notify(object); -+} -+catch (...) -+{ -+ vte::log_exception(); -+} -+#endif -+ - /** - * vte_terminal_set_scrollback_lines: - * @terminal: a #VteTerminal -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index bd49ed9b..125c109d 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -93,6 +93,9 @@ enum { - PROP_MOUSE_POINTER_AUTOHIDE, - PROP_PTY, - PROP_REWRAP_ON_RESIZE, -+#if _VTE_GTK == 3 -+ PROP_SCROLL_SPEED, -+#endif - PROP_SCROLLBACK_LINES, - PROP_SCROLL_ON_INSERT, - PROP_SCROLL_ON_KEYSTROKE, -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index d7c82654..53737f65 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -471,6 +471,9 @@ public: - bool m_scroll_on_insert{false}; - bool m_scroll_on_output{false}; - bool m_scroll_on_keystroke{true}; -+#if _VTE_GTK == 3 -+ guint m_scroll_speed; -+#endif - vte::grid::row_t m_scrollback_lines{0}; - - inline auto scroll_limit_lower() const noexcept -@@ -1534,6 +1537,9 @@ public: - bool set_input_enabled(bool enabled); - bool set_mouse_autohide(bool autohide); - bool set_rewrap_on_resize(bool rewrap); -+#if _VTE_GTK == 3 -+ bool set_scroll_speed(unsigned int scroll_speed); -+#endif - bool set_scrollback_lines(long lines); - bool set_fallback_scrolling(bool set); - auto fallback_scrolling() const noexcept { return m_fallback_scrolling; } --- -2.44.0 - - -From a3a76d2dcd4f48dbe4c19c653dbd647d2c9d4e4d Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Fri, 13 May 2016 17:54:57 +0200 -Subject: [PATCH 06/12] Test the scroll-speed property - -https://bugzilla.redhat.com/show_bug.cgi?id=1103380 ---- - src/app/app.cc | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/src/app/app.cc b/src/app/app.cc -index 875022e9..f1b96a33 100644 ---- a/src/app/app.cc -+++ b/src/app/app.cc -@@ -135,6 +135,9 @@ public: - int verbosity{0}; - double cell_height_scale{1.0}; - double cell_width_scale{1.0}; -+#if _VTE_GTK == 3 -+ unsigned int scroll_speed{0}; -+#endif - VteCursorBlinkMode cursor_blink_mode{VTE_CURSOR_BLINK_SYSTEM}; - VteCursorShape cursor_shape{VTE_CURSOR_SHAPE_BLOCK}; - VteTextBlinkMode text_blink_mode{VTE_TEXT_BLINK_ALWAYS}; -@@ -1309,6 +1312,12 @@ public: - "Enable the setting of the icon title", nullptr }, - { "output-file", 0, 0, G_OPTION_ARG_FILENAME, &output_filename, - "Save terminal contents to file at exit", nullptr }, -+#if _VTE_GTK == 3 -+ { "scroll-speed", 0, 0, G_OPTION_ARG_INT, &scroll_speed, -+ "Specify the scroll speed", nullptr }, -+#endif -+ { "scroll-unit-is-pixels", 0, 0, G_OPTION_ARG_NONE, &scroll_unit_is_pixels, -+ "Use pixels as scroll unit", nullptr }, - { "scrollback-lines", 'n', 0, G_OPTION_ARG_INT, &scrollback_lines, - "Specify the number of scrollback-lines (-1 for infinite)", nullptr }, - { "title", 0, 0, G_OPTION_ARG_STRING, &title, "Set the initial title of the window", "TITLE" }, -@@ -3279,6 +3288,9 @@ vteapp_window_constructed(GObject *object) - vte_terminal_set_scroll_on_insert(window->terminal, options.scroll_on_insert); - vte_terminal_set_scroll_on_output(window->terminal, options.scroll_on_output); - vte_terminal_set_scroll_on_keystroke(window->terminal, options.scroll_on_keystroke); -+#if _VTE_GTK == 3 -+ vte_terminal_set_scroll_speed(window->terminal, options.scroll_speed); -+#endif - vte_terminal_set_scroll_unit_is_pixels(window->terminal, options.scroll_unit_is_pixels); - vte_terminal_set_scrollback_lines(window->terminal, options.scrollback_lines); - vte_terminal_set_text_blink_mode(window->terminal, options.text_blink_mode); --- -2.44.0 - - -From 43dae28d61af42ccaa92854eaa64d5ba7d6d466d Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Wed, 7 Jan 2015 16:01:00 +0100 -Subject: [PATCH 07/12] Support preexec notifications from an interactive shell - -Add sequences - OSC 777 ; preexec BEL - OSC 777 ; preexec ST - -that can be used from an interactive shell's preexec hook to notify -the terminal emulator that a new command is about to be executed. -Examples of such hooks are Bash's PS0 and Zsh's preexec. - -The OSC 777 escape sequence is taken from Enlightenment's Terminology: -https://phab.enlightenment.org/T1765 - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 -https://bugzilla.gnome.org/show_bug.cgi?id=711060 ---- - src/vte.cc | 6 ++++++ - src/vte.sh.in | 4 ++-- - src/vte/vteterminal.h | 3 ++- - src/vtegtk.cc | 18 ++++++++++++++++++ - src/vtegtk.hh | 1 + - src/vteinternal.hh | 1 + - src/vteseq.cc | 4 ++++ - 7 files changed, 34 insertions(+), 3 deletions(-) - -diff --git a/src/vte.cc b/src/vte.cc -index 24deaead..e9e51434 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10817,6 +10817,12 @@ Terminal::emit_pending_signals() - m_notification_summary.c_str(), - m_notification_body.c_str()); - } -+ -+ if (m_pending_changes & vte::to_integral(PendingChanges::SHELL_PREEXEC)) { -+ _vte_debug_print (VTE_DEBUG_SIGNALS, -+ "Emitting `shell-preexec'.\n"); -+ g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PREEXEC], 0); -+ } - #endif - - if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) { -diff --git a/src/vte.sh.in b/src/vte.sh.in -index 157bd29c..ffc0942a 100644 ---- a/src/vte.sh.in -+++ b/src/vte.sh.in -@@ -51,9 +51,9 @@ if [[ -n "${BASH_VERSION:-}" ]]; then - # use the __vte_prompt_command function which also sets the title. - - if [[ "$(declare -p PROMPT_COMMAND 2>&1)" =~ "declare -a" ]]; then -- PROMPT_COMMAND+=(__vte_osc7) -+ PROMPT_COMMAND+=(__vte_osc7) && PS0=$(printf "\033]777;preexec\033\\") - else -- PROMPT_COMMAND="__vte_prompt_command" -+ PROMPT_COMMAND="__vte_prompt_command" && PS0=$(printf "\033]777;preexec\033\\") - fi - - # Shell integration -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index ec56a0ce..efd8732b 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -115,10 +115,11 @@ struct _VteTerminalClass { - - #if _VTE_GTK == 3 - void (*notification_received)(VteTerminal* terminal, const gchar *summary, const gchar *body); -+ void (*shell_preexec)(VteTerminal* terminal); - - /* Compatibility padding due to fedora patches intruding on our ABI */ - /*< private >*/ -- gpointer _extra_padding[2]; -+ gpointer _extra_padding[1]; - #endif /* _VTE_GTK == 3 */ - - void (*setup_context_menu)(VteTerminal* terminal, -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index a22a7049..f8d971c1 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -1385,6 +1385,7 @@ vte_terminal_class_init(VteTerminalClass *klass) - klass->char_size_changed = NULL; - #if _VTE_GTK == 3 - klass->notification_received = NULL; -+ klass->shell_preexec = NULL; - #endif - klass->window_title_changed = NULL; - klass->icon_title_changed = NULL; -@@ -1489,6 +1490,23 @@ vte_terminal_class_init(VteTerminalClass *klass) - _vte_marshal_VOID__STRING_STRING, - G_TYPE_NONE, - 2, G_TYPE_STRING, G_TYPE_STRING); -+ -+ /** -+ * VteTerminal::shell-preexec: -+ * @vteterminal: the object which received the signal -+ * -+ * Emitted when the interactive shell has read in a complete -+ * command and is about to execute it. -+ */ -+ signals[SIGNAL_SHELL_PREEXEC] = -+ g_signal_new(I_("shell-preexec"), -+ G_OBJECT_CLASS_TYPE(klass), -+ G_SIGNAL_RUN_LAST, -+ G_STRUCT_OFFSET(VteTerminalClass, shell_preexec), -+ NULL, -+ NULL, -+ g_cclosure_marshal_VOID__VOID, -+ G_TYPE_NONE, 0); - #endif - - /** -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index 125c109d..6252a2c8 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -54,6 +54,7 @@ enum { - SIGNAL_SELECTION_CHANGED, - SIGNAL_SETUP_CONTEXT_MENU, - #if _VTE_GTK == 3 -+ SIGNAL_SHELL_PREEXEC, - SIGNAL_NOTIFICATION_RECEIVED, - #endif - SIGNAL_WINDOW_TITLE_CHANGED, -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index 53737f65..987aec0e 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -734,6 +734,7 @@ public: - CWF = 1u << 2, - #if _VTE_GTK == 3 - NOTIFICATION = 1u << 3, -+ SHELL_PREEXEC = 1u << 4, - #endif - }; - unsigned m_pending_changes{0}; -diff --git a/src/vteseq.cc b/src/vteseq.cc -index 7702c477..17fb51f5 100644 ---- a/src/vteseq.cc -+++ b/src/vteseq.cc -@@ -1302,6 +1302,10 @@ Terminal::handle_urxvt_extension(vte::parser::Sequence const& seq, - m_notification_body = *token; - return; - } -+ -+ if (*token == "preexec") { -+ m_pending_changes |= vte::to_integral(PendingChanges::SHELL_PREEXEC); -+ } - } - #endif - --- -2.44.0 - - -From 1b4029e85ad578dc602fc23bc5be07cd1341cfd0 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Fri, 20 Apr 2018 18:21:53 +0200 -Subject: [PATCH 08/12] Test the shell-preexec signal - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 -https://bugzilla.gnome.org/show_bug.cgi?id=711060 ---- - src/app/app.cc | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/src/app/app.cc b/src/app/app.cc -index f1b96a33..4ccff275 100644 ---- a/src/app/app.cc -+++ b/src/app/app.cc -@@ -2960,6 +2960,12 @@ notification_received_cb(VteTerminal *terminal, - g_print("[%s]: %s\n", summary, body); - } - -+static void -+shell_preexec_cb(VteTerminal *terminal) -+{ -+ g_print("[shell] executing command\n"); -+} -+ - static void - window_lower_window_cb(VteTerminal* terminal, - VteappWindow* window) -@@ -3253,6 +3259,7 @@ vteapp_window_constructed(GObject *object) - - #if VTE_GTK == 3 - g_signal_connect(window->terminal, "notification-received", G_CALLBACK(notification_received_cb), NULL); -+ g_signal_connect(window->terminal, "shell-preexec", G_CALLBACK(shell_preexec_cb), NULL); - - if (!options.double_buffer) { - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; --- -2.44.0 - - -From 4e78d1bf97245c72aabe8bb971cadfd946c8cc17 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Wed, 2 May 2018 17:20:30 +0200 -Subject: [PATCH 09/12] Support precmd notifications from an interactive shell - -Add sequences - OSC 777 ; precmd BEL - OSC 777 ; precmd ST - -that can be used from an interactive shell's precmd hook to notify the -terminal emulator that a first level prompt is about to be shown. -Examples of such hooks are Bash's PROMPT_COMMAND and Zsh's precmd. - -The OSC 777 escape sequence is taken from Enlightenment's Terminology: -https://phab.enlightenment.org/T1765 - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 -https://bugzilla.gnome.org/show_bug.cgi?id=711060 ---- - src/vte.cc | 6 ++++++ - src/vte.sh.in | 9 ++++++++- - src/vte/vteterminal.h | 5 +---- - src/vtegtk.cc | 18 ++++++++++++++++++ - src/vtegtk.hh | 1 + - src/vteinternal.hh | 1 + - src/vteseq.cc | 4 +++- - 7 files changed, 38 insertions(+), 6 deletions(-) - -diff --git a/src/vte.cc b/src/vte.cc -index e9e51434..94a9db05 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10823,6 +10823,12 @@ Terminal::emit_pending_signals() - "Emitting `shell-preexec'.\n"); - g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PREEXEC], 0); - } -+ -+ if (m_pending_changes & vte::to_integral(PendingChanges::SHELL_PRECMD)) { -+ _vte_debug_print (VTE_DEBUG_SIGNALS, -+ "Emitting `shell-precmd'.\n"); -+ g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PRECMD], 0); -+ } - #endif - - if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) { -diff --git a/src/vte.sh.in b/src/vte.sh.in -index ffc0942a..5b415f5b 100644 ---- a/src/vte.sh.in -+++ b/src/vte.sh.in -@@ -28,6 +28,11 @@ case "$TERM" in - *) return 0 ;; - esac - -+__vte_shell_precmd() { -+ local command=$(HISTTIMEFORMAT= history 1 | sed 's/^ *[0-9]\+ *//') -+ command="${command//;/ }" -+ printf '\033]777;notify;Command completed;%s\033\\\033]777;precmd\033\\' "${command}" -+} - __vte_osc7 () { - printf "\033]7;file://%s%s\033\\" "${HOSTNAME}" "$(@libexecdir@/vte-urlencode-cwd)" - } -@@ -38,7 +43,8 @@ __vte_prompt_command() { - local pwd='~' - [ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/} - pwd="${pwd//[[:cntrl:]]}" -- printf '\033]777;notify;Command completed;%s\033\\\033]0;%s@%s:%s\033\\' "${command}" "${USER}" "${HOSTNAME%%.*}" "${pwd}" -+ printf '\033]777;notify;Command completed;%s\033\\\033]777;precmd\033\\\033]0;%s@%s:%s\033\\' "${command}" "${USER}" "${HOSTNAME%%.*}" "${pwd}" -+ __vte_shell_precmd - __vte_osc7 - } - -@@ -51,6 +57,7 @@ if [[ -n "${BASH_VERSION:-}" ]]; then - # use the __vte_prompt_command function which also sets the title. - - if [[ "$(declare -p PROMPT_COMMAND 2>&1)" =~ "declare -a" ]]; then -+ PROMPT_COMMAND+=(__vte_shell_precmd) - PROMPT_COMMAND+=(__vte_osc7) && PS0=$(printf "\033]777;preexec\033\\") - else - PROMPT_COMMAND="__vte_prompt_command" && PS0=$(printf "\033]777;preexec\033\\") -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index efd8732b..a2ff1b01 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -115,11 +115,8 @@ struct _VteTerminalClass { - - #if _VTE_GTK == 3 - void (*notification_received)(VteTerminal* terminal, const gchar *summary, const gchar *body); -+ void (*shell_precmd)(VteTerminal* terminal); - void (*shell_preexec)(VteTerminal* terminal); -- -- /* Compatibility padding due to fedora patches intruding on our ABI */ -- /*< private >*/ -- gpointer _extra_padding[1]; - #endif /* _VTE_GTK == 3 */ - - void (*setup_context_menu)(VteTerminal* terminal, -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index f8d971c1..d553a002 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -1385,6 +1385,7 @@ vte_terminal_class_init(VteTerminalClass *klass) - klass->char_size_changed = NULL; - #if _VTE_GTK == 3 - klass->notification_received = NULL; -+ klass->shell_precmd = NULL; - klass->shell_preexec = NULL; - #endif - klass->window_title_changed = NULL; -@@ -1491,6 +1492,23 @@ vte_terminal_class_init(VteTerminalClass *klass) - G_TYPE_NONE, - 2, G_TYPE_STRING, G_TYPE_STRING); - -+ /** -+ * VteTerminal::shell-precmd: -+ * @vteterminal: the object which received the signal -+ * -+ * Emitted right before an interactive shell shows a -+ * first-level prompt. -+ */ -+ signals[SIGNAL_SHELL_PRECMD] = -+ g_signal_new(I_("shell-precmd"), -+ G_OBJECT_CLASS_TYPE(klass), -+ G_SIGNAL_RUN_LAST, -+ G_STRUCT_OFFSET(VteTerminalClass, shell_precmd), -+ NULL, -+ NULL, -+ g_cclosure_marshal_VOID__VOID, -+ G_TYPE_NONE, 0); -+ - /** - * VteTerminal::shell-preexec: - * @vteterminal: the object which received the signal -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index 6252a2c8..3798ebc3 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -54,6 +54,7 @@ enum { - SIGNAL_SELECTION_CHANGED, - SIGNAL_SETUP_CONTEXT_MENU, - #if _VTE_GTK == 3 -+ SIGNAL_SHELL_PRECMD, - SIGNAL_SHELL_PREEXEC, - SIGNAL_NOTIFICATION_RECEIVED, - #endif -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index 987aec0e..cfe2e0f0 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -735,6 +735,7 @@ public: - #if _VTE_GTK == 3 - NOTIFICATION = 1u << 3, - SHELL_PREEXEC = 1u << 4, -+ SHELL_PRECMD = 1u << 5, - #endif - }; - unsigned m_pending_changes{0}; -diff --git a/src/vteseq.cc b/src/vteseq.cc -index 17fb51f5..a1155b19 100644 ---- a/src/vteseq.cc -+++ b/src/vteseq.cc -@@ -1303,7 +1303,9 @@ Terminal::handle_urxvt_extension(vte::parser::Sequence const& seq, - return; - } - -- if (*token == "preexec") { -+ if (*token == "precmd") { -+ m_pending_changes |= vte::to_integral(PendingChanges::SHELL_PRECMD); -+ } else if (*token == "preexec") { - m_pending_changes |= vte::to_integral(PendingChanges::SHELL_PREEXEC); - } - } --- -2.44.0 - - -From 9424e6c777ea0a7488457ddc7649a39ee579a68b Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Wed, 2 May 2018 17:30:48 +0200 -Subject: [PATCH 10/12] Test the shell-precmd signal - -https://bugzilla.gnome.org/show_bug.cgi?id=711059 -https://bugzilla.gnome.org/show_bug.cgi?id=711060 ---- - src/app/app.cc | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/src/app/app.cc b/src/app/app.cc -index 4ccff275..bfb3d9f8 100644 ---- a/src/app/app.cc -+++ b/src/app/app.cc -@@ -2960,6 +2960,12 @@ notification_received_cb(VteTerminal *terminal, - g_print("[%s]: %s\n", summary, body); - } - -+static void -+shell_precmd_cb(VteTerminal *terminal) -+{ -+ g_print("[shell] showing command prompt\n"); -+} -+ - static void - shell_preexec_cb(VteTerminal *terminal) - { -@@ -3259,6 +3265,7 @@ vteapp_window_constructed(GObject *object) - - #if VTE_GTK == 3 - g_signal_connect(window->terminal, "notification-received", G_CALLBACK(notification_received_cb), NULL); -+ g_signal_connect(window->terminal, "shell-precmd", G_CALLBACK(shell_precmd_cb), NULL); - g_signal_connect(window->terminal, "shell-preexec", G_CALLBACK(shell_preexec_cb), NULL); - - if (!options.double_buffer) { --- -2.44.0 - - -From 2c262f071a679451312e141d940a8819ece44b52 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Mon, 10 Jun 2019 20:30:18 +0200 -Subject: [PATCH 11/12] Support tracking the active container inside the - terminal - -Add sequences - - OSC 777 ; container ; push ; NAME ; RUNTIME ; UID BEL - OSC 777 ; container ; push ; NAME ; RUNTIME ; UID ST - OSC 777 ; container ; pop ; NAME ; RUNTIME ; UID BEL - OSC 777 ; container ; pop ; NAME ; RUNTIME ; UID ST - - OSC 777 ; container ; push ; NAME ; RUNTIME BEL - OSC 777 ; container ; push ; NAME ; RUNTIME ST - OSC 777 ; container ; pop ; NAME ; RUNTIME BEL - OSC 777 ; container ; pop ; NAME ; RUNTIME ST - -that let container tools notify the terminal emulator when entering and -leaving a container environment. The RUNTIME argument namespaces the -NAME and identifies the container tooling being used. eg., docker, -flatpak, podman, toolbox, etc.. The UID argument is the real UID of the -container tools. Only those containers that are used with the same real -UID as the VTE process are tracked. - -The OSC 777 escape sequence is taken from Enlightenment's Terminology: -https://phab.enlightenment.org/T1765 - -It's a VTE-specific extension until a standard escape sequence is -agreed upon across multiple different terminal emulators [1]. - -[1] https://gitlab.freedesktop.org/terminal-wg/specifications/issues/17 ---- - src/vte.cc | 8 ++++ - src/vte/vteterminal.h | 6 +++ - src/vtegtk.cc | 83 +++++++++++++++++++++++++++++++++++++++ - src/vtegtk.hh | 4 ++ - src/vteinternal.hh | 20 ++++++++++ - src/vteseq.cc | 90 +++++++++++++++++++++++++++++++++++++++++++ - 6 files changed, 211 insertions(+) - -diff --git a/src/vte.cc b/src/vte.cc -index 94a9db05..645bf330 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -10829,6 +10829,14 @@ Terminal::emit_pending_signals() - "Emitting `shell-precmd'.\n"); - g_signal_emit(freezer.get(), signals[SIGNAL_SHELL_PRECMD], 0); - } -+ -+ if (m_pending_changes & vte::to_integral(PendingChanges::CONTAINERS)) { -+ _vte_debug_print(VTE_DEBUG_SIGNALS, -+ "Notifying `current-container-name' and `current-container-runtime'.\n"); -+ -+ g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_CONTAINER_NAME]); -+ g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_CONTAINER_RUNTIME]); -+ } - #endif - - if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) { -diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h -index a2ff1b01..6d07e28e 100644 ---- a/src/vte/vteterminal.h -+++ b/src/vte/vteterminal.h -@@ -571,6 +571,12 @@ _VTE_PUBLIC - glong vte_terminal_get_column_count(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); - _VTE_PUBLIC - const char *vte_terminal_get_window_title(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+#if _VTE_GTK == 3 -+_VTE_PUBLIC -+const char *vte_terminal_get_current_container_name(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+_VTE_PUBLIC -+const char *vte_terminal_get_current_container_runtime(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); -+#endif - _VTE_PUBLIC - const char *vte_terminal_get_current_directory_uri(VteTerminal *terminal) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1); - _VTE_PUBLIC -diff --git a/src/vtegtk.cc b/src/vtegtk.cc -index d553a002..1af238e1 100644 ---- a/src/vtegtk.cc -+++ b/src/vtegtk.cc -@@ -1015,6 +1015,14 @@ try - case PROP_CURSOR_BLINK_MODE: - g_value_set_enum (value, vte_terminal_get_cursor_blink_mode (terminal)); - break; -+#if _VTE_GTK == 3 -+ case PROP_CURRENT_CONTAINER_NAME: -+ g_value_set_string (value, vte_terminal_get_current_container_name (terminal)); -+ break; -+ case PROP_CURRENT_CONTAINER_RUNTIME: -+ g_value_set_string (value, vte_terminal_get_current_container_runtime (terminal)); -+ break; -+#endif - case PROP_CURRENT_DIRECTORY_URI: - g_value_set_string (value, vte_terminal_get_current_directory_uri (terminal)); - break; -@@ -2613,6 +2621,29 @@ vte_terminal_class_init(VteTerminalClass *klass) - NULL, - (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); - -+#if _VTE_GTK == 3 -+ /** -+ * VteTerminal:current-container-name: -+ * -+ * The name of the current container, or %NULL if unset. -+ */ -+ pspecs[PROP_CURRENT_CONTAINER_NAME] = -+ g_param_spec_string ("current-container-name", NULL, NULL, -+ NULL, -+ (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); -+ -+ /** -+ * VteTerminal:current-container-runtime: -+ * -+ * The name of the runtime toolset used to set up the current -+ * container, or %NULL if unset. -+ */ -+ pspecs[PROP_CURRENT_CONTAINER_RUNTIME] = -+ g_param_spec_string ("current-container-runtime", NULL, NULL, -+ NULL, -+ (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); -+#endif -+ - /** - * VteTerminal:current-directory-uri: - * -@@ -5547,6 +5578,58 @@ catch (...) - return -1; - } - -+#if _VTE_GTK == 3 -+/** -+ * vte_terminal_get_current_container_name: -+ * @terminal: a #VteTerminal -+ * -+ * Returns: (nullable) (transfer none): the name of the current -+ * container, or %NULL -+ */ -+const char * -+vte_terminal_get_current_container_name(VteTerminal *terminal) noexcept -+try -+{ -+ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL); -+ auto impl = IMPL(terminal); -+ if (impl->m_containers.empty()) -+ return NULL; -+ -+ const VteContainer &container = impl->m_containers.top(); -+ return container.m_name.c_str(); -+} -+catch (...) -+{ -+ vte::log_exception(); -+ return NULL; -+} -+ -+/** -+ * vte_terminal_get_current_container_runtime: -+ * @terminal: a #VteTerminal -+ * -+ * Returns: (nullable) (transfer none): the name of the runtime -+ * toolset used to set up the current container, or %NULL -+ */ -+const char * -+vte_terminal_get_current_container_runtime(VteTerminal *terminal) noexcept -+try -+{ -+ g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL); -+ auto impl = IMPL(terminal); -+ if (impl->m_containers.empty()) -+ return NULL; -+ -+ const VteContainer &container = impl->m_containers.top(); -+ return container.m_runtime.c_str(); -+} -+catch (...) -+{ -+ vte::log_exception(); -+ return NULL; -+} -+#endif -+ - /** - * vte_terminal_get_current_directory_uri: - * @terminal: a #VteTerminal -diff --git a/src/vtegtk.hh b/src/vtegtk.hh -index 3798ebc3..065a6744 100644 ---- a/src/vtegtk.hh -+++ b/src/vtegtk.hh -@@ -77,6 +77,10 @@ enum { - PROP_CONTEXT_MENU, - PROP_CURSOR_BLINK_MODE, - PROP_CURSOR_SHAPE, -+#if _VTE_GTK == 3 -+ PROP_CURRENT_CONTAINER_NAME, -+ PROP_CURRENT_CONTAINER_RUNTIME, -+#endif - PROP_CURRENT_DIRECTORY_URI, - PROP_CURRENT_FILE_URI, - PROP_DELETE_BINDING, -diff --git a/src/vteinternal.hh b/src/vteinternal.hh -index cfe2e0f0..ccd81bef 100644 ---- a/src/vteinternal.hh -+++ b/src/vteinternal.hh -@@ -63,6 +63,9 @@ - #include - #include - #include -+#if _VTE_GTK == 3 -+#include -+#endif - #include - #include - #include -@@ -121,6 +124,20 @@ typedef enum _VteCharacterReplacement { - VTE_CHARACTER_REPLACEMENT_LINE_DRAWING - } VteCharacterReplacement; - -+#if _VTE_GTK == 3 -+struct VteContainer { -+public: -+ VteContainer(const std::string &name, const std::string &runtime) : -+ m_name{name}, -+ m_runtime{runtime} -+ { -+ } -+ -+ std::string m_name; -+ std::string m_runtime; -+}; -+#endif -+ - typedef struct _VtePaletteColor { - struct { - vte::color::rgb color; -@@ -714,6 +731,8 @@ public: - gboolean m_contents_changed_pending; - - #if _VTE_GTK == 3 -+ std::stack m_containers; -+ - /* desktop notification */ - std::string m_notification_summary; - std::string m_notification_body; -@@ -736,6 +755,7 @@ public: - NOTIFICATION = 1u << 3, - SHELL_PREEXEC = 1u << 4, - SHELL_PRECMD = 1u << 5, -+ CONTAINERS = 1u << 6, - #endif - }; - unsigned m_pending_changes{0}; -diff --git a/src/vteseq.cc b/src/vteseq.cc -index a1155b19..daa24703 100644 ---- a/src/vteseq.cc -+++ b/src/vteseq.cc -@@ -19,6 +19,7 @@ - - #include "config.h" - -+ - #include - #include - #include -@@ -39,6 +40,11 @@ - #define ST_C0 _VTE_CAP_ST - - #include -+#if _VTE_GTK == 3 -+#include -+#include -+#include -+#endif - - using namespace std::literals; - -@@ -1285,6 +1291,90 @@ Terminal::handle_urxvt_extension(vte::parser::Sequence const& seq, - if (token == endtoken) - return; - -+#if _VTE_GTK == 3 -+ if (*token == "container") { -+ ++token; -+ -+ if (token == endtoken) -+ return; -+ -+ const std::string sub_command = *token; -+ ++token; -+ -+ if (sub_command == "pop") { -+ if (token == endtoken) -+ return; -+ -+ ++token; -+ -+ if (token == endtoken) -+ return; -+ -+ ++token; -+ -+ if (token == endtoken) { -+ if (!m_containers.empty()) { -+ m_containers.pop(); -+ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); -+ } -+ -+ return; -+ } -+ -+ const std::string uid_token = *token; -+ ++token; -+ -+ const uid_t uid = getuid(); -+ const std::string uid_str = std::to_string(uid); -+ -+ if (uid_token == uid_str) { -+ if (!m_containers.empty()) { -+ m_containers.pop(); -+ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); -+ } -+ -+ return; -+ } -+ -+ return; -+ } else if (sub_command == "push") { -+ if (token == endtoken) -+ return; -+ -+ const std::string name = *token; -+ ++token; -+ -+ if (token == endtoken) -+ return; -+ -+ const std::string runtime = *token; -+ ++token; -+ -+ if (token == endtoken) { -+ m_containers.emplace(name, runtime); -+ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); -+ return; -+ } -+ -+ const std::string uid_token = *token; -+ ++token; -+ -+ const uid_t uid = getuid(); -+ const std::string uid_str = std::to_string(uid); -+ -+ if (uid_token == uid_str) { -+ m_containers.emplace(name, runtime); -+ m_pending_changes |= vte::to_integral(PendingChanges::CONTAINERS); -+ return; -+ } -+ -+ return; -+ } -+ -+ return; -+ } -+#endif -+ - if (*token == "notify") { - ++token; - --- -2.44.0 - - -From 71a9ae3e685b492d50a23604865388cba7592d54 Mon Sep 17 00:00:00 2001 -From: Kalev Lember -Date: Tue, 16 Feb 2021 16:30:44 +0100 -Subject: [PATCH 12/12] Revert "widget: Limit select-all to the writable region - not including the scrollback" - -... as decided by Fedora Workstation WG. - -https://pagure.io/fedora-workstation/issue/216 - -This reverts commit 73713ec0644e232fb740170e399282be778d97f9. ---- - src/vte.cc | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/vte.cc b/src/vte.cc -index 645bf330..d8f0faec 100644 ---- a/src/vte.cc -+++ b/src/vte.cc -@@ -7081,8 +7081,8 @@ Terminal::select_all() - - m_selecting_had_delta = TRUE; - -- m_selection_resolved.set ({ (vte::grid::row_t)m_screen->row_data->delta(), 0 }, -- { (vte::grid::row_t)m_screen->row_data->next(), 0 }); -+ m_selection_resolved.set ({ (long)m_screen->row_data->delta(), 0 }, -+ { (long)m_screen->row_data->next(), 0 }); - - _vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n"); - --- -2.44.0 - diff --git a/vte291.spec b/vte291.spec index 7008c8b..a664d79 100644 --- a/vte291.spec +++ b/vte291.spec @@ -11,7 +11,7 @@ %global pcre2_version 10.21 Name: vte291 -Version: 0.76.2 +Version: 0.76.3 Release: 1%{?dist} Summary: GTK+ 3 terminal emulator library @@ -24,7 +24,8 @@ Source0: https://download.gnome.org/sources/vte/0.76/vte-%{version}.tar.x # https://bugzilla.gnome.org/show_bug.cgi?id=711059 # https://bugzilla.redhat.com/show_bug.cgi?id=1103380 # https://pagure.io/fedora-workstation/issue/216 -Patch0: vte291-cntnr-precmd-preexec-scroll.patch +Patch: 0001-a11y-implement-GtkAccessibleText.patch +Patch: 0001-add-notification-and-shell-precmd-preexec.patch BuildRequires: pkgconfig(fribidi) >= %{fribidi_version} BuildRequires: pkgconfig(gio-2.0) >= %{glib2_version} @@ -179,6 +180,12 @@ sed -i -e "/^vte_systemduserunitdir =/s|vte_prefix|'/usr'|" meson.build %{_sysconfdir}/profile.d/vte.sh %changelog +* Mon Jun 10 2024 David King - 0.76.3-1 +- Update to 0.76.3 + +* Fri Jun 07 2024 David King - 0.76.2-2 +- Use updated notification patches from ptyxis + * Tue May 28 2024 David King - 0.76.2-1 - Update to 0.76.2