1 /*
2  * Copyright (C) 2001-2004,2009,2010 Red Hat, Inc.
3  * Copyright © 2008, 2009, 2010 Christian Persch
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include <math.h>
22 #include <search.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #ifdef HAVE_SYS_TERMIOS_H
29 #include <sys/termios.h>
30 #endif
31 #ifdef HAVE_STROPTS_H
32 #include <stropts.h>
33 #endif
34 #ifdef HAVE_SYS_STREAM_H
35 #include <sys/stream.h>
36 #endif
37 #ifdef HAVE_TERMIOS_H
38 #include <termios.h>
39 #endif
40 
41 #include <glib.h>
42 #include <glib-unix.h>
43 #include <glib/gi18n-lib.h>
44 
45 #include <vte/vte.h>
46 #include "vteinternal.hh"
47 #include "bidi.hh"
48 #include "buffer.h"
49 #include "debug.h"
50 #include "reaper.hh"
51 #include "ring.hh"
52 #include "ringview.hh"
53 #include "caps.hh"
54 #include "widget.hh"
55 
56 #ifdef HAVE_WCHAR_H
57 #include <wchar.h>
58 #endif
59 #ifdef HAVE_SYS_SYSLIMITS_H
60 #include <sys/syslimits.h>
61 #endif
62 #ifdef HAVE_SYS_WAIT_H
63 #include <sys/wait.h>
64 #endif
65 #include <glib.h>
66 #include <glib-object.h>
67 #include <gdk/gdk.h>
68 #include <gtk/gtk.h>
69 #include <pango/pango.h>
70 #include "keymap.h"
71 #include "marshal.h"
72 #include "vtepty.h"
73 #include "vtegtk.hh"
74 #include "cxx-utils.hh"
75 #include "gobject-glue.hh"
76 
77 #ifdef WITH_A11Y
78 #include "vteaccess.h"
79 #endif
80 
81 #include <new> /* placement new */
82 
83 using namespace std::literals;
84 
85 #ifndef HAVE_ROUND
round(double x)86 static inline double round(double x) {
87 	if(x - floor(x) < 0.5) {
88 		return floor(x);
89 	} else {
90 		return ceil(x);
91 	}
92 }
93 #endif
94 
95 #define WORD_CHAR_EXCEPTIONS_DEFAULT "-#%&+,./=?@\\_~\302\267"sv
96 
97 #define I_(string) (g_intern_static_string(string))
98 
99 #define VTE_DRAW_OPAQUE (1.0)
100 
101 namespace vte {
102 namespace terminal {
103 
104 static int _vte_unichar_width(gunichar c, int utf8_ambiguous_width);
105 static void stop_processing(vte::terminal::Terminal* that);
106 static void add_process_timeout(vte::terminal::Terminal* that);
107 static void add_update_timeout(vte::terminal::Terminal* that);
108 static void remove_update_timeout(vte::terminal::Terminal* that);
109 
110 static gboolean process_timeout (gpointer data) noexcept;
111 static gboolean update_timeout (gpointer data) noexcept;
112 static cairo_region_t *vte_cairo_get_clip_region (cairo_t *cr);
113 
114 /* these static variables are guarded by the GDK mutex */
115 static guint process_timeout_tag = 0;
116 static gboolean in_process_timeout;
117 static guint update_timeout_tag = 0;
118 static gboolean in_update_timeout;
119 static GList *g_active_terminals;
120 
121 class Terminal::ProcessingContext {
122 public:
123         vte::grid::row_t m_bbox_top{-G_MAXINT};
124         vte::grid::row_t m_bbox_bottom{G_MAXINT};
125         bool m_modified{false};
126         bool m_bottom{false};
127         bool m_invalidated_text{false};
128         bool m_in_scroll_region{false};
129         bool m_saved_cursor_visible{false};
130         CursorStyle m_saved_cursor_style;
131         VteVisualPosition m_saved_cursor;
132         VteScreen const* m_saved_screen{nullptr};
133 
ProcessingContext(Terminal const & terminal)134         ProcessingContext(Terminal const& terminal) noexcept
135         {
136                 auto screen = m_saved_screen = terminal.m_screen;
137 
138                 // FIXMEchpe make this a method on VteScreen
139                 m_bottom = screen->insert_delta == (long)screen->scroll_delta;
140 
141                 /* Save the current cursor position. */
142                 m_saved_cursor = screen->cursor;
143                 m_saved_cursor_visible = terminal.m_modes_private.DEC_TEXT_CURSOR();
144                 m_saved_cursor_style = terminal.m_cursor_style;
145 
146                 m_in_scroll_region = terminal.m_scrolling_restricted
147                         && (screen->cursor.row >= (screen->insert_delta + terminal.m_scrolling_region.start))
148                         && (screen->cursor.row <= (screen->insert_delta + terminal.m_scrolling_region.end));
149 
150                 //context.modified = false;
151                 //context.invalidated_text = false;
152 
153                 //context.bbox_bottom = -G_MAXINT;
154                 //context.bbox_top = G_MAXINT;
155         }
156 
157         ~ProcessingContext() = default;
158 
159         ProcessingContext(ProcessingContext const&) = delete;
160         ProcessingContext(ProcessingContext&&) = delete;
161 
162         ProcessingContext& operator=(ProcessingContext const&) = delete;
163         ProcessingContext& operator=(ProcessingContext&&) = delete;
164 
165         [[gnu::always_inline]]
pre_GRAPHIC(Terminal const & terminal)166         inline void pre_GRAPHIC(Terminal const& terminal) noexcept
167         {
168                 m_bbox_top = std::min(m_bbox_top,
169                                       terminal.m_screen->cursor.row);
170         }
171 
172         [[gnu::always_inline]]
post_GRAPHIC(Terminal & terminal)173         inline void post_GRAPHIC(Terminal& terminal) noexcept
174         {
175                 auto const* screen = terminal.m_screen;
176 
177                 if (terminal.m_line_wrapped) {
178                         terminal.m_line_wrapped = false;
179                         /* line wrapped, correct bbox */
180                         if (m_invalidated_text &&
181                             (screen->cursor.row > m_bbox_bottom + VTE_CELL_BBOX_SLACK ||
182                              screen->cursor.row < m_bbox_top - VTE_CELL_BBOX_SLACK)) {
183                                 terminal.invalidate_rows_and_context(m_bbox_top, m_bbox_bottom);
184                                 m_bbox_bottom = -G_MAXINT;
185                                 m_bbox_top = G_MAXINT;
186                         }
187                         m_bbox_top = std::min(m_bbox_top,
188                                               screen->cursor.row);
189                 }
190                 /* Add the cells over which we have moved to the region
191                  * which we need to refresh for the user. */
192                 m_bbox_bottom = std::max(m_bbox_bottom,
193                                          screen->cursor.row);
194 
195                 m_invalidated_text = true;
196                 m_modified = true;
197         }
198 
199         [[gnu::always_inline]]
post_CMD(Terminal & terminal)200         inline void post_CMD(Terminal& terminal) noexcept
201         {
202                 m_modified = true;
203 
204                 // FIXME terminal.m_screen may be != m_saved_screen, check for that!
205 
206                 auto const* screen = terminal.m_screen;
207                 auto const new_in_scroll_region = terminal.m_scrolling_restricted &&
208                         (screen->cursor.row >= (screen->insert_delta + terminal.m_scrolling_region.start)) &&
209                         (screen->cursor.row <= (screen->insert_delta + terminal.m_scrolling_region.end));
210 
211                 /* if we have moved greatly during the sequence handler, or moved
212                  * into a scroll_region from outside it, restart the bbox.
213                  */
214                 if (m_invalidated_text &&
215                     ((new_in_scroll_region && !m_in_scroll_region) ||
216                      (screen->cursor.row > m_bbox_bottom + VTE_CELL_BBOX_SLACK ||
217                       screen->cursor.row < m_bbox_top - VTE_CELL_BBOX_SLACK))) {
218                         terminal.invalidate_rows_and_context(m_bbox_top, m_bbox_bottom);
219                         m_invalidated_text = false;
220                         m_bbox_bottom = -G_MAXINT;
221                         m_bbox_top = G_MAXINT;
222                 }
223 
224                 m_in_scroll_region = new_in_scroll_region;
225         }
226 
227 }; // class ProcessingContext
228 
229 static int
_vte_unichar_width(gunichar c,int utf8_ambiguous_width)230 _vte_unichar_width(gunichar c, int utf8_ambiguous_width)
231 {
232         if (G_LIKELY (c < 0x80))
233                 return 1;
234         if (G_UNLIKELY (g_unichar_iszerowidth (c)))
235                 return 0;
236         if (G_UNLIKELY (g_unichar_iswide (c)))
237                 return 2;
238         if (G_LIKELY (utf8_ambiguous_width == 1))
239                 return 1;
240         if (G_UNLIKELY (g_unichar_iswide_cjk (c)))
241                 return 2;
242         return 1;
243 }
244 
245 static void
vte_g_array_fill(GArray * array,gconstpointer item,guint final_size)246 vte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
247 {
248 	if (array->len >= final_size)
249 		return;
250 
251 	final_size -= array->len;
252 	do {
253 		g_array_append_vals(array, item, 1);
254 	} while (--final_size);
255 }
256 
257 void
unset_widget()258 Terminal::unset_widget() noexcept
259 {
260         m_real_widget = nullptr;
261         m_terminal = nullptr;
262         m_widget = nullptr;
263 }
264 
265 // FIXMEchpe replace this with a method on VteRing
266 VteRowData*
ring_insert(vte::grid::row_t position,bool fill)267 Terminal::ring_insert(vte::grid::row_t position,
268                                 bool fill)
269 {
270 	VteRowData *row;
271 	VteRing *ring = m_screen->row_data;
272         bool const not_default_bg = (m_color_defaults.attr.back() != VTE_DEFAULT_BG);
273 
274 	while (G_UNLIKELY (_vte_ring_next (ring) < position)) {
275                 row = _vte_ring_append (ring, get_bidi_flags());
276                 if (not_default_bg)
277                         _vte_row_data_fill (row, &m_color_defaults, m_column_count);
278 	}
279         row = _vte_ring_insert (ring, position, get_bidi_flags());
280         if (fill && not_default_bg)
281                 _vte_row_data_fill (row, &m_color_defaults, m_column_count);
282 	return row;
283 }
284 
285 // FIXMEchpe replace this with a method on VteRing
286 VteRowData*
ring_append(bool fill)287 Terminal::ring_append(bool fill)
288 {
289 	return ring_insert(_vte_ring_next(m_screen->row_data), fill);
290 }
291 
292 // FIXMEchpe replace this with a method on VteRing
293 void
ring_remove(vte::grid::row_t position)294 Terminal::ring_remove(vte::grid::row_t position)
295 {
296 	_vte_ring_remove(m_screen->row_data, position);
297 }
298 
299 /* Reset defaults for character insertion. */
300 void
reset_default_attributes(bool reset_hyperlink)301 Terminal::reset_default_attributes(bool reset_hyperlink)
302 {
303         auto const hyperlink_idx_save = m_defaults.attr.hyperlink_idx;
304         m_defaults = m_color_defaults = basic_cell;
305         if (!reset_hyperlink)
306                 m_defaults.attr.hyperlink_idx = hyperlink_idx_save;
307 }
308 
309 //FIXMEchpe this function is bad
310 inline vte::view::coord_t
scroll_delta_pixel() const311 Terminal::scroll_delta_pixel() const
312 {
313         return round(m_screen->scroll_delta * m_cell_height);
314 }
315 
316 /*
317  * Terminal::pixel_to_row:
318  * @y: Y coordinate is relative to viewport, top padding excluded
319  *
320  * Returns: absolute row
321  */
322 inline vte::grid::row_t
pixel_to_row(vte::view::coord_t y) const323 Terminal::pixel_to_row(vte::view::coord_t y) const
324 {
325         return (scroll_delta_pixel() + y) / m_cell_height;
326 }
327 
328 /*
329  * Terminal::pixel_to_row:
330  * @row: absolute row
331  *
332  * Returns: Y coordinate relative to viewport with top padding excluded. If the row is
333  *   outside the viewport, may return any value < 0 or >= height
334  */
335 inline vte::view::coord_t
row_to_pixel(vte::grid::row_t row) const336 Terminal::row_to_pixel(vte::grid::row_t row) const
337 {
338         // FIXMEchpe this is bad!
339         return row * m_cell_height - (glong)round(m_screen->scroll_delta * m_cell_height);
340 }
341 
342 inline vte::grid::row_t
first_displayed_row() const343 Terminal::first_displayed_row() const
344 {
345         return pixel_to_row(0);
346 }
347 
348 inline vte::grid::row_t
last_displayed_row() const349 Terminal::last_displayed_row() const
350 {
351         /* Get the logical row number displayed at the bottom pixel position */
352         auto r = pixel_to_row(m_view_usable_extents.height() - 1);
353 
354         /* If we have an extra padding at the bottom which is currently unused,
355          * this number is one too big. Adjust here.
356          * E.g. have a terminal of size 80 x 24.5.
357          * Initially the bottom displayed row is (0-based) 23, but r is now 24.
358          * After producing more than a screenful of content and scrolling back
359          * all the way to the top, the bottom displayed row is (0-based) 24. */
360         r = MIN (r, m_screen->insert_delta + m_row_count - 1);
361         return r;
362 }
363 
364 /* Checks if the cursor is potentially at least partially onscreen.
365  * An outline cursor has an additional height of VTE_LINE_WIDTH pixels.
366  * It's also intentionally painted over the padding, up to VTE_LINE_WIDTH
367  * pixels under the real contents area. This method takes these into account.
368  * Only checks the cursor's row; not its visibility, shape, or offscreen column.
369  */
370 inline bool
cursor_is_onscreen() const371 Terminal::cursor_is_onscreen() const noexcept
372 {
373         /* Note: the cursor can only be offscreen below the visible area, not above. */
374         auto cursor_top = row_to_pixel (m_screen->cursor.row) - VTE_LINE_WIDTH;
375         auto display_bottom = m_view_usable_extents.height() + MIN(m_padding.bottom, VTE_LINE_WIDTH);
376         return cursor_top < display_bottom;
377 }
378 
379 /* Invalidate the requested rows. This is to be used when only the desired
380  * rendering changes but not the underlying data, e.g. moving or blinking
381  * cursor, highligthing with the mouse etc.
382  *
383  * Note that row_end is inclusive. This is not as nice as end-exclusive,
384  * but saves us from a +1 almost everywhere where this method is called.
385  */
386 void
invalidate_rows(vte::grid::row_t row_start,vte::grid::row_t row_end)387 Terminal::invalidate_rows(vte::grid::row_t row_start,
388                           vte::grid::row_t row_end /* inclusive */)
389 {
390 	if (G_UNLIKELY (!widget_realized()))
391                 return;
392 
393         if (m_invalidated_all)
394 		return;
395 
396         if (G_UNLIKELY (row_end < row_start))
397                 return;
398 
399 	_vte_debug_print (VTE_DEBUG_UPDATES,
400                           "Invalidating rows %ld..%ld.\n",
401                           row_start, row_end);
402 	_vte_debug_print (VTE_DEBUG_WORK, "?");
403 
404         /* Scrolled back, visible parts didn't change. */
405         if (row_start > last_displayed_row())
406                 return;
407 
408         /* Recognize if we're about to invalidate everything. */
409         if (row_start <= first_displayed_row() &&
410             row_end >= last_displayed_row()) {
411 		invalidate_all();
412 		return;
413 	}
414 
415         cairo_rectangle_int_t rect;
416 	/* Convert the column and row start and end to pixel values
417 	 * by multiplying by the size of a character cell.
418 	 * Always include the extra pixel border and overlap pixel.
419 	 */
420         // FIXMEegmont invalidate the left and right padding too
421         rect.x = -1;
422         int xend = m_column_count * m_cell_width + 1;
423         rect.width = xend - rect.x;
424 
425         /* Always add at least VTE_LINE_WIDTH pixels so the outline block cursor fits */
426         rect.y = row_to_pixel(row_start) - std::max(cell_overflow_top(), VTE_LINE_WIDTH);
427         int yend = row_to_pixel(row_end + 1) + std::max(cell_overflow_bottom(), VTE_LINE_WIDTH);
428         rect.height = yend - rect.y;
429 
430 	_vte_debug_print (VTE_DEBUG_UPDATES,
431 			"Invalidating pixels at (%d,%d)x(%d,%d).\n",
432 			rect.x, rect.y, rect.width, rect.height);
433 
434 	if (m_active_terminals_link != nullptr) {
435                 g_array_append_val(m_update_rects, rect);
436 		/* Wait a bit before doing any invalidation, just in
437 		 * case updates are coming in really soon. */
438 		add_update_timeout(this);
439 	} else {
440                 auto allocation = get_allocated_rect();
441                 rect.x += allocation.x + m_padding.left;
442                 rect.y += allocation.y + m_padding.top;
443                 cairo_region_t *region = cairo_region_create_rectangle(&rect);
444 		gtk_widget_queue_draw_region(m_widget, region);
445                 cairo_region_destroy(region);
446 	}
447 
448 	_vte_debug_print (VTE_DEBUG_WORK, "!");
449 }
450 
451 /* Invalidate the requested rows, extending the region in both directions up to
452  * an explicit newline (or a safety limit) to invalidate entire paragraphs of text.
453  * This is to be used whenever the underlying data changes, because any such
454  * change might alter the desired BiDi, syntax highlighting etc. of all other
455  * rows of the involved paragraph(s).
456  *
457  * Note that row_end is inclusive. This is not as nice as end-exclusive,
458  * but saves us from a +1 almost everywhere where this method is called.
459  */
460 void
invalidate_rows_and_context(vte::grid::row_t row_start,vte::grid::row_t row_end)461 Terminal::invalidate_rows_and_context(vte::grid::row_t row_start,
462                                       vte::grid::row_t row_end /* inclusive */)
463 {
464         if (G_UNLIKELY (!widget_realized()))
465                 return;
466 
467         if (m_invalidated_all)
468                 return;
469 
470         if (G_UNLIKELY (row_end < row_start))
471                 return;
472 
473         _vte_debug_print (VTE_DEBUG_UPDATES,
474                           "Invalidating rows %ld..%ld and context.\n",
475                           row_start, row_end);
476 
477         /* Safety limit: Scrolled back by so much that changes to the
478          * writable area may not affect the current viewport's rendering. */
479         if (m_screen->insert_delta - VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX > last_displayed_row())
480                 return;
481 
482         /* Extending the start is a bit tricky.
483          * First extend it (towards lower numbered indices), but only up to
484          * insert_delta - 1. Remember that the row at insert_delta - 1 is
485          * still in the ring, hence checking its soft_wrapped flag is fast. */
486         while (row_start >= m_screen->insert_delta) {
487                 if (!m_screen->row_data->is_soft_wrapped(row_start - 1))
488                         break;
489                 row_start--;
490         }
491 
492         /* If we haven't seen a newline yet, stop walking backwards row by row.
493          * This is because we might need to access row_stream in order to check
494          * the wrapped state, a way too expensive operation while processing
495          * incoming data. Let displaying do extra work instead.
496          * So just invalidate everything to the top. */
497         if (row_start < m_screen->insert_delta) {
498                 row_start = first_displayed_row();
499         }
500 
501         /* Extending the end is simple. Just walk until we go offscreen or
502          * find an explicit newline. */
503         while (row_end < last_displayed_row()) {
504                 if (!m_screen->row_data->is_soft_wrapped(row_end))
505                         break;
506                 row_end++;
507         }
508 
509         invalidate_rows(row_start, row_end);
510 }
511 
512 /* Convenience methods */
513 void
invalidate_row(vte::grid::row_t row)514 Terminal::invalidate_row(vte::grid::row_t row)
515 {
516         invalidate_rows(row, row);
517 }
518 
519 void
invalidate_row_and_context(vte::grid::row_t row)520 Terminal::invalidate_row_and_context(vte::grid::row_t row)
521 {
522         invalidate_rows_and_context(row, row);
523 }
524 
525 /* This is only used by the selection code, so no need to extend the area. */
526 void
invalidate(vte::grid::span const & s)527 Terminal::invalidate(vte::grid::span const& s)
528 {
529         if (!s.empty())
530                 invalidate_rows(s.start_row(), s.last_row());
531 }
532 
533 /* Invalidates the symmetrical difference ("XOR" area) of the two spans.
534  * This is only used by the selection code, so no need to extend the area. */
535 void
invalidate_symmetrical_difference(vte::grid::span const & a,vte::grid::span const & b,bool block)536 Terminal::invalidate_symmetrical_difference(vte::grid::span const& a, vte::grid::span const& b, bool block)
537 {
538         if (a.empty() || b.empty() || a.start() >= b.end() || b.start() >= a.end()) {
539                 /* One or both are empty (invalidate() will figure out which), or disjoint intervals. */
540                 invalidate (a);
541                 invalidate (b);
542                 return;
543         }
544 
545         if (block) {
546                 /* We could optimize when the columns don't change, probably not worth it. */
547                 invalidate_rows (std::min (a.start_row(), b.start_row()),
548                                  std::max (a.last_row(),  b.last_row()));
549         } else {
550                 if (a.start() != b.start()) {
551                         invalidate_rows (std::min (a.start_row(), b.start_row()),
552                                          std::max (a.start_row(), b.start_row()));
553                 }
554                 if (a.end() != b.end()) {
555                         invalidate_rows (std::min (a.last_row(), b.last_row()),
556                                          std::max (a.last_row(), b.last_row()));
557                 }
558         }
559 }
560 
561 void
invalidate_all()562 Terminal::invalidate_all()
563 {
564 	if (G_UNLIKELY (!widget_realized()))
565                 return;
566 
567 	if (m_invalidated_all) {
568 		return;
569 	}
570 
571 	_vte_debug_print (VTE_DEBUG_WORK, "*");
572 	_vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating all.\n");
573 
574 	/* replace invalid regions with one covering the whole terminal */
575 	reset_update_rects();
576 	m_invalidated_all = TRUE;
577 
578         if (m_active_terminals_link != nullptr) {
579                 auto allocation = get_allocated_rect();
580                 cairo_rectangle_int_t rect;
581                 rect.x = -m_padding.left;
582                 rect.y = -m_padding.top;
583                 rect.width = allocation.width;
584                 rect.height = allocation.height;
585 
586                 g_array_append_val(m_update_rects, rect);
587 		/* Wait a bit before doing any invalidation, just in
588 		 * case updates are coming in really soon. */
589 		add_update_timeout(this);
590 	} else {
591                 gtk_widget_queue_draw(m_widget);
592 	}
593 }
594 
595 /* Find the row in the given position in the backscroll buffer.
596  * Note that calling this method may invalidate the return value of
597  * a previous find_row_data() call. */
598 // FIXMEchpe replace this with a method on VteRing
599 VteRowData const*
find_row_data(vte::grid::row_t row) const600 Terminal::find_row_data(vte::grid::row_t row) const
601 {
602 	VteRowData const* rowdata = nullptr;
603 
604 	if (G_LIKELY(_vte_ring_contains(m_screen->row_data, row))) {
605 		rowdata = _vte_ring_index(m_screen->row_data, row);
606 	}
607 	return rowdata;
608 }
609 
610 /* Find the row in the given position in the backscroll buffer. */
611 // FIXMEchpe replace this with a method on VteRing
612 VteRowData*
find_row_data_writable(vte::grid::row_t row) const613 Terminal::find_row_data_writable(vte::grid::row_t row) const
614 {
615 	VteRowData *rowdata = nullptr;
616 
617 	if (G_LIKELY (_vte_ring_contains(m_screen->row_data, row))) {
618 		rowdata = _vte_ring_index_writable(m_screen->row_data, row);
619 	}
620 	return rowdata;
621 }
622 
623 /* Find the character an the given position in the backscroll buffer.
624  * Note that calling this method may invalidate the return value of
625  * a previous find_row_data() call. */
626 // FIXMEchpe replace this with a method on VteRing
627 VteCell const*
find_charcell(vte::grid::column_t col,vte::grid::row_t row) const628 Terminal::find_charcell(vte::grid::column_t col,
629                                   vte::grid::row_t row) const
630 {
631 	VteRowData const* rowdata;
632 	VteCell const* ret = nullptr;
633 
634 	if (_vte_ring_contains(m_screen->row_data, row)) {
635 		rowdata = _vte_ring_index(m_screen->row_data, row);
636 		ret = _vte_row_data_get (rowdata, col);
637 	}
638 	return ret;
639 }
640 
641 // FIXMEchpe replace this with a method on VteRing
642 vte::grid::column_t
find_start_column(vte::grid::column_t col,vte::grid::row_t row) const643 Terminal::find_start_column(vte::grid::column_t col,
644                                       vte::grid::row_t row) const
645 {
646 	VteRowData const* row_data = find_row_data(row);
647 	if (G_UNLIKELY (col < 0))
648 		return col;
649 	if (row_data != nullptr) {
650 		const VteCell *cell = _vte_row_data_get (row_data, col);
651 		while (col > 0 && cell != NULL && cell->attr.fragment()) {
652 			cell = _vte_row_data_get (row_data, --col);
653 		}
654 	}
655 	return MAX(col, 0);
656 }
657 
658 // FIXMEchpe replace this with a method on VteRing
659 vte::grid::column_t
find_end_column(vte::grid::column_t col,vte::grid::row_t row) const660 Terminal::find_end_column(vte::grid::column_t col,
661                                     vte::grid::row_t row) const
662 {
663 	VteRowData const* row_data = find_row_data(row);
664 	gint columns = 0;
665 	if (G_UNLIKELY (col < 0))
666 		return col;
667 	if (row_data != NULL) {
668 		const VteCell *cell = _vte_row_data_get (row_data, col);
669 		while (col > 0 && cell != NULL && cell->attr.fragment()) {
670 			cell = _vte_row_data_get (row_data, --col);
671 		}
672 		if (cell) {
673 			columns = cell->attr.columns() - 1;
674 		}
675 	}
676         // FIXMEchp m__column_count - 1 ?
677 	return MIN(col + columns, m_column_count);
678 }
679 
680 /* Sets the line ending to hard wrapped (explicit newline).
681  * Takes care of invalidating if this operation splits a paragraph into two. */
682 void
set_hard_wrapped(vte::grid::row_t row)683 Terminal::set_hard_wrapped(vte::grid::row_t row)
684 {
685         /* We can set the row just above insert_delta to hard wrapped. */
686         g_assert_cmpint(row, >=, m_screen->insert_delta - 1);
687         g_assert_cmpint(row, <, m_screen->insert_delta + m_row_count);
688 
689         VteRowData *row_data = find_row_data_writable(row);
690 
691         /* It's okay for this row not to be covered by the ring. */
692         if (row_data == nullptr || !row_data->attr.soft_wrapped)
693                 return;
694 
695         row_data->attr.soft_wrapped = false;
696 
697         m_ringview.invalidate();
698         invalidate_rows_and_context(row, row + 1);
699 }
700 
701 /* Sets the line ending to soft wrapped (overflow to the next line).
702  * Takes care of invalidating if this operation joins two paragraphs into one.
703  * Also makes sure that the joined new paragraph receives the first one's bidi flags. */
704 void
set_soft_wrapped(vte::grid::row_t row)705 Terminal::set_soft_wrapped(vte::grid::row_t row)
706 {
707         g_assert_cmpint(row, >=, m_screen->insert_delta);
708         g_assert_cmpint(row, <, m_screen->insert_delta + m_row_count);
709 
710         VteRowData *row_data = find_row_data_writable(row);
711         g_assert(row_data != nullptr);
712 
713         if (row_data->attr.soft_wrapped)
714                 return;
715 
716         row_data->attr.soft_wrapped = true;
717 
718         /* Each paragraph has to have consistent bidi flags across all of its rows.
719          * Spread the first paragraph's flags across the second one (if they differ). */
720         guint8 bidi_flags = row_data->attr.bidi_flags;
721         vte::grid::row_t i = row + 1;
722         row_data = find_row_data_writable(i);
723         if (row_data != nullptr && row_data->attr.bidi_flags != bidi_flags) {
724                 do {
725                         row_data->attr.bidi_flags = bidi_flags;
726                         if (!row_data->attr.soft_wrapped)
727                                 break;
728                         row_data = find_row_data_writable(++i);
729                 } while (row_data != nullptr);
730         }
731 
732         m_ringview.invalidate();
733         invalidate_rows_and_context(row, row + 1);
734 }
735 
736 /* Determine the width of the portion of the preedit string which lies
737  * to the left of the cursor, or the entire string, in columns. */
738 // FIXMEchpe this is for the view, so use int not gssize
739 // FIXMEchpe this is only ever called with left_only=false, so remove the param
740 gssize
get_preedit_width(bool left_only)741 Terminal::get_preedit_width(bool left_only)
742 {
743 	gssize ret = 0;
744 
745         char const *preedit = m_im_preedit.c_str();
746         for (int i = 0;
747              // FIXMEchpe preddit is != NULL at the start, and next_char never returns NULL either
748              (preedit != NULL) &&
749 		     (preedit[0] != '\0') &&
750 		     (!left_only || (i < m_im_preedit_cursor));
751              i++) {
752                 gunichar c = g_utf8_get_char(preedit);
753                 ret += _vte_unichar_width(c, m_utf8_ambiguous_width);
754                 preedit = g_utf8_next_char(preedit);
755         }
756 
757 	return ret;
758 }
759 
760 /* Determine the length of the portion of the preedit string which lies
761  * to the left of the cursor, or the entire string, in gunichars. */
762 // FIXMEchpe this returns gssize but inside it uses int...
763 gssize
get_preedit_length(bool left_only)764 Terminal::get_preedit_length(bool left_only)
765 {
766 	ssize_t i = 0;
767 
768         char const *preedit = m_im_preedit.c_str();
769         for (i = 0;
770              // FIXMEchpe useless check, see above
771              (preedit != NULL) &&
772 		     (preedit[0] != '\0') &&
773 		     (!left_only || (i < m_im_preedit_cursor));
774              i++) {
775                 preedit = g_utf8_next_char(preedit);
776         }
777 
778 	return i;
779 }
780 
781 void
invalidate_cursor_once(bool periodic)782 Terminal::invalidate_cursor_once(bool periodic)
783 {
784         if (G_UNLIKELY(!widget_realized()))
785                 return;
786 
787 	if (m_invalidated_all) {
788 		return;
789 	}
790 
791 	if (periodic) {
792 		if (!m_cursor_blinks) {
793 			return;
794 		}
795 	}
796 
797 	if (m_modes_private.DEC_TEXT_CURSOR()) {
798                 auto row = m_screen->cursor.row;
799 
800 		_vte_debug_print(VTE_DEBUG_UPDATES,
801                                  "Invalidating cursor in row %ld.\n",
802                                  row);
803                 invalidate_row(row);
804 	}
805 }
806 
807 /* Invalidate the cursor repeatedly. */
808 // FIXMEchpe this continually adds and removes the blink timeout. Find a better solution
809 bool
cursor_blink_timer_callback()810 Terminal::cursor_blink_timer_callback()
811 {
812 	m_cursor_blink_state = !m_cursor_blink_state;
813 	m_cursor_blink_time += m_cursor_blink_cycle;
814 
815 	invalidate_cursor_once(true);
816 
817 	/* only disable the blink if the cursor is currently shown.
818 	 * else, wait until next time.
819 	 */
820 	if (m_cursor_blink_time / 1000 >= m_cursor_blink_timeout &&
821 	    m_cursor_blink_state) {
822 		return false;
823         }
824 
825         m_cursor_blink_timer.schedule(m_cursor_blink_cycle, vte::glib::Timer::Priority::eLOW);
826         return false;
827 }
828 
829 /* Emit a "selection_changed" signal. */
830 void
emit_selection_changed()831 Terminal::emit_selection_changed()
832 {
833 	_vte_debug_print(VTE_DEBUG_SIGNALS,
834 			"Emitting `selection-changed'.\n");
835 	g_signal_emit(m_terminal, signals[SIGNAL_SELECTION_CHANGED], 0);
836 }
837 
838 /* Emit a "commit" signal.
839  * FIXMEchpe: remove this function
840  */
841 void
emit_commit(std::string_view const & str)842 Terminal::emit_commit(std::string_view const& str)
843 {
844         if (str.size() == 0)
845                 return;
846 
847         if (!widget() || !widget()->should_emit_signal(SIGNAL_COMMIT))
848                 return;
849 
850 	_vte_debug_print(VTE_DEBUG_SIGNALS,
851                          "Emitting `commit' of %" G_GSSIZE_FORMAT" bytes.\n", str.size());
852 
853         // FIXMEchpe we do know for a fact that all uses of this function
854         // actually passed a 0-terminated string, so we can use @str directly
855         std::string result{str}; // 0-terminated
856 
857         _VTE_DEBUG_IF(VTE_DEBUG_KEYBOARD) {
858                 for (size_t i = 0; i < result.size(); i++) {
859                         if ((((guint8) result[i]) < 32) ||
860                             (((guint8) result[i]) > 127)) {
861                                 g_printerr(
862                                            "Sending <%02x> "
863                                            "to child.\n",
864                                            result[i]);
865                         } else {
866                                 g_printerr(
867                                            "Sending '%c' "
868                                            "to child.\n",
869                                            result[i]);
870                         }
871                 }
872         }
873 
874 	g_signal_emit(m_terminal, signals[SIGNAL_COMMIT], 0, result.c_str(), (guint)result.size());
875 }
876 
877 void
queue_contents_changed()878 Terminal::queue_contents_changed()
879 {
880 	_vte_debug_print(VTE_DEBUG_SIGNALS,
881 			"Queueing `contents-changed'.\n");
882 	m_contents_changed_pending = true;
883 }
884 
885 //FIXMEchpe this has only one caller
886 void
queue_cursor_moved()887 Terminal::queue_cursor_moved()
888 {
889 	_vte_debug_print(VTE_DEBUG_SIGNALS,
890 			"Queueing `cursor-moved'.\n");
891 	m_cursor_moved_pending = true;
892 }
893 
894 void
emit_eof()895 Terminal::emit_eof()
896 {
897         if (widget())
898                 widget()->emit_eof();
899 }
900 
901 static gboolean
emit_eof_idle_cb(VteTerminal * terminal)902 emit_eof_idle_cb(VteTerminal *terminal)
903 try
904 {
905         _vte_terminal_get_impl(terminal)->emit_eof();
906 
907         return G_SOURCE_REMOVE;
908 }
909 catch (...)
910 {
911         vte::log_exception();
912         return G_SOURCE_REMOVE;
913 }
914 
915 void
queue_eof()916 Terminal::queue_eof()
917 {
918         _vte_debug_print(VTE_DEBUG_SIGNALS, "Queueing `eof'.\n");
919 
920         g_idle_add_full(G_PRIORITY_HIGH,
921                         (GSourceFunc)emit_eof_idle_cb,
922                         g_object_ref(m_terminal),
923                         g_object_unref);
924 }
925 
926 void
emit_child_exited()927 Terminal::emit_child_exited()
928 {
929         auto const status = m_child_exit_status;
930         m_child_exit_status = -1;
931 
932         if (widget())
933                 widget()->emit_child_exited(status);
934 }
935 
936 static gboolean
emit_child_exited_idle_cb(VteTerminal * terminal)937 emit_child_exited_idle_cb(VteTerminal *terminal)
938 try
939 {
940         _vte_terminal_get_impl(terminal)->emit_child_exited();
941 
942         return G_SOURCE_REMOVE;
943 }
944 catch (...)
945 {
946         vte::log_exception();
947         return G_SOURCE_REMOVE;
948 }
949 
950 /* Emit a "child-exited" signal on idle, so that if the handler destroys
951  * the terminal, we're not deep within terminal code callstack
952  */
953 void
queue_child_exited()954 Terminal::queue_child_exited()
955 {
956         _vte_debug_print(VTE_DEBUG_SIGNALS, "Queueing `child-exited'.\n");
957         m_child_exited_after_eos_pending = false;
958 
959         g_idle_add_full(G_PRIORITY_HIGH,
960                         (GSourceFunc)emit_child_exited_idle_cb,
961                         g_object_ref(m_terminal),
962                         g_object_unref);
963 }
964 
965 bool
child_exited_eos_wait_callback()966 Terminal::child_exited_eos_wait_callback()
967 {
968         /* If we get this callback, there has been some time elapsed
969          * after child-exited, but no EOS yet. This happens for example
970          * when the primary child started other processes in the background,
971          * which inherited the PTY, and thus keep it open, see
972          * https://gitlab.gnome.org/GNOME/vte/issues/204
973          *
974          * Force an EOS.
975          */
976         if (pty())
977                 pty_io_read(pty()->fd(), G_IO_HUP);
978 
979         return false; // don't run again
980 }
981 
982 /* Emit a "char-size-changed" signal. */
983 void
emit_char_size_changed(int width,int height)984 Terminal::emit_char_size_changed(int width,
985                                            int height)
986 {
987 	_vte_debug_print(VTE_DEBUG_SIGNALS,
988 			"Emitting `char-size-changed'.\n");
989         /* FIXME on next API break, change the signature */
990 	g_signal_emit(m_terminal, signals[SIGNAL_CHAR_SIZE_CHANGED], 0,
991 			      (guint)width, (guint)height);
992 }
993 
994 /* Emit an "increase-font-size" signal. */
995 void
emit_increase_font_size()996 Terminal::emit_increase_font_size()
997 {
998 	_vte_debug_print(VTE_DEBUG_SIGNALS,
999 			"Emitting `increase-font-size'.\n");
1000 	g_signal_emit(m_terminal, signals[SIGNAL_INCREASE_FONT_SIZE], 0);
1001 }
1002 
1003 /* Emit a "decrease-font-size" signal. */
1004 void
emit_decrease_font_size()1005 Terminal::emit_decrease_font_size()
1006 {
1007 	_vte_debug_print(VTE_DEBUG_SIGNALS,
1008 			"Emitting `decrease-font-size'.\n");
1009 	g_signal_emit(m_terminal, signals[SIGNAL_DECREASE_FONT_SIZE], 0);
1010 }
1011 
1012 /* Emit a "text-inserted" signal. */
1013 void
emit_text_inserted()1014 Terminal::emit_text_inserted()
1015 {
1016 #ifdef WITH_A11Y
1017 	if (!m_accessible_emit) {
1018 		return;
1019 	}
1020 	_vte_debug_print(VTE_DEBUG_SIGNALS,
1021 			"Emitting `text-inserted'.\n");
1022 	g_signal_emit(m_terminal, signals[SIGNAL_TEXT_INSERTED], 0);
1023 #endif
1024 }
1025 
1026 /* Emit a "text-deleted" signal. */
1027 void
emit_text_deleted()1028 Terminal::emit_text_deleted()
1029 {
1030 #ifdef WITH_A11Y
1031 	if (!m_accessible_emit) {
1032 		return;
1033 	}
1034 	_vte_debug_print(VTE_DEBUG_SIGNALS,
1035 			"Emitting `text-deleted'.\n");
1036 	g_signal_emit(m_terminal, signals[SIGNAL_TEXT_DELETED], 0);
1037 #endif
1038 }
1039 
1040 /* Emit a "text-modified" signal. */
1041 void
emit_text_modified()1042 Terminal::emit_text_modified()
1043 {
1044 #ifdef WITH_A11Y
1045 	if (!m_accessible_emit) {
1046 		return;
1047 	}
1048 	_vte_debug_print(VTE_DEBUG_SIGNALS,
1049                          "Emitting `text-modified'.\n");
1050 	g_signal_emit(m_terminal, signals[SIGNAL_TEXT_MODIFIED], 0);
1051 #endif
1052 }
1053 
1054 /* Emit a "text-scrolled" signal. */
1055 void
emit_text_scrolled(long delta)1056 Terminal::emit_text_scrolled(long delta)
1057 {
1058 #ifdef WITH_A11Y
1059 	if (!m_accessible_emit) {
1060 		return;
1061 	}
1062 	_vte_debug_print(VTE_DEBUG_SIGNALS,
1063 			"Emitting `text-scrolled'(%ld).\n", delta);
1064         // FIXMEchpe fix signal signature?
1065 	g_signal_emit(m_terminal, signals[SIGNAL_TEXT_SCROLLED], 0, (int)delta);
1066 #endif
1067 }
1068 
1069 void
emit_copy_clipboard()1070 Terminal::emit_copy_clipboard()
1071 {
1072 	_vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting 'copy-clipboard'.\n");
1073 	g_signal_emit(m_terminal, signals[SIGNAL_COPY_CLIPBOARD], 0);
1074 }
1075 
1076 void
emit_paste_clipboard()1077 Terminal::emit_paste_clipboard()
1078 {
1079 	_vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting 'paste-clipboard'.\n");
1080 	g_signal_emit(m_terminal, signals[SIGNAL_PASTE_CLIPBOARD], 0);
1081 }
1082 
1083 /* Emit a "hyperlink_hover_uri_changed" signal. */
1084 void
emit_hyperlink_hover_uri_changed(const GdkRectangle * bbox)1085 Terminal::emit_hyperlink_hover_uri_changed(const GdkRectangle *bbox)
1086 {
1087         GObject *object = G_OBJECT(m_terminal);
1088 
1089         _vte_debug_print(VTE_DEBUG_SIGNALS,
1090                          "Emitting `hyperlink-hover-uri-changed'.\n");
1091         g_signal_emit(m_terminal, signals[SIGNAL_HYPERLINK_HOVER_URI_CHANGED], 0, m_hyperlink_hover_uri, bbox);
1092         g_object_notify_by_pspec(object, pspecs[PROP_HYPERLINK_HOVER_URI]);
1093 }
1094 
1095 void
deselect_all()1096 Terminal::deselect_all()
1097 {
1098         if (!m_selection_resolved.empty()) {
1099 		_vte_debug_print(VTE_DEBUG_SELECTION,
1100 				"Deselecting all text.\n");
1101 
1102                 m_selection_origin = m_selection_last = { -1, -1, 1 };
1103                 resolve_selection();
1104 
1105 		/* Don't free the current selection, as we need to keep
1106 		 * hold of it for async copying from the clipboard. */
1107 
1108 		emit_selection_changed();
1109 	}
1110 }
1111 
1112 /* Clear the cache of the screen contents we keep. */
1113 void
match_contents_clear()1114 Terminal::match_contents_clear()
1115 {
1116 	match_hilite_clear();
1117 	if (m_match_contents != nullptr) {
1118 		g_free(m_match_contents);
1119 		m_match_contents = nullptr;
1120 	}
1121 	if (m_match_attributes != nullptr) {
1122 		g_array_free(m_match_attributes, TRUE);
1123 		m_match_attributes = nullptr;
1124 	}
1125 }
1126 
1127 void
match_contents_refresh()1128 Terminal::match_contents_refresh()
1129 
1130 {
1131 	match_contents_clear();
1132 	GArray *array = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
1133         auto match_contents = get_text_displayed(true /* wrap */,
1134                                                  array);
1135         m_match_contents = g_string_free(match_contents, FALSE);
1136 	m_match_attributes = array;
1137 }
1138 
1139 void
regex_match_remove_all()1140 Terminal::regex_match_remove_all() noexcept
1141 {
1142         auto& match_regexes = match_regexes_writable();
1143         match_regexes.clear();
1144         match_regexes.shrink_to_fit();
1145 
1146 	match_hilite_clear();
1147 }
1148 
1149 void
regex_match_remove(int tag)1150 Terminal::regex_match_remove(int tag) noexcept
1151 {
1152         auto i = regex_match_get_iter(tag);
1153         if (i == std::end(m_match_regexes))
1154                 return;
1155 
1156         match_regexes_writable().erase(i);
1157 }
1158 
1159 /*
1160  * match_rowcol_to_offset:
1161  * @terminal:
1162  * @column:
1163  * @row:
1164  * @offset_ptr: (out):
1165  * @sattr_ptr: (out):
1166  * @ettr_ptr: (out):
1167  *
1168  * Maps (row, column) to an offset in m_match_attributes, and returns
1169  * that offset in @offset_ptr, and the start and end of the corresponding
1170  * line in @sattr_ptr and @eattr_ptr.
1171  */
1172 bool
match_rowcol_to_offset(vte::grid::column_t column,vte::grid::row_t row,gsize * offset_ptr,gsize * sattr_ptr,gsize * eattr_ptr)1173 Terminal::match_rowcol_to_offset(vte::grid::column_t column,
1174                                            vte::grid::row_t row,
1175                                            gsize *offset_ptr,
1176                                            gsize *sattr_ptr,
1177                                            gsize *eattr_ptr)
1178 {
1179         /* FIXME: use gsize, after making sure the code below doesn't underflow offset */
1180         gssize offset, sattr, eattr;
1181         struct _VteCharAttributes *attr = NULL;
1182 
1183 	/* Map the pointer position to a portion of the string. */
1184         // FIXME do a bsearch here?
1185 	eattr = m_match_attributes->len;
1186 	for (offset = eattr; offset--; ) {
1187 		attr = &g_array_index(m_match_attributes,
1188 				      struct _VteCharAttributes,
1189 				      offset);
1190 		if (row < attr->row) {
1191 			eattr = offset;
1192 		}
1193 		if (row == attr->row &&
1194 		    column >= attr->column && column < attr->column + attr->columns) {
1195 			break;
1196 		}
1197 	}
1198 
1199 	_VTE_DEBUG_IF(VTE_DEBUG_REGEX) {
1200 		if (offset < 0)
1201 			g_printerr("Cursor is not on a character.\n");
1202 		else {
1203                         gunichar c;
1204                         char utf[7];
1205                         c = g_utf8_get_char (m_match_contents + offset);
1206                         utf[g_unichar_to_utf8(g_unichar_isprint(c) ? c : 0xFFFD, utf)] = 0;
1207 
1208 			g_printerr("Cursor is on character U+%04X '%s' at %" G_GSSIZE_FORMAT ".\n",
1209                                    c, utf, offset);
1210                 }
1211 	}
1212 
1213 	/* If the pointer isn't on a matchable character, bug out. */
1214 	if (offset < 0) {
1215 		return false;
1216 	}
1217 
1218 	/* If the pointer is on a newline, bug out. */
1219 	if (m_match_contents[offset] == '\0') {
1220 		_vte_debug_print(VTE_DEBUG_EVENTS,
1221                                  "Cursor is on newline.\n");
1222 		return false;
1223 	}
1224 
1225 	/* Snip off any final newlines. */
1226 	while (m_match_contents[eattr] == '\n' ||
1227                m_match_contents[eattr] == '\0') {
1228 		eattr--;
1229 	}
1230 	/* and scan forwards to find the end of this line */
1231 	while (!(m_match_contents[eattr] == '\n' ||
1232                  m_match_contents[eattr] == '\0')) {
1233 		eattr++;
1234 	}
1235 
1236 	/* find the start of row */
1237 	if (row == 0) {
1238 		sattr = 0;
1239 	} else {
1240 		for (sattr = offset; sattr > 0; sattr--) {
1241 			attr = &g_array_index(m_match_attributes,
1242 					      struct _VteCharAttributes,
1243 					      sattr);
1244 			if (row > attr->row) {
1245 				break;
1246 			}
1247 		}
1248 	}
1249 	/* Scan backwards to find the start of this line */
1250 	while (sattr > 0 &&
1251 		! (m_match_contents[sattr] == '\n' ||
1252                    m_match_contents[sattr] == '\0')) {
1253 		sattr--;
1254 	}
1255 	/* and skip any initial newlines. */
1256 	while (m_match_contents[sattr] == '\n' ||
1257                m_match_contents[sattr] == '\0') {
1258 		sattr++;
1259 	}
1260 	if (eattr <= sattr) { /* blank line */
1261 		return false;
1262 	}
1263 	if (eattr <= offset || sattr > offset) {
1264 		/* nothing to match on this line */
1265 		return false;
1266 	}
1267 
1268         *offset_ptr = offset;
1269         *sattr_ptr = sattr;
1270         *eattr_ptr = eattr;
1271 
1272         _VTE_DEBUG_IF(VTE_DEBUG_REGEX) {
1273                 struct _VteCharAttributes *_sattr, *_eattr;
1274                 _sattr = &g_array_index(m_match_attributes,
1275                                         struct _VteCharAttributes,
1276                                         sattr);
1277                 _eattr = &g_array_index(m_match_attributes,
1278                                         struct _VteCharAttributes,
1279                                         eattr - 1);
1280                 g_printerr("Cursor is in line from %" G_GSIZE_FORMAT "(%ld,%ld) to %" G_GSIZE_FORMAT "(%ld,%ld)\n",
1281                            sattr, _sattr->column, _sattr->row,
1282                            eattr - 1, _eattr->column, _eattr->row);
1283         }
1284 
1285         return true;
1286 }
1287 
1288 /* creates a pcre match context with appropriate limits */
1289 vte::Freeable<pcre2_match_context_8>
create_match_context()1290 Terminal::create_match_context()
1291 {
1292         auto context = vte::take_freeable(pcre2_match_context_create_8(nullptr /* general context */));
1293         pcre2_set_match_limit_8(context.get(), 65536); /* should be plenty */
1294         pcre2_set_recursion_limit_8(context.get(), 64); /* should be plenty */
1295 
1296         return context;
1297 }
1298 
1299 bool
match_check_pcre(pcre2_match_data_8 * match_data,pcre2_match_context_8 * match_context,vte::base::Regex const * regex,uint32_t match_flags,gsize sattr,gsize eattr,gsize offset,char ** result_ptr,gsize * start,gsize * end,gsize * sblank_ptr,gsize * eblank_ptr)1300 Terminal::match_check_pcre(pcre2_match_data_8 *match_data,
1301                            pcre2_match_context_8 *match_context,
1302                            vte::base::Regex const* regex,
1303                            uint32_t match_flags,
1304                            gsize sattr,
1305                            gsize eattr,
1306                            gsize offset,
1307                            char **result_ptr,
1308                            gsize *start,
1309                            gsize *end,
1310                            gsize *sblank_ptr,
1311                            gsize *eblank_ptr)
1312 {
1313         int (* match_fn) (const pcre2_code_8 *,
1314                           PCRE2_SPTR8, PCRE2_SIZE, PCRE2_SIZE, uint32_t,
1315                           pcre2_match_data_8 *, pcre2_match_context_8 *);
1316         gsize sblank = 0, eblank = G_MAXSIZE;
1317         gsize position, line_length;
1318         const char *line;
1319         int r = 0;
1320 
1321         if (regex->jited())
1322                 match_fn = pcre2_jit_match_8;
1323         else
1324                 match_fn = pcre2_match_8;
1325 
1326         line = m_match_contents;
1327         /* FIXME: what we really want is to pass the whole data to pcre2_match, but
1328          * limit matching to between sattr and eattr, so that the extra data can
1329          * satisfy lookahead assertions. This needs new pcre2 API though.
1330          */
1331         line_length = eattr;
1332 
1333         /* Iterate throught the matches until we either find one which contains the
1334          * offset, or we get no more matches.
1335          */
1336         pcre2_set_offset_limit_8(match_context, eattr);
1337         position = sattr;
1338         while (position < eattr &&
1339                ((r = match_fn(regex->code(),
1340                               (PCRE2_SPTR8)line, line_length, /* subject, length */
1341                               position, /* start offset */
1342                               match_flags |
1343                               PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY | PCRE2_PARTIAL_SOFT /* FIXME: HARD? */,
1344                               match_data,
1345                               match_context)) >= 0 || r == PCRE2_ERROR_PARTIAL)) {
1346                 gsize ko = offset;
1347                 gsize rm_so, rm_eo;
1348                 gsize *ovector;
1349 
1350                 ovector = pcre2_get_ovector_pointer_8(match_data);
1351                 rm_so = ovector[0];
1352                 rm_eo = ovector[1];
1353                 if (G_UNLIKELY(rm_so == PCRE2_UNSET || rm_eo == PCRE2_UNSET))
1354                         break;
1355 
1356                 /* The offsets should be "sane". We set NOTEMPTY, but check anyway */
1357                 if (G_UNLIKELY(position == rm_eo)) {
1358                         /* rm_eo is before the end of subject string's length, so this is safe */
1359                         position = g_utf8_next_char(line + rm_eo) - line;
1360                         continue;
1361                 }
1362 
1363                 _VTE_DEBUG_IF(VTE_DEBUG_REGEX) {
1364                         gchar *result;
1365                         struct _VteCharAttributes *_sattr, *_eattr;
1366                         result = g_strndup(line + rm_so, rm_eo - rm_so);
1367                         _sattr = &g_array_index(m_match_attributes,
1368                                                 struct _VteCharAttributes,
1369                                                 rm_so);
1370                         _eattr = &g_array_index(m_match_attributes,
1371                                                 struct _VteCharAttributes,
1372                                                 rm_eo - 1);
1373                         g_printerr("%s match `%s' from %" G_GSIZE_FORMAT "(%ld,%ld) to %" G_GSIZE_FORMAT "(%ld,%ld) (%" G_GSSIZE_FORMAT ").\n",
1374                                    r == PCRE2_ERROR_PARTIAL ? "Partial":"Full",
1375                                    result,
1376                                    rm_so,
1377                                    _sattr->column,
1378                                    _sattr->row,
1379                                    rm_eo - 1,
1380                                    _eattr->column,
1381                                    _eattr->row,
1382                                    offset);
1383                         g_free(result);
1384                 }
1385 
1386                 /* advance position */
1387                 position = rm_eo;
1388 
1389                 /* FIXME: do handle newline / partial matches at end of line/start of next line */
1390                 if (r == PCRE2_ERROR_PARTIAL)
1391                         continue;
1392 
1393                 /* If the pointer is in this substring, then we're done. */
1394                 if (ko >= rm_so && ko < rm_eo) {
1395                         *result_ptr = g_strndup(line + rm_so, rm_eo - rm_so);
1396                         *start = rm_so;
1397                         *end = rm_eo - 1;
1398                         return true;
1399                 }
1400 
1401                 if (ko >= rm_eo && rm_eo > sblank) {
1402                         sblank = rm_eo;
1403                 }
1404                 if (ko < rm_so && rm_so < eblank) {
1405                         eblank = rm_so;
1406                 }
1407         }
1408 
1409         if (G_UNLIKELY(r < PCRE2_ERROR_PARTIAL))
1410                 _vte_debug_print(VTE_DEBUG_REGEX, "Unexpected pcre2_match error code: %d\n", r);
1411 
1412         *sblank_ptr = sblank;
1413         *eblank_ptr = eblank;
1414         return false;
1415 }
1416 
1417 char *
match_check_internal_pcre(vte::grid::column_t column,vte::grid::row_t row,MatchRegex const ** match,size_t * start,size_t * end)1418 Terminal::match_check_internal_pcre(vte::grid::column_t column,
1419                                     vte::grid::row_t row,
1420                                     MatchRegex const** match,
1421                                     size_t* start,
1422                                     size_t* end)
1423 {
1424 	gsize offset, sattr, eattr, start_blank, end_blank;
1425 
1426 	_vte_debug_print(VTE_DEBUG_REGEX,
1427                          "Checking for pcre match at (%ld,%ld).\n", row, column);
1428 
1429         if (!match_rowcol_to_offset(column, row,
1430                                     &offset, &sattr, &eattr))
1431                 return nullptr;
1432 
1433 	start_blank = sattr;
1434 	end_blank = eattr;
1435 
1436         auto match_context = create_match_context();
1437         auto match_data = vte::take_freeable(pcre2_match_data_create_8(256 /* should be plenty */,
1438                                                                        nullptr /* general context */));
1439 
1440 	/* Now iterate over each regex we need to match against. */
1441         char* dingu_match{nullptr};
1442         for (auto const& rem : m_match_regexes) {
1443                 gsize sblank, eblank;
1444 
1445                 if (match_check_pcre(match_data.get(), match_context.get(),
1446                                      rem.regex(),
1447                                      rem.match_flags(),
1448                                      sattr, eattr, offset,
1449                                      &dingu_match,
1450                                      start, end,
1451                                      &sblank, &eblank)) {
1452                         _vte_debug_print(VTE_DEBUG_REGEX, "Matched dingu with tag %d\n", rem.tag());
1453                         *match = std::addressof(rem);
1454                         break;
1455                 }
1456 
1457                 if (sblank > start_blank) {
1458                         start_blank = sblank;
1459                 }
1460                 if (eblank < end_blank) {
1461                         end_blank = eblank;
1462                 }
1463 	}
1464 
1465         if (dingu_match == nullptr) {
1466                 /* If we get here, there was no dingu match.
1467                  * Record smallest span where none of the dingus match.
1468                  */
1469                 *start = start_blank;
1470                 *end = end_blank - 1;
1471                 *match = nullptr;
1472 
1473                 _VTE_DEBUG_IF(VTE_DEBUG_REGEX) {
1474                         struct _VteCharAttributes *_sattr, *_eattr;
1475                         _sattr = &g_array_index(m_match_attributes,
1476                                                 struct _VteCharAttributes,
1477                                                 start_blank);
1478                         _eattr = &g_array_index(m_match_attributes,
1479                                                 struct _VteCharAttributes,
1480                                                 end_blank - 1);
1481                         g_printerr("No-match region from %" G_GSIZE_FORMAT "(%ld,%ld) to %" G_GSIZE_FORMAT "(%ld,%ld)\n",
1482                                    start_blank, _sattr->column, _sattr->row,
1483                                    end_blank - 1, _eattr->column, _eattr->row);
1484                 }
1485         }
1486 
1487 	return dingu_match;
1488 }
1489 
1490 /*
1491  * vte_terminal_match_check_internal:
1492  * @terminal:
1493  * @column:
1494  * @row:
1495  * @match: (out):
1496  * @start: (out):
1497  * @end: (out):
1498  *
1499  * Checks m_match_contents for dingu matches, and returns the start, and
1500  * end of the match in @start, @end, and the matched regex in @match.
1501  * If no match occurs, @match will be set to %nullptr,
1502  * and if they are nonzero, @start and @end mark the smallest span in the @row
1503  * in which none of the dingus match.
1504  *
1505  * Returns: (transfer full): the matched string, or %nullptr
1506  */
1507 char *
match_check_internal(vte::grid::column_t column,vte::grid::row_t row,MatchRegex const ** match,size_t * start,size_t * end)1508 Terminal::match_check_internal(vte::grid::column_t column,
1509                                vte::grid::row_t row,
1510                                MatchRegex const** match,
1511                                size_t* start,
1512                                size_t* end)
1513 {
1514 	if (m_match_contents == nullptr) {
1515 		match_contents_refresh();
1516 	}
1517 
1518         assert(match != nullptr);
1519         assert(start != nullptr);
1520         assert(end != nullptr);
1521 
1522         *match = nullptr;
1523         *start = 0;
1524         *end = 0;
1525 
1526         return match_check_internal_pcre(column, row, match, start, end);
1527 }
1528 
1529 char*
regex_match_check(vte::grid::column_t column,vte::grid::row_t row,int * tag)1530 Terminal::regex_match_check(vte::grid::column_t column,
1531                             vte::grid::row_t row,
1532                             int* tag)
1533 {
1534 	long delta = m_screen->scroll_delta;
1535 	_vte_debug_print(VTE_DEBUG_EVENTS | VTE_DEBUG_REGEX,
1536 			"Checking for match at (%ld,%ld).\n",
1537 			row, column);
1538 
1539         char* ret{nullptr};
1540         Terminal::MatchRegex const* match{nullptr};
1541 
1542         if (m_match_span.contains(row + delta, column)) {
1543                 match = regex_match_current(); /* may be nullptr */
1544                 ret = g_strdup(m_match);
1545 	} else {
1546                 gsize start, end;
1547 
1548                 ret = match_check_internal(column, row + delta,
1549                                            &match,
1550                                            &start, &end);
1551 	}
1552 	_VTE_DEBUG_IF(VTE_DEBUG_EVENTS | VTE_DEBUG_REGEX) {
1553 		if (ret != NULL) g_printerr("Matched `%s'.\n", ret);
1554 	}
1555         if (tag != nullptr)
1556                 *tag = (match != nullptr) ? match->tag() : -1;
1557 
1558 	return ret;
1559 }
1560 
1561 /*
1562  * Terminal::view_coords_from_event:
1563  * @event: a mouse event
1564  *
1565  * Translates the event coordinates to view coordinates, by
1566  * subtracting the padding and window offset.
1567  * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
1568  * mean that the event coordinates are outside the usable area
1569  * at that side; use view_coords_visible() to check for that.
1570  */
1571 vte::view::coords
view_coords_from_event(vte::platform::MouseEvent const & event) const1572 Terminal::view_coords_from_event(vte::platform::MouseEvent const& event) const
1573 {
1574         return vte::view::coords(event.x() - m_padding.left, event.y() - m_padding.top);
1575 }
1576 
1577 bool
widget_realized() const1578 Terminal::widget_realized() const noexcept
1579 {
1580         return m_real_widget ? m_real_widget->realized() : false;
1581 }
1582 
1583 /*
1584  * Terminal::grid_coords_from_event:
1585  * @event: a mouse event
1586  *
1587  * Translates the event coordinates to view coordinates, by
1588  * subtracting the padding and window offset.
1589  * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
1590  * mean that the event coordinates are outside the usable area
1591  * at that side; use grid_coords_visible() to check for that.
1592  */
1593 vte::grid::coords
grid_coords_from_event(vte::platform::MouseEvent const & event) const1594 Terminal::grid_coords_from_event(vte::platform::MouseEvent const& event) const
1595 {
1596         return grid_coords_from_view_coords(view_coords_from_event(event));
1597 }
1598 
1599 /*
1600  * Terminal::confined_grid_coords_from_event:
1601  * @event: a mouse event
1602  *
1603  * Like grid_coords_from_event(), but also confines the coordinates
1604  * to an actual cell in the visible area.
1605  */
1606 vte::grid::coords
confined_grid_coords_from_event(vte::platform::MouseEvent const & event) const1607 Terminal::confined_grid_coords_from_event(vte::platform::MouseEvent const& event) const
1608 {
1609         auto pos = view_coords_from_event(event);
1610         return confined_grid_coords_from_view_coords(pos);
1611 }
1612 
1613 /*
1614  * Terminal::grid_coords_from_view_coords:
1615  * @pos: the view coordinates
1616  *
1617  * Translates view coordinates to grid coordinates. If the view coordinates point to
1618  * cells that are not visible, may return any value < 0 or >= m_column_count, and
1619  * < first_displayed_row() or > last_displayed_row(), resp.
1620  */
1621 vte::grid::coords
grid_coords_from_view_coords(vte::view::coords const & pos) const1622 Terminal::grid_coords_from_view_coords(vte::view::coords const& pos) const
1623 {
1624         /* Our caller had to update the ringview (we can't do because we're const). */
1625         g_assert(m_ringview.is_updated());
1626 
1627         vte::grid::column_t col;
1628         if (pos.x >= 0 && pos.x < m_view_usable_extents.width())
1629                 col = pos.x / m_cell_width;
1630         else if (pos.x < 0)
1631                 col = -1;
1632         else
1633                 col = m_column_count;
1634 
1635         vte::grid::row_t row = pixel_to_row(pos.y);
1636 
1637         /* BiDi: convert to logical column. */
1638         vte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
1639         col = bidirow->vis2log(col);
1640 
1641         return vte::grid::coords(row, col);
1642 }
1643 
1644 vte::grid::row_t
confine_grid_row(vte::grid::row_t const & row) const1645 Terminal::confine_grid_row(vte::grid::row_t const& row) const
1646 {
1647         auto first_row = first_displayed_row();
1648         auto last_row = last_displayed_row();
1649 
1650         return vte::clamp(row, first_row, last_row);
1651 }
1652 
1653 /*
1654  * Terminal::confined_grid_coords_from_view_coords:
1655  * @pos: the view coordinates
1656  *
1657  * Like grid_coords_from_view_coords(), but also confines the coordinates
1658  * to an actual cell in the visible area.
1659  */
1660 vte::grid::coords
confined_grid_coords_from_view_coords(vte::view::coords const & pos) const1661 Terminal::confined_grid_coords_from_view_coords(vte::view::coords const& pos) const
1662 {
1663         auto rowcol = grid_coords_from_view_coords(pos);
1664         return confine_grid_coords(rowcol);
1665 }
1666 
1667 /*
1668  * Terminal::view_coords_from_grid_coords:
1669  * @rowcol: the grid coordinates
1670  *
1671  * Translates grid coordinates to view coordinates. If the view coordinates are
1672  * outside the usable area, may return any value < 0 or >= m_usable_view_extents.
1673  *
1674  * Returns: %true if the coordinates are inside the usable area
1675  */
1676 vte::view::coords
view_coords_from_grid_coords(vte::grid::coords const & rowcol) const1677 Terminal::view_coords_from_grid_coords(vte::grid::coords const& rowcol) const
1678 {
1679         return vte::view::coords(rowcol.column() * m_cell_width,
1680                                  row_to_pixel(rowcol.row()));
1681 }
1682 
1683 bool
view_coords_visible(vte::view::coords const & pos) const1684 Terminal::view_coords_visible(vte::view::coords const& pos) const
1685 {
1686         return pos.x >= 0 && pos.x < m_view_usable_extents.width() &&
1687                pos.y >= 0 && pos.y < m_view_usable_extents.height();
1688 }
1689 
1690 bool
grid_coords_visible(vte::grid::coords const & rowcol) const1691 Terminal::grid_coords_visible(vte::grid::coords const& rowcol) const
1692 {
1693         return rowcol.column() >= 0 &&
1694                rowcol.column() < m_column_count &&
1695                rowcol.row() >= first_displayed_row() &&
1696                rowcol.row() <= last_displayed_row();
1697 }
1698 
1699 vte::grid::coords
confine_grid_coords(vte::grid::coords const & rowcol) const1700 Terminal::confine_grid_coords(vte::grid::coords const& rowcol) const
1701 {
1702         /* Confine clicks to the nearest actual cell. This is especially useful for
1703          * fullscreen vte so that you can click on the very edge of the screen.
1704          */
1705         auto first_row = first_displayed_row();
1706         auto last_row = last_displayed_row();
1707 
1708         return vte::grid::coords(CLAMP(rowcol.row(), first_row, last_row),
1709                                  CLAMP(rowcol.column(), 0, m_column_count - 1));
1710 }
1711 
1712 /*
1713  * Track mouse click and drag positions (the "origin" and "last" coordinates) with half cell accuracy,
1714  * that is, know whether the event occurred over the left/start or right/end half of the cell.
1715  * This is required because some selection modes care about the cell over which the event occurred,
1716  * while some care about the closest boundary between cells.
1717  *
1718  * Storing the actual view coordinates would become problematic when the font size changes (bug 756058),
1719  * and would cause too much work when the mouse moves within the half cell.
1720  *
1721  * Left/start margin or anything further to the left/start is denoted by column -1's right half,
1722  * right/end margin or anything further to the right/end is denoted by column m_column_count's left half.
1723  *
1724  * BiDi: returns logical position (start or end) for normal selection modes, visual position (left or
1725  * right) for block mode.
1726  */
1727 vte::grid::halfcoords
selection_grid_halfcoords_from_view_coords(vte::view::coords const & pos) const1728 Terminal::selection_grid_halfcoords_from_view_coords(vte::view::coords const& pos) const
1729 {
1730         /* Our caller had to update the ringview (we can't do because we're const). */
1731         g_assert(m_ringview.is_updated());
1732 
1733         vte::grid::row_t row = pixel_to_row(pos.y);
1734         vte::grid::column_t col;
1735         vte::grid::half_t half;
1736 
1737         if (pos.x < 0) {
1738                 col = -1;
1739                 half = 1;
1740         } else if (pos.x >= m_column_count * m_cell_width) {
1741                 col = m_column_count;
1742                 half = 0;
1743         } else {
1744                 col = pos.x / m_cell_width;
1745                 half = (pos.x * 2 / m_cell_width) % 2;
1746         }
1747 
1748         if (!m_selection_block_mode) {
1749                 /* BiDi: convert from visual to logical half column. */
1750                 vte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
1751 
1752                 if (bidirow->vis_is_rtl(col))
1753                         half = 1 - half;
1754                 col = bidirow->vis2log(col);
1755         }
1756 
1757         return { row, vte::grid::halfcolumn_t(col, half) };
1758 }
1759 
1760 /*
1761  * Called on Shift+Click to continue (extend or shrink) the previous selection.
1762  * Swaps the two endpoints of the selection if needed, so that m_selection_origin
1763  * contains the new fixed point and m_selection_last is the newly dragged end.
1764  * In block mode it might even switch to the other two corners.
1765  * As per GTK+'s generic selection behavior, retains the origin and last
1766  * endpoints if the Shift+click happened inside the selection.
1767  */
1768 void
selection_maybe_swap_endpoints(vte::view::coords const & pos)1769 Terminal::selection_maybe_swap_endpoints(vte::view::coords const& pos)
1770 {
1771         if (m_selection_resolved.empty())
1772                 return;
1773 
1774         /* Need to ensure the ringview is updated. */
1775         ringview_update();
1776 
1777         auto current = selection_grid_halfcoords_from_view_coords (pos);
1778 
1779         if (m_selection_block_mode) {
1780                 if ((current.row() <= m_selection_origin.row() && m_selection_origin.row() < m_selection_last.row()) ||
1781                     (current.row() >= m_selection_origin.row() && m_selection_origin.row() > m_selection_last.row())) {
1782                         // FIXME see if we can use std::swap()
1783                         auto tmp = m_selection_origin.row();
1784                         m_selection_origin.set_row(m_selection_last.row());
1785                         m_selection_last.set_row(tmp);
1786                 }
1787                 if ((current.halfcolumn() <= m_selection_origin.halfcolumn() && m_selection_origin.halfcolumn() < m_selection_last.halfcolumn()) ||
1788                     (current.halfcolumn() >= m_selection_origin.halfcolumn() && m_selection_origin.halfcolumn() > m_selection_last.halfcolumn())) {
1789                         // FIXME see if we can use std::swap()
1790                         auto tmp = m_selection_origin.halfcolumn();
1791                         m_selection_origin.set_halfcolumn(m_selection_last.halfcolumn());
1792                         m_selection_last.set_halfcolumn(tmp);
1793                 }
1794         } else {
1795                 if ((current <= m_selection_origin && m_selection_origin < m_selection_last) ||
1796                     (current >= m_selection_origin && m_selection_origin > m_selection_last)) {
1797                         using std::swap;
1798                         swap(m_selection_origin, m_selection_last);
1799                 }
1800         }
1801 
1802         _vte_debug_print(VTE_DEBUG_SELECTION,
1803                          "Selection maybe swap endpoints: origin=%s last=%s\n",
1804                          m_selection_origin.to_string(),
1805                          m_selection_last.to_string());
1806 }
1807 
1808 bool
rowcol_from_event(vte::platform::MouseEvent const & event,long * column,long * row)1809 Terminal::rowcol_from_event(vte::platform::MouseEvent const& event,
1810                             long *column,
1811                             long *row)
1812 {
1813         auto rowcol = grid_coords_from_event(event);
1814         if (!grid_coords_visible(rowcol))
1815                 return false;
1816 
1817         *column = rowcol.column();
1818         *row = rowcol.row();
1819         return true;
1820 }
1821 
1822 char *
hyperlink_check(vte::platform::MouseEvent const & event)1823 Terminal::hyperlink_check(vte::platform::MouseEvent const& event)
1824 {
1825         long col, row;
1826         const char *hyperlink;
1827         const char *separator;
1828 
1829         if (!m_allow_hyperlink)
1830                 return NULL;
1831 
1832         /* Need to ensure the ringview is updated. */
1833         ringview_update();
1834 
1835         if (!rowcol_from_event(event, &col, &row))
1836                 return NULL;
1837 
1838         _vte_ring_get_hyperlink_at_position(m_screen->row_data, row, col, false, &hyperlink);
1839 
1840         if (hyperlink != NULL) {
1841                 /* URI is after the first semicolon */
1842                 separator = strchr(hyperlink, ';');
1843                 g_assert(separator != NULL);
1844                 hyperlink = separator + 1;
1845         }
1846 
1847         _vte_debug_print (VTE_DEBUG_HYPERLINK,
1848                           "hyperlink_check: \"%s\"\n",
1849                           hyperlink);
1850 
1851         return g_strdup(hyperlink);
1852 }
1853 
1854 char *
regex_match_check(vte::platform::MouseEvent const & event,int * tag)1855 Terminal::regex_match_check(vte::platform::MouseEvent const& event,
1856                             int *tag)
1857 {
1858         long col, row;
1859 
1860         /* Need to ensure the ringview is updated. */
1861         ringview_update();
1862 
1863         if (!rowcol_from_event(event, &col, &row))
1864                 return FALSE;
1865 
1866         /* FIXME Shouldn't rely on a deprecated, not sub-row aware method. */
1867         // FIXMEchpe fix this scroll_delta substraction!
1868         return regex_match_check(col, row - (long)m_screen->scroll_delta, tag);
1869 }
1870 
1871 bool
regex_match_check_extra(vte::platform::MouseEvent const & event,vte::base::Regex const ** regexes,size_t n_regexes,uint32_t match_flags,char ** matches)1872 Terminal::regex_match_check_extra(vte::platform::MouseEvent const& event,
1873                                   vte::base::Regex const** regexes,
1874                                   size_t n_regexes,
1875                                   uint32_t match_flags,
1876                                   char** matches)
1877 {
1878 	gsize offset, sattr, eattr;
1879         bool any_matches = false;
1880         long col, row;
1881         guint i;
1882 
1883         assert(regexes != nullptr || n_regexes == 0);
1884         assert(matches != nullptr);
1885 
1886         /* Need to ensure the ringview is updated. */
1887         ringview_update();
1888 
1889         if (!rowcol_from_event(event, &col, &row))
1890                 return false;
1891 
1892 	if (m_match_contents == nullptr) {
1893 		match_contents_refresh();
1894 	}
1895 
1896         if (!match_rowcol_to_offset(col, row,
1897                                     &offset, &sattr, &eattr))
1898                 return false;
1899 
1900         auto match_context = create_match_context();
1901         auto match_data = vte::take_freeable(pcre2_match_data_create_8(256 /* should be plenty */,
1902                                                                        nullptr /* general context */));
1903 
1904         for (i = 0; i < n_regexes; i++) {
1905                 gsize start, end, sblank, eblank;
1906                 char *match_string;
1907 
1908                 g_return_val_if_fail(regexes[i] != nullptr, false);
1909 
1910                 if (match_check_pcre(match_data.get(), match_context.get(),
1911                                      regexes[i], match_flags,
1912                                      sattr, eattr, offset,
1913                                      &match_string,
1914                                      &start, &end,
1915                                      &sblank, &eblank)) {
1916                         _vte_debug_print(VTE_DEBUG_REGEX, "Matched regex with text: %s\n", match_string);
1917                         matches[i] = match_string;
1918                         any_matches = true;
1919                 } else
1920                         matches[i] = nullptr;
1921         }
1922 
1923         return any_matches;
1924 }
1925 
1926 /* Emit an adjustment changed signal on our adjustment object. */
1927 void
emit_adjustment_changed()1928 Terminal::emit_adjustment_changed()
1929 {
1930 	if (m_adjustment_changed_pending) {
1931 		bool changed = false;
1932 		gdouble current, v;
1933 
1934                 auto vadjustment = m_vadjustment.get();
1935 
1936                 auto const freezer = vte::glib::FreezeObjectNotify{vadjustment};
1937 
1938 		v = _vte_ring_delta (m_screen->row_data);
1939                 current = gtk_adjustment_get_lower(vadjustment);
1940 		if (!_vte_double_equal(current, v)) {
1941 			_vte_debug_print(VTE_DEBUG_ADJ,
1942 					"Changing lower bound from %.0f to %f\n",
1943 					 current, v);
1944                         gtk_adjustment_set_lower(vadjustment, v);
1945 			changed = true;
1946 		}
1947 
1948 		v = m_screen->insert_delta + m_row_count;
1949                 current = gtk_adjustment_get_upper(vadjustment);
1950 		if (!_vte_double_equal(current, v)) {
1951 			_vte_debug_print(VTE_DEBUG_ADJ,
1952 					"Changing upper bound from %.0f to %f\n",
1953 					 current, v);
1954                         gtk_adjustment_set_upper(vadjustment, v);
1955 			changed = true;
1956 		}
1957 
1958 		/* The step increment should always be one. */
1959                 v = gtk_adjustment_get_step_increment(vadjustment);
1960 		if (!_vte_double_equal(v, 1)) {
1961 			_vte_debug_print(VTE_DEBUG_ADJ,
1962 					"Changing step increment from %.0lf to 1\n", v);
1963                         gtk_adjustment_set_step_increment(vadjustment, 1);
1964 			changed = true;
1965 		}
1966 
1967 		/* Set the number of rows the user sees to the number of rows the
1968 		 * user sees. */
1969                 v = gtk_adjustment_get_page_size(vadjustment);
1970 		if (!_vte_double_equal(v, m_row_count)) {
1971 			_vte_debug_print(VTE_DEBUG_ADJ,
1972 					"Changing page size from %.0f to %ld\n",
1973 					 v, m_row_count);
1974                         gtk_adjustment_set_page_size(vadjustment,
1975 						     m_row_count);
1976 			changed = true;
1977 		}
1978 
1979 		/* Clicking in the empty area should scroll one screen, so set the
1980 		 * page size to the number of visible rows. */
1981                 v = gtk_adjustment_get_page_increment(vadjustment);
1982 		if (!_vte_double_equal(v, m_row_count)) {
1983 			_vte_debug_print(VTE_DEBUG_ADJ,
1984 					"Changing page increment from "
1985 					"%.0f to %ld\n",
1986 					v, m_row_count);
1987                         gtk_adjustment_set_page_increment(vadjustment,
1988 							  m_row_count);
1989 			changed = true;
1990 		}
1991 
1992 		if (changed)
1993 			_vte_debug_print(VTE_DEBUG_SIGNALS,
1994 					"Emitting adjustment_changed.\n");
1995 		m_adjustment_changed_pending = FALSE;
1996 	}
1997 	if (m_adjustment_value_changed_pending) {
1998 		double v, delta;
1999 		_vte_debug_print(VTE_DEBUG_SIGNALS,
2000 				"Emitting adjustment_value_changed.\n");
2001 		m_adjustment_value_changed_pending = FALSE;
2002 
2003                 auto vadjustment = m_vadjustment.get();
2004                 v = gtk_adjustment_get_value(vadjustment);
2005 		if (!_vte_double_equal(v, m_screen->scroll_delta)) {
2006 			/* this little dance is so that the scroll_delta is
2007 			 * updated immediately, but we still handled scrolling
2008 			 * via the adjustment - e.g. user interaction with the
2009 			 * scrollbar
2010 			 */
2011 			delta = m_screen->scroll_delta;
2012 			m_screen->scroll_delta = v;
2013                         gtk_adjustment_set_value(vadjustment, delta);
2014 		}
2015 	}
2016 }
2017 
2018 /* Queue an adjustment-changed signal to be delivered when convenient. */
2019 // FIXMEchpe this has just one caller, fold it into the call site
2020 void
queue_adjustment_changed()2021 Terminal::queue_adjustment_changed()
2022 {
2023 	m_adjustment_changed_pending = true;
2024 	add_update_timeout(this);
2025 }
2026 
2027 void
queue_adjustment_value_changed(double v)2028 Terminal::queue_adjustment_value_changed(double v)
2029 {
2030 	if (!_vte_double_equal(v, m_screen->scroll_delta)) {
2031                 _vte_debug_print(VTE_DEBUG_ADJ,
2032                                  "Adjustment value changed to %f\n",
2033                                  v);
2034 		m_screen->scroll_delta = v;
2035 		m_adjustment_value_changed_pending = true;
2036 		add_update_timeout(this);
2037 	}
2038 }
2039 
2040 void
queue_adjustment_value_changed_clamped(double v)2041 Terminal::queue_adjustment_value_changed_clamped(double v)
2042 {
2043         auto vadjustment = m_vadjustment.get();
2044         auto const lower = gtk_adjustment_get_lower(vadjustment);
2045         auto const upper = gtk_adjustment_get_upper(vadjustment);
2046 
2047 	v = CLAMP(v, lower, MAX (lower, upper - m_row_count));
2048 
2049 	queue_adjustment_value_changed(v);
2050 }
2051 
2052 void
adjust_adjustments()2053 Terminal::adjust_adjustments()
2054 {
2055 	g_assert(m_screen != nullptr);
2056 	g_assert(m_screen->row_data != nullptr);
2057 
2058 	queue_adjustment_changed();
2059 
2060 	/* The lower value should be the first row in the buffer. */
2061 	long delta = _vte_ring_delta(m_screen->row_data);
2062 	/* Snap the insert delta and the cursor position to be in the visible
2063 	 * area.  Leave the scrolling delta alone because it will be updated
2064 	 * when the adjustment changes. */
2065 	m_screen->insert_delta = MAX(m_screen->insert_delta, delta);
2066         m_screen->cursor.row = MAX(m_screen->cursor.row,
2067                                    m_screen->insert_delta);
2068 
2069 	if (m_screen->scroll_delta > m_screen->insert_delta) {
2070 		queue_adjustment_value_changed(m_screen->insert_delta);
2071 	}
2072 }
2073 
2074 /* Update the adjustment field of the widget.  This function should be called
2075  * whenever we add rows to or remove rows from the history or switch screens. */
2076 void
adjust_adjustments_full()2077 Terminal::adjust_adjustments_full()
2078 {
2079 	g_assert(m_screen != NULL);
2080 	g_assert(m_screen->row_data != NULL);
2081 
2082 	adjust_adjustments();
2083 	queue_adjustment_changed();
2084 }
2085 
2086 /* Scroll a fixed number of lines up or down in the current screen. */
2087 void
scroll_lines(long lines)2088 Terminal::scroll_lines(long lines)
2089 {
2090 	double destination;
2091 	_vte_debug_print(VTE_DEBUG_ADJ, "Scrolling %ld lines.\n", lines);
2092 	/* Calculate the ideal position where we want to be before clamping. */
2093 	destination = m_screen->scroll_delta;
2094         /* Snap to whole cell offset. */
2095         if (lines > 0)
2096                 destination = floor(destination);
2097         else if (lines < 0)
2098                 destination = ceil(destination);
2099 	destination += lines;
2100 	/* Tell the scrollbar to adjust itself. */
2101 	queue_adjustment_value_changed_clamped(destination);
2102 }
2103 
2104 /* Scroll so that the scroll delta is the minimum value. */
2105 void
maybe_scroll_to_top()2106 Terminal::maybe_scroll_to_top()
2107 {
2108 	queue_adjustment_value_changed(_vte_ring_delta(m_screen->row_data));
2109 }
2110 
2111 void
maybe_scroll_to_bottom()2112 Terminal::maybe_scroll_to_bottom()
2113 {
2114 	queue_adjustment_value_changed(m_screen->insert_delta);
2115 	_vte_debug_print(VTE_DEBUG_ADJ,
2116 			"Snapping to bottom of screen\n");
2117 }
2118 
2119 /*
2120  * Terminal::set_encoding:
2121  * @charset: (allow-none): target charset, or %NULL to use UTF-8
2122  *
2123  * Changes the encoding the terminal will expect data from the child to
2124  * be encoded with.  If @charset is %NULL, it uses "UTF-8".
2125  *
2126  * Returns: %true if the encoding could be changed to the specified one
2127  */
2128 bool
set_encoding(char const * charset,GError ** error)2129 Terminal::set_encoding(char const* charset,
2130                        GError** error)
2131 {
2132         auto const to_utf8 = bool{charset == nullptr || g_ascii_strcasecmp(charset, "UTF-8") == 0};
2133 
2134 #ifdef WITH_ICU
2135         /* Note that if the current data syntax is not a primary one, the change
2136          * will only be applied when returning to the primrary data syntax.
2137          */
2138 
2139         if (to_utf8) {
2140                 if (primary_data_syntax() == DataSyntax::ECMA48_UTF8)
2141                         return true;
2142 
2143                 m_converter.reset();
2144                 m_primary_data_syntax = DataSyntax::ECMA48_UTF8;
2145         } else {
2146                 if (primary_data_syntax() == DataSyntax::ECMA48_PCTERM &&
2147                     m_converter->charset() == charset)
2148                         return true;
2149 
2150                 try {
2151                         auto converter = vte::base::ICUConverter::make(charset, error);
2152                         if (!converter)
2153                                return false;
2154 
2155                         m_converter = std::move(converter);
2156                         m_primary_data_syntax = DataSyntax::ECMA48_PCTERM;
2157 
2158                 } catch (...) {
2159                         return vte::glib::set_error_from_exception(error);
2160                 }
2161         }
2162 
2163         /* Note: we DON'T convert any pending output from the previous charset to
2164          * the new charset, since that is in general not possible without loss, and
2165          * also the output may include binary data (Terminal::feed_child_binary()).
2166          * So we just clear the outgoing queue. (FIXMEchpe: instead, we could flush
2167          * the outgooing and only change charsets once it's empty.)
2168          * Do not clear the incoming queue.
2169          */
2170         _vte_byte_array_clear(m_outgoing);
2171 
2172         reset_decoder();
2173 
2174         if (pty())
2175                 pty()->set_utf8(primary_data_syntax() == DataSyntax::ECMA48_UTF8);
2176 
2177 	_vte_debug_print(VTE_DEBUG_IO,
2178                          "Set terminal encoding to `%s'.\n",
2179                          encoding());
2180 
2181         return true;
2182 
2183 #else
2184 
2185         if (to_utf8)
2186                 return true;
2187 
2188         g_set_error_literal(error, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION,
2189                             "ICU support not available");
2190         return false;
2191 #endif
2192 }
2193 
2194 bool
set_cjk_ambiguous_width(int width)2195 Terminal::set_cjk_ambiguous_width(int width)
2196 {
2197         g_assert(width == 1 || width == 2);
2198 
2199         if (m_utf8_ambiguous_width == width)
2200                 return false;
2201 
2202         m_utf8_ambiguous_width = width;
2203         return true;
2204 }
2205 
2206 // FIXMEchpe replace this with a method on VteRing
2207 VteRowData *
insert_rows(guint cnt)2208 Terminal::insert_rows (guint cnt)
2209 {
2210 	VteRowData *row;
2211 	do {
2212 		row = ring_append(false);
2213 	} while(--cnt);
2214 	return row;
2215 }
2216 
2217 /* Make sure we have enough rows and columns to hold data at the current
2218  * cursor position. */
2219 VteRowData *
ensure_row()2220 Terminal::ensure_row()
2221 {
2222 	VteRowData *row;
2223 
2224 	/* Figure out how many rows we need to add. */
2225 	auto const delta = m_screen->cursor.row - _vte_ring_next(m_screen->row_data) + 1;
2226 	if (delta > 0) {
2227 		row = insert_rows(delta);
2228 		adjust_adjustments();
2229 	} else {
2230 		/* Find the row the cursor is in. */
2231 		row = _vte_ring_index_writable(m_screen->row_data, m_screen->cursor.row);
2232 	}
2233 	g_assert(row != NULL);
2234 
2235 	return row;
2236 }
2237 
2238 VteRowData *
ensure_cursor()2239 Terminal::ensure_cursor()
2240 {
2241 	VteRowData *row = ensure_row();
2242         _vte_row_data_fill(row, &basic_cell, m_screen->cursor.col);
2243 
2244 	return row;
2245 }
2246 
2247 /* Update the insert delta so that the screen which includes it also
2248  * includes the end of the buffer. */
2249 void
update_insert_delta()2250 Terminal::update_insert_delta()
2251 {
2252 	/* The total number of lines.  Add one to the cursor offset
2253 	 * because it's zero-based. */
2254 	auto rows = _vte_ring_next(m_screen->row_data);
2255         auto delta = m_screen->cursor.row - rows + 1;
2256 	if (G_UNLIKELY (delta > 0)) {
2257 		insert_rows(delta);
2258 		rows = _vte_ring_next(m_screen->row_data);
2259 	}
2260 
2261 	/* Make sure that the bottom row is visible, and that it's in
2262 	 * the buffer (even if it's empty).  This usually causes the
2263 	 * top row to become a history-only row. */
2264 	delta = m_screen->insert_delta;
2265 	delta = MIN(delta, rows - m_row_count);
2266 	delta = MAX(delta,
2267                     m_screen->cursor.row - (m_row_count - 1));
2268 	delta = MAX(delta, _vte_ring_delta(m_screen->row_data));
2269 
2270 	/* Adjust the insert delta and scroll if needed. */
2271 	if (delta != m_screen->insert_delta) {
2272 		m_screen->insert_delta = delta;
2273 		adjust_adjustments();
2274 	}
2275 }
2276 
2277 /* Apply the desired mouse pointer, based on certain member variables. */
2278 void
apply_mouse_cursor()2279 Terminal::apply_mouse_cursor()
2280 {
2281         if (!widget_realized())
2282                 return;
2283 
2284         /* Show the cursor if over the widget and not autohidden, this is obvious.
2285          * Also show the cursor if outside the widget regardless of the autohidden state, so that if a popover is opened
2286          * and then the cursor returns (which doesn't trigger enter/motion events), it is visible.
2287          * That is, only hide the cursor if it's over the widget and is autohidden.
2288          * See bug 789390 and bug 789536 comment 6 for details. */
2289         if (!(m_mouse_autohide && m_mouse_cursor_autohidden && m_mouse_cursor_over_widget)) {
2290                 if (m_hyperlink_hover_idx != 0) {
2291                         _vte_debug_print(VTE_DEBUG_CURSOR,
2292                                         "Setting hyperlink mouse cursor.\n");
2293                         m_real_widget->set_cursor(vte::platform::Widget::CursorType::eHyperlink);
2294                 } else if (regex_match_has_current()) {
2295                         m_real_widget->set_cursor(regex_match_current()->cursor());
2296                 } else if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
2297 			_vte_debug_print(VTE_DEBUG_CURSOR,
2298 					"Setting mousing cursor.\n");
2299                         m_real_widget->set_cursor(vte::platform::Widget::CursorType::eMousing);
2300 		} else {
2301 			_vte_debug_print(VTE_DEBUG_CURSOR,
2302 					"Setting default mouse cursor.\n");
2303                         m_real_widget->set_cursor(vte::platform::Widget::CursorType::eDefault);
2304 		}
2305 	} else {
2306 		_vte_debug_print(VTE_DEBUG_CURSOR,
2307 				"Setting to invisible cursor.\n");
2308                 m_real_widget->set_cursor(vte::platform::Widget::CursorType::eInvisible);
2309 	}
2310 }
2311 
2312 /* Show or hide the pointer if autohiding is enabled. */
2313 void
set_pointer_autohidden(bool autohidden)2314 Terminal::set_pointer_autohidden(bool autohidden)
2315 {
2316         if (autohidden == m_mouse_cursor_autohidden)
2317                 return;
2318 
2319         m_mouse_cursor_autohidden = autohidden;
2320 
2321         if (m_mouse_autohide) {
2322                 hyperlink_hilite_update();
2323                 match_hilite_update();
2324                 apply_mouse_cursor();
2325         }
2326 }
2327 
2328 /*
2329  * Get the actually used color from the palette.
2330  * The return value can be NULL only if entry is one of VTE_CURSOR_BG,
2331  * VTE_CURSOR_FG, VTE_HIGHLIGHT_BG or VTE_HIGHLIGHT_FG.
2332  */
2333 vte::color::rgb const*
get_color(int entry) const2334 Terminal::get_color(int entry) const
2335 {
2336 	VtePaletteColor const* palette_color = &m_palette[entry];
2337 	guint source;
2338 	for (source = 0; source < G_N_ELEMENTS(palette_color->sources); source++)
2339 		if (palette_color->sources[source].is_set)
2340 			return &palette_color->sources[source].color;
2341 	return nullptr;
2342 }
2343 
2344 /* Set up a palette entry with a more-or-less match for the requested color. */
2345 void
set_color(int entry,int source,vte::color::rgb const & proposed)2346 Terminal::set_color(int entry,
2347                               int source,
2348                               vte::color::rgb const& proposed)
2349 {
2350         g_assert(entry >= 0 && entry < VTE_PALETTE_SIZE);
2351 
2352 	VtePaletteColor *palette_color = &m_palette[entry];
2353 
2354         _vte_debug_print(VTE_DEBUG_MISC,
2355                          "Set %s color[%d] to (%04x,%04x,%04x).\n",
2356                          source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
2357                          entry, proposed.red, proposed.green, proposed.blue);
2358 
2359         if (palette_color->sources[source].is_set &&
2360             palette_color->sources[source].color == proposed) {
2361                 return;
2362         }
2363         palette_color->sources[source].is_set = TRUE;
2364         palette_color->sources[source].color = proposed;
2365 
2366 	/* If we're not realized yet, there's nothing else to do. */
2367 	if (!widget_realized())
2368 		return;
2369 
2370 	/* and redraw */
2371 	if (entry == VTE_CURSOR_BG || entry == VTE_CURSOR_FG)
2372 		invalidate_cursor_once();
2373 	else
2374 		invalidate_all();
2375 }
2376 
2377 void
reset_color(int entry,int source)2378 Terminal::reset_color(int entry,
2379                                 int source)
2380 {
2381         g_assert(entry >= 0 && entry < VTE_PALETTE_SIZE);
2382 
2383 	VtePaletteColor *palette_color = &m_palette[entry];
2384 
2385         _vte_debug_print(VTE_DEBUG_MISC,
2386                          "Reset %s color[%d].\n",
2387                          source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
2388                          entry);
2389 
2390         if (!palette_color->sources[source].is_set) {
2391                 return;
2392         }
2393         palette_color->sources[source].is_set = FALSE;
2394 
2395 	/* If we're not realized yet, there's nothing else to do. */
2396 	if (!widget_realized())
2397 		return;
2398 
2399 	/* and redraw */
2400 	if (entry == VTE_CURSOR_BG || entry == VTE_CURSOR_FG)
2401 		invalidate_cursor_once();
2402 	else
2403 		invalidate_all();
2404 }
2405 
2406 bool
set_background_alpha(double alpha)2407 Terminal::set_background_alpha(double alpha)
2408 {
2409         g_assert(alpha >= 0. && alpha <= 1.);
2410 
2411         if (_vte_double_equal(alpha, m_background_alpha))
2412                 return false;
2413 
2414         _vte_debug_print(VTE_DEBUG_MISC,
2415                          "Setting background alpha to %.3f\n", alpha);
2416         m_background_alpha = alpha;
2417 
2418         invalidate_all();
2419 
2420         return true;
2421 }
2422 
2423 void
set_colors_default()2424 Terminal::set_colors_default()
2425 {
2426         set_colors(nullptr, nullptr, nullptr, 0);
2427 }
2428 
2429 /*
2430  * Terminal::set_colors:
2431  * @terminal: a #VteTerminal
2432  * @foreground: (allow-none): the new foreground color, or %NULL
2433  * @background: (allow-none): the new background color, or %NULL
2434  * @palette: (array length=palette_size zero-terminated=0): the color palette
2435  * @palette_size: the number of entries in @palette
2436  *
2437  * @palette specifies the new values for the 256 palette colors: 8 standard colors,
2438  * their 8 bright counterparts, 6x6x6 color cube, and 24 grayscale colors.
2439  * Omitted entries will default to a hardcoded value.
2440  *
2441  * @palette_size must be 0, 8, 16, 232 or 256.
2442  *
2443  * If @foreground is %NULL and @palette_size is greater than 0, the new foreground
2444  * color is taken from @palette[7].  If @background is %NULL and @palette_size is
2445  * greater than 0, the new background color is taken from @palette[0].
2446  */
2447 void
set_colors(vte::color::rgb const * foreground,vte::color::rgb const * background,vte::color::rgb const * new_palette,gsize palette_size)2448 Terminal::set_colors(vte::color::rgb const* foreground,
2449                                vte::color::rgb const* background,
2450                                vte::color::rgb const* new_palette,
2451                                gsize palette_size)
2452 {
2453 	_vte_debug_print(VTE_DEBUG_MISC,
2454 			"Set color palette [%" G_GSIZE_FORMAT " elements].\n",
2455 			palette_size);
2456 
2457 	/* Accept NULL as the default foreground and background colors if we
2458 	 * got a palette. */
2459 	if ((foreground == NULL) && (palette_size >= 8)) {
2460 		foreground = &new_palette[7];
2461 	}
2462 	if ((background == NULL) && (palette_size >= 8)) {
2463 		background = &new_palette[0];
2464 	}
2465 
2466 	/* Initialize each item in the palette if we got any entries to work
2467 	 * with. */
2468 	for (gsize i = 0; i < G_N_ELEMENTS(m_palette); i++) {
2469                 vte::color::rgb color;
2470 		bool unset = false;
2471 
2472 		if (i < 16) {
2473 			color.blue = (i & 4) ? 0xc000 : 0;
2474 			color.green = (i & 2) ? 0xc000 : 0;
2475 			color.red = (i & 1) ? 0xc000 : 0;
2476 			if (i > 7) {
2477 				color.blue += 0x3fff;
2478 				color.green += 0x3fff;
2479 				color.red += 0x3fff;
2480 			}
2481 		}
2482 		else if (i < 232) {
2483 			int j = i - 16;
2484 			int r = j / 36, g = (j / 6) % 6, b = j % 6;
2485 			int red =   (r == 0) ? 0 : r * 40 + 55;
2486 			int green = (g == 0) ? 0 : g * 40 + 55;
2487 			int blue =  (b == 0) ? 0 : b * 40 + 55;
2488 			color.red   = red | red << 8  ;
2489 			color.green = green | green << 8;
2490 			color.blue  = blue | blue << 8;
2491 		} else if (i < 256) {
2492 			int shade = 8 + (i - 232) * 10;
2493 			color.red = color.green = color.blue = shade | shade << 8;
2494 		}
2495 		else switch (i) {
2496 			case VTE_DEFAULT_BG:
2497 				if (background) {
2498 					color = *background;
2499 				} else {
2500 					color.red = 0;
2501 					color.blue = 0;
2502 					color.green = 0;
2503 				}
2504 				break;
2505 			case VTE_DEFAULT_FG:
2506 				if (foreground) {
2507 					color = *foreground;
2508 				} else {
2509 					color.red = 0xc000;
2510 					color.blue = 0xc000;
2511 					color.green = 0xc000;
2512 				}
2513 				break;
2514 			case VTE_BOLD_FG:
2515                                 unset = true;
2516                                 break;
2517 			case VTE_HIGHLIGHT_BG:
2518 				unset = true;
2519 				break;
2520 			case VTE_HIGHLIGHT_FG:
2521 				unset = true;
2522 				break;
2523 			case VTE_CURSOR_BG:
2524 				unset = true;
2525 				break;
2526 			case VTE_CURSOR_FG:
2527 				unset = true;
2528 				break;
2529 			}
2530 
2531 		/* Override from the supplied palette if there is one. */
2532 		if (i < palette_size) {
2533 			color = new_palette[i];
2534 		}
2535 
2536 		/* Set up the color entry. */
2537                 if (unset)
2538                         reset_color(i, VTE_COLOR_SOURCE_API);
2539                 else
2540                         set_color(i, VTE_COLOR_SOURCE_API, color);
2541 	}
2542 }
2543 
2544 /*
2545  * Terminal::set_color_bold:
2546  * @bold: (allow-none): the new bold color or %NULL
2547  *
2548  * Sets the color used to draw bold text in the default foreground color.
2549  * If @bold is %NULL then the default color is used.
2550  */
2551 void
set_color_bold(vte::color::rgb const & color)2552 Terminal::set_color_bold(vte::color::rgb const& color)
2553 {
2554         _vte_debug_print(VTE_DEBUG_MISC,
2555                          "Set %s color to (%04x,%04x,%04x).\n", "bold",
2556                          color.red, color.green, color.blue);
2557         set_color(VTE_BOLD_FG, VTE_COLOR_SOURCE_API, color);
2558 }
2559 
2560 void
reset_color_bold()2561 Terminal::reset_color_bold()
2562 {
2563         _vte_debug_print(VTE_DEBUG_MISC,
2564                          "Reset %s color.\n", "bold");
2565         reset_color(VTE_BOLD_FG, VTE_COLOR_SOURCE_API);
2566 }
2567 
2568 /*
2569  * Terminal::set_color_foreground:
2570  * @foreground: the new foreground color
2571  *
2572  * Sets the foreground color used to draw normal text.
2573  */
2574 void
set_color_foreground(vte::color::rgb const & color)2575 Terminal::set_color_foreground(vte::color::rgb const& color)
2576 {
2577         _vte_debug_print(VTE_DEBUG_MISC,
2578                          "Set %s color to (%04x,%04x,%04x).\n", "foreground",
2579                          color.red, color.green, color.blue);
2580 	set_color(VTE_DEFAULT_FG, VTE_COLOR_SOURCE_API, color);
2581 }
2582 
2583 /*
2584  * Terminal::set_color_background:
2585  * @background: the new background color
2586  *
2587  * Sets the background color for text which does not have a specific background
2588  * color assigned.  Only has effect when no background image is set and when
2589  * the terminal is not transparent.
2590  */
2591 void
set_color_background(vte::color::rgb const & color)2592 Terminal::set_color_background(vte::color::rgb const& color)
2593 {
2594         _vte_debug_print(VTE_DEBUG_MISC,
2595                          "Set %s color to (%04x,%04x,%04x).\n", "background",
2596                          color.red, color.green, color.blue);
2597 	set_color(VTE_DEFAULT_BG, VTE_COLOR_SOURCE_API, color);
2598 }
2599 
2600 /*
2601  * Terminal::set_color_cursor_background:
2602  * @cursor_background: (allow-none): the new color to use for the text cursor, or %NULL
2603  *
2604  * Sets the background color for text which is under the cursor.  If %NULL, text
2605  * under the cursor will be drawn with foreground and background colors
2606  * reversed.
2607  */
2608 void
set_color_cursor_background(vte::color::rgb const & color)2609 Terminal::set_color_cursor_background(vte::color::rgb const& color)
2610 {
2611         _vte_debug_print(VTE_DEBUG_MISC,
2612                          "Set %s color to (%04x,%04x,%04x).\n", "cursor background",
2613                          color.red, color.green, color.blue);
2614 	set_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_API, color);
2615 }
2616 
2617 void
reset_color_cursor_background()2618 Terminal::reset_color_cursor_background()
2619 {
2620         _vte_debug_print(VTE_DEBUG_MISC,
2621                          "Reset %s color.\n", "cursor background");
2622         reset_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_API);
2623 }
2624 
2625 /*
2626  * Terminal::set_color_cursor_foreground:
2627  * @cursor_foreground: (allow-none): the new color to use for the text cursor, or %NULL
2628  *
2629  * Sets the foreground color for text which is under the cursor.  If %NULL, text
2630  * under the cursor will be drawn with foreground and background colors
2631  * reversed.
2632  */
2633 void
set_color_cursor_foreground(vte::color::rgb const & color)2634 Terminal::set_color_cursor_foreground(vte::color::rgb const& color)
2635 {
2636         _vte_debug_print(VTE_DEBUG_MISC,
2637                          "Set %s color to (%04x,%04x,%04x).\n", "cursor foreground",
2638                          color.red, color.green, color.blue);
2639 	set_color(VTE_CURSOR_FG, VTE_COLOR_SOURCE_API, color);
2640 }
2641 
2642 void
reset_color_cursor_foreground()2643 Terminal::reset_color_cursor_foreground()
2644 {
2645         _vte_debug_print(VTE_DEBUG_MISC,
2646                          "Reset %s color.\n", "cursor foreground");
2647         reset_color(VTE_CURSOR_FG, VTE_COLOR_SOURCE_API);
2648 }
2649 
2650 /*
2651  * Terminal::set_color_highlight_background:
2652  * @highlight_background: (allow-none): the new color to use for highlighted text, or %NULL
2653  *
2654  * Sets the background color for text which is highlighted.  If %NULL,
2655  * it is unset.  If neither highlight background nor highlight foreground are set,
2656  * highlighted text (which is usually highlighted because it is selected) will
2657  * be drawn with foreground and background colors reversed.
2658  */
2659 void
set_color_highlight_background(vte::color::rgb const & color)2660 Terminal::set_color_highlight_background(vte::color::rgb const& color)
2661 {
2662         _vte_debug_print(VTE_DEBUG_MISC,
2663                          "Set %s color to (%04x,%04x,%04x).\n", "highlight background",
2664                          color.red, color.green, color.blue);
2665 	set_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_API, color);
2666 }
2667 
2668 void
reset_color_highlight_background()2669 Terminal::reset_color_highlight_background()
2670 {
2671         _vte_debug_print(VTE_DEBUG_MISC,
2672                          "Reset %s color.\n", "highlight background");
2673         reset_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_API);
2674 }
2675 
2676 /*
2677  * Terminal::set_color_highlight_foreground:
2678  * @highlight_foreground: (allow-none): the new color to use for highlighted text, or %NULL
2679  *
2680  * Sets the foreground color for text which is highlighted.  If %NULL,
2681  * it is unset.  If neither highlight background nor highlight foreground are set,
2682  * highlighted text (which is usually highlighted because it is selected) will
2683  * be drawn with foreground and background colors reversed.
2684  */
2685 void
set_color_highlight_foreground(vte::color::rgb const & color)2686 Terminal::set_color_highlight_foreground(vte::color::rgb const& color)
2687 {
2688         _vte_debug_print(VTE_DEBUG_MISC,
2689                          "Set %s color to (%04x,%04x,%04x).\n", "highlight foreground",
2690                          color.red, color.green, color.blue);
2691 	set_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_API, color);
2692 }
2693 
2694 void
reset_color_highlight_foreground()2695 Terminal::reset_color_highlight_foreground()
2696 {
2697         _vte_debug_print(VTE_DEBUG_MISC,
2698                          "Reset %s color.\n", "highlight foreground");
2699         reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_API);
2700 }
2701 
2702 /*
2703  * Terminal::cleanup_fragments:
2704  * @start: the starting column, inclusive
2705  * @end: the end column, exclusive
2706  *
2707  * Needs to be called before modifying the contents in the cursor's row,
2708  * between the two given columns.  Cleans up TAB and CJK fragments to the
2709  * left of @start and to the right of @end.  If a CJK is split in half,
2710  * the remaining half is replaced by a space.  If a TAB at @start is split,
2711  * it is replaced by spaces.  If a TAB at @end is split, it is replaced by
2712  * a shorter TAB.  @start and @end can be equal if characters will be
2713  * inserted at the location rather than overwritten.
2714  *
2715  * The area between @start and @end is not cleaned up, hence the whole row
2716  * can be left in an inconsistent state.  It is expected that the caller
2717  * will fill up that range afterwards, resulting in a consistent row again.
2718  *
2719  * Invalidates the cells that visually change outside of the range,
2720  * because the caller can't reasonably be expected to take care of this.
2721  */
2722 void
cleanup_fragments(long start,long end)2723 Terminal::cleanup_fragments(long start,
2724                                       long end)
2725 {
2726         VteRowData *row = ensure_row();
2727         const VteCell *cell_start;
2728         VteCell *cell_end, *cell_col;
2729         gboolean cell_start_is_fragment;
2730         long col;
2731 
2732         g_assert(end >= start);
2733 
2734         /* Remember whether the cell at start is a fragment.  We'll need to know it when
2735          * handling the left hand side, but handling the right hand side first might
2736          * overwrite it if start == end (inserting to the middle of a character). */
2737         cell_start = _vte_row_data_get (row, start);
2738         cell_start_is_fragment = cell_start != NULL && cell_start->attr.fragment();
2739 
2740         /* On the right hand side, try to replace a TAB by a shorter TAB if we can.
2741          * This requires that the TAB on the left (which might be the same TAB) is
2742          * not yet converted to spaces, so start on the right hand side. */
2743         cell_end = _vte_row_data_get_writable (row, end);
2744         if (G_UNLIKELY (cell_end != NULL && cell_end->attr.fragment())) {
2745                 col = end;
2746                 do {
2747                         col--;
2748                         g_assert(col >= 0);  /* The first cell can't be a fragment. */
2749                         cell_col = _vte_row_data_get_writable (row, col);
2750                 } while (cell_col->attr.fragment());
2751                 if (cell_col->c == '\t') {
2752                         _vte_debug_print(VTE_DEBUG_MISC,
2753                                          "Replacing right part of TAB with a shorter one at %ld (%ld cells) => %ld (%ld cells)\n",
2754                                          col, (long) cell_col->attr.columns(), end, (long) cell_col->attr.columns() - (end - col));
2755                         cell_end->c = '\t';
2756                         cell_end->attr.set_fragment(false);
2757                         g_assert(cell_col->attr.columns() > end - col);
2758                         cell_end->attr.set_columns(cell_col->attr.columns() - (end - col));
2759                 } else {
2760                         _vte_debug_print(VTE_DEBUG_MISC,
2761                                          "Cleaning CJK right half at %ld\n",
2762                                          end);
2763                         g_assert(end - col == 1 && cell_col->attr.columns() == 2);
2764                         cell_end->c = ' ';
2765                         cell_end->attr.set_fragment(false);
2766                         cell_end->attr.set_columns(1);
2767                         invalidate_row_and_context(m_screen->cursor.row);  /* FIXME can we do cheaper? */
2768                 }
2769         }
2770 
2771         /* Handle the left hand side.  Converting longer TABs to shorter ones probably
2772          * wouldn't make that much sense here, so instead convert to spaces. */
2773         if (G_UNLIKELY (cell_start_is_fragment)) {
2774                 gboolean keep_going = TRUE;
2775                 col = start;
2776                 do {
2777                         col--;
2778                         g_assert(col >= 0);  /* The first cell can't be a fragment. */
2779                         cell_col = _vte_row_data_get_writable (row, col);
2780                         if (!cell_col->attr.fragment()) {
2781                                 if (cell_col->c == '\t') {
2782                                         _vte_debug_print(VTE_DEBUG_MISC,
2783                                                          "Replacing left part of TAB with spaces at %ld (%ld => %ld cells)\n",
2784                                                          col, (long)cell_col->attr.columns(), start - col);
2785                                         /* nothing to do here */
2786                                 } else {
2787                                         _vte_debug_print(VTE_DEBUG_MISC,
2788                                                          "Cleaning CJK left half at %ld\n",
2789                                                          col);
2790                                         g_assert(start - col == 1);
2791                                         invalidate_row_and_context(m_screen->cursor.row);  /* FIXME can we do cheaper? */
2792                                 }
2793                                 keep_going = FALSE;
2794                         }
2795                         cell_col->c = ' ';
2796                         cell_col->attr.set_fragment(false);
2797                         cell_col->attr.set_columns(1);
2798                 } while (keep_going);
2799         }
2800 }
2801 
2802 /* Cursor down, with scrolling. */
2803 void
cursor_down(bool explicit_sequence)2804 Terminal::cursor_down(bool explicit_sequence)
2805 {
2806 	long start, end;
2807 
2808         if (m_scrolling_restricted) {
2809                 start = m_screen->insert_delta + m_scrolling_region.start;
2810                 end = m_screen->insert_delta + m_scrolling_region.end;
2811 	} else {
2812 		start = m_screen->insert_delta;
2813 		end = start + m_row_count - 1;
2814 	}
2815         if (m_screen->cursor.row == end) {
2816                 if (m_scrolling_restricted) {
2817 			if (start == m_screen->insert_delta) {
2818                                 /* Set the boundary to hard wrapped where
2819                                  * we're about to tear apart the contents. */
2820                                 set_hard_wrapped(m_screen->cursor.row);
2821 				/* Scroll this line into the scrollback
2822 				 * buffer by inserting a line at the next
2823 				 * line and scrolling the area up. */
2824 				m_screen->insert_delta++;
2825                                 m_screen->cursor.row++;
2826                                 /* Update start and end, too. */
2827 				start++;
2828 				end++;
2829                                 ring_insert(m_screen->cursor.row, false);
2830                                 /* Repaint the affected lines, which is _below_
2831                                  * the region (bug 131). No need to extend,
2832                                  * set_hard_wrapped() took care of invalidating
2833                                  * the context lines if necessary. */
2834                                 invalidate_rows(m_screen->cursor.row,
2835                                                 m_screen->insert_delta + m_row_count - 1);
2836 				/* Force scroll. */
2837 				adjust_adjustments();
2838 			} else {
2839                                 /* Set the boundaries to hard wrapped where
2840                                  * we're about to tear apart the contents. */
2841                                 set_hard_wrapped(start - 1);
2842                                 set_hard_wrapped(end);
2843                                 /* Scroll by removing a line and inserting a new one. */
2844 				ring_remove(start);
2845 				ring_insert(end, true);
2846                                 /* Repaint the affected lines. No need to extend,
2847                                  * set_hard_wrapped() took care of invalidating
2848                                  * the context lines if necessary. */
2849                                 invalidate_rows(start, end);
2850 			}
2851 		} else {
2852 			/* Scroll up with history. */
2853                         m_screen->cursor.row++;
2854 			update_insert_delta();
2855 		}
2856 
2857                 /* Handle bce (background color erase), however, diverge from xterm:
2858                  * only fill the new row with the background color if scrolling
2859                  * happens due to an explicit escape sequence, not due to autowrapping.
2860                  * See bug 754596 for details. */
2861                 bool const not_default_bg = (m_color_defaults.attr.back() != VTE_DEFAULT_BG);
2862 
2863                 if (explicit_sequence && not_default_bg) {
2864 			VteRowData *rowdata = ensure_row();
2865                         _vte_row_data_fill (rowdata, &m_color_defaults, m_column_count);
2866 		}
2867         } else if (m_screen->cursor.row < m_screen->insert_delta + m_row_count - 1) {
2868                 /* Otherwise, just move the cursor down; unless it's already in the last
2869                  * physical row (which is possible with scrolling region, see #176). */
2870                 m_screen->cursor.row++;
2871 	}
2872 }
2873 
2874 /* Drop the scrollback. */
2875 void
drop_scrollback()2876 Terminal::drop_scrollback()
2877 {
2878         /* Only for normal screen; alternate screen doesn't have a scrollback. */
2879         _vte_ring_drop_scrollback (m_normal_screen.row_data,
2880                                    m_normal_screen.insert_delta);
2881 
2882         if (m_screen == &m_normal_screen) {
2883                 queue_adjustment_value_changed(m_normal_screen.insert_delta);
2884                 adjust_adjustments_full();
2885         }
2886 }
2887 
2888 /* Restore cursor on a screen. */
2889 void
restore_cursor(VteScreen * screen__)2890 Terminal::restore_cursor(VteScreen *screen__)
2891 {
2892         screen__->cursor.col = screen__->saved.cursor.col;
2893         screen__->cursor.row = screen__->insert_delta + CLAMP(screen__->saved.cursor.row,
2894                                                               0, m_row_count - 1);
2895 
2896         m_modes_ecma.set_modes(screen__->saved.modes_ecma);
2897 
2898         m_modes_private.set_DEC_REVERSE_IMAGE(screen__->saved.reverse_mode);
2899         m_modes_private.set_DEC_ORIGIN(screen__->saved.origin_mode);
2900 
2901         m_defaults = screen__->saved.defaults;
2902         m_color_defaults = screen__->saved.color_defaults;
2903         m_character_replacements[0] = screen__->saved.character_replacements[0];
2904         m_character_replacements[1] = screen__->saved.character_replacements[1];
2905         m_character_replacement = screen__->saved.character_replacement;
2906 }
2907 
2908 /* Save cursor on a screen__. */
2909 void
save_cursor(VteScreen * screen__)2910 Terminal::save_cursor(VteScreen *screen__)
2911 {
2912         screen__->saved.cursor.col = screen__->cursor.col;
2913         screen__->saved.cursor.row = screen__->cursor.row - screen__->insert_delta;
2914 
2915         screen__->saved.modes_ecma = m_modes_ecma.get_modes();
2916 
2917         screen__->saved.reverse_mode = m_modes_private.DEC_REVERSE_IMAGE();
2918         screen__->saved.origin_mode = m_modes_private.DEC_ORIGIN();
2919 
2920         screen__->saved.defaults = m_defaults;
2921         screen__->saved.color_defaults = m_color_defaults;
2922         screen__->saved.character_replacements[0] = m_character_replacements[0];
2923         screen__->saved.character_replacements[1] = m_character_replacements[1];
2924         screen__->saved.character_replacement = m_character_replacement;
2925 }
2926 
2927 /* Insert a single character into the stored data array. */
2928 void
insert_char(gunichar c,bool insert,bool invalidate_now)2929 Terminal::insert_char(gunichar c,
2930                                 bool insert,
2931                                 bool invalidate_now)
2932 {
2933 	VteCellAttr attr;
2934 	VteRowData *row;
2935 	long col;
2936 	int columns, i;
2937 	bool line_wrapped = false; /* cursor moved before char inserted */
2938         gunichar c_unmapped = c;
2939 
2940         /* DEC Special Character and Line Drawing Set.  VT100 and higher (per XTerm docs). */
2941         static const gunichar line_drawing_map[32] = {
2942                 0x0020,  /* _ => blank (space) */
2943                 0x25c6,  /* ` => diamond */
2944                 0x2592,  /* a => checkerboard */
2945                 0x2409,  /* b => HT symbol */
2946                 0x240c,  /* c => FF symbol */
2947                 0x240d,  /* d => CR symbol */
2948                 0x240a,  /* e => LF symbol */
2949                 0x00b0,  /* f => degree */
2950                 0x00b1,  /* g => plus/minus */
2951                 0x2424,  /* h => NL symbol */
2952                 0x240b,  /* i => VT symbol */
2953                 0x2518,  /* j => downright corner */
2954                 0x2510,  /* k => upright corner */
2955                 0x250c,  /* l => upleft corner */
2956                 0x2514,  /* m => downleft corner */
2957                 0x253c,  /* n => cross */
2958                 0x23ba,  /* o => scan line 1/9 */
2959                 0x23bb,  /* p => scan line 3/9 */
2960                 0x2500,  /* q => horizontal line (also scan line 5/9) */
2961                 0x23bc,  /* r => scan line 7/9 */
2962                 0x23bd,  /* s => scan line 9/9 */
2963                 0x251c,  /* t => left t */
2964                 0x2524,  /* u => right t */
2965                 0x2534,  /* v => bottom t */
2966                 0x252c,  /* w => top t */
2967                 0x2502,  /* x => vertical line */
2968                 0x2264,  /* y => <= */
2969                 0x2265,  /* z => >= */
2970                 0x03c0,  /* { => pi */
2971                 0x2260,  /* | => not equal */
2972                 0x00a3,  /* } => pound currency sign */
2973                 0x00b7,  /* ~ => bullet */
2974         };
2975 
2976         insert |= m_modes_ecma.IRM();
2977 
2978 	/* If we've enabled the special drawing set, map the characters to
2979 	 * Unicode. */
2980         if (G_UNLIKELY (*m_character_replacement == VTE_CHARACTER_REPLACEMENT_LINE_DRAWING)) {
2981                 if (c >= 95 && c <= 126)
2982                         c = line_drawing_map[c - 95];
2983         }
2984 
2985 	/* Figure out how many columns this character should occupy. */
2986         columns = _vte_unichar_width(c, m_utf8_ambiguous_width);
2987 
2988 	/* If we're autowrapping here, do it. */
2989         col = m_screen->cursor.col;
2990 	if (G_UNLIKELY (columns && col + columns > m_column_count)) {
2991 		if (m_modes_private.DEC_AUTOWRAP()) {
2992 			_vte_debug_print(VTE_DEBUG_ADJ,
2993 					"Autowrapping before character\n");
2994 			/* Wrap. */
2995 			/* XXX clear to the end of line */
2996                         col = m_screen->cursor.col = 0;
2997 			/* Mark this line as soft-wrapped. */
2998 			row = ensure_row();
2999                         set_soft_wrapped(m_screen->cursor.row);
3000                         cursor_down(false);
3001                         ensure_row();
3002                         apply_bidi_attributes(m_screen->cursor.row, row->attr.bidi_flags, VTE_BIDI_FLAG_ALL);
3003 		} else {
3004 			/* Don't wrap, stay at the rightmost column. */
3005                         col = m_screen->cursor.col =
3006 				m_column_count - columns;
3007 		}
3008 		line_wrapped = true;
3009 	}
3010 
3011 	_vte_debug_print(VTE_DEBUG_PARSER,
3012 			"Inserting U+%04X '%lc' (colors %" G_GUINT64_FORMAT ") (%ld+%d, %ld), delta = %ld; ",
3013                          (unsigned int)c, g_unichar_isprint(c) ? c : 0xfffd,
3014                          m_color_defaults.attr.colors(),
3015                         col, columns, (long)m_screen->cursor.row,
3016 			(long)m_screen->insert_delta);
3017 
3018         //FIXMEchpe
3019         if (G_UNLIKELY(c == 0))
3020                 goto not_inserted;
3021 
3022 	if (G_UNLIKELY (columns == 0)) {
3023 
3024 		/* It's a combining mark */
3025 
3026 		long row_num;
3027 		VteCell *cell;
3028 
3029 		_vte_debug_print(VTE_DEBUG_PARSER, "combining U+%04X", c);
3030 
3031                 row_num = m_screen->cursor.row;
3032 		row = NULL;
3033 		if (G_UNLIKELY (col == 0)) {
3034 			/* We are at first column.  See if the previous line softwrapped.
3035 			 * If it did, move there.  Otherwise skip inserting. */
3036 
3037 			if (G_LIKELY (row_num > 0)) {
3038 				row_num--;
3039 				row = find_row_data_writable(row_num);
3040 
3041 				if (row) {
3042 					if (!row->attr.soft_wrapped)
3043 						row = NULL;
3044 					else
3045 						col = _vte_row_data_length (row);
3046 				}
3047 			}
3048 		} else {
3049 			row = find_row_data_writable(row_num);
3050 		}
3051 
3052 		if (G_UNLIKELY (!row || !col))
3053 			goto not_inserted;
3054 
3055 		/* Combine it on the previous cell */
3056 
3057 		col--;
3058 		cell = _vte_row_data_get_writable (row, col);
3059 
3060 		if (G_UNLIKELY (!cell))
3061 			goto not_inserted;
3062 
3063 		/* Find the previous cell */
3064 		while (cell && cell->attr.fragment() && col > 0)
3065 			cell = _vte_row_data_get_writable (row, --col);
3066 		if (G_UNLIKELY (!cell || cell->c == '\t'))
3067 			goto not_inserted;
3068 
3069 		/* Combine the new character on top of the cell string */
3070 		c = _vte_unistr_append_unichar (cell->c, c);
3071 
3072 		/* And set it */
3073 		columns = cell->attr.columns();
3074 		for (i = 0; i < columns; i++) {
3075 			cell = _vte_row_data_get_writable (row, col++);
3076 			cell->c = c;
3077 		}
3078 
3079 		goto done;
3080         } else {
3081                 m_last_graphic_character = c_unmapped;
3082 	}
3083 
3084 	/* Make sure we have enough rows to hold this data. */
3085 	row = ensure_cursor();
3086 	g_assert(row != NULL);
3087 
3088 	if (insert) {
3089                 cleanup_fragments(col, col);
3090 		for (i = 0; i < columns; i++)
3091                         _vte_row_data_insert (row, col + i, &basic_cell);
3092 	} else {
3093                 cleanup_fragments(col, col + columns);
3094 		_vte_row_data_fill (row, &basic_cell, col + columns);
3095 	}
3096 
3097         attr = m_defaults.attr;
3098 	attr.set_columns(columns);
3099 
3100 	{
3101 		VteCell *pcell = _vte_row_data_get_writable (row, col);
3102 		pcell->c = c;
3103 		pcell->attr = attr;
3104 		col++;
3105 	}
3106 
3107 	/* insert wide-char fragments */
3108 	attr.set_fragment(true);
3109 	for (i = 1; i < columns; i++) {
3110 		VteCell *pcell = _vte_row_data_get_writable (row, col);
3111 		pcell->c = c;
3112 		pcell->attr = attr;
3113 		col++;
3114 	}
3115 	if (_vte_row_data_length (row) > m_column_count)
3116 		cleanup_fragments(m_column_count, _vte_row_data_length (row));
3117 	_vte_row_data_shrink (row, m_column_count);
3118 
3119         m_screen->cursor.col = col;
3120 
3121 done:
3122         /* Signal that this part of the window needs drawing. */
3123         if (G_UNLIKELY (invalidate_now)) {
3124                 invalidate_row_and_context(m_screen->cursor.row);
3125         }
3126 
3127 	/* We added text, so make a note of it. */
3128 	m_text_inserted_flag = TRUE;
3129 
3130 not_inserted:
3131 	_vte_debug_print(VTE_DEBUG_ADJ|VTE_DEBUG_PARSER,
3132 			"insertion delta => %ld.\n",
3133 			(long)m_screen->insert_delta);
3134 
3135         m_line_wrapped = line_wrapped;
3136 }
3137 
3138 guint8
get_bidi_flags() const3139 Terminal::get_bidi_flags() const noexcept
3140 {
3141         return (m_modes_ecma.BDSM() ? VTE_BIDI_FLAG_IMPLICIT : 0) |
3142                (m_bidi_rtl ? VTE_BIDI_FLAG_RTL : 0) |
3143                (m_modes_private.VTE_BIDI_AUTO() ? VTE_BIDI_FLAG_AUTO : 0) |
3144                (m_modes_private.VTE_BIDI_BOX_MIRROR() ? VTE_BIDI_FLAG_BOX_MIRROR : 0);
3145 }
3146 
3147 /* Apply the specified BiDi parameters on the paragraph beginning at the specified line. */
3148 void
apply_bidi_attributes(vte::grid::row_t start,guint8 bidi_flags,guint8 bidi_flags_mask)3149 Terminal::apply_bidi_attributes(vte::grid::row_t start, guint8 bidi_flags, guint8 bidi_flags_mask)
3150 {
3151         vte::grid::row_t row = start;
3152         VteRowData *rowdata;
3153 
3154         bidi_flags &= bidi_flags_mask;
3155 
3156         _vte_debug_print(VTE_DEBUG_BIDI,
3157                          "Applying BiDi parameters from row %ld.\n", row);
3158 
3159         rowdata = _vte_ring_index_writable (m_screen->row_data, row);
3160         if (rowdata == nullptr || (rowdata->attr.bidi_flags & bidi_flags_mask) == bidi_flags) {
3161                 _vte_debug_print(VTE_DEBUG_BIDI,
3162                                  "BiDi parameters didn't change for this paragraph.\n");
3163                 return;
3164         }
3165 
3166         while (true) {
3167                 rowdata->attr.bidi_flags &= ~bidi_flags_mask;
3168                 rowdata->attr.bidi_flags |= bidi_flags;
3169 
3170                 if (!rowdata->attr.soft_wrapped)
3171                         break;
3172 
3173                 rowdata = _vte_ring_index_writable (m_screen->row_data, row + 1);
3174                 if (rowdata == nullptr)
3175                         break;
3176                 row++;
3177         }
3178 
3179         _vte_debug_print(VTE_DEBUG_BIDI,
3180                          "Applied BiDi parameters to rows %ld..%ld.\n", start, row);
3181 
3182         m_ringview.invalidate();
3183         invalidate_rows(start, row);
3184 }
3185 
3186 /* Apply the current BiDi parameters covered by bidi_flags_mask on the current paragraph
3187  * if the cursor is at the first position of this paragraph. */
3188 void
maybe_apply_bidi_attributes(guint8 bidi_flags_mask)3189 Terminal::maybe_apply_bidi_attributes(guint8 bidi_flags_mask)
3190 {
3191         _vte_debug_print(VTE_DEBUG_BIDI,
3192                          "Maybe applying BiDi parameters on current paragraph.\n");
3193 
3194         if (m_screen->cursor.col != 0) {
3195                 _vte_debug_print(VTE_DEBUG_BIDI,
3196                                  "No, cursor not in first column.\n");
3197                 return;
3198         }
3199 
3200         auto row = m_screen->cursor.row;
3201 
3202         if (row > _vte_ring_delta (m_screen->row_data)) {
3203                 const VteRowData *rowdata = _vte_ring_index (m_screen->row_data, row - 1);
3204                 if (rowdata != nullptr && rowdata->attr.soft_wrapped) {
3205                         _vte_debug_print(VTE_DEBUG_BIDI,
3206                                          "No, we're not after a hard wrap.\n");
3207                         return;
3208                 }
3209         }
3210 
3211         _vte_debug_print(VTE_DEBUG_BIDI,
3212                          "Yes, applying.\n");
3213 
3214         apply_bidi_attributes (row, get_bidi_flags(), bidi_flags_mask);
3215 }
3216 
3217 static void
reaper_child_exited_cb(VteReaper * reaper,int ipid,int status,vte::terminal::Terminal * that)3218 reaper_child_exited_cb(VteReaper *reaper,
3219                        int ipid,
3220                        int status,
3221                        vte::terminal::Terminal* that) noexcept
3222 try
3223 {
3224         that->child_watch_done(pid_t{ipid}, status);
3225         // @that might be destroyed at this point
3226 }
3227 catch (...)
3228 {
3229         vte::log_exception();
3230 }
3231 
3232 void
child_watch_done(pid_t pid,int status)3233 Terminal::child_watch_done(pid_t pid,
3234                            int status)
3235 {
3236 	if (pid != m_pty_pid)
3237                 return;
3238 
3239         _VTE_DEBUG_IF (VTE_DEBUG_LIFECYCLE) {
3240                 g_printerr ("Child[%d] exited with status %d\n",
3241                             pid, status);
3242 #ifdef HAVE_SYS_WAIT_H
3243                 if (WIFEXITED (status)) {
3244                         g_printerr ("Child[%d] exit code %d.\n",
3245                                     pid, WEXITSTATUS (status));
3246                 } else if (WIFSIGNALED (status)) {
3247                         g_printerr ("Child[%d] dies with signal %d.\n",
3248                                     pid, WTERMSIG (status));
3249                 }
3250 #endif
3251         }
3252 
3253         /* Disconnect from the reaper */
3254         if (m_reaper) {
3255                 g_signal_handlers_disconnect_by_func(m_reaper,
3256                                                      (gpointer)reaper_child_exited_cb,
3257                                                      this);
3258                 g_object_unref(m_reaper);
3259                 m_reaper = nullptr;
3260         }
3261 
3262         m_pty_pid = -1;
3263 
3264         /* If we still have a PTY, or data to process, defer emitting the signals
3265          * until we have EOF on the PTY, so that we can process all pending data.
3266          */
3267         if (pty() || !m_incoming_queue.empty()) {
3268                 m_child_exit_status = status;
3269                 m_child_exited_after_eos_pending = true;
3270 
3271                 m_child_exited_eos_wait_timer.schedule_seconds(2); // FIXME: better value?
3272         } else {
3273                 m_child_exited_after_eos_pending = false;
3274 
3275                 if (widget())
3276                         widget()->emit_child_exited(status);
3277         }
3278 }
3279 
3280 static void
mark_input_source_invalid_cb(vte::terminal::Terminal * that)3281 mark_input_source_invalid_cb(vte::terminal::Terminal* that)
3282 {
3283 	_vte_debug_print (VTE_DEBUG_IO, "Removed PTY input source\n");
3284 	that->m_pty_input_source = 0;
3285 }
3286 
3287 /* Read and handle data from the child. */
3288 static gboolean
io_read_cb(int fd,GIOCondition condition,vte::terminal::Terminal * that)3289 io_read_cb(int fd,
3290            GIOCondition condition,
3291            vte::terminal::Terminal* that)
3292 {
3293         return that->pty_io_read(fd, condition);
3294 }
3295 
3296 void
connect_pty_read()3297 Terminal::connect_pty_read()
3298 {
3299 	if (m_pty_input_source != 0 || !pty())
3300 		return;
3301 
3302         _vte_debug_print (VTE_DEBUG_IO, "Adding PTY input source\n");
3303 
3304         m_pty_input_source = g_unix_fd_add_full(VTE_CHILD_INPUT_PRIORITY,
3305                                                 pty()->fd(),
3306                                                 (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_ERR),
3307                                                 (GUnixFDSourceFunc)io_read_cb,
3308                                                 this,
3309                                                 (GDestroyNotify)mark_input_source_invalid_cb);
3310 }
3311 
3312 static void
mark_output_source_invalid_cb(vte::terminal::Terminal * that)3313 mark_output_source_invalid_cb(vte::terminal::Terminal* that)
3314 {
3315 	_vte_debug_print (VTE_DEBUG_IO, "Removed PTY output source\n");
3316 	that->m_pty_output_source = 0;
3317 }
3318 
3319 /* Send locally-encoded characters to the child. */
3320 static gboolean
io_write_cb(int fd,GIOCondition condition,vte::terminal::Terminal * that)3321 io_write_cb(int fd,
3322             GIOCondition condition,
3323             vte::terminal::Terminal* that)
3324 {
3325         return that->pty_io_write(fd, condition);
3326 }
3327 
3328 void
connect_pty_write()3329 Terminal::connect_pty_write()
3330 {
3331         if (m_pty_output_source != 0 || !pty())
3332                 return;
3333 
3334         g_warn_if_fail(m_input_enabled);
3335 
3336         /* Anything to write? */
3337         if (_vte_byte_array_length(m_outgoing) == 0)
3338                 return;
3339 
3340         /* Do one write. FIXMEchpe why? */
3341         if (!pty_io_write (pty()->fd(), G_IO_OUT))
3342                 return;
3343 
3344         _vte_debug_print (VTE_DEBUG_IO, "Adding PTY output source\n");
3345 
3346         m_pty_output_source = g_unix_fd_add_full(VTE_CHILD_OUTPUT_PRIORITY,
3347                                                  pty()->fd(),
3348                                                  G_IO_OUT,
3349                                                  (GUnixFDSourceFunc)io_write_cb,
3350                                                  this,
3351                                                  (GDestroyNotify)mark_output_source_invalid_cb);
3352 }
3353 
3354 void
disconnect_pty_read()3355 Terminal::disconnect_pty_read()
3356 {
3357 	if (m_pty_input_source != 0) {
3358 		_vte_debug_print (VTE_DEBUG_IO, "Removing PTY input source\n");
3359 		g_source_remove(m_pty_input_source);
3360                 // FIXMEchpe the destroy notify should already have done this!
3361 		m_pty_input_source = 0;
3362 	}
3363 }
3364 
3365 void
disconnect_pty_write()3366 Terminal::disconnect_pty_write()
3367 {
3368 	if (m_pty_output_source != 0) {
3369 		_vte_debug_print (VTE_DEBUG_IO, "Removing PTY output source\n");
3370 		g_source_remove(m_pty_output_source);
3371                 // FIXMEchpe the destroy notify should already have done this!
3372 		m_pty_output_source = 0;
3373 	}
3374 }
3375 
3376 void
pty_termios_changed()3377 Terminal::pty_termios_changed()
3378 {
3379         _vte_debug_print(VTE_DEBUG_IO, "Termios changed\n");
3380 }
3381 
3382 void
pty_scroll_lock_changed(bool locked)3383 Terminal::pty_scroll_lock_changed(bool locked)
3384 {
3385         _vte_debug_print(VTE_DEBUG_IO, "Output %s (^%c)\n",
3386                          locked ? "stopped" : "started",
3387                          locked ? 'Q' : 'S');
3388 }
3389 
3390 /*
3391  * Terminal::watch_child:
3392  * @child_pid: a #pid_t
3393  *
3394  * Watches @child_pid. When the process exists, the #VteTerminal::child-exited
3395  * signal will be called with the child's exit status.
3396  *
3397  * Prior to calling this function, a #VtePty must have been set in @terminal
3398  * using vte_terminal_set_pty().
3399  * When the child exits, the terminal's #VtePty will be set to %NULL.
3400  *
3401  * Note: g_child_watch_add() or g_child_watch_add_full() must not have
3402  * been called for @child_pid, nor a #GSource for it been created with
3403  * g_child_watch_source_new().
3404  *
3405  * Note: when using the g_spawn_async() family of functions,
3406  * the %G_SPAWN_DO_NOT_REAP_CHILD flag MUST have been passed.
3407  */
3408 void
watch_child(pid_t child_pid)3409 Terminal::watch_child (pid_t child_pid)
3410 {
3411         // FIXMEchpe: support passing child_pid = -1 to remove the wathch
3412         g_assert(child_pid != -1);
3413         if (!pty())
3414                 return;
3415 
3416         auto const freezer = vte::glib::FreezeObjectNotify{m_terminal};
3417 
3418         /* Set this as the child's pid. */
3419         m_pty_pid = child_pid;
3420 
3421         /* Catch a child-exited signal from the child pid. */
3422         auto reaper = vte_reaper_ref();
3423         vte_reaper_add_child(child_pid);
3424         if (reaper != m_reaper) {
3425                 if (m_reaper) {
3426                         g_signal_handlers_disconnect_by_func(m_reaper,
3427                                                              (gpointer)reaper_child_exited_cb,
3428                                                              this);
3429                         g_object_unref(m_reaper);
3430                 }
3431                 m_reaper = reaper; /* adopts */
3432                 g_signal_connect(m_reaper, "child-exited",
3433                                  G_CALLBACK(reaper_child_exited_cb),
3434                                  this);
3435         } else {
3436                 g_object_unref(reaper);
3437         }
3438 
3439         /* FIXMEchpe: call set_size() here? */
3440 }
3441 
3442 /* Reset the input method context. */
3443 void
im_reset()3444 Terminal::im_reset()
3445 {
3446         if (widget())
3447                 widget()->im_reset();
3448 
3449         im_preedit_reset();
3450 }
3451 
3452 void
process_incoming()3453 Terminal::process_incoming()
3454 {
3455         _vte_debug_print(VTE_DEBUG_IO,
3456                          "Handler processing %" G_GSIZE_FORMAT " bytes over %" G_GSIZE_FORMAT " chunks.\n",
3457                          m_input_bytes,
3458                          m_incoming_queue.size());
3459         _vte_debug_print (VTE_DEBUG_WORK, "(");
3460 
3461         /* We should only be called when there's data to process. */
3462         g_assert(!m_incoming_queue.empty());
3463 
3464         // FIXMEchpe move to context
3465         m_line_wrapped = false;
3466 
3467         auto bytes_processed = ssize_t{0};
3468 
3469         auto context = ProcessingContext{*this};
3470 
3471         while (!m_incoming_queue.empty()) {
3472                 auto& chunk = m_incoming_queue.front();
3473 
3474                 assert((bool)chunk);
3475 
3476                 auto const start = chunk->begin_reading();
3477 
3478                 _VTE_DEBUG_IF(VTE_DEBUG_IO) {
3479                         _vte_debug_print(VTE_DEBUG_IO,
3480                                          "Processing data syntax %d chunk %p starting at offset %u\n",
3481                                          (int)current_data_syntax(),
3482                                          (void*)chunk.get(),
3483                                          unsigned(chunk->begin_reading() - chunk->data()));
3484 
3485                         _vte_debug_hexdump("Incoming buffer",
3486                                            chunk->begin_reading(),
3487                                            chunk->size_reading());
3488                 }
3489 
3490                 switch (current_data_syntax()) {
3491                 case DataSyntax::ECMA48_UTF8:
3492                         process_incoming_utf8(context, *chunk);
3493                         break;
3494 
3495 #ifdef WITH_ICU
3496                 case DataSyntax::ECMA48_PCTERM:
3497                         process_incoming_pcterm(context, *chunk);
3498                         break;
3499 #endif
3500 
3501                 default:
3502                         g_assert_not_reached();
3503                         break;
3504                 }
3505 
3506                 bytes_processed += size_t(chunk->begin_reading() - start);
3507 
3508                 _vte_debug_print(VTE_DEBUG_IO, "read %d bytes, chunk %s, data syntax now %d\n",
3509                                  int(chunk->begin_reading() - start),
3510                                  chunk->has_reading()?"has more":"finished",
3511                                  (int)current_data_syntax());
3512                 // If all data from this chunk has been processed, go to the next one
3513                 if (!chunk->has_reading())
3514                         m_incoming_queue.pop();
3515         }
3516 
3517 #ifdef VTE_DEBUG
3518         /* Some safety checks: ensure the visible parts of the buffer
3519          * are all in the buffer. */
3520         g_assert_cmpint(m_screen->insert_delta, >=, _vte_ring_delta(m_screen->row_data));
3521 
3522         /* The cursor shouldn't be above or below the addressable
3523          * part of the display buffer. */
3524         g_assert_cmpint(m_screen->cursor.row, >=, m_screen->insert_delta);
3525 #endif
3526 
3527         if (context.m_modified) {
3528                 /* Keep the cursor on-screen if we scroll on output, or if
3529                  * we're currently at the bottom of the buffer. */
3530                 update_insert_delta();
3531                 if (m_scroll_on_output || context.m_bottom) {
3532                         maybe_scroll_to_bottom();
3533                 }
3534                 /* Deselect the current selection if its contents are changed
3535                  * by this insertion. */
3536                 if (!m_selection_resolved.empty()) {
3537                         //FIXMEchpe: this is atrocious
3538                         auto selection = get_selected_text();
3539                         if ((selection == nullptr) ||
3540                             (m_selection[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] == nullptr) ||
3541                             (strcmp(selection->str, m_selection[vte::to_integral(vte::platform::ClipboardType::PRIMARY)]->str) != 0)) {
3542                                 deselect_all();
3543                         }
3544                         if (selection)
3545                                 g_string_free(selection, TRUE);
3546                 }
3547         }
3548 
3549         if (context.m_modified || (m_screen != context.m_saved_screen)) {
3550                 m_ringview.invalidate();
3551                 /* Signal that the visible contents changed. */
3552                 queue_contents_changed();
3553         }
3554 
3555         emit_pending_signals();
3556 
3557         if (context.m_invalidated_text) {
3558                 invalidate_rows_and_context(context.m_bbox_top, context.m_bbox_bottom);
3559         }
3560 
3561         if ((context.m_saved_cursor.col != m_screen->cursor.col) ||
3562             (context.m_saved_cursor.row != m_screen->cursor.row)) {
3563                 /* invalidate the old and new cursor positions */
3564                 if (context.m_saved_cursor_visible)
3565                         invalidate_row(context.m_saved_cursor.row);
3566                 invalidate_cursor_once();
3567                 check_cursor_blink();
3568                 /* Signal that the cursor moved. */
3569                 queue_cursor_moved();
3570         } else if ((context.m_saved_cursor_visible != m_modes_private.DEC_TEXT_CURSOR()) ||
3571                    (context.m_saved_cursor_style != m_cursor_style)) {
3572                 invalidate_row(context.m_saved_cursor.row);
3573                 check_cursor_blink();
3574         }
3575 
3576         /* Tell the input method where the cursor is. */
3577         im_update_cursor();
3578 
3579         /* After processing some data, do a hyperlink GC. The multiplier is totally arbitrary, feel free to fine tune. */
3580         _vte_ring_hyperlink_maybe_gc(m_screen->row_data, bytes_processed * 8);
3581 
3582         _vte_debug_print (VTE_DEBUG_WORK, ")");
3583         _vte_debug_print (VTE_DEBUG_IO,
3584                           "%" G_GSIZE_FORMAT " bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",
3585                           m_input_bytes,
3586                           m_incoming_queue.size());
3587 }
3588 
3589 /* Note that this code is mostly copied to process_incoming_pcterm() below; any non-charset-decoding
3590  * related changes made here need to be made there, too.
3591  */
3592 void
process_incoming_utf8(ProcessingContext & context,vte::base::Chunk & chunk)3593 Terminal::process_incoming_utf8(ProcessingContext& context,
3594                                 vte::base::Chunk& chunk)
3595 {
3596         auto seq = vte::parser::Sequence{m_parser};
3597 
3598         auto const iend = chunk.end_reading();
3599         auto ip = chunk.begin_reading();
3600 
3601         while (ip < iend) {
3602 
3603                 switch (m_utf8_decoder.decode(*(ip++))) {
3604                 case vte::base::UTF8Decoder::REJECT_REWIND:
3605                         /* Rewind the stream.
3606                          * Note that this will never lead to a loop, since in the
3607                          * next round this byte *will* be consumed.
3608                          */
3609                         --ip;
3610                         [[fallthrough]];
3611                 case vte::base::UTF8Decoder::REJECT:
3612                         m_utf8_decoder.reset();
3613                         /* Fall through to insert the U+FFFD replacement character. */
3614                         [[fallthrough]];
3615                 case vte::base::UTF8Decoder::ACCEPT: {
3616                         auto rv = m_parser.feed(m_utf8_decoder.codepoint());
3617                         if (G_UNLIKELY(rv < 0)) {
3618 #ifdef DEBUG
3619                                 uint32_t c = m_utf8_decoder.codepoint();
3620                                 char c_buf[7];
3621                                 g_snprintf(c_buf, sizeof(c_buf), "%lc", c);
3622                                 char const* wp_str = g_unichar_isprint(c) ? c_buf : _vte_debug_sequence_to_string(c_buf, -1);
3623                                 _vte_debug_print(VTE_DEBUG_PARSER, "Parser error on U+%04X [%s]!\n",
3624                                                  c, wp_str);
3625 #endif
3626                                 break;
3627                         }
3628 
3629 #ifdef VTE_DEBUG
3630                         if (rv != VTE_SEQ_NONE)
3631                                 g_assert((bool)seq);
3632 #endif
3633 
3634                         _VTE_DEBUG_IF(VTE_DEBUG_PARSER) {
3635                                 if (rv != VTE_SEQ_NONE) {
3636                                         seq.print();
3637                                 }
3638                         }
3639 
3640                         // FIXMEchpe this assumes that the only handler inserting
3641                         // a character is GRAPHIC, which isn't true (at least ICH, REP, SUB
3642                         // also do, and invalidate directly for now)...
3643 
3644                         switch (rv) {
3645                         case VTE_SEQ_GRAPHIC: {
3646 
3647                                 context.pre_GRAPHIC(*this);
3648 
3649                                 // does insert_char(c, false, false)
3650                                 GRAPHIC(seq);
3651                                 _vte_debug_print(VTE_DEBUG_PARSER,
3652                                                  "Last graphic is now U+%04X %lc\n",
3653                                                  m_last_graphic_character,
3654                                                  g_unichar_isprint(m_last_graphic_character) ? m_last_graphic_character : 0xfffd);
3655 
3656                                 context.post_GRAPHIC(*this);
3657                                 break;
3658                         }
3659 
3660                         case VTE_SEQ_NONE:
3661                         case VTE_SEQ_IGNORE:
3662                                 break;
3663 
3664                         default: {
3665                                 switch (seq.command()) {
3666 #define _VTE_CMD_HANDLER(cmd)   \
3667                                 case VTE_CMD_##cmd: cmd(seq); break;
3668 #define _VTE_CMD_HANDLER_R(cmd) \
3669                                 case VTE_CMD_##cmd: if (cmd(seq)) { \
3670                                         context.post_CMD(*this); \
3671                                         goto switched_data_syntax; \
3672                                         } \
3673                                         break;
3674 #define _VTE_CMD_HANDLER_NOP(cmd)
3675 #include "parser-cmd-handlers.hh"
3676 #undef _VTE_CMD_HANDLER
3677 #undef _VTE_CMD_HANDLER_NOP
3678 #undef _VTE_CMD_HANDLER_R
3679                                 default:
3680                                         _vte_debug_print(VTE_DEBUG_PARSER,
3681                                                          "Unknown parser command %d\n", seq.command());
3682                                         break;
3683                                 }
3684 
3685                                 m_last_graphic_character = 0;
3686 
3687                                 context.post_CMD(*this);
3688                                 break;
3689                         }
3690                         }
3691                         break;
3692                 }
3693                 }
3694         }
3695 
3696         if (chunk.eos() && ip == iend) {
3697                 m_eos_pending = true;
3698                 /* If there's an unfinished character in the queue, insert a replacement character */
3699                 if (m_utf8_decoder.flush()) {
3700                         insert_char(m_utf8_decoder.codepoint(), false, true);
3701                 }
3702         }
3703 
3704 #pragma GCC diagnostic push
3705 #pragma GCC diagnostic ignored "-Wunused-label"
3706 switched_data_syntax:
3707 #pragma GCC diagnostic pop
3708 
3709         // Update start for data consumed
3710         chunk.set_begin_reading(ip);
3711 }
3712 
3713 #ifdef WITH_ICU
3714 
3715 /* Note that this is mostly a copy of process_incoming_utf8() above; any non-charset-decoding
3716  * related changes made here need to be made there, too.
3717  */
3718 void
process_incoming_pcterm(ProcessingContext & context,vte::base::Chunk & chunk)3719 Terminal::process_incoming_pcterm(ProcessingContext& context,
3720                                   vte::base::Chunk& chunk)
3721 {
3722         auto seq = vte::parser::Sequence{m_parser};
3723 
3724         auto& decoder = m_converter->decoder();
3725 
3726         auto eos = bool{false};
3727         auto flush = bool{false};
3728 
3729         auto const iend = chunk.end_reading();
3730         auto ip = chunk.begin_reading();
3731 
3732  start:
3733         while (ip < iend || flush) {
3734                 switch (decoder.decode(&ip, flush)) {
3735                 case vte::base::ICUDecoder::Result::eSomething: {
3736                         auto rv = m_parser.feed(decoder.codepoint());
3737                         if (G_UNLIKELY(rv < 0)) {
3738 #ifdef VTE_DEBUG
3739                                 uint32_t c = decoder.codepoint();
3740                                 char c_buf[7];
3741                                 g_snprintf(c_buf, sizeof(c_buf), "%lc", c);
3742                                 char const* wp_str = g_unichar_isprint(c) ? c_buf : _vte_debug_sequence_to_string(c_buf, -1);
3743                                 _vte_debug_print(VTE_DEBUG_PARSER, "Parser error on U+%04X [%s]!\n",
3744                                                  c, wp_str);
3745 #endif
3746                                 break;
3747                         }
3748 
3749 #ifdef VTE_DEBUG
3750                         if (rv != VTE_SEQ_NONE)
3751                                 g_assert((bool)seq);
3752 #endif
3753 
3754                         _VTE_DEBUG_IF(VTE_DEBUG_PARSER) {
3755                                 if (rv != VTE_SEQ_NONE) {
3756                                         seq.print();
3757                                 }
3758                         }
3759 
3760                         // FIXMEchpe this assumes that the only handler inserting
3761                         // a character is GRAPHIC, which isn't true (at least ICH, REP, SUB
3762                         // also do, and invalidate directly for now)...
3763 
3764                         switch (rv) {
3765                         case VTE_SEQ_GRAPHIC: {
3766 
3767                                 context.pre_GRAPHIC(*this);
3768 
3769                                 // does insert_char(c, false, false)
3770                                 GRAPHIC(seq);
3771                                 _vte_debug_print(VTE_DEBUG_PARSER,
3772                                                  "Last graphic is now U+%04X %lc\n",
3773                                                  m_last_graphic_character,
3774                                                  g_unichar_isprint(m_last_graphic_character) ? m_last_graphic_character : 0xfffd);
3775 
3776                                 context.post_GRAPHIC(*this);
3777                                 break;
3778                         }
3779 
3780                         case VTE_SEQ_NONE:
3781                         case VTE_SEQ_IGNORE:
3782                                 break;
3783 
3784                         default: {
3785                                 switch (seq.command()) {
3786 #define _VTE_CMD_HANDLER(cmd)   \
3787                                 case VTE_CMD_##cmd: cmd(seq); break;
3788 #define _VTE_CMD_HANDLER_R(cmd) \
3789                                 case VTE_CMD_##cmd: \
3790                                         if (cmd(seq)) { \
3791                                                 context.post_CMD(*this); \
3792                                                 goto switched_data_syntax; \
3793                                         } \
3794                                         break;
3795 #define _VTE_CMD_HANDLER_NOP(cmd)
3796 #include "parser-cmd-handlers.hh"
3797 #undef _VTE_CMD_HANDLER
3798 #undef _VTE_CMD_HANDLER_NOP
3799 #undef _VTE_CMD_HANDLER_R
3800                                 default:
3801                                         _vte_debug_print(VTE_DEBUG_PARSER,
3802                                                          "Unknown parser command %d\n", seq.command());
3803                                         break;
3804                                 }
3805 
3806                                 m_last_graphic_character = 0;
3807 
3808                                 context.post_CMD(*this);
3809                                 break;
3810                         }
3811                         }
3812                         break;
3813                 }
3814                 case vte::base::ICUDecoder::Result::eNothing:
3815                         flush = false;
3816                         break;
3817 
3818                 case vte::base::ICUDecoder::Result::eError:
3819                         // FIXMEchpe do we need ++ip here?
3820                         decoder.reset();
3821                         break;
3822 
3823                 }
3824         }
3825 
3826         if (eos) {
3827                 /* Done processing the last chunk */
3828                 m_eos_pending = true;
3829                 return;
3830         }
3831 
3832 #pragma GCC diagnostic push
3833 #pragma GCC diagnostic ignored "-Wunused-label"
3834  switched_data_syntax:
3835 #pragma GCC diagnostic pop
3836 
3837         // Update start for data consumed
3838         chunk.set_begin_reading(ip);
3839 
3840         if (chunk.eos() && ip == chunk.end_reading()) {
3841                 /* On EOS, we still need to flush the decoder before we can finish */
3842                 eos = flush = true;
3843                 goto start;
3844         }
3845 }
3846 
3847 #endif /* WITH_ICU */
3848 
3849 bool
pty_io_read(int const fd,GIOCondition const condition)3850 Terminal::pty_io_read(int const fd,
3851                       GIOCondition const condition)
3852 {
3853 	_vte_debug_print (VTE_DEBUG_WORK, ".");
3854         _vte_debug_print(VTE_DEBUG_IO, "::pty_io_read condition %02x\n", condition);
3855 
3856         /* We need to check for EOS so that we can shut down the PTY.
3857          * When we get G_IO_HUP without G_IO_IN, we can process the EOF now.
3858          * However when we get G_IO_IN | G_IO_HUP, there is still data to be
3859          * read in this round, and in potentially more rounds; read the data
3860          * now, do *not* process the EOS (unless the read returns EIO, which
3861          * does happen and appears to mean that despite G_IO_IN no data was
3862          * actually available to be read, not even the cpkt header), and
3863          * otherwise wait for further calls which will have G_IO_HUP (and
3864          * possibly G_IO_IN again).
3865          */
3866         auto eos = bool{condition == G_IO_HUP};
3867 
3868         /* There is data to read */
3869 	auto err = int{0};
3870         auto again = bool{true};
3871         vte::base::Chunk* chunk{nullptr};
3872 	if (condition & (G_IO_IN | G_IO_PRI)) {
3873 		guchar *bp;
3874 		int rem, len;
3875 		guint bytes, max_bytes;
3876 
3877 		/* Limit the amount read between updates, so as to
3878 		 * 1. maintain fairness between multiple terminals;
3879 		 * 2. prevent reading the entire output of a command in one
3880 		 *    pass, i.e. we always try to refresh the terminal ~40Hz.
3881 		 *    See time_process_incoming() where we estimate the
3882 		 *    maximum number of bytes we can read/process in between
3883 		 *    updates.
3884 		 */
3885 		max_bytes = m_active_terminals_link != nullptr ?
3886 		            g_list_length(g_active_terminals) - 1 : 0;
3887 		if (max_bytes) {
3888 			max_bytes = m_max_input_bytes / max_bytes;
3889 		} else {
3890 			max_bytes = m_max_input_bytes;
3891 		}
3892 		bytes = m_input_bytes;
3893 
3894                 /* If possible, try adding more data to the chunk at the back of the queue */
3895                 if (!m_incoming_queue.empty())
3896                         chunk = m_incoming_queue.back().get();
3897 
3898 		do {
3899                         /* No chunk, chunk sealed or at least ¾ full? Get a new chunk */
3900 			if (!chunk ||
3901                             chunk->sealed() ||
3902                             chunk->capacity_writing() < chunk->capacity() / 4) {
3903                                 m_incoming_queue.push(vte::base::Chunk::get(chunk));
3904                                 chunk = m_incoming_queue.back().get();
3905 			}
3906 
3907 			rem = chunk->capacity_writing();
3908 			bp = chunk->begin_writing();
3909 			len = 0;
3910 			do {
3911 #if defined(TIOCPKT)
3912                                 /* We'd like to read (fd, bp, rem); but due to TIOCPKT mode
3913                                  * there's an extra input byte returned at the beginning.
3914                                  * We need to see what that byte is, but otherwise drop it
3915                                  * and write continuously to chunk->data.
3916                                  */
3917                                 auto const save = bp[-1];
3918                                 errno = 0;
3919                                 auto ret = read(fd, bp - 1, rem + 1);
3920                                 auto const pkt_header = bp[-1];
3921                                 bp[-1] = save;
3922 
3923 				switch (ret){
3924 					case -1:
3925 						err = errno;
3926 						goto out;
3927 					case 0:
3928 						eos = true;
3929 						goto out;
3930 					default:
3931                                                 ret--;
3932 
3933                                                 if (pkt_header == TIOCPKT_DATA) {
3934                                                         bp += ret;
3935                                                         rem -= ret;
3936                                                         len += ret;
3937                                                 } else {
3938                                                         if (pkt_header & TIOCPKT_IOCTL) {
3939                                                                 /* We'd like to always be informed when the termios change,
3940                                                                  * so we can e.g. detect when no-echo is en/disabled and
3941                                                                  * change the cursor/input method/etc., but unfortunately
3942                                                                  * the kernel only sends this flag when (old or new) 'local flags'
3943                                                                  * include EXTPROC, which is not used often, and due to its side
3944                                                                  * effects, cannot be enabled by vte by default.
3945                                                                  *
3946                                                                  * FIXME: improve the kernel! see discussion in bug 755371
3947                                                                  * starting at comment 12
3948                                                                  */
3949                                                                 pty_termios_changed();
3950                                                         }
3951                                                         if (pkt_header & TIOCPKT_STOP) {
3952                                                                 pty_scroll_lock_changed(true);
3953                                                         }
3954                                                         if (pkt_header & TIOCPKT_START) {
3955                                                                 pty_scroll_lock_changed(false);
3956                                                         }
3957                                                 }
3958 						break;
3959 				}
3960 #elif defined(__sun) && defined(HAVE_STROPTS_H)
3961 				static unsigned char ctl_s[128];
3962 				struct strbuf ctlbuf, databuf;
3963 				int ret, flags = 0;
3964 				bool have_data = false;
3965 
3966 				ctlbuf.buf = (caddr_t)ctl_s;
3967 				ctlbuf.maxlen = sizeof(ctl_s);
3968 				databuf.buf = (caddr_t)bp;
3969 				databuf.maxlen = rem;
3970 
3971 				ret = getmsg(fd, &ctlbuf, &databuf, &flags);
3972 				if (ret == -1) {
3973 					err = errno;
3974 					goto out;
3975 				} else if (ctlbuf.len == 1) {
3976 					switch (ctl_s[0]) {
3977 					case M_IOCTL:
3978 						pty_termios_changed();
3979 						break;
3980 					case M_STOP:
3981 						pty_scroll_lock_changed(true);
3982 						break;
3983 					case M_START:
3984 						pty_scroll_lock_changed(false);
3985 						break;
3986 					case M_DATA:
3987 						have_data = true;
3988 						break;
3989 					}
3990 				} else if (ctlbuf.len == -1 && databuf.len != -1) {
3991 					// MOREDATA
3992 					have_data = true;
3993 				}
3994 
3995 				if (have_data) {
3996 					if (databuf.len == 0) {
3997 						eos = true;
3998 						goto out;
3999 					}
4000 					bp += databuf.len;
4001 					rem -= databuf.len;
4002 					len += databuf.len;
4003 				}
4004 #else /* neither TIOCPKT nor STREAMS pty */
4005 				int ret = read(fd, bp, rem);
4006 				switch (ret) {
4007 					case -1:
4008 						err = errno;
4009 						goto out;
4010 					case 0:
4011 						eos = true;
4012 						goto out;
4013 					default:
4014 						bp += ret;
4015 						rem -= ret;
4016 						len += ret;
4017 						break;
4018 				}
4019 #endif /* */
4020 			} while (rem);
4021 out:
4022 			chunk->add_size(len);
4023 			bytes += len;
4024 		} while (bytes < max_bytes &&
4025                          // This means that a read into a not-yet-¾-full
4026                          // chunk used up all the available capacity, so
4027                          // let's assume that we can read more and thus
4028                          // we'll get a new chunk in the loop above and
4029                          // continue on. (See commit 49a0cdf11.)
4030                          // Note also that on EOS or error, this condition
4031                          // is false (since there was capacity, but it wasn't
4032                          // used up).
4033 		         chunk->capacity_writing() == 0);
4034 
4035                 /* We may have an empty chunk at the back of the queue, but
4036                  * that doesn't matter, we'll fill it next time.
4037                  */
4038 
4039 		if (!is_processing()) {
4040 			add_process_timeout(this);
4041 		}
4042 		m_pty_input_active = len != 0;
4043 		m_input_bytes = bytes;
4044 		again = bytes < max_bytes;
4045 
4046 		_vte_debug_print (VTE_DEBUG_IO, "read %d/%d bytes, again? %s, active? %s\n",
4047 				bytes, max_bytes,
4048 				again ? "yes" : "no",
4049 				m_pty_input_active ? "yes" : "no");
4050 	}
4051 
4052         if (condition & G_IO_ERR)
4053                 err = EIO;
4054 
4055 	/* Error? */
4056 	switch (err) {
4057         case 0: /* no error */
4058                 break;
4059         case EIO: /* EOS */
4060                 eos = true;
4061                 break;
4062         case EAGAIN:
4063         case EBUSY: /* do nothing */
4064                 break;
4065         default:
4066                 auto errsv = vte::libc::ErrnoSaver{};
4067                 _vte_debug_print (VTE_DEBUG_IO, "Error reading from child: %s",
4068                                   g_strerror(errsv));
4069                 break;
4070 	}
4071 
4072         if (eos) {
4073 		_vte_debug_print(VTE_DEBUG_IO, "got PTY EOF\n");
4074 
4075                 /* Make a note of the EOS; but do not process it since there may be data
4076                  * to be processed first in the incoming queue.
4077                  */
4078                 if (!chunk || chunk->sealed()) {
4079                         m_incoming_queue.push(vte::base::Chunk::get(chunk));
4080                         chunk = m_incoming_queue.back().get();
4081                 }
4082 
4083                 chunk->set_sealed();
4084                 chunk->set_eos();
4085 
4086                 /* Cancel wait timer */
4087                 m_child_exited_eos_wait_timer.abort();
4088 
4089                 /* Need to process the EOS */
4090 		if (!is_processing()) {
4091 			add_process_timeout(this);
4092 		}
4093 
4094                 again = false;
4095         }
4096 
4097 	return again;
4098 }
4099 
4100 /*
4101  * Terminal::feed:
4102  * @data: data
4103  *
4104  * Interprets @data as if it were data received from a child process.
4105  */
4106 void
feed(std::string_view const & data,bool start_processing_)4107 Terminal::feed(std::string_view const& data,
4108                bool start_processing_)
4109 {
4110         auto length = data.size();
4111         auto ptr = data.data();
4112 
4113         vte::base::Chunk* chunk = nullptr;
4114         if (!m_incoming_queue.empty()) {
4115                 auto& achunk = m_incoming_queue.back();
4116                 if (length < achunk->capacity_writing() && !achunk->sealed())
4117                         chunk = achunk.get();
4118         }
4119         if (chunk == nullptr) {
4120                 m_incoming_queue.push(vte::base::Chunk::get(nullptr));
4121                 chunk = m_incoming_queue.back().get();
4122         }
4123 
4124         /* Break the incoming data into chunks. */
4125         do {
4126                 auto rem = chunk->capacity_writing();
4127                 auto len = std::min(length, rem);
4128                 memcpy (chunk->begin_writing(), ptr, len);
4129                 chunk->add_size(len);
4130                 length -= len;
4131                 if (length == 0)
4132                         break;
4133 
4134                 ptr += len;
4135 
4136                 /* Get another chunk for the remaining data */
4137                 m_incoming_queue.push(vte::base::Chunk::get(chunk));
4138                 chunk = m_incoming_queue.back().get();
4139         } while (true);
4140 
4141         if (start_processing_)
4142                 start_processing();
4143 }
4144 
4145 bool
pty_io_write(int const fd,GIOCondition const condition)4146 Terminal::pty_io_write(int const fd,
4147                        GIOCondition const condition)
4148 {
4149         auto const count = write(fd,
4150                                  m_outgoing->data,
4151                                  _vte_byte_array_length(m_outgoing));
4152 	if (count != -1) {
4153 		_VTE_DEBUG_IF (VTE_DEBUG_IO) {
4154                         _vte_debug_hexdump("Outgoing buffer written",
4155                                            (uint8_t const*)m_outgoing->data,
4156                                            count);
4157 		}
4158 		_vte_byte_array_consume(m_outgoing, count);
4159 	}
4160 
4161         /* Run again if there are more bytes to write */
4162         return _vte_byte_array_length(m_outgoing) != 0;
4163 }
4164 
4165 /* Send some UTF-8 data to the child. */
4166 void
send_child(std::string_view const & data)4167 Terminal::send_child(std::string_view const& data)
4168 {
4169         // FIXMEchpe remove
4170         if (!m_input_enabled)
4171                 return;
4172 
4173         /* Note that for backward compatibility, we need to emit the
4174          * ::commit signal even if there is no PTY. See issue vte#222.
4175          *
4176          * We use the primary data syntax to decide on the format.
4177          */
4178 
4179         switch (primary_data_syntax()) {
4180         case DataSyntax::ECMA48_UTF8:
4181                 emit_commit(data);
4182                 if (pty())
4183                         _vte_byte_array_append(m_outgoing, data.data(), data.size());
4184                 break;
4185 
4186 #ifdef WITH_ICU
4187         case DataSyntax::ECMA48_PCTERM: {
4188                 auto converted = m_converter->convert(data);
4189 
4190                 emit_commit(converted);
4191                 if (pty())
4192                         _vte_byte_array_append(m_outgoing, converted.data(), converted.size());
4193                 break;
4194         }
4195 #endif
4196 
4197         default:
4198                 g_assert_not_reached();
4199                 return;
4200         }
4201 
4202         /* If we need to start waiting for the child pty to
4203          * become available for writing, set that up here. */
4204         connect_pty_write();
4205 }
4206 
4207 /*
4208  * VteTerminal::feed_child:
4209  * @str: data to send to the child
4210  *
4211  * Sends UTF-8 text to the child as if it were entered by the user
4212  * at the keyboard.
4213  *
4214  * Does nothing if input is disabled.
4215  */
4216 void
feed_child(std::string_view const & str)4217 Terminal::feed_child(std::string_view const& str)
4218 {
4219         if (!m_input_enabled)
4220                 return;
4221 
4222         send_child(str);
4223 }
4224 
4225 /*
4226  * Terminal::feed_child_binary:
4227  * @data: data to send to the child
4228  *
4229  * Sends a block of binary data to the child.
4230  *
4231  * Does nothing if input is disabled.
4232  */
4233 void
feed_child_binary(std::string_view const & data)4234 Terminal::feed_child_binary(std::string_view const& data)
4235 {
4236         if (!m_input_enabled)
4237                 return;
4238 
4239         /* If there's a place for it to go, add the data to the
4240          * outgoing buffer. */
4241         if (!pty())
4242                 return;
4243 
4244         emit_commit(data);
4245         _vte_byte_array_append(m_outgoing, data.data(), data.size());
4246 
4247         /* If we need to start waiting for the child pty to
4248          * become available for writing, set that up here. */
4249         connect_pty_write();
4250 }
4251 
4252 void
send(vte::parser::u8SequenceBuilder const & builder,bool c1,vte::parser::u8SequenceBuilder::Introducer introducer,vte::parser::u8SequenceBuilder::ST st)4253 Terminal::send(vte::parser::u8SequenceBuilder const& builder,
4254                          bool c1,
4255                          vte::parser::u8SequenceBuilder::Introducer introducer,
4256                          vte::parser::u8SequenceBuilder::ST st) noexcept
4257 {
4258         std::string str;
4259         builder.to_string(str, c1, -1, introducer, st);
4260         feed_child(str);
4261 }
4262 
4263 void
send(vte::parser::Sequence const & seq,vte::parser::u8SequenceBuilder const & builder)4264 Terminal::send(vte::parser::Sequence const& seq,
4265                vte::parser::u8SequenceBuilder const& builder) noexcept
4266 {
4267         // FIXMEchpe always take c1 & ST from @seq?
4268         if (seq.type() == VTE_SEQ_OSC &&
4269             builder.type() == VTE_SEQ_OSC) {
4270                 /* If we reply to a BEL-terminated OSC, reply with BEL-terminated OSC
4271                  * as well, see https://bugzilla.gnome.org/show_bug.cgi?id=722446 and
4272                  * https://gitlab.gnome.org/GNOME/vte/issues/65 .
4273                  */
4274                 send(builder, false,
4275                      vte::parser::u8SequenceBuilder::Introducer::DEFAULT,
4276                      seq.st() == 0x7 ? vte::parser::u8SequenceBuilder::ST::BEL
4277                      : vte::parser::u8SequenceBuilder::ST::DEFAULT);
4278         } else {
4279                 send(builder, false);
4280         }
4281 }
4282 
4283 void
send(unsigned int type,std::initializer_list<int> params)4284 Terminal::send(unsigned int type,
4285                          std::initializer_list<int> params) noexcept
4286 {
4287         // FIXMEchpe take c1 & ST from @seq
4288         send(vte::parser::ReplyBuilder{type, params}, false);
4289 }
4290 
4291 void
reply(vte::parser::Sequence const & seq,unsigned int type,std::initializer_list<int> params)4292 Terminal::reply(vte::parser::Sequence const& seq,
4293                           unsigned int type,
4294                           std::initializer_list<int> params) noexcept
4295 {
4296         send(seq, vte::parser::ReplyBuilder{type, params});
4297 }
4298 
4299 #if 0
4300 void
4301 Terminal::reply(vte::parser::Sequence const& seq,
4302                           unsigned int type,
4303                           std::initializer_list<int> params,
4304                           std::string const& str) noexcept
4305 {
4306         vte::parser::ReplyBuilder reply_builder{type, params};
4307         reply_builder.set_string(str);
4308         send(seq, reply_builder);
4309 }
4310 #endif
4311 
4312 void
reply(vte::parser::Sequence const & seq,unsigned int type,std::initializer_list<int> params,vte::parser::ReplyBuilder const & builder)4313 Terminal::reply(vte::parser::Sequence const& seq,
4314                           unsigned int type,
4315                           std::initializer_list<int> params,
4316                           vte::parser::ReplyBuilder const& builder) noexcept
4317 {
4318         std::string str;
4319         builder.to_string(str, true, -1,
4320                           vte::parser::ReplyBuilder::Introducer::NONE,
4321                           vte::parser::ReplyBuilder::ST::NONE);
4322 
4323         vte::parser::ReplyBuilder reply_builder{type, params};
4324         reply_builder.set_string(std::move(str));
4325         send(seq, reply_builder);
4326 }
4327 
4328 void
reply(vte::parser::Sequence const & seq,unsigned int type,std::initializer_list<int> params,char const * format,...)4329 Terminal::reply(vte::parser::Sequence const& seq,
4330                           unsigned int type,
4331                           std::initializer_list<int> params,
4332                           char const* format,
4333                           ...) noexcept
4334 {
4335         char buf[128];
4336         va_list vargs;
4337         va_start(vargs, format);
4338         auto len = g_vsnprintf(buf, sizeof(buf), format, vargs);
4339         va_end(vargs);
4340         g_assert_cmpint(len, <, sizeof(buf));
4341 
4342         vte::parser::ReplyBuilder builder{type, params};
4343         builder.set_string(std::string{buf});
4344 
4345         send(seq, builder);
4346 }
4347 
4348 void
im_commit(std::string_view const & str)4349 Terminal::im_commit(std::string_view const& str)
4350 {
4351         if (!m_input_enabled)
4352                 return;
4353 
4354         _vte_debug_print(VTE_DEBUG_EVENTS,
4355                          "Input method committed `%s'.\n", std::string{str}.c_str());
4356         send_child(str);
4357 
4358 	/* Committed text was committed because the user pressed a key, so
4359 	 * we need to obey the scroll-on-keystroke setting. */
4360         if (m_scroll_on_keystroke && m_input_enabled) {
4361 		maybe_scroll_to_bottom();
4362 	}
4363 }
4364 
4365 void
im_preedit_set_active(bool active)4366 Terminal::im_preedit_set_active(bool active) noexcept
4367 {
4368 	m_im_preedit_active = active;
4369 }
4370 
4371 void
im_preedit_reset()4372 Terminal::im_preedit_reset() noexcept
4373 {
4374         m_im_preedit.clear();
4375         m_im_preedit.shrink_to_fit();
4376         m_im_preedit_cursor = 0;
4377         m_im_preedit_attrs.reset();
4378 }
4379 
4380 void
im_preedit_changed(std::string_view const & str,int cursorpos,vte::Freeable<PangoAttrList> attrs)4381 Terminal::im_preedit_changed(std::string_view const& str,
4382                              int cursorpos,
4383                              vte::Freeable<PangoAttrList> attrs) noexcept
4384 {
4385 	/* Queue the area where the current preedit string is being displayed
4386 	 * for repainting. */
4387 	invalidate_cursor_once();
4388 
4389         im_preedit_reset();
4390 	m_im_preedit = str;
4391         m_im_preedit_attrs = std::move(attrs);
4392         m_im_preedit_cursor = cursorpos;
4393 
4394         /* Invalidate again with the new cursor position */
4395 	invalidate_cursor_once();
4396 
4397         /* And tell the input method where the cursor is on the screen */
4398         im_update_cursor();
4399 }
4400 
4401 bool
im_retrieve_surrounding()4402 Terminal::im_retrieve_surrounding()
4403 {
4404         /* FIXME: implement this! Bug #726191 */
4405         return false;
4406 }
4407 
4408 bool
im_delete_surrounding(int offset,int n_chars)4409 Terminal::im_delete_surrounding(int offset,
4410                                 int n_chars)
4411 {
4412         /* FIXME: implement this! Bug #726191 */
4413         return false;
4414 }
4415 
4416 void
im_update_cursor()4417 Terminal::im_update_cursor()
4418 {
4419 	if (!widget_realized())
4420                 return;
4421 
4422         cairo_rectangle_int_t rect;
4423         rect.x = m_screen->cursor.col * m_cell_width + m_padding.left +
4424                  get_preedit_width(false) * m_cell_width;
4425         rect.width = m_cell_width; // FIXMEchpe: if columns > 1 ?
4426         rect.y = row_to_pixel(m_screen->cursor.row) + m_padding.top;
4427         rect.height = m_cell_height;
4428         m_real_widget->im_set_cursor_location(&rect);
4429 }
4430 
4431 void
set_border_padding(GtkBorder const * padding)4432 Terminal::set_border_padding(GtkBorder const* padding)
4433 {
4434         if (memcmp(padding, &m_padding, sizeof(*padding)) != 0) {
4435                 _vte_debug_print(VTE_DEBUG_MISC | VTE_DEBUG_WIDGET_SIZE,
4436                                  "Setting padding to (%d,%d,%d,%d)\n",
4437                                  padding->left, padding->right,
4438                                  padding->top, padding->bottom);
4439 
4440                 m_padding = *padding;
4441                 update_view_extents();
4442                 gtk_widget_queue_resize(m_widget);
4443         } else {
4444                 _vte_debug_print(VTE_DEBUG_MISC | VTE_DEBUG_WIDGET_SIZE,
4445                                  "Keeping padding the same at (%d,%d,%d,%d)\n",
4446                                  padding->left, padding->right,
4447                                  padding->top, padding->bottom);
4448 
4449         }
4450 }
4451 
4452 void
set_cursor_aspect(float aspect)4453 Terminal::set_cursor_aspect(float aspect)
4454 {
4455         if (_vte_double_equal(aspect, m_cursor_aspect_ratio))
4456                 return;
4457 
4458         m_cursor_aspect_ratio = aspect;
4459         invalidate_cursor_once();
4460 }
4461 
4462 void
widget_style_updated()4463 Terminal::widget_style_updated()
4464 {
4465         // FIXMEchpe: remove taking font info from the widget style
4466         update_font_desc();
4467 }
4468 
4469 void
add_cursor_timeout()4470 Terminal::add_cursor_timeout()
4471 {
4472 	if (m_cursor_blink_timer)
4473 		return; /* already added */
4474 
4475 	m_cursor_blink_time = 0;
4476         m_cursor_blink_timer.schedule(m_cursor_blink_cycle, vte::glib::Timer::Priority::eLOW);
4477 }
4478 
4479 void
remove_cursor_timeout()4480 Terminal::remove_cursor_timeout()
4481 {
4482 	if (!m_cursor_blink_timer)
4483 		return; /* already removed */
4484 
4485         m_cursor_blink_timer.abort();
4486         if (!m_cursor_blink_state) {
4487                 invalidate_cursor_once();
4488                 m_cursor_blink_state = true;
4489         }
4490 }
4491 
4492 /* Activates / disactivates the cursor blink timer to reduce wakeups */
4493 void
check_cursor_blink()4494 Terminal::check_cursor_blink()
4495 {
4496 	if (m_has_focus &&
4497 	    m_cursor_blinks &&
4498 	    m_modes_private.DEC_TEXT_CURSOR())
4499 		add_cursor_timeout();
4500 	else
4501 		remove_cursor_timeout();
4502 }
4503 
4504 void
beep()4505 Terminal::beep()
4506 {
4507 	if (m_audible_bell)
4508                 m_real_widget->beep();
4509 }
4510 
4511 bool
widget_key_press(vte::platform::KeyEvent const & event)4512 Terminal::widget_key_press(vte::platform::KeyEvent const& event)
4513 {
4514 	char *normal = NULL;
4515 	gsize normal_length = 0;
4516 	struct termios tio;
4517 	gboolean scrolled = FALSE, steal = FALSE, modifier = FALSE, handled,
4518 		 suppress_alt_esc = FALSE, add_modifiers = FALSE;
4519 	guint keyval = 0;
4520 	gunichar keychar = 0;
4521 	char keybuf[VTE_UTF8_BPC];
4522 
4523 	/* If it's a keypress, record that we got the event, in case the
4524 	 * input method takes the event from us. */
4525         // FIXMEchpe this is ::widget_key_press; what other event type could it even be!?
4526 	if (event.is_key_press()) {
4527 		/* Store a copy of the key. */
4528                 keyval = event.keyval();
4529                 m_modifiers = event.modifiers();
4530 
4531                 // FIXMEchpe?
4532 		if (m_cursor_blink_timer) {
4533 			remove_cursor_timeout();
4534 			add_cursor_timeout();
4535 		}
4536 
4537 		/* Determine if this is just a modifier key. */
4538 		modifier = _vte_keymap_key_is_modifier(keyval);
4539 
4540 		/* Unless it's a modifier key, hide the pointer. */
4541 		if (!modifier) {
4542                         set_pointer_autohidden(true);
4543 		}
4544 
4545 		_vte_debug_print(VTE_DEBUG_EVENTS,
4546 				"Keypress, modifiers=0x%x, "
4547 				"keyval=0x%x\n",
4548                                  m_modifiers,
4549                                  keyval);
4550 
4551 		/* We steal many keypad keys here. */
4552 		if (!m_im_preedit_active) {
4553 			switch (keyval) {
4554 			case GDK_KEY_KP_Add:
4555 			case GDK_KEY_KP_Subtract:
4556 			case GDK_KEY_KP_Multiply:
4557 			case GDK_KEY_KP_Divide:
4558 			case GDK_KEY_KP_Enter:
4559 				steal = TRUE;
4560 				break;
4561 			default:
4562 				break;
4563 			}
4564 			if (m_modifiers & VTE_ALT_MASK) {
4565 				steal = TRUE;
4566 			}
4567 			switch (keyval) {
4568                         case GDK_KEY_ISO_Lock:
4569                         case GDK_KEY_ISO_Level2_Latch:
4570                         case GDK_KEY_ISO_Level3_Shift:
4571                         case GDK_KEY_ISO_Level3_Latch:
4572                         case GDK_KEY_ISO_Level3_Lock:
4573                         case GDK_KEY_ISO_Level5_Shift:
4574                         case GDK_KEY_ISO_Level5_Latch:
4575                         case GDK_KEY_ISO_Level5_Lock:
4576                         case GDK_KEY_ISO_Group_Shift:
4577                         case GDK_KEY_ISO_Group_Latch:
4578                         case GDK_KEY_ISO_Group_Lock:
4579                         case GDK_KEY_ISO_Next_Group:
4580                         case GDK_KEY_ISO_Next_Group_Lock:
4581                         case GDK_KEY_ISO_Prev_Group:
4582                         case GDK_KEY_ISO_Prev_Group_Lock:
4583                         case GDK_KEY_ISO_First_Group:
4584                         case GDK_KEY_ISO_First_Group_Lock:
4585                         case GDK_KEY_ISO_Last_Group:
4586                         case GDK_KEY_ISO_Last_Group_Lock:
4587 			case GDK_KEY_Multi_key:
4588 			case GDK_KEY_Codeinput:
4589 			case GDK_KEY_SingleCandidate:
4590 			case GDK_KEY_MultipleCandidate:
4591 			case GDK_KEY_PreviousCandidate:
4592 			case GDK_KEY_Kanji:
4593 			case GDK_KEY_Muhenkan:
4594                         case GDK_KEY_Henkan_Mode:
4595                         /* case GDK_KEY_Henkan: is GDK_KEY_Henkan_Mode */
4596 			case GDK_KEY_Romaji:
4597 			case GDK_KEY_Hiragana:
4598 			case GDK_KEY_Katakana:
4599 			case GDK_KEY_Hiragana_Katakana:
4600 			case GDK_KEY_Zenkaku:
4601 			case GDK_KEY_Hankaku:
4602 			case GDK_KEY_Zenkaku_Hankaku:
4603 			case GDK_KEY_Touroku:
4604 			case GDK_KEY_Massyo:
4605 			case GDK_KEY_Kana_Lock:
4606 			case GDK_KEY_Kana_Shift:
4607 			case GDK_KEY_Eisu_Shift:
4608 			case GDK_KEY_Eisu_toggle:
4609                         /* case GDK_KEY_Kanji_Bangou: is GDK_KEY_Codeinput */
4610                         /* case GDK_KEY_Zen_Koho: is GDK_KEY_MultipleCandidate */
4611                         /* case GDK_KEY_Mae_Koho: is GDK_KEY_PreviousCandidate */
4612                         /* case GDK_KEY_kana_switch: is GDK_KEY_ISO_Group_Shift */
4613                         case GDK_KEY_Hangul:
4614                         case GDK_KEY_Hangul_Start:
4615                         case GDK_KEY_Hangul_End:
4616                         case GDK_KEY_Hangul_Hanja:
4617                         case GDK_KEY_Hangul_Jamo:
4618                         case GDK_KEY_Hangul_Romaja:
4619                         /* case GDK_KEY_Hangul_Codeinput: is GDK_KEY_Codeinput */
4620                         case GDK_KEY_Hangul_Jeonja:
4621                         case GDK_KEY_Hangul_Banja:
4622                         case GDK_KEY_Hangul_PreHanja:
4623                         case GDK_KEY_Hangul_PostHanja:
4624                         /* case GDK_KEY_Hangul_SingleCandidate: is GDK_KEY_SingleCandidate */
4625                         /* case GDK_KEY_Hangul_MultipleCandidate: is GDK_KEY_MultipleCandidate */
4626                         /* case GDK_KEY_Hangul_PreviousCandidate: is GDK_KEY_PreviousCandidate */
4627                         case GDK_KEY_Hangul_Special:
4628                         /* case GDK_KEY_Hangul_switch: is GDK_KEY_ISO_Group_Shift */
4629 
4630 				steal = FALSE;
4631 				break;
4632 			default:
4633 				break;
4634 			}
4635 		}
4636 	}
4637 
4638 	/* Let the input method at this one first. */
4639 	if (!steal && m_input_enabled) {
4640                 if (m_real_widget->im_filter_keypress(event)) {
4641 			_vte_debug_print(VTE_DEBUG_EVENTS,
4642 					"Keypress taken by IM.\n");
4643 			return true;
4644 		}
4645 	}
4646 
4647 	/* Now figure out what to send to the child. */
4648 	if (event.is_key_press() && !modifier) {
4649 		handled = FALSE;
4650 		/* Map the key to a sequence name if we can. */
4651 		switch (event.keyval()) {
4652 		case GDK_KEY_BackSpace:
4653 			switch (m_backspace_binding) {
4654 			case EraseMode::eASCII_BACKSPACE:
4655 				normal = g_strdup("");
4656 				normal_length = 1;
4657 				suppress_alt_esc = FALSE;
4658 				break;
4659 			case EraseMode::eASCII_DELETE:
4660 				normal = g_strdup("");
4661 				normal_length = 1;
4662 				suppress_alt_esc = FALSE;
4663 				break;
4664 			case EraseMode::eDELETE_SEQUENCE:
4665                                 normal = g_strdup("\e[3~");
4666                                 normal_length = 4;
4667                                 add_modifiers = TRUE;
4668 				suppress_alt_esc = TRUE;
4669 				break;
4670 			case EraseMode::eTTY:
4671 				if (pty() &&
4672 				    tcgetattr(pty()->fd(), &tio) != -1)
4673 				{
4674 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
4675 					normal_length = 1;
4676 				}
4677 				suppress_alt_esc = FALSE;
4678 				break;
4679 			case EraseMode::eAUTO:
4680 			default:
4681 #ifndef _POSIX_VDISABLE
4682 #define _POSIX_VDISABLE '\0'
4683 #endif
4684 				if (pty() &&
4685 				    tcgetattr(pty()->fd(), &tio) != -1 &&
4686 				    tio.c_cc[VERASE] != _POSIX_VDISABLE)
4687 				{
4688 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
4689 					normal_length = 1;
4690 				}
4691 				else
4692 				{
4693 					normal = g_strdup("");
4694 					normal_length = 1;
4695 					suppress_alt_esc = FALSE;
4696 				}
4697 				suppress_alt_esc = FALSE;
4698 				break;
4699 			}
4700                         /* Toggle ^H vs ^? if Ctrl is pressed */
4701                         if (normal_length == 1 && m_modifiers & GDK_CONTROL_MASK) {
4702                                 if (normal[0] == '\010')
4703                                         normal[0] = '\177';
4704                                 else if (normal[0] == '\177')
4705                                         normal[0] = '\010';
4706                         }
4707 			handled = TRUE;
4708 			break;
4709 		case GDK_KEY_KP_Delete:
4710 		case GDK_KEY_Delete:
4711 			switch (m_delete_binding) {
4712 			case EraseMode::eASCII_BACKSPACE:
4713 				normal = g_strdup("\010");
4714 				normal_length = 1;
4715 				break;
4716 			case EraseMode::eASCII_DELETE:
4717 				normal = g_strdup("\177");
4718 				normal_length = 1;
4719 				break;
4720 			case EraseMode::eTTY:
4721 				if (pty() &&
4722 				    tcgetattr(pty()->fd(), &tio) != -1)
4723 				{
4724 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
4725 					normal_length = 1;
4726 				}
4727 				suppress_alt_esc = FALSE;
4728 				break;
4729 			case EraseMode::eDELETE_SEQUENCE:
4730 			case EraseMode::eAUTO:
4731 			default:
4732                                 normal = g_strdup("\e[3~");
4733                                 normal_length = 4;
4734                                 add_modifiers = TRUE;
4735 				break;
4736 			}
4737 			handled = TRUE;
4738                         /* FIXMEchpe: why? this overrides the FALSE set above? */
4739 			suppress_alt_esc = TRUE;
4740 			break;
4741 		case GDK_KEY_KP_Insert:
4742 		case GDK_KEY_Insert:
4743 			if (m_modifiers & GDK_SHIFT_MASK) {
4744 				if (m_modifiers & GDK_CONTROL_MASK) {
4745                                         emit_paste_clipboard();
4746 					handled = TRUE;
4747 					suppress_alt_esc = TRUE;
4748 				} else {
4749                                         widget()->clipboard_request_text(vte::platform::ClipboardType::PRIMARY);
4750 					handled = TRUE;
4751 					suppress_alt_esc = TRUE;
4752 				}
4753 			} else if (m_modifiers & GDK_CONTROL_MASK) {
4754                                 emit_copy_clipboard();
4755 				handled = TRUE;
4756 				suppress_alt_esc = TRUE;
4757 			}
4758 			break;
4759 		/* Keypad/motion keys. */
4760 		case GDK_KEY_KP_Up:
4761 		case GDK_KEY_Up:
4762 			if (m_screen == &m_normal_screen &&
4763 			    m_modifiers & GDK_CONTROL_MASK &&
4764                             m_modifiers & GDK_SHIFT_MASK) {
4765 				scroll_lines(-1);
4766 				scrolled = TRUE;
4767 				handled = TRUE;
4768 				suppress_alt_esc = TRUE;
4769 			}
4770 			break;
4771 		case GDK_KEY_KP_Down:
4772 		case GDK_KEY_Down:
4773 			if (m_screen == &m_normal_screen &&
4774 			    m_modifiers & GDK_CONTROL_MASK &&
4775                             m_modifiers & GDK_SHIFT_MASK) {
4776 				scroll_lines(1);
4777 				scrolled = TRUE;
4778 				handled = TRUE;
4779 				suppress_alt_esc = TRUE;
4780 			}
4781 			break;
4782 		case GDK_KEY_KP_Page_Up:
4783 		case GDK_KEY_Page_Up:
4784 			if (m_screen == &m_normal_screen &&
4785 			    m_modifiers & GDK_SHIFT_MASK) {
4786 				scroll_pages(-1);
4787 				scrolled = TRUE;
4788 				handled = TRUE;
4789 				suppress_alt_esc = TRUE;
4790 			}
4791 			break;
4792 		case GDK_KEY_KP_Page_Down:
4793 		case GDK_KEY_Page_Down:
4794 			if (m_screen == &m_normal_screen &&
4795 			    m_modifiers & GDK_SHIFT_MASK) {
4796 				scroll_pages(1);
4797 				scrolled = TRUE;
4798 				handled = TRUE;
4799 				suppress_alt_esc = TRUE;
4800 			}
4801 			break;
4802 		case GDK_KEY_KP_Home:
4803 		case GDK_KEY_Home:
4804 			if (m_screen == &m_normal_screen &&
4805 			    m_modifiers & GDK_SHIFT_MASK) {
4806 				maybe_scroll_to_top();
4807 				scrolled = TRUE;
4808 				handled = TRUE;
4809 			}
4810 			break;
4811 		case GDK_KEY_KP_End:
4812 		case GDK_KEY_End:
4813 			if (m_screen == &m_normal_screen &&
4814 			    m_modifiers & GDK_SHIFT_MASK) {
4815 				maybe_scroll_to_bottom();
4816 				scrolled = TRUE;
4817 				handled = TRUE;
4818 			}
4819 			break;
4820 		/* Let Shift +/- tweak the font, like XTerm does. */
4821 		case GDK_KEY_KP_Add:
4822 		case GDK_KEY_KP_Subtract:
4823 			if (m_modifiers & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
4824 				switch (keyval) {
4825 				case GDK_KEY_KP_Add:
4826 					emit_increase_font_size();
4827 					handled = TRUE;
4828 					suppress_alt_esc = TRUE;
4829 					break;
4830 				case GDK_KEY_KP_Subtract:
4831 					emit_decrease_font_size();
4832 					handled = TRUE;
4833 					suppress_alt_esc = TRUE;
4834 					break;
4835 				}
4836 			}
4837 			break;
4838 		default:
4839 			break;
4840 		}
4841 		/* If the above switch statement didn't do the job, try mapping
4842 		 * it to a literal or capability name. */
4843                 if (handled == FALSE) {
4844                         if (G_UNLIKELY (m_enable_bidi &&
4845                                         m_modes_private.VTE_BIDI_SWAP_ARROW_KEYS() &&
4846                                         (keyval == GDK_KEY_Left ||
4847                                          keyval == GDK_KEY_Right ||
4848                                          keyval == GDK_KEY_KP_Left ||
4849                                          keyval == GDK_KEY_KP_Right))) {
4850                                 /* In keyboard arrow swapping mode, the left and right arrows need swapping
4851                                  * if the cursor stands inside a (possibly autodetected) RTL paragraph. */
4852                                 ensure_row();
4853                                 VteRowData const *row_data = find_row_data(m_screen->cursor.row);
4854                                 bool rtl;
4855                                 if ((row_data->attr.bidi_flags & (VTE_BIDI_FLAG_IMPLICIT | VTE_BIDI_FLAG_AUTO))
4856                                                               == (VTE_BIDI_FLAG_IMPLICIT | VTE_BIDI_FLAG_AUTO)) {
4857                                         /* Implicit paragraph with autodetection. Need to run the BiDi algorithm
4858                                          * to get the autodetected direction.
4859                                          * m_ringview is for the onscreen contents and the cursor may be offscreen.
4860                                          * Better leave that alone and use a temporary ringview for the cursor's row. */
4861                                         vte::base::RingView ringview;
4862                                         ringview.set_ring(m_screen->row_data);
4863                                         ringview.set_rows(m_screen->cursor.row, 1);
4864                                         ringview.set_width(m_column_count);
4865                                         ringview.update();
4866                                         rtl = ringview.get_bidirow(m_screen->cursor.row)->base_is_rtl();
4867                                 } else {
4868                                         /* Not an implicit paragraph with autodetection, no autodetection
4869                                          * is required. Take the direction straight from the stored data. */
4870                                         rtl = !!(row_data->attr.bidi_flags & VTE_BIDI_FLAG_RTL);
4871                                 }
4872                                 if (rtl) {
4873                                         switch (keyval) {
4874                                         case GDK_KEY_Left:
4875                                                 keyval = GDK_KEY_Right;
4876                                                 break;
4877                                         case GDK_KEY_Right:
4878                                                 keyval = GDK_KEY_Left;
4879                                                 break;
4880                                         case GDK_KEY_KP_Left:
4881                                                 keyval = GDK_KEY_KP_Right;
4882                                                 break;
4883                                         case GDK_KEY_KP_Right:
4884                                                 keyval = GDK_KEY_KP_Left;
4885                                                 break;
4886                                         }
4887                                 }
4888                         }
4889 
4890 			_vte_keymap_map(keyval, m_modifiers,
4891                                         m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
4892                                         m_modes_private.DEC_APPLICATION_KEYPAD(),
4893 					&normal,
4894 					&normal_length);
4895 			/* If we found something this way, suppress
4896 			 * escape-on-alt. */
4897                         if (normal != NULL && normal_length > 0) {
4898 				suppress_alt_esc = TRUE;
4899 			}
4900 		}
4901 
4902 		/* Shall we do this here or earlier?  See bug 375112 and bug 589557 */
4903 		if (m_modifiers & GDK_CONTROL_MASK && widget())
4904                         keyval = widget()->key_event_translate_ctrlkey(event);
4905 
4906 		/* If we didn't manage to do anything, try to salvage a
4907 		 * printable string. */
4908 		if (handled == FALSE && normal == NULL) {
4909 
4910 			/* Convert the keyval to a gunichar. */
4911 			keychar = gdk_keyval_to_unicode(keyval);
4912 			normal_length = 0;
4913 			if (keychar != 0) {
4914 				/* Convert the gunichar to a string. */
4915 				normal_length = g_unichar_to_utf8(keychar,
4916 								  keybuf);
4917 				if (normal_length != 0) {
4918 					normal = (char *)g_malloc(normal_length + 1);
4919 					memcpy(normal, keybuf, normal_length);
4920 					normal[normal_length] = '\0';
4921 				} else {
4922 					normal = NULL;
4923 				}
4924 			}
4925 			if ((normal != NULL) &&
4926 			    (m_modifiers & GDK_CONTROL_MASK)) {
4927 				/* Replace characters which have "control"
4928 				 * counterparts with those counterparts. */
4929 				for (size_t i = 0; i < normal_length; i++) {
4930 					if ((((guint8)normal[i]) >= 0x40) &&
4931 					    (((guint8)normal[i]) <  0x80)) {
4932 						normal[i] &= (~(0x60));
4933 					}
4934 				}
4935 			}
4936 			_VTE_DEBUG_IF (VTE_DEBUG_EVENTS) {
4937 				if (normal) g_printerr(
4938 						"Keypress, modifiers=0x%x, "
4939 						"keyval=0x%x, cooked string=`%s'.\n",
4940 						m_modifiers,
4941 						keyval, normal);
4942 			}
4943 		}
4944 		/* If we got normal characters, send them to the child. */
4945 		if (normal != NULL) {
4946                         if (add_modifiers) {
4947                                 _vte_keymap_key_add_key_modifiers(keyval,
4948                                                                   m_modifiers,
4949                                                                   m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
4950                                                                   &normal,
4951                                                                   &normal_length);
4952                         }
4953 			if (m_modes_private.XTERM_META_SENDS_ESCAPE() &&
4954 			    !suppress_alt_esc &&
4955 			    (normal_length > 0) &&
4956 			    (m_modifiers & VTE_ALT_MASK)) {
4957 				feed_child(_VTE_CAP_ESC, 1);
4958 			}
4959 			if (normal_length > 0) {
4960 				send_child({normal, normal_length});
4961 			}
4962 			g_free(normal);
4963 		}
4964 		/* Keep the cursor on-screen. */
4965 		if (!scrolled && !modifier &&
4966                     m_scroll_on_keystroke && m_input_enabled) {
4967 			maybe_scroll_to_bottom();
4968 		}
4969 		return true;
4970 	}
4971 	return false;
4972 }
4973 
4974 bool
widget_key_release(vte::platform::KeyEvent const & event)4975 Terminal::widget_key_release(vte::platform::KeyEvent const& event)
4976 {
4977         m_modifiers = event.modifiers();
4978 
4979 	if (m_input_enabled &&
4980             m_real_widget->im_filter_keypress(event))
4981                 return true;
4982 
4983         return false;
4984 }
4985 
4986 static const guint8 word_char_by_category[] = {
4987         [G_UNICODE_CONTROL]             = 2,
4988         [G_UNICODE_FORMAT]              = 2,
4989         [G_UNICODE_UNASSIGNED]          = 2,
4990         [G_UNICODE_PRIVATE_USE]         = 0,
4991         [G_UNICODE_SURROGATE]           = 2,
4992         [G_UNICODE_LOWERCASE_LETTER]    = 1,
4993         [G_UNICODE_MODIFIER_LETTER]     = 1,
4994         [G_UNICODE_OTHER_LETTER]        = 1,
4995         [G_UNICODE_TITLECASE_LETTER]    = 1,
4996         [G_UNICODE_UPPERCASE_LETTER]    = 1,
4997         [G_UNICODE_SPACING_MARK]        = 0,
4998         [G_UNICODE_ENCLOSING_MARK]      = 0,
4999         [G_UNICODE_NON_SPACING_MARK]    = 0,
5000         [G_UNICODE_DECIMAL_NUMBER]      = 1,
5001         [G_UNICODE_LETTER_NUMBER]       = 1,
5002         [G_UNICODE_OTHER_NUMBER]        = 1,
5003         [G_UNICODE_CONNECT_PUNCTUATION] = 0,
5004         [G_UNICODE_DASH_PUNCTUATION]    = 0,
5005         [G_UNICODE_CLOSE_PUNCTUATION]   = 0,
5006         [G_UNICODE_FINAL_PUNCTUATION]   = 0,
5007         [G_UNICODE_INITIAL_PUNCTUATION] = 0,
5008         [G_UNICODE_OTHER_PUNCTUATION]   = 0,
5009         [G_UNICODE_OPEN_PUNCTUATION]    = 0,
5010         [G_UNICODE_CURRENCY_SYMBOL]     = 0,
5011         [G_UNICODE_MODIFIER_SYMBOL]     = 0,
5012         [G_UNICODE_MATH_SYMBOL]         = 0,
5013         [G_UNICODE_OTHER_SYMBOL]        = 0,
5014         [G_UNICODE_LINE_SEPARATOR]      = 2,
5015         [G_UNICODE_PARAGRAPH_SEPARATOR] = 2,
5016         [G_UNICODE_SPACE_SEPARATOR]     = 2,
5017 };
5018 
5019 /*
5020  * Terminal::is_word_char:
5021  * @c: a candidate Unicode code point
5022  *
5023  * Checks if a particular character is considered to be part of a word or not.
5024  *
5025  * Returns: %TRUE if the character is considered to be part of a word
5026  */
5027 bool
is_word_char(gunichar c) const5028 Terminal::is_word_char(gunichar c) const
5029 {
5030         const guint8 v = word_char_by_category[g_unichar_type(c)];
5031 
5032         if (v)
5033                 return v == 1;
5034 
5035         /* Do we have an exception? */
5036         return std::find(std::begin(m_word_char_exceptions), std::end(m_word_char_exceptions), char32_t(c)) != std::end(m_word_char_exceptions);
5037 }
5038 
5039 /* Check if the characters in the two given locations are in the same class
5040  * (word vs. non-word characters).
5041  * Note that calling this method may invalidate the return value of
5042  * a previous find_row_data() call. */
5043 bool
is_same_class(vte::grid::column_t acol,vte::grid::row_t arow,vte::grid::column_t bcol,vte::grid::row_t brow) const5044 Terminal::is_same_class(vte::grid::column_t acol,
5045                                   vte::grid::row_t arow,
5046                                   vte::grid::column_t bcol,
5047                                   vte::grid::row_t brow) const
5048 {
5049 	VteCell const* pcell = nullptr;
5050 	bool word_char;
5051 	if ((pcell = find_charcell(acol, arow)) != nullptr && pcell->c != 0) {
5052                 /* Group together if they're fragments of the very same character (not just character value) */
5053                 if (arow == brow) {
5054                         auto a2 = acol, b2 = bcol;
5055                         while (a2 > 0 && find_charcell(a2, arow)->attr.fragment()) a2--;
5056                         while (b2 > 0 && find_charcell(b2, brow)->attr.fragment()) b2--;
5057                         if (a2 == b2)
5058                                 return true;
5059                 }
5060 
5061 		word_char = is_word_char(_vte_unistr_get_base(pcell->c));
5062 
5063 		/* Lets not group non-wordchars together (bug #25290) */
5064 		if (!word_char)
5065 			return false;
5066 
5067 		pcell = find_charcell(bcol, brow);
5068 		if (pcell == NULL || pcell->c == 0) {
5069 			return false;
5070 		}
5071 		if (word_char != is_word_char(_vte_unistr_get_base(pcell->c))) {
5072 			return false;
5073 		}
5074 		return true;
5075 	}
5076 	return false;
5077 }
5078 
5079 /*
5080  * Convert the mouse click or drag location (left or right half of a cell) into a selection endpoint
5081  * (a boundary between characters), extending the selection according to the current mode, in the
5082  * direction given in the @after parameter.
5083  *
5084  * All four selection modes require different strategies.
5085  *
5086  * In char mode, what matters is which vertical character boundary is closer, taking multi-cell characters
5087  * (CJKs, TABs) into account. Given the string "abcdef", if the user clicks on the boundary between "a"
5088  * and "b" (perhaps on the right half of "a", perhaps on the left half of "b"), and moves the mouse to the
5089  * boundary between "e" and "f" (perhaps a bit over "e", perhaps a bit over "f"), the selection should be
5090  * "bcde". By dragging the mouse back to approximately the click location, it is possible to select the
5091  * empty string. This is the common sense behavior impemented by basically every graphical toolkit
5092  * (unfortunately not by many terminal emulators), and also the one we go for.
5093  *
5094  * Word mode is the trickiest one. Many implementations have weird corner case bugs (e.g. don't highlight
5095  * a word if you double click on the second half of its last letter, or even highlight it if you click on
5096  * the first half of the following space). I think it is expected that double-clicking anywhere over a
5097  * word (including the first half of its first letter, or the last half of its last letter), but over no
5098  * other character, selects this entire word. By dragging the mouse it's not possible to select nothing,
5099  * the word (or non-word character) initially clicked on is always part of the selection. (An exception
5100  * is when clicking occurred over the margin, or an unused cell in a soft-wrapped row (due to CJK
5101  * wrapping).) Also, for symmetry reasons, the word (or non-word character) under the current mouse
5102  * location is also always selected.
5103  *
5104  * Line (paragraph) mode is conceptually quite similar to word mode (the cell, thus the entire row under
5105  * the click's location is always included), but is much easier to implement.
5106  *
5107  * In block mode, similarly to char mode, we care about vertical character boundary. (This is somewhat
5108  * debatable, as results in asymmetrical behavior along the two axes: a rectangle can disappear by
5109  * becoming zero wide, but not zero high.) We cannot take care of CJKs at the endpoints now because CJKs
5110  * can cross the boundary in any included row. Taking care of them needs to go to cell_is_selected_*().
5111  * We don't care about used vs. unused cells either. The event coordinate is simply rounded to the
5112  * nearest vertical cell boundary.
5113  */
5114 vte::grid::coords
resolve_selection_endpoint(vte::grid::halfcoords const & rowcolhalf,bool after) const5115 Terminal::resolve_selection_endpoint(vte::grid::halfcoords const& rowcolhalf, bool after) const
5116 {
5117         auto row = rowcolhalf.row();
5118         auto col = rowcolhalf.halfcolumn().column();  /* Points to an actual cell now. At the end of this
5119                                                          method it'll point to a boundary. */
5120         auto half = rowcolhalf.halfcolumn().half();  /* 0 for left half, 1 for right half of the cell. */
5121         VteRowData const* rowdata;
5122         VteCell const* cell;
5123         int len;
5124 
5125         if (m_selection_block_mode) {
5126                 /* Just find the nearest cell boundary within the line, not caring about CJKs, unused
5127                  * cells, or wrapping at EOL. The @after parameter is unused in this mode. */
5128                 col += half;
5129                 col = std::clamp (col, 0L, m_column_count);
5130         } else {
5131                 switch (m_selection_type) {
5132                 case SelectionType::eCHAR:
5133                         /* Find the nearest actual character boundary, taking CJKs and TABs into account.
5134                          * If at least halfway through the first unused cell, or over the right margin
5135                          * then wrap to the beginning of the next line.
5136                          * The @after parameter is unused in this mode. */
5137                         if (col < 0) {
5138                                 col = 0;
5139                         } else if (col >= m_column_count) {
5140                                 /* If on the right padding, select the entire line including a possible
5141                                  * newline character. This way if a line is fully filled and ends in a
5142                                  * newline, there's only a half cell width for which the line is selected
5143                                  * without the newline, but at least there's a way to include the newline
5144                                  * by moving the mouse to the right (bug 724253). */
5145                                 col = 0;
5146                                 row++;
5147                         } else {
5148                                 vte::grid::column_t char_begin, char_end;  /* cell boundaries */
5149                                 rowdata = find_row_data(row);
5150                                 if (rowdata && col < _vte_row_data_nonempty_length(rowdata)) {
5151                                         /* Clicked over a used cell. Check for multi-cell characters. */
5152                                         char_begin = col;
5153                                         while (char_begin > 0) {
5154                                                 cell = _vte_row_data_get (rowdata, char_begin);
5155                                                 if (!cell->attr.fragment())
5156                                                         break;
5157                                                 char_begin--;
5158                                         }
5159                                         cell = _vte_row_data_get (rowdata, char_begin);
5160                                         char_end = char_begin + cell->attr.columns();
5161                                 } else {
5162                                         /* Clicked over unused area. Just go with cell boundaries. */
5163                                         char_begin = col;
5164                                         char_end = char_begin + 1;
5165                                 }
5166                                 /* Which boundary is closer? */
5167                                 if (col * 2 + half < char_begin + char_end)
5168                                         col = char_begin;
5169                                 else
5170                                         col = char_end;
5171 
5172                                 /* Maybe wrap to the beginning of the next line. */
5173                                 if (col > (rowdata ? _vte_row_data_nonempty_length(rowdata) : 0)) {
5174                                         col = 0;
5175                                         row++;
5176                                 }
5177                         }
5178                         break;
5179 
5180                 case SelectionType::eWORD:
5181                         /* Initialization for the cumbersome cases where the click didn't occur over an actual used cell. */
5182                         rowdata = find_row_data(row);
5183                         if (col < 0) {
5184                                 /* Clicked over the left margin.
5185                                  * - If within a word (that is, the first letter in this row, and the last
5186                                  *   letter of the previous row belong to the same word) then select the
5187                                  *   letter according to the direction and continue expanding.
5188                                  * - Otherwise stop, the boundary is here before the first letter. */
5189                                 if (row > 0 &&
5190                                     (rowdata = find_row_data(row - 1)) != nullptr &&
5191                                     rowdata->attr.soft_wrapped &&
5192                                     (len = _vte_row_data_nonempty_length(rowdata)) > 0 &&
5193                                     is_same_class(len - 1, row - 1, 0, row) /* invalidates rowdata! */) {
5194                                         if (!after) {
5195                                                 col = len - 1;
5196                                                 row--;
5197                                         } else {
5198                                                 col = 0;
5199                                         }
5200                                         /* go on with expanding */
5201                                 } else {
5202                                         col = 0;  /* end-exclusive */
5203                                         break;  /* done, don't expand any more */
5204                                 }
5205                         } else if (col >= (rowdata ? _vte_row_data_nonempty_length(rowdata) : 0)) {
5206                                 /* Clicked over the right margin, or right unused area.
5207                                  * - If within a word (that is, the last letter in this row, and the first
5208                                  *   letter of the next row belong to the same word) then select the letter
5209                                  *   according to the direction and continue expanding.
5210                                  * - Otherwise, if the row is soft-wrapped and we're over the unused area
5211                                  *   (which can happen if a CJK wrapped) or over the right margin, then
5212                                  *   stop, the boundary is wrapped to the beginning of the next row.
5213                                  * - Otherwise select the newline only and stop. */
5214                                 if (rowdata != nullptr &&
5215                                     rowdata->attr.soft_wrapped) {
5216                                         if ((len = _vte_row_data_nonempty_length(rowdata)) > 0 &&
5217                                             is_same_class(len - 1, row, 0, row + 1) /* invalidates rowdata! */) {
5218                                                 if (!after) {
5219                                                         col = len - 1;
5220                                                 } else {
5221                                                         col = 0;
5222                                                         row++;
5223                                                 }
5224                                                 /* go on with expanding */
5225                                         } else {
5226                                                 col = 0;  /* end-exclusive */
5227                                                 row++;
5228                                                 break;  /* done, don't expand any more */
5229                                         }
5230                                 } else {
5231                                         if (!after) {
5232                                                 col = rowdata ? _vte_row_data_nonempty_length(rowdata) : 0;  /* end-exclusive */
5233                                         } else {
5234                                                 col = 0;  /* end-exclusive */
5235                                                 row++;
5236                                         }
5237                                         break;  /* done, don't expand any more */
5238                                 }
5239                         }
5240 
5241                         /* Expand in the given direction. */
5242                         if (!after) {
5243                                 /* Keep selecting to the left (and then up) as long as the next character
5244                                  * we look at is of the same class as the current start point. */
5245                                 while (true) {
5246                                         /* Back up within the row. */
5247                                         for (; col > 0; col--) {
5248                                                 if (!is_same_class(col - 1, row, col, row)) {
5249                                                         break;
5250                                                 }
5251                                         }
5252                                         if (col > 0) {
5253                                                 /* We hit a stopping point, so stop. */
5254                                                 break;
5255                                         }
5256                                         if (row == 0) {
5257                                                 /* At the very beginning. */
5258                                                 break;
5259                                         }
5260                                         rowdata = find_row_data(row - 1);
5261                                         if (!rowdata || !rowdata->attr.soft_wrapped) {
5262                                                 /* Reached a hard newline. */
5263                                                 break;
5264                                         }
5265                                         len = _vte_row_data_nonempty_length(rowdata);
5266                                         /* len might be smaller than m_column_count if a CJK wrapped */
5267                                         if (!is_same_class(len - 1, row - 1, col, row) /* invalidates rowdata! */) {
5268                                                 break;
5269                                         }
5270                                         /* Move on to the previous line. */
5271                                         col = len - 1;
5272                                         row--;
5273                                 }
5274                         } else {
5275                                 /* Keep selecting to the right (and then down) as long as the next character
5276                                  * we look at is of the same class as the current end point. */
5277                                 while (true) {
5278                                         rowdata = find_row_data(row);
5279                                         if (!rowdata) {
5280                                                 break;
5281                                         }
5282                                         len = _vte_row_data_nonempty_length(rowdata);
5283                                         bool soft_wrapped = rowdata->attr.soft_wrapped;
5284                                         /* Move forward within the row. */
5285                                         for (; col < len - 1; col++) {
5286                                                 if (!is_same_class(col, row, col + 1, row) /* invalidates rowdata! */) {
5287                                                         break;
5288                                                 }
5289                                         }
5290                                         if (col < len - 1) {
5291                                                 /* We hit a stopping point, so stop. */
5292                                                 break;
5293                                         }
5294                                         if (!soft_wrapped) {
5295                                                 /* Reached a hard newline. */
5296                                                 break;
5297                                         }
5298                                         if (!is_same_class(col, row, 0, row + 1)) {
5299                                                 break;
5300                                         }
5301                                         /* Move on to the next line. */
5302                                         col = 0;
5303                                         row++;
5304                                 }
5305                                 col++;  /* col points to an actual cell, we need end-exclusive instead. */
5306                         }
5307                         break;
5308 
5309                 case SelectionType::eLINE:
5310                         if (!after) {
5311                                 /* Back up as far as we can go. */
5312                                 while (row > 0 &&
5313                                        _vte_ring_contains (m_screen->row_data, row - 1) &&
5314                                        m_screen->row_data->is_soft_wrapped(row - 1)) {
5315                                         row--;
5316                                 }
5317                         } else {
5318                                 /* Move forward as far as we can go. */
5319                                 while (_vte_ring_contains (m_screen->row_data, row) &&
5320                                        m_screen->row_data->is_soft_wrapped(row)) {
5321                                         row++;
5322                                 }
5323                                 row++;  /* One more row, since the column is 0. */
5324                         }
5325                         col = 0;
5326                         break;
5327                 }
5328         }
5329 
5330         return { row, col };
5331 }
5332 
5333 /*
5334  * Creates the selection's span from the origin and last coordinates.
5335  *
5336  * The origin and last points might be in reverse order; in block mode they might even point to the
5337  * two other corners of the rectangle than the ones we're interested in.
5338  * The resolved span will contain the endpoints in the proper order.
5339  *
5340  * In word & line (paragraph) modes it extends the selection accordingly.
5341  *
5342  * Also makes sure to invalidate the regions that changed, and update m_selecting_had_delta.
5343  *
5344  * FIXMEegmont it always resolves both endpoints. With a bit of extra bookkeeping it could usually
5345  * just resolve the moving one.
5346  */
5347 void
resolve_selection()5348 Terminal::resolve_selection()
5349 {
5350         if (m_selection_origin.row() < 0 || m_selection_last.row() < 0) {
5351                 invalidate (m_selection_resolved);
5352                 m_selection_resolved.clear();
5353                 _vte_debug_print(VTE_DEBUG_SELECTION, "Selection resolved to %s.\n", m_selection_resolved.to_string());
5354                 return;
5355         }
5356 
5357         auto m_selection_resolved_old = m_selection_resolved;
5358 
5359         if (m_selection_block_mode) {
5360                 auto top    = std::min (m_selection_origin.row(), m_selection_last.row());
5361                 auto bottom = std::max (m_selection_origin.row(), m_selection_last.row());
5362                 auto left   = std::min (m_selection_origin.halfcolumn(), m_selection_last.halfcolumn());
5363                 auto right  = std::max (m_selection_origin.halfcolumn(), m_selection_last.halfcolumn());
5364 
5365                 auto topleft     = resolve_selection_endpoint ({ top,    left  }, false);
5366                 auto bottomright = resolve_selection_endpoint ({ bottom, right }, true);
5367 
5368                 if (topleft.column() == bottomright.column()) {
5369                         m_selection_resolved.clear();
5370                 } else {
5371                         m_selection_resolved.set (topleft, bottomright);
5372                 }
5373         } else {
5374                 auto start = std::min (m_selection_origin, m_selection_last);
5375                 auto end   = std::max (m_selection_origin, m_selection_last);
5376 
5377                 m_selection_resolved.set (resolve_selection_endpoint (start, false),
5378                                           resolve_selection_endpoint (end,   true));
5379         }
5380 
5381         if (!m_selection_resolved.empty())
5382                 m_selecting_had_delta = true;
5383 
5384         _vte_debug_print(VTE_DEBUG_SELECTION, "Selection resolved to %s.\n", m_selection_resolved.to_string());
5385 
5386         invalidate_symmetrical_difference (m_selection_resolved_old, m_selection_resolved, m_selection_block_mode);
5387 }
5388 
5389 void
modify_selection(vte::view::coords const & pos)5390 Terminal::modify_selection (vte::view::coords const& pos)
5391 {
5392         g_assert (m_selecting);
5393 
5394         /* Need to ensure the ringview is updated. */
5395         ringview_update();
5396 
5397         auto current = selection_grid_halfcoords_from_view_coords (pos);
5398 
5399         if (current == m_selection_last)
5400                 return;
5401 
5402         _vte_debug_print(VTE_DEBUG_SELECTION,
5403                          "Selection dragged to %s.\n",
5404                          current.to_string());
5405 
5406         m_selection_last = current;
5407         resolve_selection();
5408 }
5409 
5410 /* Check if a cell is selected or not. BiDi: the coordinate is logical. */
5411 bool
cell_is_selected_log(vte::grid::column_t lcol,vte::grid::row_t row) const5412 Terminal::cell_is_selected_log(vte::grid::column_t lcol,
5413                                vte::grid::row_t row) const
5414 {
5415         /* Our caller had to update the ringview (we can't do because we're const). */
5416         g_assert(m_ringview.is_updated());
5417 
5418         if (m_selection_block_mode) {
5419                 /* In block mode, make sure CJKs and TABs aren't cut in half. */
5420                 while (lcol > 0) {
5421                         VteCell const* cell = find_charcell(lcol, row);
5422                         if (!cell || !cell->attr.fragment())
5423                                 break;
5424                         lcol--;
5425                 }
5426                 /* Convert to visual. */
5427                 vte::base::BidiRow const* bidirow = m_ringview.get_bidirow(row);
5428                 vte::grid::column_t vcol = bidirow->log2vis(lcol);
5429                 return m_selection_resolved.box_contains ({ row, vcol });
5430         } else {
5431                 /* In normal modes, resolve_selection() made sure to generate such boundaries for m_selection_resolved. */
5432                 return m_selection_resolved.contains ({ row, lcol });
5433         }
5434 }
5435 
5436 /* Check if a cell is selected or not. BiDi: the coordinate is visual. */
5437 bool
cell_is_selected_vis(vte::grid::column_t vcol,vte::grid::row_t row) const5438 Terminal::cell_is_selected_vis(vte::grid::column_t vcol,
5439                                vte::grid::row_t row) const
5440 {
5441         /* Our caller had to update the ringview (we can't do because we're const). */
5442         g_assert(m_ringview.is_updated());
5443 
5444         /* Convert to logical column. */
5445         vte::base::BidiRow const* bidirow = m_ringview.get_bidirow(row);
5446         vte::grid::column_t lcol = bidirow->vis2log(vcol);
5447 
5448         return cell_is_selected_log(lcol, row);
5449 }
5450 
5451 void
widget_clipboard_text_received(vte::platform::Clipboard const & clipboard,std::string_view const & data)5452 Terminal::widget_clipboard_text_received(vte::platform::Clipboard const& clipboard,
5453                                          std::string_view const& data)
5454 {
5455 	gchar *paste, *p;
5456         gsize run;
5457         unsigned char c;
5458 
5459         auto const len = data.size();
5460         auto text = data.data();
5461 
5462         /* Convert newlines to carriage returns, which more software
5463          * is able to cope with (cough, pico, cough).
5464          * Filter out control chars except HT, CR (even stricter than xterm).
5465          * Also filter out C1 controls: U+0080 (0xC2 0x80) - U+009F (0xC2 0x9F). */
5466         p = paste = (gchar *) g_malloc(len + 1);
5467         while (p != nullptr && text[0] != '\0') {
5468                 run = strcspn(text, "\x01\x02\x03\x04\x05\x06\x07"
5469                               "\x08\x0A\x0B\x0C\x0E\x0F"
5470                               "\x10\x11\x12\x13\x14\x15\x16\x17"
5471                               "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
5472                               "\x7F\xC2");
5473                 memcpy(p, text, run);
5474                 p += run;
5475                 text += run;
5476                 switch (text[0]) {
5477                 case '\x00':
5478                         break;
5479                 case '\x0A':
5480                         *p = '\x0D';
5481                         p++;
5482                         text++;
5483                         break;
5484                 case '\xC2':
5485                         c = text[1];
5486                         if (c >= 0x80 && c <= 0x9F) {
5487                                 /* Skip both bytes of a C1 */
5488                                 text += 2;
5489                         } else {
5490                                 /* Move along, nothing to see here */
5491                                 *p = '\xC2';
5492                                 p++;
5493                                 text++;
5494                         }
5495                         break;
5496                 default:
5497                         /* Swallow this byte */
5498                         text++;
5499                         break;
5500                 }
5501         }
5502 
5503         bool const bracketed_paste = m_modes_private.XTERM_READLINE_BRACKETED_PASTE();
5504         // FIXMEchpe can we not hardcode C0 controls here?
5505         if (bracketed_paste)
5506                 feed_child("\e[200~"sv);
5507         // FIXMEchpe add a way to avoid the extra string copy done here
5508         feed_child(paste, p - paste);
5509         if (bracketed_paste)
5510                 feed_child("\e[201~"sv);
5511         g_free(paste);
5512 }
5513 
5514 bool
feed_mouse_event(vte::grid::coords const & rowcol,int button,bool is_drag,bool is_release)5515 Terminal::feed_mouse_event(vte::grid::coords const& rowcol /* confined */,
5516                                      int button,
5517                                      bool is_drag,
5518                                      bool is_release)
5519 {
5520 	unsigned char cb = 0;
5521 
5522         /* Don't send events on scrollback contents: bug 755187. */
5523         if (grid_coords_in_scrollback(rowcol))
5524                 return false;
5525 
5526 	/* Make coordinates 1-based. */
5527 	auto cx = rowcol.column() + 1;
5528 	auto cy = rowcol.row() - m_screen->insert_delta + 1;
5529 
5530 	/* Encode the button information in cb. */
5531 	switch (button) {
5532         case 0:                 /* No button, just dragging. */
5533                 cb = 3;
5534                 break;
5535 	case 1:			/* Left. */
5536 		cb = 0;
5537 		break;
5538 	case 2:			/* Middle. */
5539 		cb = 1;
5540 		break;
5541 	case 3:			/* Right. */
5542 		cb = 2;
5543 		break;
5544 	case 4:
5545 		cb = 64;	/* Scroll up. */
5546 		break;
5547 	case 5:
5548 		cb = 65;	/* Scroll down. */
5549 		break;
5550 	}
5551 
5552 	/* With the exception of the 1006 mode, button release is also encoded here. */
5553 	/* Note that if multiple extensions are enabled, the 1006 is used, so it's okay to check for only that. */
5554 	if (is_release && !m_modes_private.XTERM_MOUSE_EXT_SGR()) {
5555 		cb = 3;
5556 	}
5557 
5558 	/* Encode the modifiers. */
5559         if (m_mouse_tracking_mode >= MouseTrackingMode::eSEND_XY_ON_BUTTON) {
5560                 if (m_modifiers & GDK_SHIFT_MASK) {
5561                         cb |= 4;
5562                 }
5563                 if (m_modifiers & VTE_ALT_MASK) {
5564                         cb |= 8;
5565                 }
5566                 if (m_modifiers & GDK_CONTROL_MASK) {
5567                         cb |= 16;
5568                 }
5569         }
5570 
5571 	/* Encode a drag event. */
5572 	if (is_drag) {
5573 		cb |= 32;
5574 	}
5575 
5576 	/* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
5577 	if (m_modes_private.XTERM_MOUSE_EXT_SGR()) {
5578 		/* xterm's extended mode (1006) */
5579                 send(is_release ? VTE_REPLY_XTERM_MOUSE_EXT_SGR_REPORT_BUTTON_RELEASE
5580                                 : VTE_REPLY_XTERM_MOUSE_EXT_SGR_REPORT_BUTTON_PRESS,
5581                      {cb, (int)cx, (int)cy});
5582 	} else if (cx <= 223 && cy <= 223) {
5583 		/* legacy mode */
5584                 char buf[8];
5585                 size_t len = g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "M%c%c%c", 32 + cb, 32 + (guchar)cx, 32 + (guchar)cy);
5586 
5587                 /* Send event direct to the child, this is binary not text data */
5588                 feed_child_binary({buf, len});
5589 	}
5590 
5591         return true;
5592 }
5593 
5594 void
feed_focus_event(bool in)5595 Terminal::feed_focus_event(bool in)
5596 {
5597         send(in ? VTE_REPLY_XTERM_FOCUS_IN : VTE_REPLY_XTERM_FOCUS_OUT, {});
5598 }
5599 
5600 void
feed_focus_event_initial()5601 Terminal::feed_focus_event_initial()
5602 {
5603         /* We immediately send the terminal a focus event, since otherwise
5604          * it has no way to know the current status.
5605          */
5606         feed_focus_event(m_has_focus);
5607 }
5608 
5609 void
maybe_feed_focus_event(bool in)5610 Terminal::maybe_feed_focus_event(bool in)
5611 {
5612         if (m_modes_private.XTERM_FOCUS())
5613                 feed_focus_event(in);
5614 }
5615 
5616 /*
5617  * Terminal::maybe_send_mouse_button:
5618  * @terminal:
5619  * @event:
5620  *
5621  * Sends a mouse button click or release notification to the application,
5622  * if the terminal is in mouse tracking mode.
5623  *
5624  * Returns: %TRUE iff the event was consumed
5625  */
5626 bool
maybe_send_mouse_button(vte::grid::coords const & unconfined_rowcol,vte::platform::MouseEvent const & event)5627 Terminal::maybe_send_mouse_button(vte::grid::coords const& unconfined_rowcol,
5628                                   vte::platform::MouseEvent const& event)
5629 {
5630 	switch (event.type()) {
5631         case vte::platform::EventBase::Type::eMOUSE_PRESS:
5632                 if (event.press_count() != 1 ||
5633                     (m_mouse_tracking_mode < MouseTrackingMode::eSEND_XY_ON_CLICK)) {
5634 			return false;
5635 		}
5636 		break;
5637         case vte::platform::EventBase::Type::eMOUSE_RELEASE:
5638                 if (event.press_count() != 1 ||
5639                     (m_mouse_tracking_mode < MouseTrackingMode::eSEND_XY_ON_BUTTON)) {
5640 			return false;
5641 		}
5642 		break;
5643 	default:
5644 		return false;
5645 	}
5646 
5647         auto rowcol = confine_grid_coords(unconfined_rowcol);
5648         return feed_mouse_event(rowcol,
5649                                 event.button_value(),
5650                                 false /* not drag */,
5651                                 event.is_mouse_release());
5652 }
5653 
5654 /*
5655  * Terminal::maybe_send_mouse_drag:
5656  * @terminal:
5657  * @event:
5658  *
5659  * Sends a mouse motion notification to the application,
5660  * if the terminal is in mouse tracking mode.
5661  *
5662  * Returns: %TRUE iff the event was consumed
5663  */
5664 bool
maybe_send_mouse_drag(vte::grid::coords const & unconfined_rowcol,vte::platform::MouseEvent const & event)5665 Terminal::maybe_send_mouse_drag(vte::grid::coords const& unconfined_rowcol,
5666                                 vte::platform::MouseEvent const& event)
5667 {
5668         /* Need to ensure the ringview is updated. */
5669         ringview_update();
5670 
5671         auto rowcol = confine_grid_coords(unconfined_rowcol);
5672 
5673 	/* First determine if we even want to send notification. */
5674         switch (event.type()) {
5675         case vte::platform::EventBase::Type::eMOUSE_MOTION:
5676 		if (m_mouse_tracking_mode < MouseTrackingMode::eCELL_MOTION_TRACKING)
5677 			return false;
5678 
5679 		if (m_mouse_tracking_mode < MouseTrackingMode::eALL_MOTION_TRACKING) {
5680 
5681                         if (m_mouse_pressed_buttons == 0) {
5682 				return false;
5683 			}
5684 			/* The xterm doc is not clear as to whether
5685 			 * all-tracking also sends degenerate same-cell events;
5686                          * we don't.
5687                          */
5688                         if (rowcol == confined_grid_coords_from_view_coords(m_mouse_last_position))
5689 				return false;
5690 		}
5691 		break;
5692 	default:
5693 		return false;
5694 	}
5695 
5696         /* As per xterm, report the leftmost pressed button - if any. */
5697         int button;
5698         if (m_mouse_pressed_buttons & 1)
5699                 button = 1;
5700         else if (m_mouse_pressed_buttons & 2)
5701                 button = 2;
5702         else if (m_mouse_pressed_buttons & 4)
5703                 button = 3;
5704         else
5705                 button = 0;
5706 
5707         return feed_mouse_event(rowcol,
5708                                 button,
5709                                 true /* drag */,
5710                                 false /* not release */);
5711 }
5712 
5713 /*
5714  * Terminal::hyperlink_invalidate_and_get_bbox
5715  *
5716  * Invalidates cells belonging to the non-zero hyperlink idx, in order to
5717  * stop highlighting the previously hovered hyperlink or start highlighting
5718  * the new one. Optionally stores the coordinates of the bounding box.
5719  */
5720 void
hyperlink_invalidate_and_get_bbox(vte::base::Ring::hyperlink_idx_t idx,GdkRectangle * bbox)5721 Terminal::hyperlink_invalidate_and_get_bbox(vte::base::Ring::hyperlink_idx_t idx,
5722                                                       GdkRectangle *bbox)
5723 {
5724         auto first_row = first_displayed_row();
5725         auto end_row = last_displayed_row() + 1;
5726         vte::grid::row_t row, top = LONG_MAX, bottom = -1;
5727         vte::grid::column_t col, left = LONG_MAX, right = -1;
5728         const VteRowData *rowdata;
5729 
5730         g_assert (idx != 0);
5731 
5732         for (row = first_row; row < end_row; row++) {
5733                 rowdata = _vte_ring_index(m_screen->row_data, row);
5734                 if (rowdata != NULL) {
5735                         bool do_invalidate_row = false;
5736                         for (col = 0; col < rowdata->len; col++) {
5737                                 if (G_UNLIKELY (rowdata->cells[col].attr.hyperlink_idx == idx)) {
5738                                         do_invalidate_row = true;
5739                                         top = MIN(top, row);
5740                                         bottom = MAX(bottom, row);
5741                                         left = MIN(left, col);
5742                                         right = MAX(right, col);
5743                                 }
5744                         }
5745                         if (G_UNLIKELY (do_invalidate_row)) {
5746                                 invalidate_row(row);
5747                         }
5748                 }
5749         }
5750 
5751         if (bbox == NULL)
5752                 return;
5753 
5754         /* If bbox != NULL, we're looking for the new hovered hyperlink which always has onscreen bits. */
5755         g_assert (top != LONG_MAX && bottom != -1 && left != LONG_MAX && right != -1);
5756 
5757         auto allocation = get_allocated_rect();
5758         bbox->x = allocation.x + m_padding.left + left * m_cell_width;
5759         bbox->y = allocation.y + m_padding.top + row_to_pixel(top);
5760         bbox->width = (right - left + 1) * m_cell_width;
5761         bbox->height = (bottom - top + 1) * m_cell_height;
5762         _vte_debug_print (VTE_DEBUG_HYPERLINK,
5763                           "Hyperlink bounding box: x=%d y=%d w=%d h=%d\n",
5764                           bbox->x, bbox->y, bbox->width, bbox->height);
5765 }
5766 
5767 /*
5768  * Terminal::hyperlink_hilite_update:
5769  *
5770  * Checks the coordinates for hyperlink. Updates m_hyperlink_hover_idx
5771  * and m_hyperlink_hover_uri, and schedules to update the highlighting.
5772  */
5773 void
hyperlink_hilite_update()5774 Terminal::hyperlink_hilite_update()
5775 {
5776         const VteRowData *rowdata;
5777         bool do_check_hilite;
5778         vte::grid::coords rowcol;
5779         vte::base::Ring::hyperlink_idx_t new_hyperlink_hover_idx = 0;
5780         GdkRectangle bbox;
5781         const char *separator;
5782 
5783         if (!m_allow_hyperlink)
5784                 return;
5785 
5786         _vte_debug_print (VTE_DEBUG_HYPERLINK,
5787                          "hyperlink_hilite_update\n");
5788 
5789         /* Need to ensure the ringview is updated. */
5790         ringview_update();
5791 
5792         /* m_mouse_last_position contains the current position, see bug 789536 comment 24. */
5793         auto pos = m_mouse_last_position;
5794 
5795         /* Whether there's any chance we'd highlight something */
5796         do_check_hilite = view_coords_visible(pos) &&
5797                           m_mouse_cursor_over_widget &&
5798                           !(m_mouse_autohide && m_mouse_cursor_autohidden) &&
5799                           !m_selecting;
5800         if (do_check_hilite) {
5801                 rowcol = grid_coords_from_view_coords(pos);
5802                 rowdata = find_row_data(rowcol.row());
5803                 if (rowdata && rowcol.column() < rowdata->len) {
5804                         new_hyperlink_hover_idx = rowdata->cells[rowcol.column()].attr.hyperlink_idx;
5805                 }
5806         }
5807 
5808         if (new_hyperlink_hover_idx == m_hyperlink_hover_idx) {
5809                 _vte_debug_print (VTE_DEBUG_HYPERLINK,
5810                                   "hyperlink did not change\n");
5811                 return;
5812         }
5813 
5814         /* Invalidate cells of the old hyperlink. */
5815         if (m_hyperlink_hover_idx != 0) {
5816                 hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, NULL);
5817         }
5818 
5819         /* This might be different from new_hyperlink_hover_idx. If in the stream, that one contains
5820          * the pseudo idx VTE_HYPERLINK_IDX_TARGET_IN_STREAM and now a real idx is allocated.
5821          * Plus, the ring's internal belief of the hovered hyperlink is also updated. */
5822         if (do_check_hilite) {
5823                 m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, rowcol.row(), rowcol.column(), true, &m_hyperlink_hover_uri);
5824         } else {
5825                 m_hyperlink_hover_idx = 0;
5826                 m_hyperlink_hover_uri = nullptr;
5827         }
5828 
5829         /* Invalidate cells of the new hyperlink. Get the bounding box. */
5830         if (m_hyperlink_hover_idx != 0) {
5831                 /* URI is after the first semicolon */
5832                 separator = strchr(m_hyperlink_hover_uri, ';');
5833                 g_assert(separator != NULL);
5834                 m_hyperlink_hover_uri = separator + 1;
5835 
5836                 hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, &bbox);
5837                 g_assert(bbox.width > 0 && bbox.height > 0);
5838         }
5839         _vte_debug_print(VTE_DEBUG_HYPERLINK,
5840                          "Hover idx: %d \"%s\"\n",
5841                          m_hyperlink_hover_idx,
5842                          m_hyperlink_hover_uri);
5843 
5844         /* Underlining hyperlinks has precedence over regex matches. So when the hovered hyperlink changes,
5845          * the regex match might need to become or stop being underlined. */
5846         if (regex_match_has_current())
5847                 invalidate_match_span();
5848 
5849         apply_mouse_cursor();
5850 
5851         emit_hyperlink_hover_uri_changed(m_hyperlink_hover_idx != 0 ? &bbox : NULL);
5852 }
5853 
5854 /*
5855  * Terminal::match_hilite_clear:
5856  *
5857  * Reset match variables and invalidate the old match region if highlighted.
5858  */
5859 void
match_hilite_clear()5860 Terminal::match_hilite_clear()
5861 {
5862         if (regex_match_has_current())
5863                 invalidate_match_span();
5864 
5865         m_match_span.clear();
5866         m_match_current = nullptr;
5867 
5868         g_free(m_match);
5869         m_match = nullptr;
5870 }
5871 
5872 /* This is only used by the dingu matching code, so no need to extend the area. */
5873 void
invalidate_match_span()5874 Terminal::invalidate_match_span()
5875 {
5876         _vte_debug_print(VTE_DEBUG_EVENTS,
5877                          "Invalidating match span %s\n", m_match_span.to_string());
5878         invalidate(m_match_span);
5879 }
5880 
5881 /*
5882  * Terminal::match_hilite_update:
5883  *
5884  * Checks the coordinates for dingu matches, setting m_match_span to
5885  * the match region or the no-matches region, and if there is a match,
5886  * sets it to display highlighted.
5887  */
5888 void
match_hilite_update()5889 Terminal::match_hilite_update()
5890 {
5891         /* Need to ensure the ringview is updated. */
5892         ringview_update();
5893 
5894         /* m_mouse_last_position contains the current position, see bug 789536 comment 24. */
5895         auto pos = m_mouse_last_position;
5896 
5897         glong col = pos.x / m_cell_width;
5898         glong row = pixel_to_row(pos.y);
5899 
5900         /* BiDi: convert to logical column. */
5901         vte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
5902         col = bidirow->vis2log(col);
5903 
5904 	_vte_debug_print(VTE_DEBUG_EVENTS,
5905                          "Match hilite update (%ld, %ld) -> %ld, %ld\n",
5906                          pos.x, pos.y, col, row);
5907 
5908         /* Whether there's any chance we'd highlight something */
5909         bool do_check_hilite = view_coords_visible(pos) &&
5910                                m_mouse_cursor_over_widget &&
5911                                !(m_mouse_autohide && m_mouse_cursor_autohidden) &&
5912                                !m_selecting;
5913         if (!do_check_hilite) {
5914                 if (regex_match_has_current())
5915                          match_hilite_clear();
5916                 return;
5917         }
5918 
5919         if (m_match_span.contains(row, col)) {
5920                 /* Already highlighted. */
5921                 return;
5922         }
5923 
5924         /* Reset match variables and invalidate the old match region if highlighted */
5925         match_hilite_clear();
5926 
5927         /* Check for matches. */
5928 	gsize start, end;
5929         auto new_match = match_check_internal(col,
5930                                               row,
5931                                               &m_match_current,
5932                                               &start,
5933                                               &end);
5934 
5935 	/* Read the new locations. */
5936 	if (start < m_match_attributes->len &&
5937             end < m_match_attributes->len) {
5938                 struct _VteCharAttributes const *sa, *ea;
5939 		sa = &g_array_index(m_match_attributes,
5940                                    struct _VteCharAttributes,
5941                                    start);
5942                 ea = &g_array_index(m_match_attributes,
5943                                     struct _VteCharAttributes,
5944                                     end);
5945 
5946                 /* convert from inclusive to exclusive (a.k.a. boundary) ending, taking a possible last CJK character into account */
5947                 m_match_span = vte::grid::span(sa->row, sa->column, ea->row, ea->column + ea->columns);
5948 	}
5949 
5950         g_assert(!m_match); /* from match_hilite_clear() above */
5951 	m_match = new_match;
5952 
5953 	if (m_match) {
5954 		_vte_debug_print(VTE_DEBUG_EVENTS,
5955 				"Matched %s.\n", m_match_span.to_string());
5956                 invalidate_match_span();
5957         } else {
5958 		_vte_debug_print(VTE_DEBUG_EVENTS,
5959                                  "No matches %s.\n", m_match_span.to_string());
5960 	}
5961 
5962         apply_mouse_cursor();
5963 }
5964 
5965 void
widget_clipboard_data_clear(vte::platform::Clipboard const & clipboard)5966 Terminal::widget_clipboard_data_clear(vte::platform::Clipboard const& clipboard)
5967 {
5968         if (m_changing_selection)
5969                 return;
5970 
5971         switch (clipboard.type()) {
5972         case vte::platform::ClipboardType::PRIMARY:
5973 		if (m_selection_owned[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] &&
5974                     !m_selection_resolved.empty()) {
5975 			_vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n");
5976 			deselect_all();
5977 		}
5978 
5979                 [[fallthrough]];
5980         case vte::platform::ClipboardType::CLIPBOARD:
5981                 m_selection_owned[vte::to_integral(clipboard.type())] = false;
5982                 break;
5983         }
5984 }
5985 
5986 std::optional<std::string_view>
widget_clipboard_data_get(vte::platform::Clipboard const & clipboard,vte::platform::ClipboardFormat format)5987 Terminal::widget_clipboard_data_get(vte::platform::Clipboard const& clipboard,
5988                                     vte::platform::ClipboardFormat format)
5989 {
5990         auto const sel = vte::to_integral(clipboard.type());
5991 
5992         if (m_selection[sel] == nullptr)
5993                 return std::nullopt;
5994 
5995         _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) {
5996                 g_printerr("Setting selection %d (%" G_GSIZE_FORMAT " UTF-8 bytes.) for target %s\n",
5997                            sel,
5998                            m_selection[sel]->len,
5999                            format == vte::platform::ClipboardFormat::HTML ? "HTML" : "TEXT");
6000                 _vte_debug_hexdump("Selection data", (uint8_t const*)m_selection[sel]->str, m_selection[sel]->len);
6001         }
6002 
6003         return std::string_view{m_selection[sel]->str, m_selection[sel]->len};
6004 }
6005 
6006 /* Convert the internal color code (either index or RGB) into RGB. */
6007 template <unsigned int redbits,
6008           unsigned int greenbits,
6009           unsigned int bluebits>
6010 void
rgb_from_index(guint index,vte::color::rgb & color) const6011 Terminal::rgb_from_index(guint index,
6012                                    vte::color::rgb& color) const
6013 {
6014         bool dim = false;
6015         if (!(index & VTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)) && (index & VTE_DIM_COLOR)) {
6016                 index &= ~VTE_DIM_COLOR;
6017                 dim = true;
6018         }
6019 
6020 	if (index >= VTE_LEGACY_COLORS_OFFSET && index < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_FULL_COLOR_SET_SIZE)
6021 		index -= VTE_LEGACY_COLORS_OFFSET;
6022 	if (index < VTE_PALETTE_SIZE) {
6023                 color = *get_color(index);
6024                 if (dim) {
6025                         /* magic formula taken from xterm */
6026                         color.red = color.red * 2 / 3;
6027                         color.green = color.green * 2 / 3;
6028                         color.blue = color.blue * 2 / 3;
6029                 }
6030 	} else if (index & VTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)) {
6031                 color.red   = VTE_RGB_COLOR_GET_COMPONENT(index, greenbits + bluebits, redbits) * 0x101U;
6032                 color.green = VTE_RGB_COLOR_GET_COMPONENT(index, bluebits, greenbits) * 0x101U;
6033                 color.blue  = VTE_RGB_COLOR_GET_COMPONENT(index, 0, bluebits) * 0x101U;
6034 	} else {
6035 		g_assert_not_reached();
6036 	}
6037 }
6038 
6039 GString*
get_text(vte::grid::row_t start_row,vte::grid::column_t start_col,vte::grid::row_t end_row,vte::grid::column_t end_col,bool block,bool wrap,GArray * attributes)6040 Terminal::get_text(vte::grid::row_t start_row,
6041                              vte::grid::column_t start_col,
6042                              vte::grid::row_t end_row,
6043                              vte::grid::column_t end_col,
6044                              bool block,
6045                              bool wrap,
6046                              GArray *attributes)
6047 {
6048 	const VteCell *pcell = NULL;
6049 	GString *string;
6050 	struct _VteCharAttributes attr;
6051 	vte::color::rgb fore, back;
6052         std::unique_ptr<vte::base::RingView> ringview;
6053         vte::base::BidiRow const *bidirow = nullptr;
6054         vte::grid::column_t vcol;
6055 
6056 	if (attributes)
6057 		g_array_set_size (attributes, 0);
6058 
6059 	string = g_string_new(NULL);
6060 	memset(&attr, 0, sizeof(attr));
6061 
6062         if (start_col < 0)
6063                 start_col = 0;
6064 
6065         if (m_enable_bidi && block) {
6066                 /* Rectangular selection operates on the visual contents, not the logical.
6067                  * m_ringview corresponds to the currently onscreen bits, therefore does not
6068                  * necessarily include the entire selection. Also we want m_ringview's size
6069                  * to be limited, even if the user selects a giant rectangle.
6070                  * So use a new ringview for the selection. */
6071                 ringview = std::make_unique<vte::base::RingView>();
6072                 ringview->set_ring(m_screen->row_data);
6073                 ringview->set_rows(start_row, end_row - start_row + 1);
6074                 ringview->set_width(m_column_count);
6075                 ringview->update();
6076         }
6077 
6078         vte::grid::column_t lcol = block ? 0 : start_col;
6079         vte::grid::row_t row;
6080         for (row = start_row; row < end_row + 1; row++, lcol = 0) {
6081 		VteRowData const* row_data = find_row_data(row);
6082                 gsize last_empty, last_nonempty;
6083                 vte::grid::column_t last_emptycol, last_nonemptycol;
6084                 vte::grid::column_t line_last_column = (!block && row == end_row) ? end_col : m_column_count;
6085 
6086                 last_empty = last_nonempty = string->len;
6087                 last_emptycol = last_nonemptycol = -1;
6088 
6089 		attr.row = row;
6090                 attr.column = lcol;
6091 		pcell = NULL;
6092 		if (row_data != NULL) {
6093                         bidirow = ringview ? ringview->get_bidirow(row) : nullptr;
6094                         while (lcol < line_last_column &&
6095                                (pcell = _vte_row_data_get (row_data, lcol))) {
6096 
6097                                 /* In block mode, we scan each row from its very beginning to its very end in logical order,
6098                                  * and here filter out the characters that are visually outside of the block. */
6099                                 if (bidirow) {
6100                                         vcol = bidirow->log2vis(lcol);
6101                                         if (vcol < start_col || vcol >= end_col) {
6102                                                 lcol++;
6103                                                 continue;
6104                                         }
6105                                 }
6106 
6107                                 attr.column = lcol;
6108 
6109 				/* If it's not part of a multi-column character,
6110 				 * and passes the selection criterion, add it to
6111 				 * the selection. */
6112 				if (!pcell->attr.fragment()) {
6113 					/* Store the attributes of this character. */
6114                                         // FIXMEchpe shouldn't this use determine_colors?
6115                                         uint32_t fg, bg, dc;
6116                                         vte_color_triple_get(pcell->attr.colors(), &fg, &bg, &dc);
6117                                         rgb_from_index<8, 8, 8>(fg, fore);
6118                                         rgb_from_index<8, 8, 8>(bg, back);
6119 					attr.fore.red = fore.red;
6120 					attr.fore.green = fore.green;
6121 					attr.fore.blue = fore.blue;
6122 					attr.back.red = back.red;
6123 					attr.back.green = back.green;
6124 					attr.back.blue = back.blue;
6125 					attr.underline = (pcell->attr.underline() == 1);
6126 					attr.strikethrough = pcell->attr.strikethrough();
6127                                         attr.columns = pcell->attr.columns();
6128 
6129 					/* Store the cell string */
6130 					if (pcell->c == 0) {
6131                                                 /* Empty cells of nondefault background color are
6132                                                  * stored as NUL characters. Treat them as spaces,
6133                                                  * but make a note of the last occurrence. */
6134 						g_string_append_c (string, ' ');
6135                                                 last_empty = string->len;
6136                                                 last_emptycol = lcol;
6137 					} else {
6138 						_vte_unistr_append_to_string (pcell->c, string);
6139                                                 last_nonempty = string->len;
6140                                                 last_nonemptycol = lcol;
6141 					}
6142 
6143 					/* If we added text to the string, record its
6144 					 * attributes, one per byte. */
6145 					if (attributes) {
6146 						vte_g_array_fill(attributes,
6147 								&attr, string->len);
6148 					}
6149 				}
6150 
6151                                 lcol++;
6152 			}
6153 		}
6154 
6155                 /* Empty cells of nondefault background color can appear anywhere in a line,
6156                  * not just at the end, e.g. between "foo" and "bar" here:
6157                  *   echo -e '\e[46mfoo\e[K\e[7Gbar\e[m'
6158                  * Strip off the trailing ones, preserve the middle ones. */
6159                 if (last_empty > last_nonempty) {
6160 
6161                         lcol = last_emptycol + 1;
6162 
6163                         if (row_data != NULL) {
6164                                 while ((pcell = _vte_row_data_get (row_data, lcol))) {
6165                                         lcol++;
6166 
6167                                         if (pcell->attr.fragment())
6168                                                 continue;
6169 
6170                                         if (pcell->c != 0)
6171                                                 break;
6172                                 }
6173                         }
6174                         if (pcell == NULL) {
6175                                 g_string_truncate(string, last_nonempty);
6176                                 if (attributes)
6177                                         g_array_set_size(attributes, string->len);
6178                                 attr.column = last_nonemptycol;
6179                         }
6180                 }
6181 
6182 		/* Adjust column, in case we want to append a newline */
6183                 //FIXMEchpe MIN ?
6184 		attr.column = MAX(m_column_count, attr.column + 1);
6185 
6186 		/* Add a newline in block mode. */
6187 		if (block) {
6188 			string = g_string_append_c(string, '\n');
6189 		}
6190 		/* Else, if the last visible column on this line was in range and
6191 		 * not soft-wrapped, append a newline. */
6192 		else if (row < end_row) {
6193 			/* If we didn't softwrap, add a newline. */
6194 			/* XXX need to clear row->soft_wrap on deletion! */
6195                         if (!m_screen->row_data->is_soft_wrapped(row)) {
6196 				string = g_string_append_c(string, '\n');
6197 			}
6198 		}
6199 
6200 		/* Make sure that the attributes array is as long as the string. */
6201 		if (attributes) {
6202 			vte_g_array_fill (attributes, &attr, string->len);
6203 		}
6204 	}
6205 
6206 	/* Sanity check. */
6207         if (attributes != nullptr)
6208                 g_assert_cmpuint(string->len, ==, attributes->len);
6209 
6210         return string;
6211 }
6212 
6213 GString*
get_text_displayed(bool wrap,GArray * attributes)6214 Terminal::get_text_displayed(bool wrap,
6215                                        GArray *attributes)
6216 {
6217         return get_text(first_displayed_row(), 0,
6218                         last_displayed_row() + 1, 0,
6219                         false /* block */, wrap,
6220                         attributes);
6221 }
6222 
6223 /* This is distinct from just using first/last_displayed_row since a11y
6224  * doesn't know about sub-row displays.
6225  */
6226 GString*
get_text_displayed_a11y(bool wrap,GArray * attributes)6227 Terminal::get_text_displayed_a11y(bool wrap,
6228                                             GArray *attributes)
6229 {
6230         return get_text(m_screen->scroll_delta, 0,
6231                         m_screen->scroll_delta + m_row_count - 1 + 1, 0,
6232                         false /* block */, wrap,
6233                         attributes);
6234 }
6235 
6236 GString*
get_selected_text(GArray * attributes)6237 Terminal::get_selected_text(GArray *attributes)
6238 {
6239         return get_text(m_selection_resolved.start_row(),
6240                         m_selection_resolved.start_column(),
6241                         m_selection_resolved.end_row(),
6242                         m_selection_resolved.end_column(),
6243                         m_selection_block_mode,
6244                         true /* wrap */,
6245                         attributes);
6246 }
6247 
6248 #ifdef VTE_DEBUG
6249 unsigned int
checksum_area(vte::grid::row_t start_row,vte::grid::column_t start_col,vte::grid::row_t end_row,vte::grid::column_t end_col)6250 Terminal::checksum_area(vte::grid::row_t start_row,
6251                                   vte::grid::column_t start_col,
6252                                   vte::grid::row_t end_row,
6253                                   vte::grid::column_t end_col)
6254 {
6255         unsigned int checksum = 0;
6256 
6257         auto text = get_text(start_row, start_col, end_row, end_col,
6258                              true /* block */, false /* wrap */,
6259                              nullptr /* not interested in attributes */);
6260         if (text == nullptr)
6261                 return checksum;
6262 
6263         char const* end = (char const*)text->str + text->len;
6264         for (char const *p = text->str; p < end; p = g_utf8_next_char(p)) {
6265                 auto const c = g_utf8_get_char(p);
6266                 if (c == '\n')
6267                         continue;
6268                 checksum += c;
6269         }
6270         g_string_free(text, true);
6271 
6272         return checksum & 0xffff;
6273 }
6274 #endif /* VTE_DEBUG */
6275 
6276 /*
6277  * Compares the visual attributes of a VteCellAttr for equality, but ignores
6278  * attributes that tend to change from character to character or are otherwise
6279  * strange (in particular: fragment, columns).
6280  */
6281 // FIXMEchpe: make VteCellAttr a class with operator==
6282 static bool
vte_terminal_cellattr_equal(VteCellAttr const * attr1,VteCellAttr const * attr2)6283 vte_terminal_cellattr_equal(VteCellAttr const* attr1,
6284                             VteCellAttr const* attr2)
6285 {
6286         //FIXMEchpe why exclude DIM here?
6287 	return (((attr1->attr ^ attr2->attr) & VTE_ATTR_ALL_MASK) == 0 &&
6288                 attr1->colors()       == attr2->colors()   &&
6289                 attr1->hyperlink_idx  == attr2->hyperlink_idx);
6290 }
6291 
6292 /*
6293  * Wraps a given string according to the VteCellAttr in HTML tags. Used
6294  * old-style HTML (and not CSS) for better compatibility with, for example,
6295  * evolution's mail editor component.
6296  */
6297 char *
cellattr_to_html(VteCellAttr const * attr,char const * text) const6298 Terminal::cellattr_to_html(VteCellAttr const* attr,
6299                                      char const* text) const
6300 {
6301 	GString *string;
6302         guint fore, back, deco;
6303 
6304 	string = g_string_new(text);
6305 
6306         determine_colors(attr, false, false, &fore, &back, &deco);
6307 
6308 	if (attr->bold()) {
6309 		g_string_prepend(string, "<b>");
6310 		g_string_append(string, "</b>");
6311 	}
6312 	if (attr->italic()) {
6313 		g_string_prepend(string, "<i>");
6314 		g_string_append(string, "</i>");
6315 	}
6316         /* <u> should be inside <font> so that it inherits its color by default */
6317         if (attr->underline() != 0) {
6318                 static const char styles[][7] = {"", "single", "double", "wavy"};
6319                 char *tag, *colorattr;
6320 
6321                 if (deco != VTE_DEFAULT_FG) {
6322                         vte::color::rgb color;
6323 
6324                         rgb_from_index<4, 5, 4>(deco, color);
6325                         colorattr = g_strdup_printf(";text-decoration-color:#%02X%02X%02X",
6326                                                     color.red >> 8,
6327                                                     color.green >> 8,
6328                                                     color.blue >> 8);
6329                 } else {
6330                         colorattr = g_strdup("");
6331                 }
6332 
6333                 tag = g_strdup_printf("<u style=\"text-decoration-style:%s%s\">",
6334                                       styles[attr->underline()],
6335                                       colorattr);
6336                 g_string_prepend(string, tag);
6337                 g_free(tag);
6338                 g_free(colorattr);
6339                 g_string_append(string, "</u>");
6340         }
6341 	if (fore != VTE_DEFAULT_FG || attr->reverse()) {
6342 		vte::color::rgb color;
6343                 char *tag;
6344 
6345                 rgb_from_index<8, 8, 8>(fore, color);
6346 		tag = g_strdup_printf("<font color=\"#%02X%02X%02X\">",
6347                                       color.red >> 8,
6348                                       color.green >> 8,
6349                                       color.blue >> 8);
6350 		g_string_prepend(string, tag);
6351 		g_free(tag);
6352 		g_string_append(string, "</font>");
6353 	}
6354 	if (back != VTE_DEFAULT_BG || attr->reverse()) {
6355 		vte::color::rgb color;
6356                 char *tag;
6357 
6358                 rgb_from_index<8, 8, 8>(back, color);
6359 		tag = g_strdup_printf("<span style=\"background-color:#%02X%02X%02X\">",
6360                                       color.red >> 8,
6361                                       color.green >> 8,
6362                                       color.blue >> 8);
6363 		g_string_prepend(string, tag);
6364 		g_free(tag);
6365 		g_string_append(string, "</span>");
6366 	}
6367 	if (attr->strikethrough()) {
6368 		g_string_prepend(string, "<strike>");
6369 		g_string_append(string, "</strike>");
6370 	}
6371 	if (attr->overline()) {
6372 		g_string_prepend(string, "<span style=\"text-decoration-line:overline\">");
6373 		g_string_append(string, "</span>");
6374 	}
6375 	if (attr->blink()) {
6376 		g_string_prepend(string, "<blink>");
6377 		g_string_append(string, "</blink>");
6378 	}
6379 	/* reverse and invisible are not supported */
6380 
6381 	return g_string_free(string, FALSE);
6382 }
6383 
6384 /*
6385  * Similar to find_charcell(), but takes a VteCharAttribute for
6386  * indexing and returns the VteCellAttr.
6387  */
6388 VteCellAttr const*
char_to_cell_attr(VteCharAttributes const * attr) const6389 Terminal::char_to_cell_attr(VteCharAttributes const* attr) const
6390 {
6391 	VteCell const* cell = find_charcell(attr->column, attr->row);
6392 	if (cell)
6393 		return &cell->attr;
6394 	return nullptr;
6395 }
6396 
6397 /*
6398  * Terminal::attributes_to_html:
6399  * @text: A string as returned by the vte_terminal_get_* family of functions.
6400  * @attrs: (array) (element-type Vte.CharAttributes): text attributes, as created by vte_terminal_get_*
6401  *
6402  * Marks the given text up according to the given attributes, using HTML <span>
6403  * commands, and wraps the string in a <pre> element. The attributes have to be
6404  * "fresh" in the sense that the terminal must not have changed since they were
6405  * obtained using the vte_terminal_get* function.
6406  *
6407  * Returns: (transfer full): a newly allocated text string, or %NULL.
6408  */
6409 GString*
attributes_to_html(GString * text_string,GArray * attrs)6410 Terminal::attributes_to_html(GString* text_string,
6411                                        GArray* attrs)
6412 {
6413 	GString *string;
6414 	guint from,to;
6415 	const VteCellAttr *attr;
6416 	char *escaped, *marked;
6417 
6418         char const* text = text_string->str;
6419         auto len = text_string->len;
6420         g_assert_cmpuint(len, ==, attrs->len);
6421 
6422 	/* Initial size fits perfectly if the text has no attributes and no
6423 	 * characters that need to be escaped
6424          */
6425 	string = g_string_sized_new (len + 11);
6426 
6427 	g_string_append(string, "<pre>");
6428 	/* Find streches with equal attributes. Newlines are treated specially,
6429 	 * so that the <span> do not cover multiple lines.
6430          */
6431 	from = to = 0;
6432 	while (text[from] != '\0') {
6433 		g_assert(from == to);
6434 		if (text[from] == '\n') {
6435 			g_string_append_c(string, '\n');
6436 			from = ++to;
6437 		} else {
6438 			attr = char_to_cell_attr(
6439 				&g_array_index(attrs, VteCharAttributes, from));
6440 			while (text[to] != '\0' && text[to] != '\n' &&
6441 			       vte_terminal_cellattr_equal(attr,
6442                                                            char_to_cell_attr(
6443 						&g_array_index(attrs, VteCharAttributes, to))))
6444 			{
6445 				to++;
6446 			}
6447 			escaped = g_markup_escape_text(text + from, to - from);
6448 			marked = cellattr_to_html(attr, escaped);
6449 			g_string_append(string, marked);
6450 			g_free(escaped);
6451 			g_free(marked);
6452 			from = to;
6453 		}
6454 	}
6455 	g_string_append(string, "</pre>");
6456 
6457 	return string;
6458 }
6459 
6460 /* Place the selected text onto the clipboard.  Do this asynchronously so that
6461  * we get notified when the selection we placed on the clipboard is replaced. */
6462 void
widget_copy(vte::platform::ClipboardType type,vte::platform::ClipboardFormat format)6463 Terminal::widget_copy(vte::platform::ClipboardType type,
6464                       vte::platform::ClipboardFormat format)
6465 {
6466         /* Only put HTML on the CLIPBOARD, not PRIMARY */
6467         assert(type == vte::platform::ClipboardType::CLIPBOARD ||
6468                format == vte::platform::ClipboardFormat::TEXT);
6469 
6470 	/* Chuck old selected text and retrieve the newly-selected text. */
6471         GArray *attributes = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
6472         auto selection = get_selected_text(attributes);
6473 
6474         auto const sel = vte::to_integral(type);
6475         if (m_selection[sel]) {
6476                 g_string_free(m_selection[sel], TRUE);
6477                 m_selection[sel] = nullptr;
6478         }
6479 
6480         if (selection == nullptr) {
6481                 g_array_free(attributes, TRUE);
6482                 m_selection_owned[sel] = false;
6483                 return;
6484         }
6485 
6486         if (format == vte::platform::ClipboardFormat::HTML) {
6487                 m_selection[sel] = attributes_to_html(selection, attributes);
6488                 g_string_free(selection, TRUE);
6489         } else {
6490                 m_selection[sel] = selection;
6491         }
6492 
6493 	g_array_free (attributes, TRUE);
6494 
6495 	/* Place the text on the clipboard. */
6496         _vte_debug_print(VTE_DEBUG_SELECTION,
6497                          "Assuming ownership of selection.\n");
6498 
6499         m_selection_owned[sel] = true;
6500         m_selection_format[sel] = format;
6501 
6502         m_changing_selection = true;
6503         widget()->clipboard_offer_data(type, format);
6504         m_changing_selection = false;
6505 }
6506 
6507 /* Confine coordinates into the visible area. Padding is already subtracted. */
6508 void
confine_coordinates(long * xp,long * yp)6509 Terminal::confine_coordinates(long *xp,
6510                                         long *yp)
6511 {
6512 	long x = *xp;
6513 	long y = *yp;
6514         long y_stop;
6515 
6516         /* Allow to use the bottom extra padding only if there's content there. */
6517         y_stop = MIN(m_view_usable_extents.height(),
6518                      row_to_pixel(m_screen->insert_delta + m_row_count));
6519 
6520 	if (y < 0) {
6521 		y = 0;
6522 		if (!m_selection_block_mode)
6523 			x = 0;
6524         } else if (y >= y_stop) {
6525                 y = y_stop - 1;
6526 		if (!m_selection_block_mode)
6527 			x = m_column_count * m_cell_width - 1;
6528 	}
6529 	if (x < 0) {
6530 		x = 0;
6531 	} else if (x >= m_column_count * m_cell_width) {
6532 		x = m_column_count * m_cell_width - 1;
6533 	}
6534 
6535 	*xp = x;
6536 	*yp = y;
6537 }
6538 
6539 /* Start selection at the location of the event.
6540  * In case of regular selection, this is called with the original click's location
6541  * once the mouse has moved by the gtk drag threshold.
6542  */
6543 void
start_selection(vte::view::coords const & pos,SelectionType type)6544 Terminal::start_selection (vte::view::coords const& pos,
6545                            SelectionType type)
6546 {
6547 	if (m_selection_block_mode)
6548 		type = SelectionType::eCHAR;
6549 
6550         /* Need to ensure the ringview is updated. */
6551         ringview_update();
6552 
6553         m_selection_origin = m_selection_last = selection_grid_halfcoords_from_view_coords(pos);
6554 
6555 	/* Record the selection type. */
6556 	m_selection_type = type;
6557 	m_selecting = TRUE;
6558         m_selecting_had_delta = false;  /* resolve_selection() below will most likely flip it to true. */
6559         m_will_select_after_threshold = false;
6560 
6561 	_vte_debug_print(VTE_DEBUG_SELECTION,
6562                          "Selection started at %s.\n",
6563                          m_selection_origin.to_string());
6564 
6565         /* Take care of updating the display. */
6566         resolve_selection();
6567 
6568 	/* Temporarily stop caring about input from the child. */
6569 	disconnect_pty_read();
6570 }
6571 
6572 bool
maybe_end_selection()6573 Terminal::maybe_end_selection()
6574 {
6575 	if (m_selecting) {
6576 		/* Copy only if something was selected. */
6577                 if (!m_selection_resolved.empty() &&
6578 		    m_selecting_had_delta) {
6579                         widget_copy(vte::platform::ClipboardType::PRIMARY,
6580                                     vte::platform::ClipboardFormat::TEXT);
6581 			emit_selection_changed();
6582 		}
6583                 stop_autoscroll();  /* Required before setting m_selecting to false, see #105. */
6584 		m_selecting = false;
6585 
6586 		/* Reconnect to input from the child if we paused it. */
6587 		connect_pty_read();
6588 
6589 		return true;
6590 	}
6591 
6592         if (m_will_select_after_threshold)
6593                 return true;
6594 
6595         return false;
6596 }
6597 
6598 /*
6599  * Terminal::select_all:
6600  *
6601  * Selects all text within the terminal. Note that we only select the writable
6602  * region, *not* the scrollback buffer, due to this potentially selecting so
6603  * much data that putting it on the clipboard either hangs the process for a long
6604  * time or even crash it directly. (FIXME!)
6605  */
6606 void
select_all()6607 Terminal::select_all()
6608 {
6609 	deselect_all();
6610 
6611 	m_selecting_had_delta = TRUE;
6612 
6613         m_selection_resolved.set({m_screen->insert_delta, 0},
6614                                  {_vte_ring_next(m_screen->row_data), 0});
6615 
6616 	_vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n");
6617 
6618         widget_copy(vte::platform::ClipboardType::PRIMARY,
6619                     vte::platform::ClipboardFormat::TEXT);
6620 	emit_selection_changed();
6621 
6622 	invalidate_all();
6623 }
6624 
6625 bool
mouse_autoscroll_timer_callback()6626 Terminal::mouse_autoscroll_timer_callback()
6627 {
6628 	bool extend = false;
6629 	long x, y, xmax, ymax;
6630 	glong adj;
6631 
6632         auto again = bool{true};
6633 
6634 	/* Provide an immediate effect for mouse wigglers. */
6635 	if (m_mouse_last_position.y < 0) {
6636 		if (m_vadjustment) {
6637 			/* Try to scroll up by one line. */
6638 			adj = m_screen->scroll_delta - 1;
6639 			queue_adjustment_value_changed_clamped(adj);
6640 			extend = true;
6641 		}
6642 		_vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling down.\n");
6643 	}
6644 	if (m_mouse_last_position.y >= m_view_usable_extents.height()) {
6645 		if (m_vadjustment) {
6646 			/* Try to scroll up by one line. */
6647 			adj = m_screen->scroll_delta + 1;
6648 			queue_adjustment_value_changed_clamped(adj);
6649 			extend = true;
6650 		}
6651 		_vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling up.\n");
6652 	}
6653 	if (extend) {
6654                 // FIXMEchpe use confine_view_coords here
6655 		/* Don't select off-screen areas.  That just confuses people. */
6656 		xmax = m_column_count * m_cell_width;
6657 		ymax = m_row_count * m_cell_height;
6658 
6659 		x = CLAMP(m_mouse_last_position.x, 0, xmax);
6660 		y = CLAMP(m_mouse_last_position.y, 0, ymax);
6661 		/* If we clamped the Y, mess with the X to get the entire
6662 		 * lines. */
6663 		if (m_mouse_last_position.y < 0 && !m_selection_block_mode) {
6664 			x = 0;
6665 		}
6666 		if (m_mouse_last_position.y >= ymax && !m_selection_block_mode) {
6667 			x = m_column_count * m_cell_width;
6668 		}
6669 		/* Extend selection to cover the newly-scrolled area. */
6670                 modify_selection(vte::view::coords(x, y));
6671 	} else {
6672 		/* Stop autoscrolling. */
6673                 again = false;
6674 	}
6675 	return again;
6676 }
6677 
6678 /* Start autoscroll. */
6679 void
start_autoscroll()6680 Terminal::start_autoscroll()
6681 {
6682 	if (m_mouse_autoscroll_timer)
6683                 return;
6684 
6685         m_mouse_autoscroll_timer.schedule(666 / m_row_count, // FIXME WTF?
6686                                           vte::glib::Timer::Priority::eLOW);
6687 }
6688 
6689 bool
widget_mouse_motion(vte::platform::MouseEvent const & event)6690 Terminal::widget_mouse_motion(vte::platform::MouseEvent const& event)
6691 {
6692         /* Need to ensure the ringview is updated. */
6693         ringview_update();
6694 
6695         auto pos = view_coords_from_event(event);
6696         auto rowcol = grid_coords_from_view_coords(pos);
6697 
6698 	_vte_debug_print(VTE_DEBUG_EVENTS,
6699                          "Motion notify %s %s\n",
6700                          pos.to_string(), rowcol.to_string());
6701 
6702         m_modifiers = event.modifiers();
6703 
6704         if (m_will_select_after_threshold) {
6705                 if (!gtk_drag_check_threshold(m_widget,
6706                                               m_mouse_last_position.x,
6707                                               m_mouse_last_position.y,
6708                                               pos.x, pos.y))
6709                         return true;
6710 
6711                 start_selection(vte::view::coords(m_mouse_last_position.x, m_mouse_last_position.y),
6712                                 SelectionType::eCHAR);
6713         }
6714 
6715         auto handled = bool{false};
6716         if (m_selecting &&
6717             (m_mouse_handled_buttons & 1) != 0) {
6718                 _vte_debug_print(VTE_DEBUG_EVENTS, "Mousing drag 1.\n");
6719                 modify_selection(pos);
6720 
6721                 /* Start scrolling if we need to. */
6722                 if (pos.y < 0 || pos.y >= m_view_usable_extents.height()) {
6723                         /* Give mouse wigglers something. */
6724                         stop_autoscroll();
6725                         mouse_autoscroll_timer_callback();
6726                         start_autoscroll();
6727                 }
6728 
6729                 handled = true;
6730         }
6731 
6732         if (!handled && m_input_enabled)
6733                 maybe_send_mouse_drag(rowcol, event);
6734 
6735         if (pos != m_mouse_last_position) {
6736                 m_mouse_last_position = pos;
6737 
6738                 set_pointer_autohidden(false);
6739                 hyperlink_hilite_update();
6740                 match_hilite_update();
6741         }
6742 
6743 	return handled;
6744 }
6745 
6746 bool
widget_mouse_press(vte::platform::MouseEvent const & event)6747 Terminal::widget_mouse_press(vte::platform::MouseEvent const& event)
6748 {
6749 	bool handled = false;
6750 	gboolean start_selecting = FALSE, extend_selecting = FALSE;
6751 
6752         /* Need to ensure the ringview is updated. */
6753         ringview_update();
6754 
6755         auto pos = view_coords_from_event(event);
6756         auto rowcol = grid_coords_from_view_coords(pos);
6757 
6758         m_modifiers = event.modifiers();
6759 
6760         switch (event.press_count()) {
6761         case 1: /* single click */
6762 		_vte_debug_print(VTE_DEBUG_EVENTS,
6763                                  "Button %d single-click at %s\n",
6764                                  event.button_value(),
6765                                  rowcol.to_string());
6766 		/* Handle this event ourselves. */
6767                 switch (event.button()) {
6768                 case vte::platform::MouseEvent::Button::eLEFT:
6769 			_vte_debug_print(VTE_DEBUG_EVENTS,
6770 					"Handling click ourselves.\n");
6771 			/* Grab focus. */
6772 			if (!m_has_focus)
6773                                 widget()->grab_focus();
6774 
6775 			/* If we're in event mode, and the user held down the
6776 			 * shift key, we start selecting. */
6777 			if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
6778 				if (m_modifiers & GDK_SHIFT_MASK) {
6779 					start_selecting = TRUE;
6780 				}
6781 			} else {
6782 				/* If the user hit shift, then extend the
6783 				 * selection instead. */
6784 				if ((m_modifiers & GDK_SHIFT_MASK) &&
6785                                     !m_selection_resolved.empty()) {
6786 					extend_selecting = TRUE;
6787 				} else {
6788 					start_selecting = TRUE;
6789 				}
6790 			}
6791 			if (start_selecting) {
6792 				deselect_all();
6793                                 m_will_select_after_threshold = true;
6794                                 m_selection_block_mode = !!(m_modifiers & GDK_CONTROL_MASK);
6795 				handled = true;
6796 			}
6797 			if (extend_selecting) {
6798 				/* The whole selection code needs to be
6799 				 * rewritten.  For now, put this here to
6800 				 * fix bug 614658 */
6801 				m_selecting = TRUE;
6802                                 selection_maybe_swap_endpoints (pos);
6803                                 modify_selection(pos);
6804 				handled = true;
6805 			}
6806 			break;
6807 		/* Paste if the user pressed shift or we're not sending events
6808 		 * to the app. */
6809                 case vte::platform::MouseEvent::Button::eMIDDLE:
6810 			if ((m_modifiers & GDK_SHIFT_MASK) ||
6811 			    m_mouse_tracking_mode == MouseTrackingMode::eNONE) {
6812                                 if (widget()->primary_paste_enabled()) {
6813                                         widget()->clipboard_request_text(vte::platform::ClipboardType::PRIMARY);
6814                                         handled = true;
6815                                 }
6816 			}
6817 			break;
6818                 case vte::platform::MouseEvent::Button::eRIGHT:
6819 		default:
6820 			break;
6821 		}
6822                 if (event.button_value() >= 1 && event.button_value() <= 3) {
6823                         if (handled)
6824                                 m_mouse_handled_buttons |= (1 << (event.button_value() - 1));
6825                         else
6826                                 m_mouse_handled_buttons &= ~(1 << (event.button_value() - 1));
6827                 }
6828 		/* If we haven't done anything yet, try sending the mouse
6829 		 * event to the app. */
6830 		if (handled == FALSE) {
6831                         handled = maybe_send_mouse_button(rowcol, event);
6832 		}
6833 		break;
6834         case 2: /* double click */
6835 		_vte_debug_print(VTE_DEBUG_EVENTS,
6836                                  "Button %d double-click at %s\n",
6837                                  event.button_value(),
6838                                  rowcol.to_string());
6839                 switch (event.button()) {
6840                 case vte::platform::MouseEvent::Button::eLEFT:
6841                         if (m_will_select_after_threshold) {
6842                                 start_selection(pos,
6843                                                 SelectionType::eCHAR);
6844 				handled = true;
6845 			}
6846                         if ((m_mouse_handled_buttons & 1) != 0) {
6847                                 start_selection(pos,
6848                                                 SelectionType::eWORD);
6849 				handled = true;
6850 			}
6851 			break;
6852                 case vte::platform::MouseEvent::Button::eMIDDLE:
6853                 case vte::platform::MouseEvent::Button::eRIGHT:
6854 		default:
6855 			break;
6856 		}
6857 		break;
6858         case 3: /* triple click */
6859 		_vte_debug_print(VTE_DEBUG_EVENTS,
6860                                  "Button %d triple-click at %s\n",
6861                                  event.button_value(),
6862                                  rowcol.to_string());
6863                 switch (event.button()) {
6864                 case vte::platform::MouseEvent::Button::eLEFT:
6865                         if ((m_mouse_handled_buttons & 1) != 0) {
6866                                 start_selection(pos,
6867                                                 SelectionType::eLINE);
6868 				handled = true;
6869 			}
6870 			break;
6871                 case vte::platform::MouseEvent::Button::eMIDDLE:
6872                 case vte::platform::MouseEvent::Button::eRIGHT:
6873 		default:
6874 			break;
6875 		}
6876 	default:
6877 		break;
6878 	}
6879 
6880 	/* Save the pointer state for later use. */
6881         if (event.button_value() >= 1 && event.button_value() <= 3)
6882                 m_mouse_pressed_buttons |= (1 << (event.button_value() - 1));
6883 
6884 	m_mouse_last_position = pos;
6885 
6886         set_pointer_autohidden(false);
6887         hyperlink_hilite_update();
6888         match_hilite_update();
6889 
6890 	return handled;
6891 }
6892 
6893 bool
widget_mouse_release(vte::platform::MouseEvent const & event)6894 Terminal::widget_mouse_release(vte::platform::MouseEvent const& event)
6895 {
6896 	bool handled = false;
6897 
6898         /* Need to ensure the ringview is updated. */
6899         ringview_update();
6900 
6901         auto pos = view_coords_from_event(event);
6902         auto rowcol = grid_coords_from_view_coords(pos);
6903 
6904 	stop_autoscroll();
6905 
6906         m_modifiers = event.modifiers();
6907 
6908         switch (event.type()) {
6909         case vte::platform::EventBase::Type::eMOUSE_RELEASE:
6910 		_vte_debug_print(VTE_DEBUG_EVENTS,
6911                                  "Button %d released at %s\n",
6912                                  event.button_value(), rowcol.to_string());
6913                 switch (event.button()) {
6914                 case vte::platform::MouseEvent::Button::eLEFT:
6915                         if ((m_mouse_handled_buttons & 1) != 0)
6916                                 handled = maybe_end_selection();
6917 			break;
6918                 case vte::platform::MouseEvent::Button::eMIDDLE:
6919                         handled = (m_mouse_handled_buttons & 2) != 0;
6920                         m_mouse_handled_buttons &= ~2;
6921 			break;
6922                 case vte::platform::MouseEvent::Button::eRIGHT:
6923 		default:
6924 			break;
6925 		}
6926 		if (!handled && m_input_enabled) {
6927                         handled = maybe_send_mouse_button(rowcol, event);
6928 		}
6929 		break;
6930 	default:
6931 		break;
6932 	}
6933 
6934 	/* Save the pointer state for later use. */
6935         if (event.button_value() >= 1 && event.button_value() <= 3)
6936                 m_mouse_pressed_buttons &= ~(1 << (event.button_value() - 1));
6937 
6938 	m_mouse_last_position = pos;
6939         m_will_select_after_threshold = false;
6940 
6941         set_pointer_autohidden(false);
6942         hyperlink_hilite_update();
6943         match_hilite_update();
6944 
6945 	return handled;
6946 }
6947 
6948 void
widget_focus_in()6949 Terminal::widget_focus_in()
6950 {
6951 	_vte_debug_print(VTE_DEBUG_EVENTS, "Focus in.\n");
6952 
6953         m_has_focus = true;
6954         widget()->grab_focus();
6955 
6956 	/* We only have an IM context when we're realized, and there's not much
6957 	 * point to painting the cursor if we don't have a window. */
6958 	if (widget_realized()) {
6959 		m_cursor_blink_state = TRUE;
6960 
6961                 /* If blinking gets enabled now, do a full repaint.
6962                  * If blinking gets disabled, only repaint if there's blinking stuff present
6963                  * (we could further optimize by checking its current phase). */
6964                 if (m_text_blink_mode == TextBlinkMode::eFOCUSED ||
6965                     (m_text_blink_mode == TextBlinkMode::eUNFOCUSED && m_text_blink_timer)) {
6966                         invalidate_all();
6967                 }
6968 
6969 		check_cursor_blink();
6970 
6971                 m_real_widget->im_focus_in();
6972 		invalidate_cursor_once();
6973                 maybe_feed_focus_event(true);
6974 	}
6975 }
6976 
6977 void
widget_focus_out()6978 Terminal::widget_focus_out()
6979 {
6980 	_vte_debug_print(VTE_DEBUG_EVENTS, "Focus out.\n");
6981 
6982 	/* We only have an IM context when we're realized, and there's not much
6983 	 * point to painting ourselves if we don't have a window. */
6984 	if (widget_realized()) {
6985                 maybe_feed_focus_event(false);
6986 
6987 		maybe_end_selection();
6988 
6989                 /* If blinking gets enabled now, do a full repaint.
6990                  * If blinking gets disabled, only repaint if there's blinking stuff present
6991                  * (we could further optimize by checking its current phase). */
6992                 if (m_text_blink_mode == TextBlinkMode::eUNFOCUSED ||
6993                     (m_text_blink_mode == TextBlinkMode::eFOCUSED && m_text_blink_timer)) {
6994                         invalidate_all();
6995                 }
6996 
6997                 m_real_widget->im_focus_out();
6998 		invalidate_cursor_once();
6999 
7000                 m_mouse_pressed_buttons = 0;
7001                 m_mouse_handled_buttons = 0;
7002 	}
7003 
7004 	m_has_focus = false;
7005 	check_cursor_blink();
7006 }
7007 
7008 void
widget_mouse_enter(vte::platform::MouseEvent const & event)7009 Terminal::widget_mouse_enter(vte::platform::MouseEvent const& event)
7010 {
7011         auto pos = view_coords_from_event(event);
7012 
7013         // FIXMEchpe read event modifiers here
7014 
7015 	_vte_debug_print(VTE_DEBUG_EVENTS, "Enter at %s\n", pos.to_string());
7016 
7017         m_mouse_cursor_over_widget = TRUE;
7018         m_mouse_last_position = pos;
7019 
7020         set_pointer_autohidden(false);
7021         hyperlink_hilite_update();
7022         match_hilite_update();
7023         apply_mouse_cursor();
7024 }
7025 
7026 void
widget_mouse_leave(vte::platform::MouseEvent const & event)7027 Terminal::widget_mouse_leave(vte::platform::MouseEvent const& event)
7028 {
7029         auto pos = view_coords_from_event(event);
7030 
7031         // FIXMEchpe read event modifiers here
7032 
7033 	_vte_debug_print(VTE_DEBUG_EVENTS, "Leave at %s\n", pos.to_string());
7034 
7035         m_mouse_cursor_over_widget = FALSE;
7036         m_mouse_last_position = pos;
7037 
7038         hyperlink_hilite_update();
7039         match_hilite_update();
7040         apply_mouse_cursor();
7041 }
7042 
7043 /* Apply the changed metrics, and queue a resize if need be.
7044  *
7045  * The cell's height consists of 4 parts, from top to bottom:
7046  * - char_spacing.top: half of the extra line spacing,
7047  * - char_ascent: the font's ascent,
7048  * - char_descent: the font's descent,
7049  * - char_spacing.bottom: the other half of the extra line spacing.
7050  * Extra line spacing is typically 0, beef up cell_height_scale to get actual pixels
7051  * here. Similarly, increase cell_width_scale to get nonzero char_spacing.{left,right}.
7052  */
7053 void
apply_font_metrics(int cell_width_unscaled,int cell_height_unscaled,int cell_width,int cell_height,int char_ascent,int char_descent,GtkBorder char_spacing)7054 Terminal::apply_font_metrics(int cell_width_unscaled,
7055                                        int cell_height_unscaled,
7056                                        int cell_width,
7057                                        int cell_height,
7058                                        int char_ascent,
7059                                        int char_descent,
7060                                        GtkBorder char_spacing)
7061 {
7062         int char_height;
7063 	bool resize = false, cresize = false;
7064 
7065 	/* Sanity check for broken font changes. */
7066         cell_width_unscaled = MAX(cell_width_unscaled, 1);
7067         cell_height_unscaled = MAX(cell_height_unscaled, 2);
7068         cell_width = MAX(cell_width, 1);
7069         cell_height = MAX(cell_height, 2);
7070         char_ascent = MAX(char_ascent, 1);
7071         char_descent = MAX(char_descent, 1);
7072 
7073         /* For convenience only. */
7074         char_height = char_ascent + char_descent;
7075 
7076 	/* Change settings, and keep track of when we've changed anything. */
7077         if (cell_width_unscaled != m_cell_width_unscaled) {
7078                 cresize = true;
7079                 m_cell_width_unscaled = cell_width_unscaled;
7080 	}
7081         if (cell_height_unscaled != m_cell_height_unscaled) {
7082                 cresize = true;
7083                 m_cell_height_unscaled = cell_height_unscaled;
7084 	}
7085         if (cell_width != m_cell_width) {
7086 		resize = cresize = true;
7087                 m_cell_width = cell_width;
7088 	}
7089         if (cell_height != m_cell_height) {
7090 		resize = cresize = true;
7091                 m_cell_height = cell_height;
7092 	}
7093         if (char_ascent != m_char_ascent) {
7094 		resize = true;
7095                 m_char_ascent = char_ascent;
7096 	}
7097         if (char_descent != m_char_descent) {
7098 		resize = true;
7099                 m_char_descent = char_descent;
7100 	}
7101         if (memcmp(&char_spacing, &m_char_padding, sizeof(GtkBorder)) != 0) {
7102                 resize = true;
7103                 m_char_padding = char_spacing;
7104         }
7105         m_line_thickness = MAX (MIN (char_descent / 2, char_height / 14), 1);
7106         /* FIXME take these from pango_font_metrics_get_{underline,strikethrough}_{position,thickness} */
7107         m_underline_thickness = m_line_thickness;
7108         m_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - m_underline_thickness);
7109         m_double_underline_thickness = m_line_thickness;
7110         /* FIXME make sure this doesn't reach the baseline (switch to thinner lines, or one thicker line in that case) */
7111         m_double_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - 3 * m_double_underline_thickness);
7112         m_undercurl_thickness = m_line_thickness;
7113         m_undercurl_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - _vte_draw_get_undercurl_height(cell_width, m_undercurl_thickness));
7114         m_strikethrough_thickness = m_line_thickness;
7115         m_strikethrough_position = char_spacing.top + char_ascent - char_height / 4;
7116         m_overline_thickness = m_line_thickness;
7117         m_overline_position = char_spacing.top;  /* FIXME */
7118         m_regex_underline_thickness = 1;  /* FIXME */
7119         m_regex_underline_position = char_spacing.top + char_height - m_regex_underline_thickness;  /* FIXME */
7120 
7121 	/* Queue a resize if anything's changed. */
7122 	if (resize) {
7123 		if (widget_realized()) {
7124 			gtk_widget_queue_resize_no_redraw(m_widget);
7125 		}
7126 	}
7127 	/* Emit a signal that the font changed. */
7128 	if (cresize) {
7129                 if (pty()) {
7130                         /* Update pixel size of PTY. */
7131                         pty()->set_size(m_row_count,
7132                                         m_column_count,
7133                                         m_cell_height_unscaled,
7134                                         m_cell_width_unscaled);
7135                 }
7136 		emit_char_size_changed(m_cell_width, m_cell_height);
7137 	}
7138 	/* Repaint. */
7139 	invalidate_all();
7140 }
7141 
7142 void
ensure_font()7143 Terminal::ensure_font()
7144 {
7145 	{
7146 		/* Load default fonts, if no fonts have been loaded. */
7147 		if (!m_has_fonts) {
7148                         update_font_desc();
7149 		}
7150 		if (m_fontdirty) {
7151                         int cell_width_unscaled, cell_height_unscaled;
7152                         int cell_width, cell_height;
7153                         int char_ascent, char_descent;
7154                         GtkBorder char_spacing;
7155 			m_fontdirty = false;
7156 
7157                         if (!_vte_double_equal(m_font_scale, 1.)) {
7158                                 m_draw.set_text_font(
7159                                                      m_widget,
7160                                                      m_unscaled_font_desc.get(),
7161                                                      m_cell_width_scale,
7162                                                      m_cell_height_scale);
7163                                 m_draw.get_text_metrics(
7164                                                         &cell_width_unscaled, &cell_height_unscaled,
7165                                                         nullptr, nullptr, nullptr);
7166                         }
7167 
7168 			m_draw.set_text_font(
7169                                                  m_widget,
7170                                                  m_fontdesc.get(),
7171                                                  m_cell_width_scale,
7172                                                  m_cell_height_scale);
7173 			m_draw.get_text_metrics(
7174                                                     &cell_width, &cell_height,
7175                                                     &char_ascent, &char_descent,
7176                                                     &char_spacing);
7177 
7178                         if (_vte_double_equal(m_font_scale, 1.)) {
7179                                 cell_width_unscaled = cell_width;
7180                                 cell_height_unscaled = cell_height;
7181                         }
7182 
7183                         apply_font_metrics(cell_width_unscaled, cell_height_unscaled,
7184                                            cell_width, cell_height,
7185                                            char_ascent, char_descent,
7186                                            char_spacing);
7187 		}
7188 	}
7189 }
7190 
7191 void
update_font()7192 Terminal::update_font()
7193 {
7194         /* We'll get called again later */
7195         if (!m_unscaled_font_desc)
7196                 return;
7197 
7198         auto desc = vte::take_freeable(pango_font_description_copy(m_unscaled_font_desc.get()));
7199 
7200         double size = pango_font_description_get_size(desc.get());
7201         if (pango_font_description_get_size_is_absolute(desc.get())) {
7202                 pango_font_description_set_absolute_size(desc.get(), m_font_scale * size);
7203         } else {
7204                 pango_font_description_set_size(desc.get(), m_font_scale * size);
7205         }
7206 
7207         m_fontdesc = std::move(desc);
7208         m_fontdirty = true;
7209         m_has_fonts = true;
7210 
7211         /* Set the drawing font. */
7212         if (widget_realized()) {
7213                 ensure_font();
7214         }
7215 }
7216 
7217 /*
7218  * Terminal::set_font_desc:
7219  * @font_desc: (allow-none): a #PangoFontDescription for the desired font, or %nullptr
7220  *
7221  * Sets the font used for rendering all text displayed by the terminal,
7222  * overriding any fonts set using gtk_widget_modify_font().  The terminal
7223  * will immediately attempt to load the desired font, retrieve its
7224  * metrics, and attempt to resize itself to keep the same number of rows
7225  * and columns.  The font scale is applied to the specified font.
7226  */
7227 bool
set_font_desc(vte::Freeable<PangoFontDescription> font_desc)7228 Terminal::set_font_desc(vte::Freeable<PangoFontDescription> font_desc)
7229 {
7230         m_api_font_desc = std::move(font_desc);
7231         return update_font_desc();
7232 }
7233 
7234 bool
update_font_desc()7235 Terminal::update_font_desc()
7236 {
7237         auto desc = vte::Freeable<PangoFontDescription>{};
7238 
7239         auto context = gtk_widget_get_style_context(m_widget);
7240         gtk_style_context_save(context);
7241         gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
7242         gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font",
7243                               &vte::get_freeable(desc),
7244                               nullptr);
7245         gtk_style_context_restore(context);
7246 
7247 	pango_font_description_set_family_static(desc.get(), "monospace");
7248 
7249 	if (m_api_font_desc) {
7250 		pango_font_description_merge(desc.get(), m_api_font_desc.get(), true);
7251 		_VTE_DEBUG_IF(VTE_DEBUG_MISC) {
7252 			if (desc) {
7253 				char *tmp;
7254 				tmp = pango_font_description_to_string(desc.get());
7255 				g_printerr("Using pango font \"%s\".\n", tmp);
7256 				g_free (tmp);
7257 			}
7258 		}
7259 	} else {
7260 		_vte_debug_print(VTE_DEBUG_MISC,
7261 				"Using default monospace font.\n");
7262 	}
7263 
7264         /* Sanitise the font description.
7265          *
7266          * Gravity makes no sense in vte.
7267          * Style needs to be default, and weight needs to allow bolding,
7268          * since those are set via SGR attributes.
7269          *
7270          * Allowing weight <= medium does not absolutely guarantee that bold
7271          * actually is bolder than the specified weight, but should be good enough
7272          * until we can check that the actually loaded bold font is weightier than
7273          * the normal font (FIXME!).
7274          *
7275          * As a special exception, allow any weight if bold-is-bright is enabled.
7276          */
7277         pango_font_description_unset_fields(desc.get(),
7278                                             PangoFontMask(PANGO_FONT_MASK_GRAVITY |
7279                                                           PANGO_FONT_MASK_STYLE));
7280         if ((pango_font_description_get_set_fields(desc.get()) & PANGO_FONT_MASK_WEIGHT) &&
7281             (pango_font_description_get_weight(desc.get()) > PANGO_WEIGHT_MEDIUM) &&
7282             !m_bold_is_bright) {
7283                 pango_font_description_set_weight(desc.get(),
7284                                                   std::min(pango_font_description_get_weight(desc.get()),
7285                                                            PANGO_WEIGHT_MEDIUM));
7286         }
7287 
7288         bool const same_desc = m_unscaled_font_desc &&
7289                 pango_font_description_equal(m_unscaled_font_desc.get(), desc.get());
7290 
7291 	/* Note that we proceed to recreating the font even if the description
7292 	 * are the same.  This is because maybe screen
7293 	 * font options were changed, or new fonts installed.  Those will be
7294 	 * detected at font creation time and respected.
7295 	 */
7296 
7297         m_unscaled_font_desc = std::move(desc);
7298         update_font();
7299 
7300         return !same_desc;
7301 }
7302 
7303 bool
set_font_scale(gdouble scale)7304 Terminal::set_font_scale(gdouble scale)
7305 {
7306         /* FIXME: compare old and new scale in pixel space */
7307         if (_vte_double_equal(scale, m_font_scale))
7308                 return false;
7309 
7310         m_font_scale = scale;
7311         update_font();
7312 
7313         return true;
7314 }
7315 
7316 bool
set_cell_width_scale(double scale)7317 Terminal::set_cell_width_scale(double scale)
7318 {
7319         /* FIXME: compare old and new scale in pixel space */
7320         if (_vte_double_equal(scale, m_cell_width_scale))
7321                 return false;
7322 
7323         m_cell_width_scale = scale;
7324         /* Set the drawing font. */
7325         m_fontdirty = true;
7326         if (widget_realized()) {
7327                 ensure_font();
7328         }
7329 
7330         return true;
7331 }
7332 
7333 bool
set_cell_height_scale(double scale)7334 Terminal::set_cell_height_scale(double scale)
7335 {
7336         /* FIXME: compare old and new scale in pixel space */
7337         if (_vte_double_equal(scale, m_cell_height_scale))
7338                 return false;
7339 
7340         m_cell_height_scale = scale;
7341         /* Set the drawing font. */
7342         m_fontdirty = true;
7343         if (widget_realized()) {
7344                 ensure_font();
7345         }
7346 
7347         return true;
7348 }
7349 
7350 /* Read and refresh our perception of the size of the PTY. */
7351 void
refresh_size()7352 Terminal::refresh_size()
7353 {
7354         if (!pty())
7355                 return;
7356 
7357 	int rows, columns;
7358         if (!pty()->get_size(&rows, &columns)) {
7359                 /* Error reading PTY size, use defaults */
7360                 rows = VTE_ROWS;
7361                 columns = VTE_COLUMNS;
7362 	}
7363 
7364         if (m_row_count == rows &&
7365             m_column_count == columns)
7366                 return;
7367 
7368         m_row_count = rows;
7369         m_column_count = columns;
7370         m_tabstops.resize(columns);
7371 }
7372 
7373 /* Resize the given screen (normal or alternate) of the terminal. */
7374 void
screen_set_size(VteScreen * screen_,long old_columns,long old_rows,bool do_rewrap)7375 Terminal::screen_set_size(VteScreen *screen_,
7376                                     long old_columns,
7377                                     long old_rows,
7378                                     bool do_rewrap)
7379 {
7380 	VteRing *ring = screen_->row_data;
7381 	VteVisualPosition cursor_saved_absolute;
7382 	VteVisualPosition below_viewport;
7383 	VteVisualPosition below_current_paragraph;
7384         VteVisualPosition selection_start, selection_end;
7385 	VteVisualPosition *markers[7];
7386         gboolean was_scrolled_to_top = ((long) ceil(screen_->scroll_delta) == _vte_ring_delta(ring));
7387         gboolean was_scrolled_to_bottom = ((long) screen_->scroll_delta == screen_->insert_delta);
7388 	glong old_top_lines;
7389 	double new_scroll_delta;
7390 
7391         if (m_selection_block_mode && do_rewrap && old_columns != m_column_count)
7392                 deselect_all();
7393 
7394 	_vte_debug_print(VTE_DEBUG_RESIZE,
7395 			"Resizing %s screen_\n"
7396 			"Old  insert_delta=%ld  scroll_delta=%f\n"
7397                         "     cursor (absolute)  row=%ld  col=%ld\n"
7398 			"     cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n",
7399 			screen_ == &m_normal_screen ? "normal" : "alternate",
7400 			screen_->insert_delta, screen_->scroll_delta,
7401                         screen_->cursor.row, screen_->cursor.col,
7402                         screen_->saved.cursor.row, screen_->saved.cursor.col);
7403 
7404         cursor_saved_absolute.row = screen_->saved.cursor.row + screen_->insert_delta;
7405         cursor_saved_absolute.col = screen_->saved.cursor.col;
7406 	below_viewport.row = screen_->scroll_delta + old_rows;
7407 	below_viewport.col = 0;
7408         below_current_paragraph.row = screen_->cursor.row + 1;
7409 	while (below_current_paragraph.row < _vte_ring_next(ring)
7410 	    && _vte_ring_index(ring, below_current_paragraph.row - 1)->attr.soft_wrapped) {
7411 		below_current_paragraph.row++;
7412 	}
7413 	below_current_paragraph.col = 0;
7414         memset(&markers, 0, sizeof(markers));
7415         markers[0] = &cursor_saved_absolute;
7416         markers[1] = &below_viewport;
7417         markers[2] = &below_current_paragraph;
7418         markers[3] = &screen_->cursor;
7419         if (!m_selection_resolved.empty()) {
7420                 selection_start.row = m_selection_resolved.start_row();
7421                 selection_start.col = m_selection_resolved.start_column();
7422                 selection_end.row = m_selection_resolved.end_row();
7423                 selection_end.col = m_selection_resolved.end_column();
7424                 markers[4] = &selection_start;
7425                 markers[5] = &selection_end;
7426 	}
7427 
7428 	old_top_lines = below_current_paragraph.row - screen_->insert_delta;
7429 
7430 	if (do_rewrap && old_columns != m_column_count)
7431 		_vte_ring_rewrap(ring, m_column_count, markers);
7432 
7433 	if (_vte_ring_length(ring) > m_row_count) {
7434 		/* The content won't fit without scrollbars. Before figuring out the position, we might need to
7435 		   drop some lines from the ring if the cursor is not at the bottom, as XTerm does. See bug 708213.
7436 		   This code is really tricky, see ../doc/rewrap.txt for details! */
7437 		glong new_top_lines, drop1, drop2, drop3, drop;
7438 		screen_->insert_delta = _vte_ring_next(ring) - m_row_count;
7439 		new_top_lines = below_current_paragraph.row - screen_->insert_delta;
7440 		drop1 = _vte_ring_length(ring) - m_row_count;
7441 		drop2 = _vte_ring_next(ring) - below_current_paragraph.row;
7442 		drop3 = old_top_lines - new_top_lines;
7443 		drop = MIN(MIN(drop1, drop2), drop3);
7444 		if (drop > 0) {
7445 			int new_ring_next = screen_->insert_delta + m_row_count - drop;
7446 			_vte_debug_print(VTE_DEBUG_RESIZE,
7447 					"Dropping %ld [== MIN(%ld, %ld, %ld)] rows at the bottom\n",
7448 					drop, drop1, drop2, drop3);
7449 			_vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
7450 		}
7451 	}
7452 
7453         if (!m_selection_resolved.empty()) {
7454                 m_selection_resolved.set ({ selection_start.row, selection_start.col },
7455                                           { selection_end.row, selection_end.col });
7456 	}
7457 
7458 	/* Figure out new insert and scroll deltas */
7459 	if (_vte_ring_length(ring) <= m_row_count) {
7460 		/* Everything fits without scrollbars. Align at top. */
7461 		screen_->insert_delta = _vte_ring_delta(ring);
7462 		new_scroll_delta = screen_->insert_delta;
7463 		_vte_debug_print(VTE_DEBUG_RESIZE,
7464 				"Everything fits without scrollbars\n");
7465 	} else {
7466 		/* Scrollbar required. Can't afford unused lines at bottom. */
7467 		screen_->insert_delta = _vte_ring_next(ring) - m_row_count;
7468 		if (was_scrolled_to_bottom) {
7469 			/* Was scrolled to bottom, keep this way. */
7470 			new_scroll_delta = screen_->insert_delta;
7471 			_vte_debug_print(VTE_DEBUG_RESIZE,
7472 					"Scroll to bottom\n");
7473 		} else if (was_scrolled_to_top) {
7474 			/* Was scrolled to top, keep this way. Not sure if this special case is worth it. */
7475 			new_scroll_delta = _vte_ring_delta(ring);
7476 			_vte_debug_print(VTE_DEBUG_RESIZE,
7477 					"Scroll to top\n");
7478 		} else {
7479 			/* Try to scroll so that the bottom visible row stays.
7480 			   More precisely, the character below the bottom left corner stays in that
7481 			   (invisible) row.
7482 			   So if the bottom of the screen_ was at a hard line break then that hard
7483 			   line break will stay there.
7484 			   TODO: What would be the best behavior if the bottom of the screen_ is a
7485 			   soft line break, i.e. only a partial line is visible at the bottom? */
7486 			new_scroll_delta = below_viewport.row - m_row_count;
7487 			/* Keep the old fractional part. */
7488 			new_scroll_delta += screen_->scroll_delta - floor(screen_->scroll_delta);
7489 			_vte_debug_print(VTE_DEBUG_RESIZE,
7490 					"Scroll so bottom row stays\n");
7491 		}
7492 	}
7493 
7494 	/* Don't clamp, they'll be clamped when restored. Until then remember off-screen_ values
7495 	   since they might become on-screen_ again on subsequent resizes. */
7496         screen_->saved.cursor.row = cursor_saved_absolute.row - screen_->insert_delta;
7497         screen_->saved.cursor.col = cursor_saved_absolute.col;
7498 
7499 	_vte_debug_print(VTE_DEBUG_RESIZE,
7500 			"New  insert_delta=%ld  scroll_delta=%f\n"
7501                         "     cursor (absolute)  row=%ld  col=%ld\n"
7502 			"     cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n\n",
7503 			screen_->insert_delta, new_scroll_delta,
7504                         screen_->cursor.row, screen_->cursor.col,
7505                         screen_->saved.cursor.row, screen_->saved.cursor.col);
7506 
7507 	if (screen_ == m_screen)
7508 		queue_adjustment_value_changed(new_scroll_delta);
7509 	else
7510 		screen_->scroll_delta = new_scroll_delta;
7511 }
7512 
7513 void
set_size(long columns,long rows)7514 Terminal::set_size(long columns,
7515                              long rows)
7516 {
7517 	glong old_columns, old_rows;
7518 
7519 	_vte_debug_print(VTE_DEBUG_RESIZE,
7520 			"Setting PTY size to %ldx%ld.\n",
7521 			columns, rows);
7522 
7523 	old_rows = m_row_count;
7524 	old_columns = m_column_count;
7525 
7526 	if (pty()) {
7527 		/* Try to set the terminal size, and read it back,
7528 		 * in case something went awry.
7529                  */
7530 		if (!pty()->set_size(rows,
7531                                      columns,
7532                                      m_cell_height_unscaled,
7533                                      m_cell_width_unscaled)) {
7534                         // nothing we can do here
7535                 }
7536 		refresh_size();
7537 	} else {
7538 		m_row_count = rows;
7539 		m_column_count = columns;
7540                 m_tabstops.resize(columns);
7541 	}
7542 	if (old_rows != m_row_count || old_columns != m_column_count) {
7543                 m_scrolling_restricted = FALSE;
7544 
7545                 _vte_ring_set_visible_rows(m_normal_screen.row_data, m_row_count);
7546                 _vte_ring_set_visible_rows(m_alternate_screen.row_data, m_row_count);
7547 
7548 		/* Resize the normal screen and (if rewrapping is enabled) rewrap it even if the alternate screen is visible: bug 415277 */
7549 		screen_set_size(&m_normal_screen, old_columns, old_rows, m_rewrap_on_resize);
7550 		/* Resize the alternate screen if it's the current one, but never rewrap it: bug 336238 comment 60 */
7551 		if (m_screen == &m_alternate_screen)
7552 			screen_set_size(&m_alternate_screen, old_columns, old_rows, false);
7553 
7554                 /* Ensure scrollback buffers cover the screen. */
7555                 set_scrollback_lines(m_scrollback_lines);
7556 
7557                 /* Ensure the cursor is valid */
7558                 m_screen->cursor.row = CLAMP (m_screen->cursor.row,
7559                                               _vte_ring_delta (m_screen->row_data),
7560                                               MAX (_vte_ring_delta (m_screen->row_data),
7561                                                    _vte_ring_next (m_screen->row_data) - 1));
7562 
7563 		adjust_adjustments_full();
7564 		gtk_widget_queue_resize_no_redraw(m_widget);
7565 		/* Our visible text changed. */
7566 		emit_text_modified();
7567 	}
7568 }
7569 
7570 /* Redraw the widget. */
7571 static void
vte_terminal_vadjustment_value_changed_cb(vte::terminal::Terminal * that)7572 vte_terminal_vadjustment_value_changed_cb(vte::terminal::Terminal* that) noexcept
7573 try
7574 {
7575         that->vadjustment_value_changed();
7576 }
7577 catch (...)
7578 {
7579         vte::log_exception();
7580 }
7581 
7582 void
vadjustment_value_changed()7583 Terminal::vadjustment_value_changed()
7584 {
7585 	/* Read the new adjustment value and save the difference. */
7586         auto const adj = gtk_adjustment_get_value(m_vadjustment.get());
7587 	double dy = adj - m_screen->scroll_delta;
7588 	m_screen->scroll_delta = adj;
7589 
7590 	/* Sanity checks. */
7591         if (G_UNLIKELY(!widget_realized()))
7592                 return;
7593 
7594         /* FIXME: do this check in pixel space */
7595 	if (!_vte_double_equal(dy, 0)) {
7596 		_vte_debug_print(VTE_DEBUG_ADJ,
7597 			    "Scrolling by %f\n", dy);
7598 
7599                 invalidate_all();
7600                 match_contents_clear();
7601 		emit_text_scrolled(dy);
7602 		queue_contents_changed();
7603 	} else {
7604 		_vte_debug_print(VTE_DEBUG_ADJ, "Not scrolling\n");
7605 	}
7606 }
7607 
7608 void
widget_set_vadjustment(vte::glib::RefPtr<GtkAdjustment> && adjustment)7609 Terminal::widget_set_vadjustment(vte::glib::RefPtr<GtkAdjustment>&& adjustment)
7610 {
7611         if (adjustment && adjustment == m_vadjustment)
7612                 return;
7613         if (!adjustment && m_vadjustment)
7614                 return;
7615 
7616         if (m_vadjustment) {
7617 		/* Disconnect our signal handlers from this object. */
7618                 g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
7619 						     (void*)vte_terminal_vadjustment_value_changed_cb,
7620 						     this);
7621 	}
7622 
7623         if (adjustment)
7624                 m_vadjustment = std::move(adjustment);
7625         else
7626                 m_vadjustment = vte::glib::make_ref_sink(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0)));
7627 
7628 	/* We care about the offset, not the top or bottom. */
7629         g_signal_connect_swapped(m_vadjustment.get(),
7630 				 "value-changed",
7631 				 G_CALLBACK(vte_terminal_vadjustment_value_changed_cb),
7632 				 this);
7633 }
7634 
Terminal(vte::platform::Widget * w,VteTerminal * t)7635 Terminal::Terminal(vte::platform::Widget* w,
7636                    VteTerminal *t) :
7637         m_real_widget(w),
7638         m_terminal(t),
7639         m_widget(&t->widget),
7640         m_normal_screen(VTE_SCROLLBACK_INIT, true),
7641         m_alternate_screen(VTE_ROWS, false),
7642         m_screen(&m_normal_screen)
7643 {
7644 	widget_set_vadjustment({});
7645 
7646         /* Inits allocation to 1x1 @ -1,-1 */
7647         cairo_rectangle_int_t allocation;
7648         gtk_widget_get_allocation(m_widget, &allocation);
7649         set_allocated_rect(allocation);
7650 
7651 	/* NOTE! We allocated zeroed memory, just fill in non-zero stuff. */
7652 
7653         // FIXMEegmont make this store row indices only, maybe convert to a bitmap
7654         m_update_rects = g_array_sized_new(FALSE /* zero terminated */,
7655                                            FALSE /* clear */,
7656                                            sizeof(cairo_rectangle_int_t),
7657                                            32 /* preallocated size */);
7658 
7659 	/* Set up dummy metrics, value != 0 to avoid division by 0 */
7660         // FIXMEchpe this is wrong. These values must not be used before
7661         // the view has been set up, so if they are, that's a bug
7662 	m_cell_width = 1;
7663 	m_cell_height = 1;
7664 	m_char_ascent = 1;
7665 	m_char_descent = 1;
7666 	m_char_padding = {0, 0, 0, 0};
7667 	m_line_thickness = 1;
7668 	m_underline_position = 1;
7669         m_double_underline_position = 1;
7670         m_undercurl_position = 1.;
7671 	m_strikethrough_position = 1;
7672         m_overline_position = 1;
7673         m_regex_underline_position = 1;
7674 
7675         reset_default_attributes(true);
7676 
7677 	/* Set up the desired palette. */
7678 	set_colors_default();
7679 	for (auto i = 0; i < VTE_PALETTE_SIZE; i++)
7680 		m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
7681 
7682 	/* Set up I/O encodings. */
7683 	m_outgoing = _vte_byte_array_new();
7684 
7685 	/* Setting the terminal type and size requires the PTY master to
7686 	 * be set up properly first. */
7687         set_size(VTE_COLUMNS, VTE_ROWS);
7688 
7689         /* Default is 0, forces update in vte_terminal_set_scrollback_lines */
7690 	set_scrollback_lines(VTE_SCROLLBACK_INIT);
7691 
7692         /* Initialize the saved cursor. */
7693         save_cursor(&m_normal_screen);
7694         save_cursor(&m_alternate_screen);
7695 
7696 	/* Matching data. */
7697         m_match_span.clear(); // FIXMEchpe unnecessary
7698 	match_hilite_clear(); // FIXMEchpe unnecessary
7699 
7700         /* Word chars */
7701         set_word_char_exceptions(WORD_CHAR_EXCEPTIONS_DEFAULT);
7702 
7703         update_view_extents();
7704 
7705 #ifdef VTE_DEBUG
7706         if (g_test_flags != 0) {
7707                 feed("\e[1m\e[31mWARNING:\e[39m Test mode enabled. This is insecure!\e[0m\n\e[G"sv, false);
7708         }
7709 #endif
7710 
7711 #ifndef WITH_GNUTLS
7712         std::string str{"\e[1m\e[31m"};
7713         str.append(_("WARNING"));
7714         str.append(":\e[39m ");
7715         str.append(_("GnuTLS not enabled; data will be written to disk unencrypted!"));
7716         str.append("\e[0m\n\e[G");
7717 
7718         feed(str, false);
7719 #endif
7720 }
7721 
7722 void
widget_get_preferred_width(int * minimum_width,int * natural_width)7723 Terminal::widget_get_preferred_width(int *minimum_width,
7724                                                int *natural_width)
7725 {
7726 	_vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_width()\n");
7727 
7728 	ensure_font();
7729 
7730         refresh_size();
7731 
7732 	*minimum_width = m_cell_width * 2;  /* have room for a CJK or emoji */
7733         *natural_width = m_cell_width * m_column_count;
7734 
7735 	*minimum_width += m_padding.left +
7736                           m_padding.right;
7737 	*natural_width += m_padding.left +
7738                           m_padding.right;
7739 
7740 	_vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
7741 			"[Terminal %p] minimum_width=%d, natural_width=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",
7742                         m_terminal,
7743 			*minimum_width, *natural_width,
7744 			m_column_count,
7745                          m_row_count,
7746                          m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
7747 }
7748 
7749 void
widget_get_preferred_height(int * minimum_height,int * natural_height)7750 Terminal::widget_get_preferred_height(int *minimum_height,
7751                                                 int *natural_height)
7752 {
7753 	_vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_height()\n");
7754 
7755 	ensure_font();
7756 
7757         refresh_size();
7758 
7759 	*minimum_height = m_cell_height * 1;
7760         *natural_height = m_cell_height * m_row_count;
7761 
7762 	*minimum_height += m_padding.top +
7763 			   m_padding.bottom;
7764 	*natural_height += m_padding.top +
7765 			   m_padding.bottom;
7766 
7767 	_vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
7768 			"[Terminal %p] minimum_height=%d, natural_height=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",
7769                         m_terminal,
7770 			*minimum_height, *natural_height,
7771 			m_column_count,
7772                          m_row_count,
7773                          m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
7774 }
7775 
7776 void
widget_size_allocate(GtkAllocation * allocation)7777 Terminal::widget_size_allocate(GtkAllocation *allocation)
7778 {
7779 	glong width, height;
7780 	gboolean repaint, update_scrollback;
7781 
7782 	_vte_debug_print(VTE_DEBUG_LIFECYCLE,
7783 			"vte_terminal_size_allocate()\n");
7784 
7785 	width = (allocation->width - (m_padding.left + m_padding.right)) /
7786 		m_cell_width;
7787 	height = (allocation->height - (m_padding.top + m_padding.bottom)) /
7788 		 m_cell_height;
7789 	width = MAX(width, 1);
7790 	height = MAX(height, 1);
7791 
7792 	_vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
7793 			"[Terminal %p] Sizing window to %dx%d (%ldx%ld, padding %d,%d;%d,%d).\n",
7794                         m_terminal,
7795 			allocation->width, allocation->height,
7796                          width, height,
7797                          m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
7798 
7799         auto current_allocation = get_allocated_rect();
7800 
7801 	repaint = current_allocation.width != allocation->width
7802 			|| current_allocation.height != allocation->height;
7803 	update_scrollback = current_allocation.height != allocation->height;
7804 
7805 	/* Set our allocation to match the structure. */
7806 	gtk_widget_set_allocation(m_widget, allocation);
7807         set_allocated_rect(*allocation);
7808 
7809 	if (width != m_column_count
7810 			|| height != m_row_count
7811 			|| update_scrollback)
7812 	{
7813 		/* Set the size of the pseudo-terminal. */
7814 		set_size(width, height);
7815 
7816 		/* Notify viewers that the contents have changed. */
7817 		queue_contents_changed();
7818 	}
7819 
7820 	if (widget_realized()) {
7821 		/* Force a repaint if we were resized. */
7822 		if (repaint) {
7823 			reset_update_rects();
7824 			invalidate_all();
7825 		}
7826 	}
7827 }
7828 
7829 void
widget_unmap()7830 Terminal::widget_unmap()
7831 {
7832         m_ringview.pause();
7833 }
7834 
7835 void
widget_unrealize()7836 Terminal::widget_unrealize()
7837 {
7838 	/* Deallocate the cursors. */
7839         m_mouse_cursor_over_widget = FALSE;
7840 
7841 	match_hilite_clear();
7842 
7843 	m_im_preedit_active = FALSE;
7844 
7845 	/* Drop font cache */
7846         m_draw.clear_font_cache();
7847 	m_fontdirty = true;
7848 
7849         /* Remove the cursor blink timeout function. */
7850 	remove_cursor_timeout();
7851 
7852         /* Remove the contents blink timeout function. */
7853         m_text_blink_timer.abort();
7854 
7855 	/* Cancel any pending redraws. */
7856 	remove_update_timeout(this);
7857 
7858 	/* Cancel any pending signals */
7859 	m_contents_changed_pending = FALSE;
7860 	m_cursor_moved_pending = FALSE;
7861 	m_text_modified_flag = FALSE;
7862 	m_text_inserted_flag = FALSE;
7863 	m_text_deleted_flag = FALSE;
7864 
7865 	/* Clear modifiers. */
7866 	m_modifiers = 0;
7867 
7868 	/* Free any selected text, but if we currently own the selection,
7869 	 * throw the text onto the clipboard without an owner so that it
7870 	 * doesn't just disappear. */
7871         for (auto sel_type : {vte::platform::ClipboardType::CLIPBOARD,
7872                               vte::platform::ClipboardType::PRIMARY}) {
7873                 auto const sel = vte::to_integral(sel_type);
7874 		if (m_selection[sel] != nullptr) {
7875 			if (m_selection_owned[sel]) {
7876                                 // FIXMEchpe we should check m_selection_format[sel]
7877                                 // and also put text/html on if it's HTML format
7878                                 widget()->clipboard_set_text(sel_type,
7879                                                              {m_selection[sel]->str,
7880                                                               m_selection[sel]->len});
7881 			}
7882 			g_string_free(m_selection[sel], TRUE);
7883                         m_selection[sel] = nullptr;
7884 		}
7885 	}
7886 }
7887 
7888 void
set_blink_settings(bool blink,int blink_time,int blink_timeout)7889 Terminal::set_blink_settings(bool blink,
7890                              int blink_time,
7891                              int blink_timeout) noexcept
7892 {
7893         m_cursor_blink_cycle = blink_time / 2;
7894         m_cursor_blink_timeout = blink_timeout;
7895 
7896         update_cursor_blinks();
7897 
7898         /* Misuse gtk-cursor-blink-time for text blinking as well. This might change in the future. */
7899         m_text_blink_cycle = m_cursor_blink_cycle;
7900         if (m_text_blink_timer) {
7901                 /* The current phase might have changed, and an already installed
7902                  * timer to blink might fire too late. So remove the timer and
7903                  * repaint the contents (which will install a correct new timer). */
7904                 m_text_blink_timer.abort();
7905                 invalidate_all();
7906         }
7907 }
7908 
~Terminal()7909 Terminal::~Terminal()
7910 {
7911         /* Make sure not to change selection while in destruction. See issue vte#89. */
7912         m_changing_selection = true;
7913 
7914         terminate_child();
7915         unset_pty(false /* don't notify widget */);
7916         remove_update_timeout(this);
7917 
7918         /* Stop processing input. */
7919         stop_processing(this);
7920 
7921 	/* Free matching data. */
7922 	if (m_match_attributes != NULL) {
7923 		g_array_free(m_match_attributes, TRUE);
7924 	}
7925 	g_free(m_match_contents);
7926 
7927 	if (m_search_attrs)
7928 		g_array_free (m_search_attrs, TRUE);
7929 
7930 	/* Disconnect from autoscroll requests. */
7931 	stop_autoscroll();
7932 
7933 	/* Cancel pending adjustment change notifications. */
7934 	m_adjustment_changed_pending = FALSE;
7935 
7936         /* Stop listening for child-exited signals. */
7937         if (m_reaper) {
7938                 g_signal_handlers_disconnect_by_func(m_reaper,
7939                                                      (gpointer)reaper_child_exited_cb,
7940                                                      this);
7941                 g_object_unref(m_reaper);
7942         }
7943 
7944 	/* Discard any pending data. */
7945 	_vte_byte_array_free(m_outgoing);
7946         m_outgoing = nullptr;
7947 
7948 	/* Free public-facing data. */
7949         if (m_vadjustment) {
7950 		/* Disconnect our signal handlers from this object. */
7951                 g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
7952 						     (void*)vte_terminal_vadjustment_value_changed_cb,
7953 						     this);
7954 	}
7955 
7956         /* Update rects */
7957         g_array_free(m_update_rects, TRUE /* free segment */);
7958 }
7959 
7960 void
widget_realize()7961 Terminal::widget_realize()
7962 {
7963         m_mouse_cursor_over_widget = FALSE;  /* We'll receive an enter_notify_event if the window appears under the cursor. */
7964 
7965 	m_im_preedit_active = FALSE;
7966 
7967 	/* Clear modifiers. */
7968 	m_modifiers = 0;
7969 
7970         // Create the font cache
7971 	ensure_font();
7972 }
7973 
7974 // FIXMEchpe probably @attr should be passed by ref
7975 void
determine_colors(VteCellAttr const * attr,bool is_selected,bool is_cursor,guint * pfore,guint * pback,guint * pdeco) const7976 Terminal::determine_colors(VteCellAttr const* attr,
7977                                      bool is_selected,
7978                                      bool is_cursor,
7979                                      guint *pfore,
7980                                      guint *pback,
7981                                      guint *pdeco) const
7982 {
7983         guint fore, back, deco;
7984 
7985         g_assert(attr);
7986 
7987 	/* Start with cell colors */
7988         vte_color_triple_get(attr->colors(), &fore, &back, &deco);
7989 
7990 	/* Reverse-mode switches default fore and back colors */
7991         if (G_UNLIKELY (m_modes_private.DEC_REVERSE_IMAGE())) {
7992 		if (fore == VTE_DEFAULT_FG)
7993 			fore = VTE_DEFAULT_BG;
7994 		if (back == VTE_DEFAULT_BG)
7995 			back = VTE_DEFAULT_FG;
7996 	}
7997 
7998 	/* Handle bold by using set bold color or brightening */
7999         if (attr->bold()) {
8000                 if (fore == VTE_DEFAULT_FG && get_color(VTE_BOLD_FG) != NULL) {
8001 			fore = VTE_BOLD_FG;
8002                 } else if (m_bold_is_bright &&
8003                            fore >= VTE_LEGACY_COLORS_OFFSET &&
8004                            fore < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) {
8005 			fore += VTE_COLOR_BRIGHT_OFFSET;
8006 		}
8007 	}
8008 
8009         /* Handle dim colors.  Only apply to palette colors, dimming direct RGB wouldn't make sense.
8010          * Apply to the foreground color only, but do this before handling reverse/highlight so that
8011          * those can be used to dim the background instead. */
8012         if (attr->dim() && !(fore & VTE_RGB_COLOR_MASK(8, 8, 8))) {
8013 	        fore |= VTE_DIM_COLOR;
8014         }
8015 
8016 	/* Reverse cell? */
8017 	if (attr->reverse()) {
8018                 using std::swap;
8019                 swap(fore, back);
8020 	}
8021 
8022 	/* Selection: use hightlight back/fore, or inverse */
8023 	if (is_selected) {
8024 		/* XXX what if hightlight back is same color as current back? */
8025 		bool do_swap = true;
8026 		if (get_color(VTE_HIGHLIGHT_BG) != NULL) {
8027 			back = VTE_HIGHLIGHT_BG;
8028 			do_swap = false;
8029 		}
8030 		if (get_color(VTE_HIGHLIGHT_FG) != NULL) {
8031 			fore = VTE_HIGHLIGHT_FG;
8032 			do_swap = false;
8033 		}
8034 		if (do_swap) {
8035                         using std::swap;
8036                         swap(fore, back);
8037                 }
8038 	}
8039 
8040 	/* Cursor: use cursor back, or inverse */
8041 	if (is_cursor) {
8042 		/* XXX what if cursor back is same color as current back? */
8043                 bool do_swap = true;
8044                 if (get_color(VTE_CURSOR_BG) != NULL) {
8045                         back = VTE_CURSOR_BG;
8046                         do_swap = false;
8047                 }
8048                 if (get_color(VTE_CURSOR_FG) != NULL) {
8049                         fore = VTE_CURSOR_FG;
8050                         do_swap = false;
8051                 }
8052                 if (do_swap) {
8053                         using std::swap;
8054                         swap(fore, back);
8055                 }
8056 	}
8057 
8058 	/* Invisible? */
8059         /* FIXME: This is dead code, this is not where we actually handle invisibile.
8060          * Instead, draw_cells() is not called from draw_rows().
8061          * That is required for the foreground to be transparent if so is the background. */
8062         if (attr->invisible()) {
8063                 fore = back;
8064                 deco = VTE_DEFAULT_FG;
8065 	}
8066 
8067 	*pfore = fore;
8068 	*pback = back;
8069         *pdeco = deco;
8070 }
8071 
8072 void
determine_colors(VteCell const * cell,bool highlight,guint * fore,guint * back,guint * deco) const8073 Terminal::determine_colors(VteCell const* cell,
8074                                      bool highlight,
8075                                      guint *fore,
8076                                      guint *back,
8077                                      guint *deco) const
8078 {
8079 	determine_colors(cell ? &cell->attr : &basic_cell.attr,
8080                          highlight, false /* not cursor */,
8081                          fore, back, deco);
8082 }
8083 
8084 void
determine_cursor_colors(VteCell const * cell,bool highlight,guint * fore,guint * back,guint * deco) const8085 Terminal::determine_cursor_colors(VteCell const* cell,
8086                                             bool highlight,
8087                                             guint *fore,
8088                                             guint *back,
8089                                             guint *deco) const
8090 {
8091 	determine_colors(cell ? &cell->attr : &basic_cell.attr,
8092                          highlight, true /* cursor */,
8093                          fore, back, deco);
8094 }
8095 
8096 void
resolve_normal_colors(VteCell const * cell,unsigned * pfore,unsigned * pback,vte::color::rgb & fg,vte::color::rgb & bg)8097 Terminal::resolve_normal_colors(VteCell const* cell,
8098                                 unsigned* pfore,
8099                                 unsigned* pback,
8100                                 vte::color::rgb& fg,
8101                                 vte::color::rgb& bg)
8102 {
8103         auto deco = unsigned{};
8104         determine_colors(cell, false, pfore, pback, &deco);
8105         rgb_from_index<8, 8, 8>(*pfore, fg);
8106         rgb_from_index<8, 8, 8>(*pback, bg);
8107 }
8108 
8109 // FIXMEchpe this constantly removes and reschedules the timer. improve this!
8110 bool
text_blink_timer_callback()8111 Terminal::text_blink_timer_callback()
8112 {
8113         invalidate_all();
8114         return false; /* don't run again */
8115 }
8116 
8117 /* Draw a string of characters with similar attributes. */
8118 void
draw_cells(vte::view::DrawingContext::TextRequest * items,gssize n,uint32_t fore,uint32_t back,uint32_t deco,bool clear,bool draw_default_bg,uint32_t attr,bool hyperlink,bool hilite,int column_width,int row_height)8119 Terminal::draw_cells(vte::view::DrawingContext::TextRequest* items,
8120                                gssize n,
8121                                uint32_t fore,
8122                                uint32_t back,
8123                                uint32_t deco,
8124                                bool clear,
8125                                bool draw_default_bg,
8126                                uint32_t attr,
8127                                bool hyperlink,
8128                                bool hilite,
8129                                int column_width,
8130                                int row_height)
8131 {
8132         int i, xl, xr, y;
8133 	gint columns = 0;
8134         vte::color::rgb fg, bg, dc;
8135 
8136 	g_assert(n > 0);
8137 #if 0
8138 	_VTE_DEBUG_IF(VTE_DEBUG_CELLS) {
8139 		GString *str = g_string_new (NULL);
8140 		gchar *tmp;
8141 		for (i = 0; i < n; i++) {
8142 			g_string_append_unichar (str, items[i].c);
8143 		}
8144 		tmp = g_string_free (str, FALSE);
8145                 g_printerr ("draw_cells('%s', fore=%d, back=%d, deco=%d, bold=%d,"
8146                                 " ul=%d, strike=%d, ol=%d, blink=%d,"
8147                                 " hyperlink=%d, hilite=%d, boxed=%d)\n",
8148                                 tmp, fore, back, deco, bold,
8149                                 underline, strikethrough, overline, blink,
8150                                 hyperlink, hilite, boxed);
8151 		g_free (tmp);
8152 	}
8153 #endif
8154 
8155         rgb_from_index<8, 8, 8>(fore, fg);
8156         rgb_from_index<8, 8, 8>(back, bg);
8157         // FIXMEchpe defer resolving deco color until we actually need to draw an underline?
8158         if (deco == VTE_DEFAULT_FG)
8159                 dc = fg;
8160         else
8161                 rgb_from_index<4, 5, 4>(deco, dc);
8162 
8163         if (clear && (draw_default_bg || back != VTE_DEFAULT_BG)) {
8164                 /* Paint the background. */
8165                 i = 0;
8166                 while (i < n) {
8167                         xl = items[i].x;
8168                         xr = items[i].x + items[i].columns * column_width;
8169                         y = items[i].y;
8170                         /* Items are not necessarily contiguous in LTR order.
8171                          * Combine as long as they form a single visual run. */
8172                         for (i++; i < n && items[i].y == y; i++) {
8173                                 if (G_LIKELY (items[i].x == xr)) {
8174                                         xr += items[i].columns * column_width;  /* extend to the right */
8175                                 } else if (items[i].x + items[i].columns * column_width == xl) {
8176                                         xl = items[i].x;                        /* extend to the left */
8177                                 } else {
8178                                         break;                                  /* break the run */
8179                                 }
8180                         }
8181 
8182                         m_draw.fill_rectangle(
8183                                               xl,
8184                                               y,
8185                                               xr - xl, row_height,
8186                                               &bg, VTE_DRAW_OPAQUE);
8187                 }
8188         }
8189 
8190         if (attr & VTE_ATTR_BLINK) {
8191                 /* Notify the caller that cells with the "blink" attribute were encountered (regardless of
8192                  * whether they're actually painted or skipped now), so that the caller can set up a timer
8193                  * to make them blink if it wishes to. */
8194                 m_text_to_blink = true;
8195 
8196                 /* This is for the "off" state of blinking text. Invisible text could also be handled here,
8197                  * but it's not, it's handled outside by not even calling this method.
8198                  * Setting fg = bg and painting the text would not work for two reasons: it'd be opaque
8199                  * even if the background is translucent, and this method can be called with a continuous
8200                  * run of identical fg, yet different bg colored cells. So we simply bail out. */
8201                 if (!m_text_blink_state)
8202                         return;
8203         }
8204 
8205         /* Draw whatever SFX are required. Do this before drawing the letters,
8206          * so that if the descent of a letter crosses an underline of a different color,
8207          * it's the letter's color that wins. Other kinds of decorations always have the
8208          * same color as the text, so the order is irrelevant there. */
8209         if ((attr & (VTE_ATTR_UNDERLINE_MASK |
8210                      VTE_ATTR_STRIKETHROUGH_MASK |
8211                      VTE_ATTR_OVERLINE_MASK |
8212                      VTE_ATTR_BOXED_MASK)) |
8213             hyperlink | hilite) {
8214 		i = 0;
8215                 while (i < n) {
8216                         xl = items[i].x;
8217                         xr = items[i].x + items[i].columns * column_width;
8218                         columns = items[i].columns;
8219 			y = items[i].y;
8220                         /* Items are not necessarily contiguous in LTR order.
8221                          * Combine as long as they form a single visual run. */
8222                         for (i++; i < n && items[i].y == y; i++) {
8223                                 if (G_LIKELY (items[i].x == xr)) {
8224                                         xr += items[i].columns * column_width;  /* extend to the right */
8225                                         columns += items[i].columns;
8226                                 } else if (items[i].x + items[i].columns * column_width == xl) {
8227                                         xl = items[i].x;                        /* extend to the left */
8228                                         columns += items[i].columns;
8229                                 } else {
8230                                         break;                                  /* break the run */
8231                                 }
8232 			}
8233                         switch (vte_attr_get_value(attr, VTE_ATTR_UNDERLINE_VALUE_MASK, VTE_ATTR_UNDERLINE_SHIFT)) {
8234                         case 1:
8235                                 m_draw.draw_line(
8236                                                     xl,
8237                                                     y + m_underline_position,
8238                                                     xr - 1,
8239                                                     y + m_underline_position + m_underline_thickness - 1,
8240                                                     VTE_LINE_WIDTH,
8241                                                     &dc, VTE_DRAW_OPAQUE);
8242                                 break;
8243                         case 2:
8244                                 m_draw.draw_line(
8245                                                     xl,
8246                                                     y + m_double_underline_position,
8247                                                     xr - 1,
8248                                                     y + m_double_underline_position + m_double_underline_thickness - 1,
8249                                                     VTE_LINE_WIDTH,
8250                                                     &dc, VTE_DRAW_OPAQUE);
8251                                 m_draw.draw_line(
8252                                                     xl,
8253                                                     y + m_double_underline_position + 2 * m_double_underline_thickness,
8254                                                     xr - 1,
8255                                                     y + m_double_underline_position + 3 * m_double_underline_thickness - 1,
8256                                                     VTE_LINE_WIDTH,
8257                                                     &dc, VTE_DRAW_OPAQUE);
8258                                 break;
8259                         case 3:
8260                                 m_draw.draw_undercurl(
8261                                                          xl,
8262                                                          y + m_undercurl_position,
8263                                                          m_undercurl_thickness,
8264                                                          columns,
8265                                                          &dc, VTE_DRAW_OPAQUE);
8266                                 break;
8267 			}
8268 			if (attr & VTE_ATTR_STRIKETHROUGH) {
8269                                 m_draw.draw_line(
8270                                                     xl,
8271                                                     y + m_strikethrough_position,
8272                                                     xr - 1,
8273                                                     y + m_strikethrough_position + m_strikethrough_thickness - 1,
8274                                                     VTE_LINE_WIDTH,
8275                                                     &fg, VTE_DRAW_OPAQUE);
8276 			}
8277                         if (attr & VTE_ATTR_OVERLINE) {
8278                                 m_draw.draw_line(
8279                                                     xl,
8280                                                     y + m_overline_position,
8281                                                     xr - 1,
8282                                                     y + m_overline_position + m_overline_thickness - 1,
8283                                                     VTE_LINE_WIDTH,
8284                                                     &fg, VTE_DRAW_OPAQUE);
8285                         }
8286 			if (hilite) {
8287                                 m_draw.draw_line(
8288                                                     xl,
8289                                                     y + m_regex_underline_position,
8290                                                     xr - 1,
8291                                                     y + m_regex_underline_position + m_regex_underline_thickness - 1,
8292                                                     VTE_LINE_WIDTH,
8293                                                     &fg, VTE_DRAW_OPAQUE);
8294                         } else if (hyperlink) {
8295                                 for (double j = 1.0 / 6.0; j < columns; j += 0.5) {
8296                                         m_draw.fill_rectangle(
8297                                                                  xl + j * column_width,
8298                                                                  y + m_regex_underline_position,
8299                                                                  MAX(column_width / 6.0, 1.0),
8300                                                                  m_regex_underline_thickness,
8301                                                                  &fg, VTE_DRAW_OPAQUE);
8302                                 }
8303                         }
8304 			if (attr & VTE_ATTR_BOXED) {
8305                                 m_draw.draw_rectangle(
8306                                                          xl,
8307                                                          y,
8308                                                          xr - xl,
8309                                                          row_height,
8310                                                          &fg, VTE_DRAW_OPAQUE);
8311 			}
8312                 }
8313 	}
8314 
8315         m_draw.draw_text(
8316                        items, n,
8317                        attr,
8318                        &fg, VTE_DRAW_OPAQUE);
8319 }
8320 
8321 /* FIXME: we don't have a way to tell GTK+ what the default text attributes
8322  * should be, so for now at least it's assuming white-on-black is the norm and
8323  * is using "black-on-white" to signify "inverse".  Pick up on that state and
8324  * fix things.  Do this here, so that if we suddenly get red-on-black, we'll do
8325  * the right thing. */
8326 void
fudge_pango_colors(GSList * attributes,VteCell * cells,gsize n)8327 Terminal::fudge_pango_colors(GSList *attributes,
8328                                        VteCell *cells,
8329                                        gsize n)
8330 {
8331 	gsize i, sumlen = 0;
8332 	struct _fudge_cell_props{
8333 		gboolean saw_fg, saw_bg;
8334 		vte::color::rgb fg, bg;
8335 		guint index;
8336 	}*props = g_newa (struct _fudge_cell_props, n);
8337 
8338 	for (i = 0; i < n; i++) {
8339 		gchar ubuf[7];
8340 		gint len = g_unichar_to_utf8 (cells[i].c, ubuf);
8341 		props[i].index = sumlen;
8342 		props[i].saw_fg = props[i].saw_bg = FALSE;
8343 		sumlen += len;
8344 	}
8345 
8346 	while (attributes != NULL) {
8347 		PangoAttribute *attr = (PangoAttribute *)attributes->data;
8348 		PangoAttrColor *color;
8349 		switch (attr->klass->type) {
8350 		case PANGO_ATTR_FOREGROUND:
8351 			for (i = 0; i < n; i++) {
8352 				if (props[i].index < attr->start_index) {
8353 					continue;
8354 				}
8355 				if (props[i].index >= attr->end_index) {
8356 					break;
8357 				}
8358 				props[i].saw_fg = TRUE;
8359 				color = (PangoAttrColor*) attr;
8360 				props[i].fg = color->color;
8361 			}
8362 			break;
8363 		case PANGO_ATTR_BACKGROUND:
8364 			for (i = 0; i < n; i++) {
8365 				if (props[i].index < attr->start_index) {
8366 					continue;
8367 				}
8368 				if (props[i].index >= attr->end_index) {
8369 					break;
8370 				}
8371 				props[i].saw_bg = TRUE;
8372 				color = (PangoAttrColor*) attr;
8373 				props[i].bg = color->color;
8374 			}
8375 			break;
8376 		default:
8377 			break;
8378 		}
8379 		attributes = g_slist_next(attributes);
8380 	}
8381 
8382 	for (i = 0; i < n; i++) {
8383 		if (props[i].saw_fg && props[i].saw_bg &&
8384 				(props[i].fg.red == 0xffff) &&
8385 				(props[i].fg.green == 0xffff) &&
8386 				(props[i].fg.blue == 0xffff) &&
8387 				(props[i].bg.red == 0) &&
8388 				(props[i].bg.green == 0) &&
8389 				(props[i].bg.blue == 0)) {
8390                         cells[i].attr.copy_colors(m_color_defaults.attr);
8391 			cells[i].attr.set_reverse(true);
8392 		}
8393 	}
8394 }
8395 
8396 /* Apply the attribute given in the PangoAttribute to the list of cells. */
8397 void
apply_pango_attr(PangoAttribute * attr,VteCell * cells,gsize n_cells)8398 Terminal::apply_pango_attr(PangoAttribute *attr,
8399                                      VteCell *cells,
8400                                      gsize n_cells)
8401 {
8402 	guint i, ival;
8403 	PangoAttrInt *attrint;
8404 	PangoAttrColor *attrcolor;
8405 
8406 	switch (attr->klass->type) {
8407 	case PANGO_ATTR_FOREGROUND:
8408 	case PANGO_ATTR_BACKGROUND:
8409 		attrcolor = (PangoAttrColor*) attr;
8410                 ival = VTE_RGB_COLOR(8, 8, 8,
8411                                      ((attrcolor->color.red & 0xFF00) >> 8),
8412                                      ((attrcolor->color.green & 0xFF00) >> 8),
8413                                      ((attrcolor->color.blue & 0xFF00) >> 8));
8414 		for (i = attr->start_index;
8415 		     i < attr->end_index && i < n_cells;
8416 		     i++) {
8417 			if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
8418                                 cells[i].attr.set_fore(ival);
8419 			}
8420 			if (attr->klass->type == PANGO_ATTR_BACKGROUND) {
8421                                 cells[i].attr.set_back(ival);
8422 			}
8423 		}
8424 		break;
8425 	case PANGO_ATTR_UNDERLINE_COLOR:
8426 		attrcolor = (PangoAttrColor*) attr;
8427                 ival = VTE_RGB_COLOR(4, 5, 4,
8428                                      ((attrcolor->color.red & 0xFF00) >> 8),
8429                                      ((attrcolor->color.green & 0xFF00) >> 8),
8430                                      ((attrcolor->color.blue & 0xFF00) >> 8));
8431 		for (i = attr->start_index;
8432 		     i < attr->end_index && i < n_cells;
8433 		     i++) {
8434 			if (attr->klass->type == PANGO_ATTR_UNDERLINE) {
8435                                 cells[i].attr.set_deco(ival);
8436 			}
8437 		}
8438 		break;
8439 	case PANGO_ATTR_STRIKETHROUGH:
8440 		attrint = (PangoAttrInt*) attr;
8441 		ival = attrint->value;
8442 		for (i = attr->start_index;
8443 		     (i < attr->end_index) && (i < n_cells);
8444 		     i++) {
8445 			cells[i].attr.set_strikethrough(ival != FALSE);
8446 		}
8447 		break;
8448 	case PANGO_ATTR_UNDERLINE:
8449 		attrint = (PangoAttrInt*) attr;
8450 		ival = attrint->value;
8451 		for (i = attr->start_index;
8452 		     (i < attr->end_index) && (i < n_cells);
8453 		     i++) {
8454                         unsigned int underline = 0;
8455                         switch (ival) {
8456                         case PANGO_UNDERLINE_SINGLE:
8457                                 underline = 1;
8458                                 break;
8459                         case PANGO_UNDERLINE_DOUBLE:
8460                                 underline = 2;
8461                                 break;
8462                         case PANGO_UNDERLINE_ERROR:
8463                                 underline = 3; /* wavy */
8464                                 break;
8465                         case PANGO_UNDERLINE_NONE:
8466                         case PANGO_UNDERLINE_LOW: /* FIXME */
8467                                 underline = 0;
8468                                 break;
8469                         }
8470 			cells[i].attr.set_underline(underline);
8471 		}
8472 		break;
8473 	case PANGO_ATTR_WEIGHT:
8474 		attrint = (PangoAttrInt*) attr;
8475 		ival = attrint->value;
8476 		for (i = attr->start_index;
8477 		     (i < attr->end_index) && (i < n_cells);
8478 		     i++) {
8479 			cells[i].attr.set_bold(ival >= PANGO_WEIGHT_BOLD);
8480 		}
8481 		break;
8482 	case PANGO_ATTR_STYLE:
8483 		attrint = (PangoAttrInt*) attr;
8484 		ival = attrint->value;
8485 		for (i = attr->start_index;
8486 		     (i < attr->end_index) && (i < n_cells);
8487 		     i++) {
8488 			cells[i].attr.set_italic(ival != PANGO_STYLE_NORMAL);
8489 		}
8490 		break;
8491 	default:
8492 		break;
8493 	}
8494 }
8495 
8496 /* Convert a PangoAttrList and a location in that list to settings in a
8497  * charcell structure.  The cells array is assumed to contain enough items
8498  * so that all ranges in the attribute list can be mapped into the array, which
8499  * typically means that the cell array should have the same length as the
8500  * string (byte-wise) which the attributes describe. */
8501 void
translate_pango_cells(PangoAttrList * attrs,VteCell * cells,gsize n_cells)8502 Terminal::translate_pango_cells(PangoAttrList *attrs,
8503                                           VteCell *cells,
8504                                           gsize n_cells)
8505 {
8506 	PangoAttribute *attr;
8507 	PangoAttrIterator *attriter;
8508 	GSList *list, *listiter;
8509 	guint i;
8510 
8511 	for (i = 0; i < n_cells; i++) {
8512                 cells[i] = m_color_defaults;
8513 	}
8514 
8515 	attriter = pango_attr_list_get_iterator(attrs);
8516 	if (attriter != NULL) {
8517 		do {
8518 			list = pango_attr_iterator_get_attrs(attriter);
8519 			if (list != NULL) {
8520 				for (listiter = list;
8521 				     listiter != NULL;
8522 				     listiter = g_slist_next(listiter)) {
8523 					attr = (PangoAttribute *)listiter->data;
8524 					apply_pango_attr(attr, cells, n_cells);
8525 				}
8526 				attr = (PangoAttribute *)list->data;
8527 				fudge_pango_colors(
8528 								 list,
8529 								 cells +
8530 								 attr->start_index,
8531 								 MIN(n_cells, attr->end_index) -
8532 								 attr->start_index);
8533 				g_slist_free_full(list, (GDestroyNotify)pango_attribute_destroy);
8534 			}
8535 		} while (pango_attr_iterator_next(attriter) == TRUE);
8536 		pango_attr_iterator_destroy(attriter);
8537 	}
8538 }
8539 
8540 /* Draw the listed items using the given attributes.  Tricky because the
8541  * attribute string is indexed by byte in the UTF-8 representation of the string
8542  * of characters.  Because we draw a character at a time, this is slower. */
8543 void
draw_cells_with_attributes(vte::view::DrawingContext::TextRequest * items,gssize n,PangoAttrList * attrs,bool draw_default_bg,gint column_width,gint height)8544 Terminal::draw_cells_with_attributes(vte::view::DrawingContext::TextRequest* items,
8545                                                gssize n,
8546                                                PangoAttrList *attrs,
8547                                                bool draw_default_bg,
8548                                                gint column_width,
8549                                                gint height)
8550 {
8551         int i, j, cell_count;
8552 	VteCell *cells;
8553 	char scratch_buf[VTE_UTF8_BPC];
8554         guint fore, back, deco;
8555 
8556 	/* Note: since this function is only called with the pre-edit text,
8557 	 * all the items contain gunichar only, not vteunistr. */
8558         // FIXMEchpe is that really true for all input methods?
8559 
8560         uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
8561 
8562 	for (i = 0, cell_count = 0; i < n; i++) {
8563 		cell_count += g_unichar_to_utf8(items[i].c, scratch_buf);
8564 	}
8565 	cells = g_new(VteCell, cell_count);
8566 	translate_pango_cells(attrs, cells, cell_count);
8567 	for (i = 0, j = 0; i < n; i++) {
8568                 determine_colors(&cells[j], false, &fore, &back, &deco);
8569 		draw_cells(items + i, 1,
8570 					fore,
8571 					back,
8572                                         deco,
8573 					TRUE, draw_default_bg,
8574 					cells[j].attr.attr & attr_mask,
8575                                         m_allow_hyperlink && cells[j].attr.hyperlink_idx != 0,
8576 					FALSE, column_width, height);
8577 		j += g_unichar_to_utf8(items[i].c, scratch_buf);
8578 	}
8579 	g_free(cells);
8580 }
8581 
8582 void
ringview_update()8583 Terminal::ringview_update()
8584 {
8585         auto first_row = first_displayed_row();
8586         auto last_row = last_displayed_row();
8587         if (cursor_is_onscreen())
8588                 last_row = std::max(last_row, m_screen->cursor.row);
8589 
8590         m_ringview.set_ring (m_screen->row_data);
8591         m_ringview.set_rows (first_row, last_row - first_row + 1);
8592         m_ringview.set_width (m_column_count);
8593         m_ringview.set_enable_bidi (m_enable_bidi);
8594         m_ringview.set_enable_shaping (m_enable_shaping);
8595         m_ringview.update ();
8596 }
8597 
8598 /* Paint the contents of a given row at the given location.  Take advantage
8599  * of multiple-draw APIs by finding runs of characters with identical
8600  * attributes and bundling them together. */
8601 void
draw_rows(VteScreen * screen_,cairo_region_t const * region,vte::grid::row_t start_row,vte::grid::row_t end_row,gint start_y,gint column_width,gint row_height)8602 Terminal::draw_rows(VteScreen *screen_,
8603                     cairo_region_t const* region,
8604                     vte::grid::row_t start_row,
8605                     vte::grid::row_t end_row,
8606                     gint start_y, /* must be the start of a row */
8607                     gint column_width,
8608                     gint row_height)
8609 {
8610         vte::grid::row_t row;
8611         vte::grid::column_t i, j, lcol, vcol;
8612         int y;
8613         guint fore = VTE_DEFAULT_FG, nfore, back = VTE_DEFAULT_BG, nback, deco = VTE_DEFAULT_FG, ndeco;
8614         gboolean hyperlink = FALSE, nhyperlink;  /* non-hovered explicit hyperlink, needs dashed underlining */
8615         gboolean hilite = FALSE, nhilite;        /* hovered explicit hyperlink or regex match, needs continuous underlining */
8616         gboolean selected;
8617         gboolean nrtl = FALSE, rtl;  /* for debugging */
8618         uint32_t attr = 0, nattr;
8619 	guint item_count;
8620 	const VteCell *cell;
8621 	VteRowData const* row_data;
8622         vte::base::BidiRow const* bidirow;
8623 
8624         auto const column_count = m_column_count;
8625         uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
8626 
8627         /* Need to ensure the ringview is updated. */
8628         ringview_update();
8629 
8630         auto items = g_newa(vte::view::DrawingContext::TextRequest, column_count);
8631 
8632         /* Paint the background.
8633          * Do it first for all the cells we're about to paint, before drawing the glyphs,
8634          * so that overflowing bits of a glyph (to the right or downwards) won't be
8635          * chopped off by another cell's background, not even across changes of the
8636          * background or any other attribute.
8637          * Process each row independently. */
8638         int const rect_width = get_allocated_width();
8639 
8640         /* The rect contains the area of the row, and is moved row-wise in the loop. */
8641         auto rect = cairo_rectangle_int_t{-m_padding.left, start_y, rect_width, row_height};
8642         for (row = start_row, y = start_y;
8643              row < end_row;
8644              row++, y += row_height, rect.y = y /* same as rect.y += row_height */) {
8645                 /* Check whether we need to draw this row at all */
8646                 if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
8647                         continue;
8648 
8649 		row_data = find_row_data(row);
8650                 bidirow = m_ringview.get_bidirow(row);
8651 
8652                 _VTE_DEBUG_IF (VTE_DEBUG_BIDI) {
8653                         /* Debug: Highlight the paddings of RTL rows with a slightly different background. */
8654                         if (bidirow->base_is_rtl()) {
8655                                 vte::color::rgb bg;
8656                                 rgb_from_index<8, 8, 8>(VTE_DEFAULT_BG, bg);
8657                                 /* Go halfway towards #C0C0C0. */
8658                                 bg.red   = (bg.red   + 0xC000) / 2;
8659                                 bg.green = (bg.green + 0xC000) / 2;
8660                                 bg.blue  = (bg.blue  + 0xC000) / 2;
8661                                 m_draw.fill_rectangle(
8662                                                           -m_padding.left,
8663                                                           y,
8664                                                           m_padding.left,
8665                                                           row_height,
8666                                                           &bg, VTE_DRAW_OPAQUE);
8667                                 m_draw.fill_rectangle(
8668                                                           column_count * column_width,
8669                                                           y,
8670                                                           rect_width - m_padding.left - column_count * column_width,
8671                                                           row_height,
8672                                                           &bg, VTE_DRAW_OPAQUE);
8673                         }
8674                 }
8675 
8676                 i = j = 0;
8677                 /* Walk the line.
8678                  * Locate runs of identical bg colors within a row, and paint each run as a single rectangle. */
8679                 do {
8680                         /* Get the first cell's contents. */
8681                         cell = row_data ? _vte_row_data_get (row_data, bidirow->vis2log(i)) : nullptr;
8682                         /* Find the colors for this cell. */
8683                         selected = cell_is_selected_vis(i, row);
8684                         determine_colors(cell, selected, &fore, &back, &deco);
8685                         rtl = bidirow->vis_is_rtl(i);
8686 
8687                         while (++j < column_count) {
8688                                 /* Retrieve the next cell. */
8689                                 cell = row_data ? _vte_row_data_get (row_data, bidirow->vis2log(j)) : nullptr;
8690                                 /* Resolve attributes to colors where possible and
8691                                  * compare visual attributes to the first character
8692                                  * in this chunk. */
8693                                 selected = cell_is_selected_vis(j, row);
8694                                 determine_colors(cell, selected, &nfore, &nback, &ndeco);
8695                                 nrtl = bidirow->vis_is_rtl(j);
8696                                 if (nback != back || (_vte_debug_on (VTE_DEBUG_BIDI) && nrtl != rtl)) {
8697                                         break;
8698                                 }
8699                         }
8700                         if (back != VTE_DEFAULT_BG) {
8701                                 vte::color::rgb bg;
8702                                 rgb_from_index<8, 8, 8>(back, bg);
8703                                 m_draw.fill_rectangle(
8704                                                           i * column_width,
8705                                                           y,
8706                                                           (j - i) * column_width,
8707                                                           row_height,
8708                                                           &bg, VTE_DRAW_OPAQUE);
8709                         }
8710 
8711                         _VTE_DEBUG_IF (VTE_DEBUG_BIDI) {
8712                                 /* Debug: Highlight RTL letters and RTL rows with a slightly different background. */
8713                                 vte::color::rgb bg;
8714                                 rgb_from_index<8, 8, 8>(back, bg);
8715                                 /* Go halfway towards #C0C0C0. */
8716                                 bg.red   = (bg.red   + 0xC000) / 2;
8717                                 bg.green = (bg.green + 0xC000) / 2;
8718                                 bg.blue  = (bg.blue  + 0xC000) / 2;
8719                                 int y1 = y + round(row_height / 8.);
8720                                 int y2 = y + row_height - round(row_height / 8.);
8721                                 /* Paint the top and bottom eighth of the cell with this more gray background
8722                                  * if the paragraph has a resolved RTL base direction. */
8723                                 if (bidirow->base_is_rtl()) {
8724                                         m_draw.fill_rectangle(
8725                                                                   i * column_width,
8726                                                                   y,
8727                                                                   (j - i) * column_width,
8728                                                                   y1 - y,
8729                                                                   &bg, VTE_DRAW_OPAQUE);
8730                                         m_draw.fill_rectangle(
8731                                                                   i * column_width,
8732                                                                   y2,
8733                                                                   (j - i) * column_width,
8734                                                                   y + row_height - y2,
8735                                                                   &bg, VTE_DRAW_OPAQUE);
8736                                 }
8737                                 /* Paint the middle three quarters of the cell with this more gray background
8738                                  * if the current character has a resolved RTL direction. */
8739                                 if (rtl) {
8740                                         m_draw.fill_rectangle(
8741                                                                   i * column_width,
8742                                                                   y1,
8743                                                                   (j - i) * column_width,
8744                                                                   y2 - y1,
8745                                                                   &bg, VTE_DRAW_OPAQUE);
8746                                 }
8747                         }
8748 
8749                         /* We'll need to continue at the first cell which didn't
8750                          * match the first one in this set. */
8751                         i = j;
8752                 } while (i < column_count);
8753         }
8754 
8755 
8756         /* Render the text.
8757          * The rect contains the area of the row (enlarged a bit at the top and bottom
8758          * to allow the text to overdraw a bit), and is moved row-wise in the loop.
8759          */
8760         rect = cairo_rectangle_int_t{-m_padding.left,
8761                                      start_y - cell_overflow_top(),
8762                                      rect_width,
8763                                      row_height + cell_overflow_top() + cell_overflow_bottom()};
8764 
8765         for (row = start_row, y = start_y;
8766              row < end_row;
8767              row++, y += row_height, rect.y += row_height) {
8768                 /* Check whether we need to draw this row at all */
8769                 if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
8770                         continue;
8771 
8772                 row_data = find_row_data(row);
8773                 if (row_data == NULL)
8774                         continue; /* Skip row. */
8775 
8776                 /* Ensure that drawing is restricted to the cell (plus the overdraw area) */
8777                 _vte_draw_autoclip_t clipper{m_draw, &rect};
8778 
8779                 bidirow = m_ringview.get_bidirow(row);
8780 
8781                 /* Walk the line in logical order.
8782                  * Locate runs of identical attributes within a row, and draw each run using a single draw_cells() call. */
8783                 item_count = 0;
8784                 // FIXME No need for the "< column_count" safety cap once bug 135 is addressed.
8785                 for (lcol = 0; lcol < row_data->len && lcol < column_count; ) {
8786                         vcol = bidirow->log2vis(lcol);
8787 
8788                         /* Get the character cell's contents. */
8789                         cell = _vte_row_data_get (row_data, lcol);
8790                         g_assert(cell != nullptr);
8791 
8792                         nhyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0);
8793                         nhilite = (nhyperlink && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) ||
8794                                   (!nhyperlink && regex_match_has_current() && m_match_span.contains(row, lcol));
8795                         if (cell->c == 0 ||
8796                             ((cell->c == ' ' || cell->c == '\t') &&  // FIXME '\t' is newly added now, double check
8797                              cell->attr.has_none(VTE_ATTR_UNDERLINE_MASK |
8798                                                  VTE_ATTR_STRIKETHROUGH_MASK |
8799                                                  VTE_ATTR_OVERLINE_MASK) &&
8800                              !nhyperlink &&
8801                              !nhilite) ||
8802                             cell->attr.fragment() ||
8803                             cell->attr.invisible()) {
8804                                 /* Skip empty or fragment cell, but erase on ' ' and '\t', since
8805                                  * it may be overwriting an image. */
8806                                 lcol++;
8807                                 continue;
8808                         }
8809 
8810                         /* Find the colors for this cell. */
8811                         nattr = cell->attr.attr;
8812                         selected = cell_is_selected_log(lcol, row);
8813                         determine_colors(cell, selected, &nfore, &nback, &ndeco);
8814 
8815                         /* See if it no longer fits the run. */
8816                         if (item_count > 0 &&
8817                                    (((attr ^ nattr) & (VTE_ATTR_BOLD_MASK |
8818                                                        VTE_ATTR_ITALIC_MASK |
8819                                                        VTE_ATTR_UNDERLINE_MASK |
8820                                                        VTE_ATTR_STRIKETHROUGH_MASK |
8821                                                        VTE_ATTR_OVERLINE_MASK |
8822                                                        VTE_ATTR_BLINK_MASK |
8823                                                        VTE_ATTR_INVISIBLE_MASK)) ||  // FIXME or just simply "attr != nattr"?
8824                                     fore != nfore ||
8825                                     back != nback ||
8826                                     deco != ndeco ||
8827                                     hyperlink != nhyperlink ||
8828                                     hilite != nhilite)) {
8829                                 /* Draw the completed run of cells and start a new one. */
8830                                 draw_cells(items, item_count,
8831                                            fore, back, deco, FALSE, FALSE,
8832                                            attr & attr_mask,
8833                                            hyperlink, hilite,
8834                                            column_width, row_height);
8835                                 item_count = 0;
8836                         }
8837 
8838                         /* Combine with subsequent spacing marks. */
8839                         vteunistr c = cell->c;
8840                         j = lcol + cell->attr.columns();
8841                         if (G_UNLIKELY (lcol == 0 && g_unichar_ismark (_vte_unistr_get_base (cell->c)))) {
8842                                 /* A rare special case: the first cell contains a spacing mark.
8843                                  * Place on top of a NBSP, along with additional spacing marks if any,
8844                                  * and display beginning at offscreen column -1.
8845                                  * Additional spacing marks, if any, will be combined by the loop below. */
8846                                 c = _vte_unistr_append_unistr (0x00A0, cell->c);
8847                                 lcol = -1;
8848                         }
8849                         // FIXME No need for the "< column_count" safety cap once bug 135 is addressed.
8850                         while (j < row_data->len && j < column_count) {
8851                                 /* Combine with subsequent spacing marks. */
8852                                 cell = _vte_row_data_get (row_data, j);
8853                                 if (cell && !cell->attr.fragment() && g_unichar_ismark (_vte_unistr_get_base (cell->c))) {
8854                                         c = _vte_unistr_append_unistr (c, cell->c);
8855                                         j += cell->attr.columns();
8856                                 } else {
8857                                         break;
8858                                 }
8859                         }
8860 
8861                         attr = nattr;
8862                         fore = nfore;
8863                         back = nback;
8864                         deco = ndeco;
8865                         hyperlink = nhyperlink;
8866                         hilite = nhilite;
8867 
8868                         g_assert_cmpint (item_count, <, column_count);
8869                         items[item_count].c = bidirow->vis_get_shaped_char(vcol, c);
8870                         items[item_count].columns = j - lcol;
8871                         items[item_count].x = (vcol - (bidirow->vis_is_rtl(vcol) ? items[item_count].columns - 1 : 0)) * column_width;
8872                         items[item_count].y = y;
8873                         items[item_count].mirror = bidirow->vis_is_rtl(vcol);
8874                         items[item_count].box_mirror = !!(row_data->attr.bidi_flags & VTE_BIDI_FLAG_BOX_MIRROR);
8875                         item_count++;
8876 
8877                         g_assert_cmpint (j, >, lcol);
8878                         lcol = j;
8879                 }
8880 
8881                 /* Draw the last run of cells in the row. */
8882                 if (item_count > 0) {
8883                         draw_cells(items, item_count,
8884                                    fore, back, deco, FALSE, FALSE,
8885                                    attr & attr_mask,
8886                                    hyperlink, hilite,
8887                                    column_width, row_height);
8888                 }
8889         }
8890 }
8891 
8892 void
paint_cursor()8893 Terminal::paint_cursor()
8894 {
8895         vte::view::DrawingContext::TextRequest item;
8896         vte::grid::row_t drow;
8897         vte::grid::column_t lcol, vcol;
8898         int width, height, cursor_width;
8899         guint fore, back, deco;
8900 	vte::color::rgb bg;
8901 	int x, y;
8902 	gboolean blink, selected, focus;
8903 
8904         //FIXMEchpe this should already be reflected in the m_cursor_blink_state below
8905 	if (!m_modes_private.DEC_TEXT_CURSOR())
8906 		return;
8907 
8908         if (m_im_preedit_active)
8909                 return;
8910 
8911 	focus = m_has_focus;
8912 	blink = m_cursor_blink_state;
8913 
8914 	if (focus && !blink)
8915 		return;
8916 
8917         lcol = m_screen->cursor.col;
8918         drow = m_screen->cursor.row;
8919 	width = m_cell_width;
8920 	height = m_cell_height;
8921 
8922         if (!cursor_is_onscreen())
8923                 return;
8924         if (CLAMP(lcol, 0, m_column_count - 1) != lcol)
8925 		return;
8926 
8927         /* Need to ensure the ringview is updated. */
8928         ringview_update();
8929 
8930         /* Find the first cell of the character "under" the cursor.
8931          * This is for CJK.  For TAB, paint the cursor where it really is. */
8932         VteRowData const *row_data = find_row_data(drow);
8933         vte::base::BidiRow const *bidirow = m_ringview.get_bidirow(drow);
8934 
8935         auto cell = find_charcell(lcol, drow);
8936         while (cell != NULL && cell->attr.fragment() && cell->c != '\t' && lcol > 0) {
8937                 lcol--;
8938                 cell = find_charcell(lcol, drow);
8939 	}
8940 
8941 	/* Draw the cursor. */
8942         vcol = bidirow->log2vis(lcol);
8943         item.c = (cell && cell->c) ? bidirow->vis_get_shaped_char(vcol, cell->c) : ' ';
8944 	item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns() : 1;
8945         item.x = (vcol - ((cell && bidirow->vis_is_rtl(vcol)) ? cell->attr.columns() - 1 : 0)) * width;
8946 	item.y = row_to_pixel(drow);
8947         item.mirror = bidirow->vis_is_rtl(vcol);
8948         item.box_mirror = (row_data && (row_data->attr.bidi_flags & VTE_BIDI_FLAG_BOX_MIRROR));
8949         auto const attr = cell && cell->c ? cell->attr.attr : 0;
8950 
8951         selected = cell_is_selected_log(lcol, drow);
8952         determine_cursor_colors(cell, selected, &fore, &back, &deco);
8953         rgb_from_index<8, 8, 8>(back, bg);
8954 
8955 	x = item.x;
8956 	y = item.y;
8957 
8958         switch (decscusr_cursor_shape()) {
8959 
8960 		case CursorShape::eIBEAM: {
8961                         /* Draw at the very left of the cell (before the spacing), even in case of CJK.
8962                          * IMO (egmont) not overrunning the letter improves readability, vertical movement
8963                          * looks good (no zigzag even when a somewhat wider glyph that starts filling up
8964                          * the left spacing, or CJK that begins further to the right is encountered),
8965                          * and also this is where it looks good if background colors change, including
8966                          * Shift+arrows highlighting experience in some editors. As per the behavior of
8967                          * word processors, don't increase the height by the line spacing. */
8968                         int stem_width;
8969 
8970                         stem_width = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
8971                         stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, m_cell_width);
8972 
8973                         /* The I-beam goes to the right edge of the cell if its character has RTL resolved direction. */
8974                         if (bidirow->vis_is_rtl(vcol))
8975                                 x += item.columns * m_cell_width - stem_width;
8976 
8977                         m_draw.fill_rectangle(
8978                                                  x, y + m_char_padding.top, stem_width, m_char_ascent + m_char_descent,
8979                                                  &bg, VTE_DRAW_OPAQUE);
8980 
8981                         /* Show the direction of the current character if the paragraph contains a mixture
8982                          * of directions.
8983                          * FIXME Do this for the other cursor shapes, too. Need to find a good visual design. */
8984                         if (focus && bidirow->has_foreign())
8985                                 m_draw.fill_rectangle(
8986                                                          bidirow->vis_is_rtl(vcol) ? x - stem_width : x + stem_width,
8987                                                          y + m_char_padding.top,
8988                                                          stem_width, stem_width,
8989                                                          &bg, VTE_DRAW_OPAQUE);
8990 			break;
8991                 }
8992 
8993 		case CursorShape::eUNDERLINE: {
8994                         /* The width is at least the overall width of the cell (or two cells) minus the two
8995                          * half spacings on the two edges. That is, underlines under a CJK are more than twice
8996                          * as wide as narrow characters in case of letter spacing. Plus, if necessary, the width
8997                          * is increased to span under the entire glyph. Vertical position is not affected by
8998                          * line spacing. */
8999 
9000                         int line_height, left, right;
9001 
9002 			/* use height (not width) so underline and ibeam will
9003 			 * be equally visible */
9004                         line_height = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
9005                         line_height = CLAMP (line_height, VTE_LINE_WIDTH, m_char_ascent + m_char_descent);
9006 
9007                         left = m_char_padding.left;
9008                         right = item.columns * m_cell_width - m_char_padding.right;
9009 
9010                         if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9011                                 int l, r;
9012                                 m_draw.get_char_edges(cell->c, cell->attr.columns(), attr, l, r);
9013                                 left = MIN(left, l);
9014                                 right = MAX(right, r);
9015                         }
9016 
9017                         m_draw.fill_rectangle(
9018                                                  x + left, y + m_cell_height - m_char_padding.bottom - line_height,
9019                                                  right - left, line_height,
9020                                                  &bg, VTE_DRAW_OPAQUE);
9021 			break;
9022                 }
9023 
9024 		case CursorShape::eBLOCK:
9025                         /* Include the spacings in the cursor, see bug 781479 comments 39-44.
9026                          * Make the cursor even wider if the glyph is wider. */
9027 
9028                         cursor_width = item.columns * width;
9029                         if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9030                                 int l, r;
9031                                 m_draw.get_char_edges(cell->c, cell->attr.columns(), attr, l /* unused */, r);
9032                                 cursor_width = MAX(cursor_width, r);
9033 			}
9034 
9035                         uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
9036 
9037 			if (focus) {
9038 				/* just reverse the character under the cursor */
9039                                 m_draw.fill_rectangle(
9040 							     x, y,
9041                                                          cursor_width, height,
9042                                                          &bg, VTE_DRAW_OPAQUE);
9043 
9044                                 if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9045                                         draw_cells(
9046                                                         &item, 1,
9047                                                         fore, back, deco, TRUE, FALSE,
9048                                                         cell->attr.attr & attr_mask,
9049                                                         m_allow_hyperlink && cell->attr.hyperlink_idx != 0,
9050                                                         FALSE,
9051                                                         width,
9052                                                         height);
9053 				}
9054 
9055 			} else {
9056 				/* draw a box around the character */
9057                                 m_draw.draw_rectangle(
9058 							     x - VTE_LINE_WIDTH,
9059 							     y - VTE_LINE_WIDTH,
9060 							     cursor_width + 2*VTE_LINE_WIDTH,
9061                                                          height + 2*VTE_LINE_WIDTH,
9062                                                          &bg, VTE_DRAW_OPAQUE);
9063 			}
9064 
9065 			break;
9066 	}
9067 }
9068 
9069 void
paint_im_preedit_string()9070 Terminal::paint_im_preedit_string()
9071 {
9072         int vcol, columns;
9073         long row;
9074 	long width, height;
9075 	int i, len;
9076 
9077 	if (m_im_preedit.empty())
9078 		return;
9079 
9080         /* Need to ensure the ringview is updated. */
9081         ringview_update();
9082 
9083         /* Get the row's BiDi information. */
9084         row = m_screen->cursor.row;
9085         if (row < first_displayed_row() || row > last_displayed_row())
9086                 return;
9087         vte::base::BidiRow const *bidirow = m_ringview.get_bidirow(row);
9088 
9089 	/* Keep local copies of rendering information. */
9090 	width = m_cell_width;
9091 	height = m_cell_height;
9092 
9093 	/* Find out how many columns the pre-edit string takes up. */
9094 	columns = get_preedit_width(false);
9095 	len = get_preedit_length(false);
9096 
9097 	/* If the pre-edit string won't fit on the screen if we start
9098 	 * drawing it at the cursor's position, move it left. */
9099         vcol = bidirow->log2vis(m_screen->cursor.col);
9100         if (vcol + columns > m_column_count) {
9101                 vcol = MAX(0, m_column_count - columns);
9102 	}
9103 
9104 	/* Draw the preedit string, boxed. */
9105 	if (len > 0) {
9106 		const char *preedit = m_im_preedit.c_str();
9107 		int preedit_cursor;
9108 
9109                 auto items = g_new0(vte::view::DrawingContext::TextRequest, len);
9110 		for (i = columns = 0; i < len; i++) {
9111 			items[i].c = g_utf8_get_char(preedit);
9112                         items[i].columns = _vte_unichar_width(items[i].c,
9113                                                               m_utf8_ambiguous_width);
9114                         items[i].x = (vcol + columns) * width;
9115 			items[i].y = row_to_pixel(m_screen->cursor.row);
9116 			columns += items[i].columns;
9117 			preedit = g_utf8_next_char(preedit);
9118 		}
9119                 if (G_LIKELY(m_clear_background)) {
9120                         m_draw.clear(
9121                                         vcol * width,
9122                                         row_to_pixel(m_screen->cursor.row),
9123                                         width * columns,
9124                                         height,
9125                                         get_color(VTE_DEFAULT_BG), m_background_alpha);
9126                 }
9127 		draw_cells_with_attributes(
9128 							items, len,
9129 							m_im_preedit_attrs.get(),
9130 							TRUE,
9131 							width, height);
9132 		preedit_cursor = m_im_preedit_cursor;
9133 
9134 		if (preedit_cursor >= 0 && preedit_cursor < len) {
9135                         uint32_t fore, back, deco;
9136                         vte_color_triple_get(m_color_defaults.attr.colors(), &fore, &back, &deco);
9137 
9138 			/* Cursored letter in reverse. */
9139 			draw_cells(
9140 						&items[preedit_cursor], 1,
9141                                                 fore, back, deco,
9142                                                 TRUE,  /* clear */
9143                                                 TRUE,  /* draw_default_bg */
9144                                                 VTE_ATTR_NONE | VTE_ATTR_BOXED,
9145                                                 FALSE, /* hyperlink */
9146                                                 FALSE, /* hilite */
9147 						width, height);
9148 		}
9149 		g_free(items);
9150 	}
9151 }
9152 
9153 void
widget_draw(cairo_t * cr)9154 Terminal::widget_draw(cairo_t *cr)
9155 {
9156         cairo_rectangle_int_t clip_rect;
9157         cairo_region_t *region;
9158         int allocated_width, allocated_height;
9159         int extra_area_for_cursor;
9160         bool text_blink_enabled_now;
9161         gint64 now = 0;
9162 
9163         if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
9164                 return;
9165 
9166         _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_draw()\n");
9167         _vte_debug_print (VTE_DEBUG_WORK, "+");
9168         _vte_debug_print (VTE_DEBUG_UPDATES, "Draw (%d,%d)x(%d,%d)\n",
9169                           clip_rect.x, clip_rect.y,
9170                           clip_rect.width, clip_rect.height);
9171 
9172         region = vte_cairo_get_clip_region (cr);
9173         if (region == NULL)
9174                 return;
9175 
9176         allocated_width = get_allocated_width();
9177         allocated_height = get_allocated_height();
9178 
9179 	/* Designate the start of the drawing operation and clear the area. */
9180 	m_draw.set_cairo(cr);
9181 
9182         if (G_LIKELY(m_clear_background)) {
9183                 m_draw.clear(0, 0,
9184                                  allocated_width, allocated_height,
9185                                  get_color(VTE_DEFAULT_BG), m_background_alpha);
9186         }
9187 
9188         /* Clip vertically, for the sake of smooth scrolling. We want the top and bottom paddings to be unused.
9189          * Don't clip horizontally so that antialiasing can legally overflow to the right padding. */
9190         cairo_save(cr);
9191         cairo_rectangle(cr, 0, m_padding.top, allocated_width, allocated_height - m_padding.top - m_padding.bottom);
9192         cairo_clip(cr);
9193 
9194         cairo_translate(cr, m_padding.left, m_padding.top);
9195 
9196         /* Transform to view coordinates */
9197         cairo_region_translate(region, -m_padding.left, -m_padding.top);
9198 
9199         /* Whether blinking text should be visible now */
9200         m_text_blink_state = true;
9201         text_blink_enabled_now = (unsigned)m_text_blink_mode & (unsigned)(m_has_focus ? TextBlinkMode::eFOCUSED : TextBlinkMode::eUNFOCUSED);
9202         if (text_blink_enabled_now) {
9203                 now = g_get_monotonic_time() / 1000;
9204                 if (now % (m_text_blink_cycle * 2) >= m_text_blink_cycle)
9205                         m_text_blink_state = false;
9206         }
9207         /* Painting will flip this if it encounters any cell with blink attribute */
9208         m_text_to_blink = false;
9209 
9210         /* and now paint them */
9211         auto const first_row = first_displayed_row();
9212         draw_rows(m_screen,
9213                   region,
9214                   first_row,
9215                   last_displayed_row() + 1,
9216                   row_to_pixel(first_row),
9217                   m_cell_width,
9218                   m_cell_height);
9219 
9220 	paint_im_preedit_string();
9221 
9222         cairo_restore(cr);
9223 
9224         /* Re-clip, allowing VTE_LINE_WIDTH more pixel rows for the outline cursor. */
9225         /* TODOegmont: It's really ugly to do it here. */
9226         cairo_save(cr);
9227         extra_area_for_cursor = (decscusr_cursor_shape() == CursorShape::eBLOCK && !m_has_focus) ? VTE_LINE_WIDTH : 0;
9228         cairo_rectangle(cr, 0, m_padding.top - extra_area_for_cursor, allocated_width, allocated_height - m_padding.top - m_padding.bottom + 2 * extra_area_for_cursor);
9229         cairo_clip(cr);
9230 
9231         cairo_translate(cr, m_padding.left, m_padding.top);
9232 
9233 	paint_cursor();
9234 
9235 	cairo_restore(cr);
9236 
9237 	/* Done with various structures. */
9238 	m_draw.set_cairo(nullptr);
9239 
9240         cairo_region_destroy (region);
9241 
9242         /* If painting encountered any cell with blink attribute, we might need to set up a timer.
9243          * Blinking is implemented using a one-shot (not repeating) timer that keeps getting reinstalled
9244          * here as long as blinking cells are encountered during (re)painting. This way there's no need
9245          * for an explicit step to stop the timer when blinking cells are no longer present, this happens
9246          * implicitly by the timer not getting reinstalled anymore (often after a final unnecessary but
9247          * harmless repaint). */
9248         if (G_UNLIKELY (m_text_to_blink && text_blink_enabled_now && !m_text_blink_timer))
9249                 m_text_blink_timer.schedule(m_text_blink_cycle - now % m_text_blink_cycle,
9250                                             vte::glib::Timer::Priority::eLOW);
9251 
9252         m_invalidated_all = FALSE;
9253 }
9254 
9255 /* Handle an expose event by painting the exposed area. */
9256 static cairo_region_t *
vte_cairo_get_clip_region(cairo_t * cr)9257 vte_cairo_get_clip_region (cairo_t *cr)
9258 {
9259         cairo_rectangle_list_t *list;
9260         cairo_region_t *region;
9261         int i;
9262 
9263         list = cairo_copy_clip_rectangle_list (cr);
9264         if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) {
9265                 cairo_rectangle_int_t clip_rect;
9266 
9267                 cairo_rectangle_list_destroy (list);
9268 
9269                 if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
9270                         return NULL;
9271                 return cairo_region_create_rectangle (&clip_rect);
9272         }
9273 
9274 
9275         region = cairo_region_create ();
9276         for (i = list->num_rectangles - 1; i >= 0; --i) {
9277                 cairo_rectangle_t *rect = &list->rectangles[i];
9278                 cairo_rectangle_int_t clip_rect;
9279 
9280                 clip_rect.x = floor (rect->x);
9281                 clip_rect.y = floor (rect->y);
9282                 clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x;
9283                 clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y;
9284 
9285                 if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS) {
9286                         cairo_region_destroy (region);
9287                         region = NULL;
9288                         break;
9289                 }
9290         }
9291 
9292         cairo_rectangle_list_destroy (list);
9293         return region;
9294 }
9295 
9296 bool
widget_mouse_scroll(vte::platform::ScrollEvent const & event)9297 Terminal::widget_mouse_scroll(vte::platform::ScrollEvent const& event)
9298 {
9299 	gdouble v;
9300 	gint cnt, i;
9301 	int button;
9302 
9303         /* Need to ensure the ringview is updated. */
9304         ringview_update();
9305 
9306         m_modifiers = event.modifiers();
9307         m_mouse_smooth_scroll_delta += event.dy();
9308 
9309 	/* If we're running a mouse-aware application, map the scroll event
9310 	 * to a button press on buttons four and five. */
9311 	if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
9312 		cnt = m_mouse_smooth_scroll_delta;
9313 		if (cnt == 0)
9314 			return true;
9315 		m_mouse_smooth_scroll_delta -= cnt;
9316 		_vte_debug_print(VTE_DEBUG_EVENTS,
9317 				"Scroll application by %d lines, smooth scroll delta set back to %f\n",
9318 				cnt, m_mouse_smooth_scroll_delta);
9319 
9320 		button = cnt > 0 ? 5 : 4;
9321 		if (cnt < 0)
9322 			cnt = -cnt;
9323 		for (i = 0; i < cnt; i++) {
9324 			/* Encode the parameters and send them to the app. */
9325                         feed_mouse_event(grid_coords_from_view_coords(m_mouse_last_position),
9326                                          button,
9327                                          false /* not drag */,
9328                                          false /* not release */);
9329 		}
9330 		return true;
9331 	}
9332 
9333         v = MAX (1., ceil (gtk_adjustment_get_page_increment (m_vadjustment.get()) / 10.));
9334 	_vte_debug_print(VTE_DEBUG_EVENTS,
9335 			"Scroll speed is %d lines per non-smooth scroll unit\n",
9336 			(int) v);
9337 	if (m_screen == &m_alternate_screen &&
9338             m_modes_private.XTERM_ALTBUF_SCROLL()) {
9339 		char *normal;
9340 		gsize normal_length;
9341 
9342 		cnt = v * m_mouse_smooth_scroll_delta;
9343 		if (cnt == 0)
9344 			return true;
9345 		m_mouse_smooth_scroll_delta -= cnt / v;
9346 		_vte_debug_print(VTE_DEBUG_EVENTS,
9347 				"Scroll by %d lines, smooth scroll delta set back to %f\n",
9348 				cnt, m_mouse_smooth_scroll_delta);
9349 
9350 		/* In the alternate screen there is no scrolling,
9351 		 * so fake a few cursor keystrokes. */
9352 
9353 		_vte_keymap_map (
9354 				cnt > 0 ? GDK_KEY_Down : GDK_KEY_Up,
9355 				m_modifiers,
9356                                 m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
9357                                 m_modes_private.DEC_APPLICATION_KEYPAD(),
9358 				&normal,
9359 				&normal_length);
9360 		if (cnt < 0)
9361 			cnt = -cnt;
9362 		for (i = 0; i < cnt; i++) {
9363 			send_child({normal, normal_length});
9364 		}
9365 		g_free (normal);
9366                 return true;
9367 	} else if (m_fallback_scrolling) {
9368 		/* Perform a history scroll. */
9369 		double dcnt = m_screen->scroll_delta + v * m_mouse_smooth_scroll_delta;
9370 		queue_adjustment_value_changed_clamped(dcnt);
9371 		m_mouse_smooth_scroll_delta = 0;
9372                 return true;
9373 	}
9374         return false;
9375 }
9376 
9377 bool
set_audible_bell(bool setting)9378 Terminal::set_audible_bell(bool setting)
9379 {
9380         if (setting == m_audible_bell)
9381                 return false;
9382 
9383 	m_audible_bell = setting;
9384         return true;
9385 }
9386 
9387 bool
set_text_blink_mode(TextBlinkMode setting)9388 Terminal::set_text_blink_mode(TextBlinkMode setting)
9389 {
9390         if (setting == m_text_blink_mode)
9391                 return false;
9392 
9393         m_text_blink_mode = setting;
9394         invalidate_all();
9395 
9396         return true;
9397 }
9398 
9399 bool
set_enable_bidi(bool setting)9400 Terminal::set_enable_bidi(bool setting)
9401 {
9402         if (setting == m_enable_bidi)
9403                 return false;
9404 
9405         m_enable_bidi = setting;
9406         m_ringview.invalidate();
9407         invalidate_all();
9408 
9409         /* Chances are that we can free up some BiDi/shaping buffers that we
9410          * won't need for a while. */
9411         if (!setting)
9412                 m_ringview.pause();
9413 
9414         return true;
9415 }
9416 
9417 bool
set_enable_shaping(bool setting)9418 Terminal::set_enable_shaping(bool setting)
9419 {
9420         if (setting == m_enable_shaping)
9421                 return false;
9422 
9423         m_enable_shaping = setting;
9424         m_ringview.invalidate();
9425         invalidate_all();
9426 
9427         /* Chances are that we can free up some BiDi/shaping buffers that we
9428          * won't need for a while. */
9429         if (!setting)
9430                 m_ringview.pause();
9431 
9432         return true;
9433 }
9434 
9435 bool
set_allow_bold(bool setting)9436 Terminal::set_allow_bold(bool setting)
9437 {
9438         if (setting == m_allow_bold)
9439                 return false;
9440 
9441 	m_allow_bold = setting;
9442 	invalidate_all();
9443 
9444         return true;
9445 }
9446 
9447 bool
set_bold_is_bright(bool setting)9448 Terminal::set_bold_is_bright(bool setting)
9449 {
9450         if (setting == m_bold_is_bright)
9451                 return false;
9452 
9453 	m_bold_is_bright = setting;
9454 
9455         /* Need to re-sanitise the font description to ensure bold is distinct. */
9456         update_font_desc();
9457 
9458 	invalidate_all();
9459 
9460         return true;
9461 }
9462 
9463 bool
set_allow_hyperlink(bool setting)9464 Terminal::set_allow_hyperlink(bool setting)
9465 {
9466         if (setting == m_allow_hyperlink)
9467                 return false;
9468 
9469         if (setting == false) {
9470                 m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, -1, -1, true, NULL);
9471                 g_assert (m_hyperlink_hover_idx == 0);
9472                 m_hyperlink_hover_uri = NULL;
9473                 emit_hyperlink_hover_uri_changed(NULL);  /* FIXME only emit if really changed */
9474                 m_defaults.attr.hyperlink_idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, NULL);
9475                 g_assert (m_defaults.attr.hyperlink_idx == 0);
9476         }
9477 
9478         m_allow_hyperlink = setting;
9479         invalidate_all();
9480 
9481         return true;
9482 }
9483 
9484 bool
set_fallback_scrolling(bool set)9485 Terminal::set_fallback_scrolling(bool set)
9486 {
9487         if (set == m_fallback_scrolling)
9488                 return false;
9489 
9490         m_fallback_scrolling = set;
9491         return true;
9492 }
9493 
9494 bool
set_scroll_on_output(bool scroll)9495 Terminal::set_scroll_on_output(bool scroll)
9496 {
9497         if (scroll == m_scroll_on_output)
9498                 return false;
9499 
9500         m_scroll_on_output = scroll;
9501         return true;
9502 }
9503 
9504 bool
set_scroll_on_keystroke(bool scroll)9505 Terminal::set_scroll_on_keystroke(bool scroll)
9506 {
9507         if (scroll == m_scroll_on_keystroke)
9508                 return false;
9509 
9510         m_scroll_on_keystroke = scroll;
9511         return true;
9512 }
9513 
9514 bool
set_rewrap_on_resize(bool rewrap)9515 Terminal::set_rewrap_on_resize(bool rewrap)
9516 {
9517         if (rewrap == m_rewrap_on_resize)
9518                 return false;
9519 
9520         m_rewrap_on_resize = rewrap;
9521         return true;
9522 }
9523 
9524 void
update_cursor_blinks()9525 Terminal::update_cursor_blinks()
9526 {
9527         bool blink = false;
9528 
9529         switch (decscusr_cursor_blink()) {
9530         case CursorBlinkMode::eSYSTEM:
9531                 gboolean v;
9532                 g_object_get(gtk_widget_get_settings(m_widget),
9533                                                      "gtk-cursor-blink",
9534                                                      &v, nullptr);
9535                 blink = v != FALSE;
9536                 break;
9537         case CursorBlinkMode::eON:
9538                 blink = true;
9539                 break;
9540         case CursorBlinkMode::eOFF:
9541                 blink = false;
9542                 break;
9543         }
9544 
9545 	if (m_cursor_blinks == blink)
9546 		return;
9547 
9548 	m_cursor_blinks = blink;
9549 	check_cursor_blink();
9550 }
9551 
9552 bool
set_cursor_blink_mode(CursorBlinkMode mode)9553 Terminal::set_cursor_blink_mode(CursorBlinkMode mode)
9554 {
9555         if (mode == m_cursor_blink_mode)
9556                 return false;
9557 
9558         m_cursor_blink_mode = mode;
9559         update_cursor_blinks();
9560 
9561         return true;
9562 }
9563 
9564 bool
set_cursor_shape(CursorShape shape)9565 Terminal::set_cursor_shape(CursorShape shape)
9566 {
9567         if (shape == m_cursor_shape)
9568                 return false;
9569 
9570         m_cursor_shape = shape;
9571 	invalidate_cursor_once();
9572 
9573         return true;
9574 }
9575 
9576 /* DECSCUSR set cursor style */
9577 bool
set_cursor_style(CursorStyle style)9578 Terminal::set_cursor_style(CursorStyle style)
9579 {
9580         if (m_cursor_style == style)
9581                 return false;
9582 
9583         m_cursor_style = style;
9584         update_cursor_blinks();
9585         /* and this will also make cursor shape match the DECSCUSR style */
9586         invalidate_cursor_once();
9587 
9588         return true;
9589 }
9590 
9591 /*
9592  * Terminal::decscusr_cursor_blink:
9593  *
9594  * Returns the cursor blink mode set by DECSCUSR. If DECSCUSR was never
9595  * called, or it set the blink mode to terminal default, this returns the
9596  * value set via API or in dconf. Internal use only.
9597  *
9598  * Return value: cursor blink mode
9599  */
9600 Terminal::CursorBlinkMode
decscusr_cursor_blink() const9601 Terminal::decscusr_cursor_blink() const noexcept
9602 {
9603         switch (m_cursor_style) {
9604         default:
9605         case CursorStyle::eTERMINAL_DEFAULT:
9606                 return m_cursor_blink_mode;
9607         case CursorStyle::eBLINK_BLOCK:
9608         case CursorStyle::eBLINK_UNDERLINE:
9609         case CursorStyle::eBLINK_IBEAM:
9610                 return CursorBlinkMode::eON;
9611         case CursorStyle::eSTEADY_BLOCK:
9612         case CursorStyle::eSTEADY_UNDERLINE:
9613         case CursorStyle::eSTEADY_IBEAM:
9614                 return CursorBlinkMode::eOFF;
9615         }
9616 }
9617 
9618 /*
9619  * Terminal::decscusr_cursor_shape:
9620  * @terminal: a #VteTerminal
9621  *
9622  * Returns the cursor shape set by DECSCUSR. If DECSCUSR was never called,
9623  * or it set the cursor shape to terminal default, this returns the value
9624  * set via API. Internal use only.
9625  *
9626  * Return value: cursor shape
9627  */
9628 Terminal::CursorShape
decscusr_cursor_shape() const9629 Terminal::decscusr_cursor_shape() const noexcept
9630 {
9631         switch (m_cursor_style) {
9632         default:
9633         case CursorStyle::eTERMINAL_DEFAULT:
9634                 return m_cursor_shape;
9635         case CursorStyle::eBLINK_BLOCK:
9636         case CursorStyle::eSTEADY_BLOCK:
9637                 return CursorShape::eBLOCK;
9638         case CursorStyle::eBLINK_UNDERLINE:
9639         case CursorStyle::eSTEADY_UNDERLINE:
9640                 return CursorShape::eUNDERLINE;
9641         case CursorStyle::eBLINK_IBEAM:
9642         case CursorStyle::eSTEADY_IBEAM:
9643                 return CursorShape::eIBEAM;
9644         }
9645 }
9646 
9647 bool
set_scrollback_lines(long lines)9648 Terminal::set_scrollback_lines(long lines)
9649 {
9650         glong low, high, next;
9651         double scroll_delta;
9652 	VteScreen *scrn;
9653 
9654 	if (lines < 0)
9655 		lines = G_MAXLONG;
9656 
9657 #if 0
9658         /* FIXME: this breaks the scrollbar range, bug #562511 */
9659         if (lines == m_scrollback_lines)
9660                 return false;
9661 #endif
9662 
9663 	_vte_debug_print (VTE_DEBUG_MISC,
9664 			"Setting scrollback lines to %ld\n", lines);
9665 
9666 	m_scrollback_lines = lines;
9667 
9668         /* The main screen gets the full scrollback buffer. */
9669         scrn = &m_normal_screen;
9670         lines = MAX (lines, m_row_count);
9671         next = MAX (m_screen->cursor.row + 1,
9672                     _vte_ring_next (scrn->row_data));
9673         _vte_ring_resize (scrn->row_data, lines);
9674         low = _vte_ring_delta (scrn->row_data);
9675         high = lines + MIN (G_MAXLONG - lines, low - m_row_count + 1);
9676         scrn->insert_delta = CLAMP (scrn->insert_delta, low, high);
9677         scrn->scroll_delta = CLAMP (scrn->scroll_delta, low, scrn->insert_delta);
9678         next = MIN (next, scrn->insert_delta + m_row_count);
9679         if (_vte_ring_next (scrn->row_data) > next){
9680                 _vte_ring_shrink (scrn->row_data, next - low);
9681         }
9682 
9683         /* The alternate scrn isn't allowed to scroll at all. */
9684         scrn = &m_alternate_screen;
9685         _vte_ring_resize (scrn->row_data, m_row_count);
9686         scrn->scroll_delta = _vte_ring_delta (scrn->row_data);
9687         scrn->insert_delta = _vte_ring_delta (scrn->row_data);
9688         if (_vte_ring_next (scrn->row_data) > scrn->insert_delta + m_row_count){
9689                 _vte_ring_shrink (scrn->row_data, m_row_count);
9690         }
9691 
9692 	/* Adjust the scrollbar to the new location. */
9693 	/* Hack: force a change in scroll_delta even if the value remains, so that
9694 	   vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 676075. */
9695         scroll_delta = m_screen->scroll_delta;
9696 	m_screen->scroll_delta = -1;
9697 	queue_adjustment_value_changed(scroll_delta);
9698 	adjust_adjustments_full();
9699 
9700         return true;
9701 }
9702 
9703 bool
set_backspace_binding(EraseMode binding)9704 Terminal::set_backspace_binding(EraseMode binding)
9705 {
9706         if (binding == m_backspace_binding)
9707                 return false;
9708 
9709 	m_backspace_binding = binding;
9710         return true;
9711 }
9712 
9713 bool
set_delete_binding(EraseMode binding)9714 Terminal::set_delete_binding(EraseMode binding)
9715 {
9716         if (binding == m_delete_binding)
9717                 return false;
9718 
9719 	m_delete_binding = binding;
9720         return true;
9721 }
9722 
9723 bool
set_mouse_autohide(bool autohide)9724 Terminal::set_mouse_autohide(bool autohide)
9725 {
9726         if (autohide == m_mouse_autohide)
9727                 return false;
9728 
9729 	m_mouse_autohide = autohide;
9730 
9731         if (m_mouse_cursor_autohidden) {
9732                 hyperlink_hilite_update();
9733                 match_hilite_update();
9734                 apply_mouse_cursor();
9735         }
9736         return true;
9737 }
9738 
9739 void
reset_decoder()9740 Terminal::reset_decoder()
9741 {
9742         switch (primary_data_syntax()) {
9743         case DataSyntax::ECMA48_UTF8:
9744                 m_utf8_decoder.reset();
9745                 break;
9746 
9747 #ifdef WITH_ICU
9748         case DataSyntax::ECMA48_PCTERM:
9749                 m_converter->decoder().reset();
9750                 break;
9751 #endif
9752 
9753         default:
9754                 g_assert_not_reached();
9755         }
9756 }
9757 
9758 void
reset_data_syntax()9759 Terminal::reset_data_syntax()
9760 {
9761         if (current_data_syntax() == primary_data_syntax())
9762                 return;
9763 
9764         switch (current_data_syntax()) {
9765         default:
9766                 break;
9767         }
9768 
9769         pop_data_syntax();
9770 }
9771 
9772 /*
9773  * Terminal::reset:
9774  * @clear_tabstops: whether to reset tabstops
9775  * @clear_history: whether to empty the terminal's scrollback buffer
9776  *
9777  * Resets as much of the terminal's internal state as possible, discarding any
9778  * unprocessed input data, resetting character attributes, cursor state,
9779  * national character set state, status line, terminal modes (insert/delete),
9780  * selection state, and encoding.
9781  *
9782  */
9783 void
reset(bool clear_tabstops,bool clear_history,bool from_api)9784 Terminal::reset(bool clear_tabstops,
9785                           bool clear_history,
9786                           bool from_api)
9787 {
9788         if (from_api && !m_input_enabled)
9789                 return;
9790 
9791         auto const freezer = vte::glib::FreezeObjectNotify{m_terminal};
9792 
9793         m_bell_pending = false;
9794 
9795 	/* Clear the output buffer. */
9796 	_vte_byte_array_clear(m_outgoing);
9797 
9798 	/* Reset charset substitution state. */
9799 
9800         /* Reset decoder */
9801         reset_decoder();
9802 
9803         /* Reset parser */
9804         reset_data_syntax();
9805         m_parser.reset();
9806         m_last_graphic_character = 0;
9807 
9808         /* Reset modes */
9809         m_modes_ecma.reset();
9810         m_modes_private.clear_saved();
9811         m_modes_private.reset();
9812 
9813         /* Reset tabstops */
9814         if (clear_tabstops) {
9815                 m_tabstops.reset();
9816         }
9817 
9818         /* Window title stack */
9819         if (clear_history) {
9820                 m_window_title_stack.clear();
9821         }
9822 
9823         update_mouse_protocol();
9824 
9825 	/* Reset the color palette. Only the 256 indexed colors, not the special ones, as per xterm. */
9826 	for (int i = 0; i < 256; i++)
9827 		m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
9828 	/* Reset the default attributes.  Reset the alternate attribute because
9829 	 * it's not a real attribute, but we need to treat it as one here. */
9830         reset_default_attributes(true);
9831         /* Reset charset modes. */
9832         m_character_replacements[0] = VTE_CHARACTER_REPLACEMENT_NONE;
9833         m_character_replacements[1] = VTE_CHARACTER_REPLACEMENT_NONE;
9834         m_character_replacement = &m_character_replacements[0];
9835 	/* Clear the scrollback buffers and reset the cursors. Switch to normal screen. */
9836 	if (clear_history) {
9837                 m_screen = &m_normal_screen;
9838                 m_normal_screen.scroll_delta = m_normal_screen.insert_delta =
9839                         _vte_ring_reset(m_normal_screen.row_data);
9840                 m_normal_screen.cursor.row = m_normal_screen.insert_delta;
9841                 m_normal_screen.cursor.col = 0;
9842                 m_alternate_screen.scroll_delta = m_alternate_screen.insert_delta =
9843                         _vte_ring_reset(m_alternate_screen.row_data);
9844                 m_alternate_screen.cursor.row = m_alternate_screen.insert_delta;
9845                 m_alternate_screen.cursor.col = 0;
9846                 /* Adjust the scrollbar to the new location. */
9847                 /* Hack: force a change in scroll_delta even if the value remains, so that
9848                    vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 730599. */
9849                 m_screen->scroll_delta = -1;
9850                 queue_adjustment_value_changed(m_screen->insert_delta);
9851 		adjust_adjustments_full();
9852 	}
9853         /* DECSCUSR cursor style */
9854         set_cursor_style(CursorStyle::eTERMINAL_DEFAULT);
9855 	/* Reset restricted scrolling regions, leave insert mode, make
9856 	 * the cursor visible again. */
9857         m_scrolling_restricted = FALSE;
9858         /* Reset the visual bits of selection on hard reset, see bug 789954. */
9859         if (clear_history) {
9860                 deselect_all();
9861                 stop_autoscroll();  /* Required before setting m_selecting to false, see #105. */
9862                 m_selecting = FALSE;
9863                 m_selecting_had_delta = FALSE;
9864                 m_selection_origin = m_selection_last = { -1, -1, 1 };
9865                 m_selection_resolved.clear();
9866         }
9867 
9868 	/* Reset mouse motion events. */
9869         m_mouse_pressed_buttons = 0;
9870         m_mouse_handled_buttons = 0;
9871 	m_mouse_last_position = vte::view::coords(-1, -1);
9872 	m_mouse_smooth_scroll_delta = 0.;
9873 	/* Clear modifiers. */
9874 	m_modifiers = 0;
9875 
9876         /* Reset the saved cursor. */
9877         save_cursor(&m_normal_screen);
9878         save_cursor(&m_alternate_screen);
9879         /* BiDi */
9880         m_bidi_rtl = FALSE;
9881 	/* Cause everything to be redrawn (or cleared). */
9882 	invalidate_all();
9883 
9884         /* Reset XTerm window controls */
9885         m_xterm_wm_iconified = false;
9886 }
9887 
9888 void
unset_pty(bool notify_widget)9889 Terminal::unset_pty(bool notify_widget)
9890 {
9891         /* This may be called from inside or from widget,
9892          * and must notify the widget if not called from it.
9893          */
9894 
9895         disconnect_pty_read();
9896         disconnect_pty_write();
9897 
9898         m_child_exited_eos_wait_timer.abort();
9899 
9900         /* Clear incoming and outgoing queues */
9901         m_input_bytes = 0;
9902         m_incoming_queue = {};
9903         _vte_byte_array_clear(m_outgoing);
9904 
9905         stop_processing(this); // FIXMEchpe only if m_incoming_queue.empty() !!!
9906 
9907         reset_decoder();
9908 
9909         m_pty.reset();
9910 
9911         if (notify_widget && widget())
9912                 widget()->unset_pty();
9913 }
9914 
9915 bool
set_pty(vte::base::Pty * new_pty)9916 Terminal::set_pty(vte::base::Pty *new_pty)
9917 {
9918         if (pty().get() == new_pty)
9919                 return false;
9920 
9921         if (pty()) {
9922                 unset_pty(false /* don't notify widget */);
9923         }
9924 
9925         m_pty = vte::base::make_ref(new_pty);
9926         if (!new_pty)
9927                 return true;
9928 
9929         set_size(m_column_count, m_row_count);
9930 
9931         if (!pty()->set_utf8(primary_data_syntax() == DataSyntax::ECMA48_UTF8)) {
9932                 // nothing we can do here
9933         }
9934 
9935         /* Open channels to listen for input on. */
9936         connect_pty_read();
9937 
9938         return true;
9939 }
9940 
9941 bool
terminate_child()9942 Terminal::terminate_child() noexcept
9943 {
9944 	if (m_pty_pid == -1)
9945                 return false;
9946 
9947         auto pgrp = getpgid(m_pty_pid);
9948         if (pgrp != -1 && pgrp != getpgid(getpid())) {
9949                 kill(-pgrp, SIGHUP);
9950         }
9951 
9952         kill(m_pty_pid, SIGHUP);
9953         m_pty_pid = -1;
9954 
9955         return true;
9956 }
9957 
9958 /* We need this bit of glue to ensure that accessible objects will always
9959  * get signals. */
9960 void
subscribe_accessible_events()9961 Terminal::subscribe_accessible_events()
9962 {
9963 #ifdef WITH_A11Y
9964 	m_accessible_emit = true;
9965 #endif
9966 }
9967 
9968 void
select_text(vte::grid::column_t start_col,vte::grid::row_t start_row,vte::grid::column_t end_col,vte::grid::row_t end_row)9969 Terminal::select_text(vte::grid::column_t start_col,
9970                                 vte::grid::row_t start_row,
9971                                 vte::grid::column_t end_col,
9972                                 vte::grid::row_t end_row)
9973 {
9974 	deselect_all();
9975 
9976 	m_selection_type = SelectionType::eCHAR;
9977 	m_selecting_had_delta = true;
9978         m_selection_resolved.set ({ start_row, start_col },
9979                                   { end_row, end_col });
9980         widget_copy(vte::platform::ClipboardType::PRIMARY,
9981                     vte::platform::ClipboardFormat::TEXT);
9982 	emit_selection_changed();
9983 
9984         invalidate_rows(start_row, end_row);
9985 }
9986 
9987 void
select_empty(vte::grid::column_t col,vte::grid::row_t row)9988 Terminal::select_empty(vte::grid::column_t col,
9989                                  vte::grid::row_t row)
9990 {
9991         select_text(col, row, col, row);
9992 }
9993 
9994 static void
remove_process_timeout_source(void)9995 remove_process_timeout_source(void)
9996 {
9997 	if (process_timeout_tag == 0)
9998                 return;
9999 
10000         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing process timeout\n");
10001         g_source_remove (process_timeout_tag);
10002         process_timeout_tag = 0;
10003 }
10004 
10005 static void
add_update_timeout(vte::terminal::Terminal * that)10006 add_update_timeout(vte::terminal::Terminal* that)
10007 {
10008 	if (update_timeout_tag == 0) {
10009 		_vte_debug_print (VTE_DEBUG_TIMEOUT,
10010 				"Starting update timeout\n");
10011 		update_timeout_tag =
10012 			g_timeout_add_full (GDK_PRIORITY_REDRAW,
10013 					VTE_UPDATE_TIMEOUT,
10014 					update_timeout, NULL,
10015 					NULL);
10016 	}
10017 	if (!in_process_timeout) {
10018                 remove_process_timeout_source();
10019         }
10020 	if (that->m_active_terminals_link == nullptr) {
10021 		_vte_debug_print (VTE_DEBUG_TIMEOUT,
10022 				"Adding terminal to active list\n");
10023 		that->m_active_terminals_link = g_active_terminals =
10024 			g_list_prepend(g_active_terminals, that);
10025 	}
10026 }
10027 
10028 void
reset_update_rects()10029 Terminal::reset_update_rects()
10030 {
10031         g_array_set_size(m_update_rects, 0);
10032 	m_invalidated_all = FALSE;
10033 }
10034 
10035 static bool
remove_from_active_list(vte::terminal::Terminal * that)10036 remove_from_active_list(vte::terminal::Terminal* that)
10037 {
10038 	if (that->m_active_terminals_link == nullptr ||
10039             that->m_update_rects->len != 0)
10040                 return false;
10041 
10042         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing terminal from active list\n");
10043         g_active_terminals = g_list_delete_link(g_active_terminals, that->m_active_terminals_link);
10044         that->m_active_terminals_link = nullptr;
10045         return true;
10046 }
10047 
10048 static void
stop_processing(vte::terminal::Terminal * that)10049 stop_processing(vte::terminal::Terminal* that)
10050 {
10051         if (!remove_from_active_list(that))
10052                 return;
10053 
10054         if (g_active_terminals != nullptr)
10055                 return;
10056 
10057         if (!in_process_timeout) {
10058                 remove_process_timeout_source();
10059         }
10060         if (in_update_timeout == FALSE &&
10061             update_timeout_tag != 0) {
10062                 _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing update timeout\n");
10063                 g_source_remove (update_timeout_tag);
10064                 update_timeout_tag = 0;
10065         }
10066 }
10067 
10068 static void
remove_update_timeout(vte::terminal::Terminal * that)10069 remove_update_timeout(vte::terminal::Terminal* that)
10070 {
10071 	that->reset_update_rects();
10072         stop_processing(that);
10073 }
10074 
10075 static void
add_process_timeout(vte::terminal::Terminal * that)10076 add_process_timeout(vte::terminal::Terminal* that)
10077 {
10078 	_vte_debug_print(VTE_DEBUG_TIMEOUT,
10079 			"Adding terminal to active list\n");
10080 	that->m_active_terminals_link = g_active_terminals =
10081 		g_list_prepend(g_active_terminals, that);
10082 	if (update_timeout_tag == 0 &&
10083 			process_timeout_tag == 0) {
10084 		_vte_debug_print(VTE_DEBUG_TIMEOUT,
10085 				"Starting process timeout\n");
10086 		process_timeout_tag =
10087 			g_timeout_add (VTE_DISPLAY_TIMEOUT,
10088 					process_timeout, NULL);
10089 	}
10090 }
10091 
10092 void
start_processing()10093 Terminal::start_processing()
10094 {
10095 	if (!is_processing())
10096 		add_process_timeout(this);
10097 }
10098 
10099 void
emit_pending_signals()10100 Terminal::emit_pending_signals()
10101 {
10102         auto const freezer = vte::glib::FreezeObjectNotify{m_terminal};
10103 
10104 	emit_adjustment_changed();
10105 
10106 	if (m_pending_changes & vte::to_integral(PendingChanges::TITLE)) {
10107                 if (m_window_title != m_window_title_pending) {
10108                         m_window_title.swap(m_window_title_pending);
10109 
10110                         _vte_debug_print(VTE_DEBUG_SIGNALS,
10111                                          "Emitting `window-title-changed'.\n");
10112                         g_signal_emit(freezer.get(), signals[SIGNAL_WINDOW_TITLE_CHANGED], 0);
10113                         g_object_notify_by_pspec(freezer.get(), pspecs[PROP_WINDOW_TITLE]);
10114                 }
10115 
10116                 m_window_title_pending.clear();
10117 	}
10118 
10119 	if (m_pending_changes & vte::to_integral(PendingChanges::CWD)) {
10120                 if (m_current_directory_uri != m_current_directory_uri_pending) {
10121                         m_current_directory_uri.swap(m_current_directory_uri_pending);
10122 
10123                         _vte_debug_print(VTE_DEBUG_SIGNALS,
10124                                          "Emitting `current-directory-uri-changed'.\n");
10125                         g_signal_emit(freezer.get(), signals[SIGNAL_CURRENT_DIRECTORY_URI_CHANGED], 0);
10126                         g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_DIRECTORY_URI]);
10127                 }
10128 
10129                 m_current_directory_uri_pending.clear();
10130         }
10131 
10132         if (m_pending_changes & vte::to_integral(PendingChanges::CWF)) {
10133                 if (m_current_file_uri != m_current_file_uri_pending) {
10134                         m_current_file_uri.swap(m_current_file_uri_pending);
10135 
10136                         _vte_debug_print(VTE_DEBUG_SIGNALS,
10137                                          "Emitting `current-file-uri-changed'.\n");
10138                         g_signal_emit(freezer.get(), signals[SIGNAL_CURRENT_FILE_URI_CHANGED], 0);
10139                         g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_FILE_URI]);
10140                 }
10141 
10142                 m_current_file_uri_pending.clear();
10143         }
10144 
10145         m_pending_changes = 0;
10146 
10147 	/* Flush any pending "inserted" signals. */
10148 
10149         if (m_cursor_moved_pending) {
10150                 _vte_debug_print(VTE_DEBUG_SIGNALS,
10151                                  "Emitting `cursor-moved'.\n");
10152                 g_signal_emit(freezer.get(), signals[SIGNAL_CURSOR_MOVED], 0);
10153                 m_cursor_moved_pending = false;
10154         }
10155         if (m_text_modified_flag) {
10156                 _vte_debug_print(VTE_DEBUG_SIGNALS,
10157                                  "Emitting buffered `text-modified'.\n");
10158                 emit_text_modified();
10159                 m_text_modified_flag = false;
10160         }
10161         if (m_text_inserted_flag) {
10162                 _vte_debug_print(VTE_DEBUG_SIGNALS,
10163                                  "Emitting buffered `text-inserted'\n");
10164                 emit_text_inserted();
10165                 m_text_inserted_flag = false;
10166         }
10167         if (m_text_deleted_flag) {
10168                 _vte_debug_print(VTE_DEBUG_SIGNALS,
10169                                  "Emitting buffered `text-deleted'\n");
10170                 emit_text_deleted();
10171                 m_text_deleted_flag = false;
10172 	}
10173 	if (m_contents_changed_pending) {
10174                 /* Update hyperlink and dingus match set. */
10175 		match_contents_clear();
10176 		if (m_mouse_cursor_over_widget) {
10177                         hyperlink_hilite_update();
10178                         match_hilite_update();
10179 		}
10180 
10181 		_vte_debug_print(VTE_DEBUG_SIGNALS,
10182 				"Emitting `contents-changed'.\n");
10183 		g_signal_emit(m_terminal, signals[SIGNAL_CONTENTS_CHANGED], 0);
10184 		m_contents_changed_pending = false;
10185 	}
10186         if (m_bell_pending) {
10187                 auto const timestamp = g_get_monotonic_time();
10188                 if ((timestamp - m_bell_timestamp) >= VTE_BELL_MINIMUM_TIME_DIFFERENCE) {
10189                         beep();
10190                         emit_bell();
10191 
10192                         m_bell_timestamp = timestamp;
10193                  }
10194 
10195                 m_bell_pending = false;
10196         }
10197 
10198         auto const eos = m_eos_pending;
10199         if (m_eos_pending) {
10200                 queue_eof();
10201                 m_eos_pending = false;
10202 
10203                 unset_pty();
10204         }
10205 
10206         if (m_child_exited_after_eos_pending && eos) {
10207                 /* The signal handler could destroy the terminal, so send the signal on idle */
10208                 queue_child_exited();
10209                 m_child_exited_after_eos_pending = false;
10210         }
10211 }
10212 
10213 void
time_process_incoming()10214 Terminal::time_process_incoming()
10215 {
10216 	g_timer_reset(process_timer);
10217 	process_incoming();
10218 	auto elapsed = g_timer_elapsed(process_timer, NULL) * 1000;
10219 	gssize target = VTE_MAX_PROCESS_TIME / elapsed * m_input_bytes;
10220 	m_max_input_bytes = (m_max_input_bytes + target) / 2;
10221 }
10222 
10223 bool
process(bool emit_adj_changed)10224 Terminal::process(bool emit_adj_changed)
10225 {
10226         if (pty()) {
10227                 if (m_pty_input_active ||
10228                     m_pty_input_source == 0) {
10229                         m_pty_input_active = false;
10230                         /* Do one read directly. FIXMEchpe: Why? */
10231                         pty_io_read(pty()->fd(), G_IO_IN);
10232                 }
10233                 connect_pty_read();
10234         }
10235         if (emit_adj_changed)
10236                 emit_adjustment_changed();
10237 
10238         bool is_active = !m_incoming_queue.empty();
10239         if (is_active) {
10240                 if (VTE_MAX_PROCESS_TIME) {
10241                         time_process_incoming();
10242                 } else {
10243                         process_incoming();
10244                 }
10245                 m_input_bytes = 0;
10246         } else
10247                 emit_pending_signals();
10248 
10249         return is_active;
10250 }
10251 
10252 
10253 /* We need to keep a reference to the terminals in the
10254  * g_active_terminals list while iterating over it, since
10255  * in some language bindings the callbacks we emit
10256  * during processing may cause their GC to run, causing
10257  * later elements in this list to be removed from the list.
10258  * See issue vte#270.
10259  */
10260 
10261 static void
unref_active_terminals(GList * list)10262 unref_active_terminals(GList* list)
10263 {
10264         g_list_free_full(list, GDestroyNotify(g_object_unref));
10265 }
10266 
10267 static auto
ref_active_terminals()10268 ref_active_terminals() noexcept
10269 {
10270         GList* list = nullptr;
10271         for (auto l = g_active_terminals; l != nullptr; l = l->next) {
10272                 auto that = reinterpret_cast<vte::terminal::Terminal*>(l->data);
10273                 list = g_list_prepend(list, g_object_ref(that->vte_terminal()));
10274         }
10275 
10276         return std::unique_ptr<GList, decltype(&unref_active_terminals)>{list, &unref_active_terminals};
10277 }
10278 
10279 /* This function is called after DISPLAY_TIMEOUT ms.
10280  * It makes sure initial output is never delayed by more than DISPLAY_TIMEOUT
10281  */
10282 static gboolean
process_timeout(gpointer data)10283 process_timeout (gpointer data) noexcept
10284 try
10285 {
10286 	GList *l, *next;
10287 	gboolean again;
10288 
10289 	in_process_timeout = TRUE;
10290 
10291 	_vte_debug_print (VTE_DEBUG_WORK, "<");
10292 	_vte_debug_print (VTE_DEBUG_TIMEOUT,
10293                           "Process timeout:  %d active\n",
10294                           g_list_length(g_active_terminals));
10295 
10296         auto death_grip = ref_active_terminals();
10297 
10298 	for (l = g_active_terminals; l != NULL; l = next) {
10299 		auto that = reinterpret_cast<vte::terminal::Terminal*>(l->data);
10300 		bool active;
10301 
10302 		next = l->next;
10303 
10304 		if (l != g_active_terminals) {
10305 			_vte_debug_print (VTE_DEBUG_WORK, "T");
10306 		}
10307 
10308                 // FIXMEchpe find out why we don't emit_adjustment_changed() here!!
10309                 active = that->process(false);
10310 
10311 		if (!active) {
10312                         remove_from_active_list(that);
10313 		}
10314 	}
10315 
10316 	_vte_debug_print (VTE_DEBUG_WORK, ">");
10317 
10318 	if (g_active_terminals != nullptr && update_timeout_tag == 0) {
10319 		again = TRUE;
10320 	} else {
10321 		_vte_debug_print(VTE_DEBUG_TIMEOUT,
10322 				"Stopping process timeout\n");
10323 		process_timeout_tag = 0;
10324 		again = FALSE;
10325 	}
10326 
10327 	in_process_timeout = FALSE;
10328 
10329 	if (again) {
10330 		/* Force us to relinquish the CPU as the child is running
10331 		 * at full tilt and making us run to keep up...
10332 		 */
10333 		g_usleep (0);
10334 	} else if (update_timeout_tag == 0) {
10335 		/* otherwise free up memory used to capture incoming data */
10336                 vte::base::Chunk::prune();
10337 	}
10338 
10339 	return again;
10340 }
10341 catch (...)
10342 {
10343         vte::log_exception();
10344         return true; // false?
10345 }
10346 
10347 bool
invalidate_dirty_rects_and_process_updates()10348 Terminal::invalidate_dirty_rects_and_process_updates()
10349 {
10350         if (G_UNLIKELY(!widget_realized()))
10351                 return false;
10352 
10353 	if (G_UNLIKELY (!m_update_rects->len))
10354 		return false;
10355 
10356         auto region = cairo_region_create();
10357         auto n_rects = m_update_rects->len;
10358         for (guint i = 0; i < n_rects; i++) {
10359                 cairo_rectangle_int_t *rect = &g_array_index(m_update_rects, cairo_rectangle_int_t, i);
10360                 cairo_region_union_rectangle(region, rect);
10361 	}
10362         g_array_set_size(m_update_rects, 0);
10363 	m_invalidated_all = false;
10364 
10365         auto allocation = get_allocated_rect();
10366         cairo_region_translate(region,
10367                                allocation.x + m_padding.left,
10368                                allocation.y + m_padding.top);
10369 
10370 	/* and perform the merge with the window visible area */
10371         gtk_widget_queue_draw_region(m_widget, region);
10372 	cairo_region_destroy (region);
10373 
10374 	return true;
10375 }
10376 
10377 static gboolean
update_repeat_timeout(gpointer data)10378 update_repeat_timeout (gpointer data)
10379 {
10380 	GList *l, *next;
10381 	bool again;
10382 
10383 	in_update_timeout = TRUE;
10384 
10385 	_vte_debug_print (VTE_DEBUG_WORK, "[");
10386 	_vte_debug_print (VTE_DEBUG_TIMEOUT,
10387                           "Repeat timeout:  %d active\n",
10388                           g_list_length(g_active_terminals));
10389 
10390         auto death_grip = ref_active_terminals();
10391 
10392 	for (l = g_active_terminals; l != NULL; l = next) {
10393 		auto that = reinterpret_cast<vte::terminal::Terminal*>(l->data);
10394 
10395                 next = l->next;
10396 
10397 		if (l != g_active_terminals) {
10398 			_vte_debug_print (VTE_DEBUG_WORK, "T");
10399 		}
10400 
10401                 that->process(true);
10402 
10403 		again = that->invalidate_dirty_rects_and_process_updates();
10404 		if (!again) {
10405                         remove_from_active_list(that);
10406 		}
10407 	}
10408 
10409 	_vte_debug_print (VTE_DEBUG_WORK, "]");
10410 
10411 	/* We only stop the timer if no update request was received in this
10412          * past cycle.  Technically, always stop this timer object and maybe
10413          * reinstall a new one because we need to delay by the amount of time
10414          * it took to repaint the screen: bug 730732.
10415 	 */
10416 	if (g_active_terminals == nullptr) {
10417 		_vte_debug_print(VTE_DEBUG_TIMEOUT,
10418 				"Stopping update timeout\n");
10419 		update_timeout_tag = 0;
10420 		again = false;
10421         } else {
10422                 update_timeout_tag =
10423                         g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
10424                                             VTE_UPDATE_REPEAT_TIMEOUT,
10425                                             update_repeat_timeout, NULL,
10426                                             NULL);
10427                 again = true;
10428 	}
10429 
10430 	in_update_timeout = FALSE;
10431 
10432 	if (again) {
10433 		/* Force us to relinquish the CPU as the child is running
10434 		 * at full tilt and making us run to keep up...
10435 		 */
10436 		g_usleep (0);
10437 	} else {
10438 		/* otherwise free up memory used to capture incoming data */
10439                 vte::base::Chunk::prune();
10440 	}
10441 
10442         return FALSE;  /* If we need to go again, we already have a new timer for that. */
10443 }
10444 
10445 static gboolean
update_timeout(gpointer data)10446 update_timeout (gpointer data) noexcept
10447 try
10448 {
10449 	GList *l, *next;
10450 
10451 	in_update_timeout = TRUE;
10452 
10453 	_vte_debug_print (VTE_DEBUG_WORK, "{");
10454 	_vte_debug_print (VTE_DEBUG_TIMEOUT,
10455                           "Update timeout:  %d active\n",
10456                           g_list_length(g_active_terminals));
10457 
10458         remove_process_timeout_source();
10459 
10460 	for (l = g_active_terminals; l != NULL; l = next) {
10461 		auto that = reinterpret_cast<vte::terminal::Terminal*>(l->data);
10462 
10463                 next = l->next;
10464 
10465 		if (l != g_active_terminals) {
10466 			_vte_debug_print (VTE_DEBUG_WORK, "T");
10467 		}
10468 
10469                 that->process(true);
10470 
10471                 that->invalidate_dirty_rects_and_process_updates();
10472 	}
10473 
10474 	_vte_debug_print (VTE_DEBUG_WORK, "}");
10475 
10476 	/* Set a timer such that we do not invalidate for a while. */
10477 	/* This limits the number of times we draw to ~40fps. */
10478 	update_timeout_tag =
10479 		g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
10480 				    VTE_UPDATE_REPEAT_TIMEOUT,
10481 				    update_repeat_timeout, NULL,
10482 				    NULL);
10483 	in_update_timeout = FALSE;
10484 
10485 	return FALSE;
10486 }
10487 catch (...)
10488 {
10489         vte::log_exception();
10490         return true; // false?
10491 }
10492 
10493 bool
write_contents_sync(GOutputStream * stream,VteWriteFlags flags,GCancellable * cancellable,GError ** error)10494 Terminal::write_contents_sync (GOutputStream *stream,
10495                                          VteWriteFlags flags,
10496                                          GCancellable *cancellable,
10497                                          GError **error)
10498 {
10499 	return _vte_ring_write_contents (m_screen->row_data,
10500 					 stream, flags,
10501 					 cancellable, error);
10502 }
10503 
10504 /*
10505  * Buffer search
10506  */
10507 
10508 /* TODO Add properties & signals */
10509 
10510 /*
10511  * Terminal::search_set_regex:
10512  * @regex: (allow-none): a #VteRegex, or %nullptr
10513  * @flags: PCRE2 match flags, or 0
10514  *
10515  * Sets the regex to search for. Unsets the search regex when passed %nullptr.
10516  */
10517 bool
search_set_regex(vte::base::RefPtr<vte::base::Regex> && regex,uint32_t flags)10518 Terminal::search_set_regex (vte::base::RefPtr<vte::base::Regex>&& regex,
10519                             uint32_t flags)
10520 {
10521         if (regex == m_search_regex &&
10522             flags == m_search_regex_match_flags)
10523                 return false;
10524 
10525         m_search_regex = std::move(regex);
10526         m_search_regex_match_flags = flags;
10527 
10528 	invalidate_all();
10529 
10530         return true;
10531 }
10532 
10533 bool
search_set_wrap_around(bool wrap)10534 Terminal::search_set_wrap_around(bool wrap)
10535 {
10536         if (wrap == m_search_wrap_around)
10537                 return false;
10538 
10539         m_search_wrap_around = wrap;
10540         return true;
10541 }
10542 
10543 bool
search_rows(pcre2_match_context_8 * match_context,pcre2_match_data_8 * match_data,vte::grid::row_t start_row,vte::grid::row_t end_row,bool backward)10544 Terminal::search_rows(pcre2_match_context_8 *match_context,
10545                       pcre2_match_data_8 *match_data,
10546                       vte::grid::row_t start_row,
10547                       vte::grid::row_t end_row,
10548                       bool backward)
10549 {
10550 	int start, end;
10551 	long start_col, end_col;
10552 	VteCharAttributes *ca;
10553 	GArray *attrs;
10554 	gdouble value, page_size;
10555 
10556 	auto row_text = get_text(start_row, 0,
10557                                  end_row, 0,
10558                                  false /* block */,
10559                                  true /* wrap */,
10560                                  nullptr);
10561 
10562         int (* match_fn) (const pcre2_code_8 *,
10563                           PCRE2_SPTR8, PCRE2_SIZE, PCRE2_SIZE, uint32_t,
10564                           pcre2_match_data_8 *, pcre2_match_context_8 *);
10565         gsize *ovector, so, eo;
10566         int r;
10567 
10568         if (m_search_regex->jited())
10569                 match_fn = pcre2_jit_match_8;
10570         else
10571                 match_fn = pcre2_match_8;
10572 
10573         r = match_fn(m_search_regex->code(),
10574                      (PCRE2_SPTR8)row_text->str, row_text->len , /* subject, length */
10575                      0, /* start offset */
10576                      m_search_regex_match_flags |
10577                      PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY | PCRE2_PARTIAL_SOFT /* FIXME: HARD? */,
10578                      match_data,
10579                      match_context);
10580 
10581         if (r == PCRE2_ERROR_NOMATCH) {
10582                 g_string_free (row_text, TRUE);
10583                 return false;
10584         }
10585         // FIXME: handle partial matches (PCRE2_ERROR_PARTIAL)
10586         if (r < 0) {
10587                 g_string_free (row_text, TRUE);
10588                 return false;
10589         }
10590 
10591         ovector = pcre2_get_ovector_pointer_8(match_data);
10592         so = ovector[0];
10593         eo = ovector[1];
10594         if (G_UNLIKELY(so == PCRE2_UNSET || eo == PCRE2_UNSET)) {
10595                 g_string_free (row_text, TRUE);
10596                 return false;
10597         }
10598 
10599         start = so;
10600         end = eo;
10601 
10602 	/* Fetch text again, with attributes */
10603 	g_string_free(row_text, TRUE);
10604 	if (!m_search_attrs)
10605 		m_search_attrs = g_array_new (FALSE, TRUE, sizeof (VteCharAttributes));
10606 	attrs = m_search_attrs;
10607 	row_text = get_text(start_row, 0,
10608                             end_row, 0,
10609                             false /* block */,
10610                             true /* wrap */,
10611                             attrs);
10612 
10613 	ca = &g_array_index (attrs, VteCharAttributes, start);
10614 	start_row = ca->row;
10615 	start_col = ca->column;
10616 	ca = &g_array_index (attrs, VteCharAttributes, end - 1);
10617 	end_row = ca->row;
10618         end_col = ca->column + ca->columns;
10619 
10620 	g_string_free (row_text, TRUE);
10621 
10622 	select_text(start_col, start_row, end_col, end_row);
10623 	/* Quite possibly the math here should not access adjustment directly... */
10624         value = gtk_adjustment_get_value(m_vadjustment.get());
10625         page_size = gtk_adjustment_get_page_size(m_vadjustment.get());
10626 	if (backward) {
10627 		if (end_row < value || end_row > value + page_size - 1)
10628 			queue_adjustment_value_changed_clamped(end_row - page_size + 1);
10629 	} else {
10630 		if (start_row < value || start_row > value + page_size - 1)
10631 			queue_adjustment_value_changed_clamped(start_row);
10632 	}
10633 
10634 	return true;
10635 }
10636 
10637 bool
search_rows_iter(pcre2_match_context_8 * match_context,pcre2_match_data_8 * match_data,vte::grid::row_t start_row,vte::grid::row_t end_row,bool backward)10638 Terminal::search_rows_iter(pcre2_match_context_8 *match_context,
10639                                      pcre2_match_data_8 *match_data,
10640                                      vte::grid::row_t start_row,
10641                                      vte::grid::row_t end_row,
10642                                      bool backward)
10643 {
10644 	const VteRowData *row;
10645 	long iter_start_row, iter_end_row;
10646 
10647 	if (backward) {
10648 		iter_start_row = end_row;
10649 		while (iter_start_row > start_row) {
10650 			iter_end_row = iter_start_row;
10651 
10652 			do {
10653 				iter_start_row--;
10654 				row = find_row_data(iter_start_row);
10655 			} while (row && row->attr.soft_wrapped);
10656 
10657 			if (search_rows(match_context, match_data,
10658                                         iter_start_row, iter_end_row, backward))
10659 				return true;
10660 		}
10661 	} else {
10662 		iter_end_row = start_row;
10663 		while (iter_end_row < end_row) {
10664 			iter_start_row = iter_end_row;
10665 
10666 			do {
10667 				row = find_row_data(iter_end_row);
10668 				iter_end_row++;
10669 			} while (row && row->attr.soft_wrapped);
10670 
10671 			if (search_rows(match_context, match_data,
10672                                         iter_start_row, iter_end_row, backward))
10673 				return true;
10674 		}
10675 	}
10676 
10677 	return false;
10678 }
10679 
10680 bool
search_find(bool backward)10681 Terminal::search_find (bool backward)
10682 {
10683         vte::grid::row_t buffer_start_row, buffer_end_row;
10684         vte::grid::row_t last_start_row, last_end_row;
10685         bool match_found = true;
10686 
10687         if (!m_search_regex)
10688                 return false;
10689 
10690 	/* TODO
10691 	 * Currently We only find one result per extended line, and ignore columns
10692 	 * Moreover, the whole search thing is implemented very inefficiently.
10693 	 */
10694 
10695         auto match_context = create_match_context();
10696         auto match_data = vte::take_freeable(pcre2_match_data_create_8(256 /* should be plenty */,
10697                                                                        nullptr /* general context */));
10698 
10699 	buffer_start_row = _vte_ring_delta (m_screen->row_data);
10700 	buffer_end_row = _vte_ring_next (m_screen->row_data);
10701 
10702         if (!m_selection_resolved.empty()) {
10703                 last_start_row = m_selection_resolved.start_row();
10704                 last_end_row = m_selection_resolved.end_row() + 1;
10705 	} else {
10706 		last_start_row = m_screen->scroll_delta + m_row_count;
10707 		last_end_row = m_screen->scroll_delta;
10708 	}
10709 	last_start_row = MAX (buffer_start_row, last_start_row);
10710 	last_end_row = MIN (buffer_end_row, last_end_row);
10711 
10712 	/* If search fails, we make an empty selection at the last searched
10713 	 * position... */
10714 	if (backward) {
10715 		if (search_rows_iter(match_context.get(), match_data.get(),
10716                                       buffer_start_row, last_start_row, backward))
10717 			goto found;
10718 		if (m_search_wrap_around &&
10719 		    search_rows_iter(match_context.get(), match_data.get(),
10720                                       last_end_row, buffer_end_row, backward))
10721 			goto found;
10722                 if (!m_selection_resolved.empty()) {
10723 			if (m_search_wrap_around)
10724                             select_empty(m_selection_resolved.start_column(), m_selection_resolved.start_row());
10725 			else
10726 			    select_empty(-1, buffer_start_row - 1);
10727 		}
10728                 match_found = false;
10729 	} else {
10730 		if (search_rows_iter(match_context.get(), match_data.get(),
10731                                       last_end_row, buffer_end_row, backward))
10732 			goto found;
10733 		if (m_search_wrap_around &&
10734 		    search_rows_iter(match_context.get(), match_data.get(),
10735                                       buffer_start_row, last_start_row, backward))
10736 			goto found;
10737                 if (!m_selection_resolved.empty()) {
10738 			if (m_search_wrap_around)
10739                                 select_empty(m_selection_resolved.end_column(), m_selection_resolved.end_row());
10740 			else
10741                                 select_empty(0, buffer_end_row);
10742 		}
10743                 match_found = false;
10744 	}
10745 
10746  found:
10747 
10748 	return match_found;
10749 }
10750 
10751 /*
10752  * Terminal::set_input_enabled:
10753  * @enabled: whether to enable user input
10754  *
10755  * Enables or disables user input. When user input is disabled,
10756  * the terminal's child will not receive any key press, or mouse button
10757  * press or motion events sent to it.
10758  *
10759  * Returns: %true iff the setting changed
10760  */
10761 bool
set_input_enabled(bool enabled)10762 Terminal::set_input_enabled (bool enabled)
10763 {
10764         if (enabled == m_input_enabled)
10765                 return false;
10766 
10767         m_input_enabled = enabled;
10768 
10769         auto context = gtk_widget_get_style_context(m_widget);
10770 
10771         /* FIXME: maybe hide cursor when input disabled, too? */
10772 
10773         if (enabled) {
10774                 if (m_has_focus)
10775                         widget()->im_focus_in();
10776 
10777                 gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY);
10778         } else {
10779                 im_reset();
10780                 if (m_has_focus)
10781                         widget()->im_focus_out();
10782 
10783                 disconnect_pty_write();
10784                 _vte_byte_array_clear(m_outgoing);
10785 
10786                 gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY);
10787         }
10788 
10789         return true;
10790 }
10791 
10792 std::optional<std::vector<char32_t>>
process_word_char_exceptions(std::string_view str_view) const10793 Terminal::process_word_char_exceptions(std::string_view str_view) const noexcept
10794 {
10795         auto str = str_view.data();
10796 
10797         auto array = std::vector<char32_t>{};
10798         array.reserve(g_utf8_strlen(str, -1));
10799 
10800         for (auto const* p = str; *p; p = g_utf8_next_char(p)) {
10801                 auto const c = g_utf8_get_char(p);
10802 
10803                 /* For forward compatibility reasons, we skip
10804                  * characters that aren't supposed to be here,
10805                  * instead of erroring out.
10806                  */
10807                 /* '-' must only be used*  at the start of the string */
10808                 if (c == (gunichar)'-' && p != str)
10809                         continue;
10810                 if (!g_unichar_isgraph(c))
10811                         continue;
10812                 if (g_unichar_isspace(c))
10813                         continue;
10814                 if (g_unichar_isalnum(c))
10815                         continue;
10816 
10817                 array.push_back(c);
10818         }
10819 
10820         /* Sort the result since we want to use bsearch on it */
10821         std::sort(std::begin(array), std::end(array));
10822 
10823         /* Check that no character occurs twice */
10824         for (size_t i = 1; i < array.size(); ++i) {
10825                 if (array[i-1] != array[i])
10826                         continue;
10827 
10828                 return std::nullopt;
10829         }
10830 
10831 #if 0
10832         /* Debug */
10833         for (auto const c : array) {
10834                 char utf[7];
10835                 utf[g_unichar_to_utf8(c, utf)] = '\0';
10836                 g_printerr("Word char exception: U+%04X %s\n", c, utf);
10837         }
10838 #endif
10839 
10840         return array;
10841 }
10842 
10843 /*
10844  * Terminal::set_word_char_exceptions:
10845  * @exceptions: a string of ASCII punctuation characters, or %nullptr
10846  *
10847  * With this function you can provide a set of characters which will
10848  * be considered parts of a word when doing word-wise selection, in
10849  * addition to the default which only considers alphanumeric characters
10850  * part of a word.
10851  *
10852  * The characters in @exceptions must be non-alphanumeric, each character
10853  * must occur only once, and if @exceptions contains the character
10854  * U+002D HYPHEN-MINUS, it must be at the start of the string.
10855  *
10856  * Use %nullptr to reset the set of exception characters to the default.
10857  *
10858  * Returns: %true if the word char exceptions changed
10859  */
10860 bool
set_word_char_exceptions(std::optional<std::string_view> stropt)10861 Terminal::set_word_char_exceptions(std::optional<std::string_view> stropt)
10862 {
10863         if (auto array = process_word_char_exceptions(stropt ? stropt.value() : WORD_CHAR_EXCEPTIONS_DEFAULT)) {
10864                 m_word_char_exceptions = *array;
10865                 return true;
10866         }
10867 
10868         return false;
10869 }
10870 
10871 void
set_clear_background(bool setting)10872 Terminal::set_clear_background(bool setting)
10873 {
10874         if (m_clear_background == setting)
10875                 return;
10876 
10877         m_clear_background = setting;
10878         invalidate_all();
10879 }
10880 
10881 } // namespace terminal
10882 } // namespace vte
10883