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
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include <vice.h>
21 
22 #define GLIB_DISABLE_DEPRECATION_WARNINGS /* FIXME */
23 
24 #include <math.h>
25 #include <search.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #define howmany(x,y) (((x)+((y)-1))/(y))
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <math.h>
32 
33 #include <glib.h>
34 
35 #include "novte.h"
36 #include "vteinternal.hh"
37 #include "buffer.h"
38 #include "debug.h"
39 #include "vteconv.h"
40 #include "vtedraw.hh"
41 #include "ring.h"
42 #include "caps.hh"
43 
44 #ifdef HAVE_WCHAR_H
45 #include <wchar.h>
46 #endif
47 #ifdef HAVE_SYS_SYSLIMITS_H
48 #include <sys/syslimits.h>
49 #endif
50 #ifdef HAVE_SYS_WAIT_H
51 #include <sys/wait.h>
52 #endif
53 #include <glib.h>
54 #include <glib-object.h>
55 #include <gdk/gdk.h>
56 #include <gtk/gtk.h>
57 #include <pango/pango.h>
58 #include "iso2022.h"
59 #include "keymap.h"
60 #include "marshal.h"
61 #include "matcher.hh"
62 #include "vteaccess.h"
63 #include "vtegtk.hh"
64 
65 #include <new> /* placement new */
66 
67 #ifndef LINE_MAX    /* FIXME */
68 #define LINE_MAX 256
69 #endif
70 
71 /* Some sanity checks */
72 /* FIXMEchpe: move this to there when splitting _vte_incoming_chunk into its own file */
73 static_assert(sizeof(struct _vte_incoming_chunk) <= VTE_INPUT_CHUNK_SIZE, "_vte_incoming_chunk too large");
74 static_assert(offsetof(struct _vte_incoming_chunk, data) == offsetof(struct _vte_incoming_chunk, dataminusone) + 1, "_vte_incoming_chunk layout wrong");
75 
76 
77 #ifndef HAVE_ROUND
round(double x)78 static inline double round(double x) {
79     if(x - floor(x) < 0.5) {
80         return floor(x);
81     } else {
82         return ceil(x);
83     }
84 }
85 #endif
86 
87 #define WORD_CHAR_EXCEPTIONS_DEFAULT "-#%&+,./=?@\\_~\302\267"
88 
89 #define I_(string) (g_intern_static_string(string))
90 
91 static int _vte_unichar_width(gunichar c, int utf8_ambiguous_width);
92 static void stop_processing(VteTerminalPrivate *that);
93 static void add_process_timeout(VteTerminalPrivate *that);
94 static void add_update_timeout(VteTerminalPrivate *that);
95 static void remove_update_timeout(VteTerminalPrivate *that);
96 
97 static gboolean process_timeout (gpointer data);
98 static gboolean update_timeout (gpointer data);
99 static cairo_region_t *vte_cairo_get_clip_region (cairo_t *cr);
100 
101 /* these static variables are guarded by the GDK mutex */
102 static guint process_timeout_tag = 0;
103 static gboolean in_process_timeout;
104 static guint update_timeout_tag = 0;
105 static gboolean in_update_timeout;
106 static GList *g_active_terminals;
107 
_vte_unichar_width(gunichar c,int utf8_ambiguous_width)108 static int _vte_unichar_width(gunichar c, int utf8_ambiguous_width)
109 {
110         if (G_LIKELY (c < 0x80)) {
111                 return 1;
112         }
113         if (G_UNLIKELY (g_unichar_iszerowidth (c))) {
114                 return 0;
115         }
116         if (G_UNLIKELY (g_unichar_iswide (c))) {
117                 return 2;
118         }
119         if (G_LIKELY (utf8_ambiguous_width == 1)) {
120                 return 1;
121         }
122         if (G_UNLIKELY (g_unichar_iswide_cjk (c))) {
123                 return 2;
124         }
125         return 1;
126 }
127 
128 /* process incoming data without copying */
129 static struct _vte_incoming_chunk *free_chunks;
get_chunk(void)130 static struct _vte_incoming_chunk *get_chunk (void)
131 {
132     struct _vte_incoming_chunk *chunk = NULL;
133     if (free_chunks) {
134         chunk = free_chunks;
135         free_chunks = free_chunks->next;
136     }
137     if (chunk == NULL) {
138         chunk = g_new (struct _vte_incoming_chunk, 1);
139     }
140     chunk->next = NULL;
141     chunk->len = 0;
142     return chunk;
143 }
release_chunk(struct _vte_incoming_chunk * chunk)144 static void release_chunk (struct _vte_incoming_chunk *chunk)
145 {
146     chunk->next = free_chunks;
147     chunk->len = free_chunks ? free_chunks->len + 1 : 0;
148     free_chunks = chunk;
149 }
prune_chunks(guint len)150 static void prune_chunks (guint len)
151 {
152     struct _vte_incoming_chunk *chunk = NULL;
153     if (len && free_chunks != NULL) {
154         if (free_chunks->len > len) {
155             struct _vte_incoming_chunk *last;
156             chunk = free_chunks;
157             while (free_chunks->len > len) {
158                 last = free_chunks;
159                 free_chunks = free_chunks->next;
160             }
161             last->next = NULL;
162         }
163     } else {
164         chunk = free_chunks;
165         free_chunks = NULL;
166     }
167     while (chunk != NULL) {
168         struct _vte_incoming_chunk *next = chunk->next;
169         g_free (chunk);
170         chunk = next;
171     }
172 }
_vte_incoming_chunks_release(struct _vte_incoming_chunk * chunk)173 static void _vte_incoming_chunks_release (struct _vte_incoming_chunk *chunk)
174 {
175     while (chunk) {
176         struct _vte_incoming_chunk *next = chunk->next;
177         release_chunk (chunk);
178         chunk = next;
179     }
180 }
_vte_incoming_chunks_length(struct _vte_incoming_chunk * chunk)181 static gsize _vte_incoming_chunks_length (struct _vte_incoming_chunk *chunk)
182 {
183     gsize len = 0;
184     while (chunk) {
185         len += chunk->len;
186         chunk = chunk->next;
187     }
188     return len;
189 }
190 #if 0
191 static gsize _vte_incoming_chunks_count (struct _vte_incoming_chunk *chunk)
192 {
193     gsize cnt = 0;
194     while (chunk) {
195         cnt ++;
196         chunk = chunk->next;
197     }
198     return cnt;
199 }
200 #endif
_vte_incoming_chunks_reverse(struct _vte_incoming_chunk * chunk)201 static struct _vte_incoming_chunk *_vte_incoming_chunks_reverse(struct _vte_incoming_chunk *chunk)
202 {
203     struct _vte_incoming_chunk *prev = NULL;
204     while (chunk) {
205         struct _vte_incoming_chunk *next = chunk->next;
206         chunk->next = prev;
207         prev = chunk;
208         chunk = next;
209     }
210     return prev;
211 }
212 
vte_g_array_fill(GArray * array,gconstpointer item,guint final_size)213 static void vte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
214 {
215     if (array->len >= final_size) {
216         return;
217     }
218 
219     final_size -= array->len;
220     do {
221         g_array_append_vals(array, item, 1);
222     } while (--final_size);
223 }
224 
225 /* FIXMEchpe replace this with a method on VteRing */
ring_insert(vte::grid::row_t position,bool fill)226 VteRowData* VteTerminalPrivate::ring_insert(vte::grid::row_t position, bool fill)
227 {
228     VteRowData *row;
229     VteRing *ring = m_screen->row_data;
230     bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);
231 
232     while (G_UNLIKELY (_vte_ring_next (ring) < position)) {
233         row = _vte_ring_append (ring);
234         if (not_default_bg) {
235             _vte_row_data_fill (row, &m_fill_defaults, m_column_count);
236         }
237     }
238     row = _vte_ring_insert (ring, position);
239     if (fill && not_default_bg) {
240         _vte_row_data_fill (row, &m_fill_defaults, m_column_count);
241     }
242     return row;
243 }
244 
245 // FIXMEchpe replace this with a method on VteRing
ring_append(bool fill)246 VteRowData* VteTerminalPrivate::ring_append(bool fill)
247 {
248     return ring_insert(_vte_ring_next(m_screen->row_data), fill);
249 }
250 
251 // FIXMEchpe replace this with a method on VteRing
ring_remove(vte::grid::row_t position)252 void VteTerminalPrivate::ring_remove(vte::grid::row_t position)
253 {
254     _vte_ring_remove(m_screen->row_data, position);
255 }
256 
257 /* Reset defaults for character insertion. */
258 void
reset_default_attributes(bool reset_hyperlink)259 VteTerminalPrivate::reset_default_attributes(bool reset_hyperlink)
260 {
261     hyperlink_idx_t hyperlink_idx_save = m_defaults.attr.hyperlink_idx;
262     m_defaults = m_color_defaults = m_fill_defaults = basic_cell;
263     if (!reset_hyperlink) {
264             m_defaults.attr.hyperlink_idx = hyperlink_idx_save;
265     }
266 }
267 
268 /* FIXMEchpe this function is bad */
scroll_delta_pixel() const269 inline vte::view::coord_t VteTerminalPrivate::scroll_delta_pixel() const
270 {
271         return round(m_screen->scroll_delta * m_cell_height);
272 }
273 
274 /*
275  * VteTerminalPrivate::pixel_to_row:
276  * @y: Y coordinate is relative to viewport, top padding excluded
277  *
278  * Returns: absolute row
279  */
pixel_to_row(vte::view::coord_t y) const280 inline vte::grid::row_t VteTerminalPrivate::pixel_to_row(vte::view::coord_t y) const
281 {
282         return (scroll_delta_pixel() + y) / m_cell_height;
283 }
284 
285 /*
286  * VteTerminalPrivate::pixel_to_row:
287  * @row: absolute row
288  *
289  * Returns: Y coordinate relative to viewport with top padding excluded. If the row is
290  *   outside the viewport, may return any value < 0 or >= height
291  */
row_to_pixel(vte::grid::row_t row) const292 inline vte::view::coord_t VteTerminalPrivate::row_to_pixel(vte::grid::row_t row) const
293 {
294         /* FIXMEchpe this is bad! */
295         return row * m_cell_height - (glong)round(m_screen->scroll_delta * m_cell_height);
296 }
297 
first_displayed_row() const298 inline vte::grid::row_t VteTerminalPrivate::first_displayed_row() const
299 {
300         return pixel_to_row(0);
301 }
302 
last_displayed_row() const303 inline vte::grid::row_t VteTerminalPrivate::last_displayed_row() const
304 {
305         /* Get the logical row number displayed at the bottom pixel position */
306         auto r = pixel_to_row(m_view_usable_extents.height() - 1);
307 
308         /* If we have an extra padding at the bottom which is currently unused,
309          * this number is one too big. Adjust here.
310          * E.g. have a terminal of size 80 x 24.5.
311          * Initially the bottom displayed row is (0-based) 23, but r is now 24.
312          * After producing more than a screenful of content and scrolling back
313          * all the way to the top, the bottom displayed row is (0-based) 24. */
314         r = MIN (r, m_screen->insert_delta + m_row_count - 1);
315         return r;
316 }
317 
invalidate_cells(vte::grid::column_t column_start,int n_columns,vte::grid::row_t row_start,int n_rows)318 void VteTerminalPrivate::invalidate_cells(vte::grid::column_t column_start,
319                                             int n_columns,
320                                             vte::grid::row_t row_start,
321                                             int n_rows)
322 {
323     if (G_UNLIKELY (!widget_realized())) {
324                 return;
325     }
326 
327     /* FIXMEchpe: == 0 is fine, but somehow sometimes we
328     * get an actual negative n_columns value passed!?
329     */
330     if (n_columns <= 0 || n_rows <= 0) {
331         return;
332     }
333 
334     if (m_invalidated_all) {
335         return;
336     }
337 
338     _vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating cells at (%ld,%ld)x(%d,%d).\n",
339                         column_start, row_start, n_columns, n_rows);
340     _vte_debug_print (VTE_DEBUG_WORK, "?");
341 
342     if (n_columns == m_column_count && n_rows == m_row_count) {
343         invalidate_all();
344         return;
345     }
346 
347     cairo_rectangle_int_t rect;
348     /* Convert the column and row start and end to pixel values
349      * by multiplying by the size of a character cell.
350      * Always include the extra pixel border and overlap pixel.
351      */
352     rect.x = column_start * m_cell_width - 1;
353     /* The extra + 1 is for the faux-bold overdraw */
354     int xend = (column_start + n_columns) * m_cell_width + 1 + 1;
355     rect.width = xend - rect.x;
356 
357     rect.y = row_to_pixel(row_start) - 1;
358     int yend = row_to_pixel(row_start + n_rows) + 1;
359     rect.height = yend - rect.y;
360 
361     _vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating pixels at (%d,%d)x(%d,%d).\n",
362                         rect.x, rect.y, rect.width, rect.height);
363 
364     if (m_active_terminals_link != nullptr) {
365         g_array_append_val(m_update_rects, rect);
366         /* Wait a bit before doing any invalidation, just in
367          * case updates are coming in really soon. */
368         add_update_timeout(this);
369     } else {
370         auto allocation = get_allocated_rect();
371         rect.x += allocation.x + m_padding.left;
372         rect.y += allocation.y + m_padding.top;
373         cairo_region_t *region = cairo_region_create_rectangle(&rect);
374         gtk_widget_queue_draw_region(m_widget, region);
375         cairo_region_destroy(region);
376     }
377 
378     _vte_debug_print (VTE_DEBUG_WORK, "!");
379 }
380 
invalidate_region(vte::grid::column_t scolumn,vte::grid::column_t ecolumn,vte::grid::row_t srow,vte::grid::row_t erow,bool block)381 void VteTerminalPrivate::invalidate_region(vte::grid::column_t scolumn,
382                                             vte::grid::column_t ecolumn,
383                                             vte::grid::row_t srow,
384                                             vte::grid::row_t erow,
385                                             bool block)
386 {
387     if (block || srow == erow) {
388         invalidate_cells(scolumn, ecolumn - scolumn + 1, srow, erow - srow + 1);
389     } else {
390         invalidate_cells(scolumn, m_column_count - scolumn, srow, 1);
391         invalidate_cells(0, m_column_count, srow + 1, erow - srow - 1);
392         invalidate_cells(0, ecolumn + 1, erow, 1);
393     }
394 }
395 
invalidate(vte::grid::span const & s,bool block)396 void VteTerminalPrivate::invalidate(vte::grid::span const& s, bool block)
397 {
398     invalidate_region(s.start_column(), s.end_column(), s.start_row(), s.end_row(), block);
399 }
400 
invalidate_all()401 void VteTerminalPrivate::invalidate_all()
402 {
403     if (G_UNLIKELY (!widget_realized())) {
404                 return;
405     }
406 
407     if (m_invalidated_all) {
408         return;
409     }
410 
411     _vte_debug_print (VTE_DEBUG_WORK, "*");
412     _vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating all.\n");
413 
414     /* replace invalid regions with one covering the whole terminal */
415     reset_update_rects();
416     m_invalidated_all = TRUE;
417 
418     if (m_active_terminals_link != nullptr) {
419         auto allocation = get_allocated_rect();
420         cairo_rectangle_int_t rect;
421         rect.x = -m_padding.left;
422         rect.y = -m_padding.top;
423         rect.width = allocation.width;
424         rect.height = allocation.height;
425 
426         g_array_append_val(m_update_rects, rect);
427         /* Wait a bit before doing any invalidation, just in
428          * case updates are coming in really soon. */
429         add_update_timeout(this);
430     } else {
431         gtk_widget_queue_draw(m_widget);
432     }
433 }
434 
435 /* FIXMEchpe: remove this obsolete function. It became useless long ago
436  * when we stopped moving window contents around on scrolling. */
437 /* Scroll a rectangular region up or down by a fixed number of lines,
438  * negative = up, positive = down. */
scroll_region(long row,long count,long delta)439 void VteTerminalPrivate::scroll_region (long row, long count, long delta)
440 {
441     if ((delta == 0) || (count == 0)) {
442         /* Shenanigans! */
443         return;
444     }
445 
446     if (count >= m_row_count) {
447         /* We have to repaint the entire window. */
448         invalidate_all();
449     } else {
450         /* We have to repaint the area which is to be
451          * scrolled. */
452         invalidate_cells(
453                      0, m_column_count,
454                      row, count);
455     }
456 }
457 
458 /* Find the row in the given position in the backscroll buffer. */
459 /* FIXMEchpe replace this with a method on VteRing */
find_row_data(vte::grid::row_t row) const460 VteRowData const* VteTerminalPrivate::find_row_data(vte::grid::row_t row) const
461 {
462     VteRowData const* rowdata = nullptr;
463 
464     if (G_LIKELY(_vte_ring_contains(m_screen->row_data, row))) {
465         rowdata = _vte_ring_index(m_screen->row_data, row);
466     }
467     return rowdata;
468 }
469 
470 /* Find the row in the given position in the backscroll buffer. */
471 /* FIXMEchpe replace this with a method on VteRing */
find_row_data_writable(vte::grid::row_t row) const472 VteRowData* VteTerminalPrivate::find_row_data_writable(vte::grid::row_t row) const
473 {
474     VteRowData *rowdata = nullptr;
475 
476     if (G_LIKELY (_vte_ring_contains(m_screen->row_data, row))) {
477         rowdata = _vte_ring_index_writable(m_screen->row_data, row);
478     }
479     return rowdata;
480 }
481 
482 /* Find the character an the given position in the backscroll buffer. */
483 /* FIXMEchpe replace this with a method on VteRing */
find_charcell(vte::grid::column_t col,vte::grid::row_t row) const484 VteCell const* VteTerminalPrivate::find_charcell(vte::grid::column_t col,
485                                   vte::grid::row_t row) const
486 {
487     VteRowData const* rowdata;
488     VteCell const* ret = nullptr;
489 
490     if (_vte_ring_contains(m_screen->row_data, row)) {
491         rowdata = _vte_ring_index(m_screen->row_data, row);
492         ret = _vte_row_data_get (rowdata, col);
493     }
494     return ret;
495 }
496 
497 /* FIXMEchpe replace this with a method on VteRing */
find_start_column(vte::grid::column_t col,vte::grid::row_t row) const498 vte::grid::column_t VteTerminalPrivate::find_start_column(vte::grid::column_t col,
499                                                             vte::grid::row_t row) const
500 {
501     VteRowData const* row_data = find_row_data(row);
502     if (G_UNLIKELY (col < 0)) {
503         return col;
504     }
505     if (row_data != nullptr) {
506         const VteCell *cell = _vte_row_data_get (row_data, col);
507         while (col > 0 && cell != NULL && cell->attr.fragment()) {
508             cell = _vte_row_data_get (row_data, --col);
509         }
510     }
511     return MAX(col, 0);
512 }
513 
514 /* FIXMEchpe replace this with a method on VteRing */
find_end_column(vte::grid::column_t col,vte::grid::row_t row) const515 vte::grid::column_t VteTerminalPrivate::find_end_column(vte::grid::column_t col,
516                                                         vte::grid::row_t row) const
517 {
518     VteRowData const* row_data = find_row_data(row);
519     gint columns = 0;
520     if (G_UNLIKELY (col < 0)) {
521         return col;
522     }
523     if (row_data != NULL) {
524         const VteCell *cell = _vte_row_data_get (row_data, col);
525         while (col > 0 && cell != NULL && cell->attr.fragment()) {
526             cell = _vte_row_data_get (row_data, --col);
527         }
528         if (cell) {
529             columns = cell->attr.columns() - 1;
530         }
531     }
532     // FIXMEchp m__column_count - 1 ?
533     return MIN(col + columns, m_column_count);
534 }
535 
536 /* Determine the width of the portion of the preedit string which lies
537  * to the left of the cursor, or the entire string, in columns. */
538 /* FIXMEchpe this is for the view, so use int not gssize */
539 /* FIXMEchpe this is only ever called with left_only=false, so remove the param */
get_preedit_width(bool left_only)540 gssize VteTerminalPrivate::get_preedit_width(bool left_only)
541 {
542     gssize ret = 0;
543 
544     if (m_im_preedit != NULL) {
545         char const *preedit = m_im_preedit;
546         for (int i = 0;
547              /* FIXMEchpe preddit is != NULL at the start, and next_char never returns NULL either */
548              (preedit != NULL) &&
549              (preedit[0] != '\0') &&
550              (!left_only || (i < m_im_preedit_cursor));
551              i++) {
552             gunichar c = g_utf8_get_char(preedit);
553             ret += _vte_unichar_width(c, m_utf8_ambiguous_width);
554             preedit = g_utf8_next_char(preedit);
555         }
556     }
557 
558     return ret;
559 }
560 
561 /* Determine the length of the portion of the preedit string which lies
562  * to the left of the cursor, or the entire string, in gunichars. */
563 /* FIXMEchpe this returns gssize but inside it uses int... */
get_preedit_length(bool left_only)564 gssize VteTerminalPrivate::get_preedit_length(bool left_only)
565 {
566     int i = 0;
567 
568     if (m_im_preedit != NULL) {
569         char const *preedit = m_im_preedit;
570         for (i = 0;
571              /* FIXMEchpe useless check, see above */
572              (preedit != NULL) &&
573              (preedit[0] != '\0') &&
574              (!left_only || (i < m_im_preedit_cursor));
575              i++) {
576             preedit = g_utf8_next_char(preedit);
577         }
578     }
579 
580     return i;
581 }
582 
invalidate_cell(vte::grid::column_t col,vte::grid::row_t row)583 void VteTerminalPrivate::invalidate_cell(vte::grid::column_t col, vte::grid::row_t row)
584 {
585     int columns;
586     guint style;
587 
588     if (G_UNLIKELY (!widget_realized())) {
589         return;
590     }
591 
592     if (m_invalidated_all) {
593         return;
594     }
595 
596     columns = 1;
597     auto row_data = find_row_data(row);
598     if (row_data != NULL) {
599         const VteCell *cell;
600         cell = _vte_row_data_get (row_data, col);
601         if (cell != NULL) {
602             while (cell->attr.fragment() && col> 0) {
603                 cell = _vte_row_data_get (row_data, --col);
604             }
605             columns = cell->attr.columns();
606             style = _vte_draw_get_style(cell->attr.bold(), cell->attr.italic());
607             if (cell->c != 0) {
608                 int right;
609                 _vte_draw_get_char_edges(m_draw, cell->c, columns, style, NULL, &right);
610                 columns = MAX(columns, howmany(right, m_cell_width));
611             }
612         }
613     }
614 
615     _vte_debug_print(VTE_DEBUG_UPDATES, "Invalidating cell at (%ld,%ld-%ld).\n",
616                         row, col, col + columns);
617 
618     invalidate_cells(col, columns, row, 1);
619 }
620 
invalidate_cursor_once(bool periodic)621 void VteTerminalPrivate::invalidate_cursor_once(bool periodic)
622 {
623     if (G_UNLIKELY(!widget_realized())) {
624         return;
625     }
626 
627     if (m_invalidated_all) {
628         return;
629     }
630 
631     if (periodic) {
632         if (!m_cursor_blinks) {
633             return;
634         }
635     }
636 
637     if (m_cursor_visible) {
638         auto preedit_width = get_preedit_width(false);
639         auto row = m_screen->cursor.row;
640         auto column = m_screen->cursor.col;
641         long columns = 1;
642         column = find_start_column(column, row);
643 
644         auto cell = find_charcell(column, row);
645         if (cell != NULL) {
646             columns = cell->attr.columns();
647             auto style = _vte_draw_get_style(cell->attr.bold(), cell->attr.italic());
648             if (cell->c != 0) {
649                 int right;
650                 _vte_draw_get_char_edges(m_draw, cell->c, columns, style, NULL, &right);
651                 columns = MAX(columns, howmany(right, m_cell_width));
652             }
653         }
654         columns = MAX(columns, preedit_width);
655         if (column + columns > m_column_count) {
656             column = MAX(0, m_column_count - columns);
657         }
658 
659         _vte_debug_print(VTE_DEBUG_UPDATES, "Invalidating cursor at (%ld,%ld-%ld).\n",
660                                                 row, column, column + columns);
661         invalidate_cells(column, columns, row, 1);
662     }
663 }
664 
665 /* Invalidate the cursor repeatedly. */
666 /* FIXMEchpe this continually adds and removes the blink timeout. Find a better solution */
invalidate_cursor_periodic_cb(VteTerminalPrivate * that)667 static gboolean invalidate_cursor_periodic_cb(VteTerminalPrivate *that)
668 {
669     that->invalidate_cursor_periodic();
670     return G_SOURCE_REMOVE;
671 }
672 
invalidate_cursor_periodic()673 void VteTerminalPrivate::invalidate_cursor_periodic()
674 {
675     m_cursor_blink_state = !m_cursor_blink_state;
676     m_cursor_blink_time += m_cursor_blink_cycle;
677 
678     m_cursor_blink_tag = 0;
679     invalidate_cursor_once(true);
680 
681     /* only disable the blink if the cursor is currently shown.
682      * else, wait until next time.
683      */
684     if (m_cursor_blink_time / 1000 >= m_cursor_blink_timeout && m_cursor_blink_state) {
685         return;
686     }
687 
688     m_cursor_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
689                                                 m_cursor_blink_cycle,
690                                                 (GSourceFunc)invalidate_cursor_periodic_cb,
691                                                 this,
692                                                 NULL);
693 }
694 
695 /* Emit a "selection_changed" signal. */
emit_selection_changed()696 void VteTerminalPrivate::emit_selection_changed()
697 {
698     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `selection-changed'.\n");
699     g_signal_emit(m_terminal, signals[SIGNAL_SELECTION_CHANGED], 0);
700 }
701 
702 /* Emit a "commit" signal. */
emit_commit(char const * text,gssize length)703 void VteTerminalPrivate::emit_commit(char const* text, gssize length)
704 {
705     char const* result = NULL;
706     char *wrapped = NULL;
707 
708     _vte_debug_print(VTE_DEBUG_SIGNALS,
709             "Emitting `commit' of %" G_GSSIZE_FORMAT" bytes.\n", length);
710 
711     if (length == -1) {
712         length = strlen(text);
713         result = text;
714     } else {
715         /* FIXMEchpe why use the slice allocator here? */
716         result = wrapped = (char *) g_slice_alloc(length + 1);
717         memcpy(wrapped, text, length);
718         wrapped[length] = '\0';
719     }
720 
721     g_signal_emit(m_terminal, signals[SIGNAL_COMMIT], 0, result, (guint)length);
722 
723     if(wrapped) {
724         g_slice_free1(length+1, wrapped);
725     }
726 }
727 
queue_contents_changed()728 void VteTerminalPrivate::queue_contents_changed()
729 {
730     _vte_debug_print(VTE_DEBUG_SIGNALS, "Queueing `contents-changed'.\n");
731     m_contents_changed_pending = true;
732 }
733 
734 /* FIXMEchpe this has only one caller */
queue_cursor_moved()735 void VteTerminalPrivate::queue_cursor_moved()
736 {
737     _vte_debug_print(VTE_DEBUG_SIGNALS, "Queueing `cursor-moved'.\n");
738     m_cursor_moved_pending = true;
739 }
740 
emit_eof_idle_cb(VteTerminal * terminal)741 static gboolean emit_eof_idle_cb(VteTerminal *terminal)
742 {
743     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
744     gdk_threads_enter ();
745     G_GNUC_END_IGNORE_DEPRECATIONS;
746 
747     _vte_terminal_get_impl(terminal)->emit_eof();
748 
749     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
750     gdk_threads_leave ();
751     G_GNUC_END_IGNORE_DEPRECATIONS;
752 
753     return G_SOURCE_REMOVE;
754 }
755 
emit_eof()756 void VteTerminalPrivate::emit_eof()
757 {
758     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `eof'.\n");
759     g_signal_emit(m_terminal, signals[SIGNAL_EOF], 0);
760 }
761 
762 /* Emit a "eof" signal. */
763 /* FIXMEchpe any particular reason not to handle this immediately? */
queue_eof()764 void VteTerminalPrivate::queue_eof()
765 {
766         _vte_debug_print(VTE_DEBUG_SIGNALS, "Queueing `eof'.\n");
767         g_idle_add_full(G_PRIORITY_HIGH,
768                         (GSourceFunc)emit_eof_idle_cb,
769                         g_object_ref(m_terminal),
770                         g_object_unref);
771 }
772 
773 /* Emit a "char-size-changed" signal. */
emit_char_size_changed(int width,int height)774 void VteTerminalPrivate::emit_char_size_changed(int width, int height)
775 {
776     _vte_debug_print(VTE_DEBUG_SIGNALS,
777             "Emitting `char-size-changed'.\n");
778     /* FIXME on next API break, change the signature */
779     g_signal_emit(m_terminal, signals[SIGNAL_CHAR_SIZE_CHANGED], 0,
780                     (guint)width, (guint)height);
781 }
782 
783 /* Emit an "increase-font-size" signal. */
emit_increase_font_size()784 void VteTerminalPrivate::emit_increase_font_size()
785 {
786     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `increase-font-size'.\n");
787     g_signal_emit(m_terminal, signals[SIGNAL_INCREASE_FONT_SIZE], 0);
788 }
789 
790 /* Emit a "decrease-font-size" signal. */
emit_decrease_font_size()791 void VteTerminalPrivate::emit_decrease_font_size()
792 {
793     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `decrease-font-size'.\n");
794     g_signal_emit(m_terminal, signals[SIGNAL_DECREASE_FONT_SIZE], 0);
795 }
796 
797 /* Emit a "text-inserted" signal. */
emit_text_inserted()798 void VteTerminalPrivate::emit_text_inserted()
799 {
800     if (!m_accessible_emit) {
801         return;
802     }
803     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `text-inserted'.\n");
804     g_signal_emit(m_terminal, signals[SIGNAL_TEXT_INSERTED], 0);
805 }
806 
807 /* Emit a "text-deleted" signal. */
emit_text_deleted()808 void VteTerminalPrivate::emit_text_deleted()
809 {
810     if (!m_accessible_emit) {
811         return;
812     }
813     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `text-deleted'.\n");
814     g_signal_emit(m_terminal, signals[SIGNAL_TEXT_DELETED], 0);
815 }
816 
817 /* Emit a "text-modified" signal. */
emit_text_modified()818 void VteTerminalPrivate::emit_text_modified()
819 {
820     if (!m_accessible_emit) {
821         return;
822     }
823     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `text-modified'.\n");
824     g_signal_emit(m_terminal, signals[SIGNAL_TEXT_MODIFIED], 0);
825 }
826 
827 /* Emit a "text-scrolled" signal. */
emit_text_scrolled(long delta)828 void VteTerminalPrivate::emit_text_scrolled(long delta)
829 {
830     if (!m_accessible_emit) {
831         return;
832     }
833     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `text-scrolled'(%ld).\n", delta);
834     /* FIXMEchpe fix signal signature? */
835     g_signal_emit(m_terminal, signals[SIGNAL_TEXT_SCROLLED], 0, (int)delta);
836 }
837 
emit_copy_clipboard()838 void VteTerminalPrivate::emit_copy_clipboard()
839 {
840     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting 'copy-clipboard'.\n");
841     g_signal_emit(m_terminal, signals[SIGNAL_COPY_CLIPBOARD], 0);
842 }
843 
emit_paste_clipboard()844 void VteTerminalPrivate::emit_paste_clipboard()
845 {
846     _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting 'paste-clipboard'.\n");
847     g_signal_emit(m_terminal, signals[SIGNAL_PASTE_CLIPBOARD], 0);
848 }
849 
850 /* Emit a "hyperlink_hover_uri_changed" signal. */
emit_hyperlink_hover_uri_changed(const GdkRectangle * bbox)851 void VteTerminalPrivate::emit_hyperlink_hover_uri_changed(const GdkRectangle *bbox)
852 {
853         GObject *object = G_OBJECT(m_terminal);
854 
855         _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `hyperlink-hover-uri-changed'.\n");
856         g_signal_emit(m_terminal, signals[SIGNAL_HYPERLINK_HOVER_URI_CHANGED], 0, m_hyperlink_hover_uri, bbox);
857         g_object_notify_by_pspec(object, pspecs[PROP_HYPERLINK_HOVER_URI]);
858 }
859 
deselect_all()860 void VteTerminalPrivate::deselect_all()
861 {
862     if (m_has_selection) {
863         gint sx, sy, ex, ey, extra;
864 
865         _vte_debug_print(VTE_DEBUG_SELECTION, "Deselecting all text.\n");
866 
867         m_has_selection = FALSE;
868         /* Don't free the current selection, as we need to keep
869          * hold of it for async copying from the clipboard. */
870 
871         emit_selection_changed();
872 
873         sx = m_selection_start.col;
874         sy = m_selection_start.row;
875         ex = m_selection_end.col;
876         ey = m_selection_end.row;
877         extra = m_selection_block_mode ? (VTE_TAB_WIDTH_MAX - 1) : 0;
878         invalidate_region(MIN (sx, ex), MAX (sx, ex) + extra,
879                             MIN (sy, ey),   MAX (sy, ey),
880                             false);
881     }
882 }
883 
884 /* FIXMEchpe make m_tabstops a hashset */
885 
886 /* Remove a tabstop. */
clear_tabstop(int column)887 void VteTerminalPrivate::clear_tabstop(int column)
888 {
889     if (m_tabstops) {
890         /* Remove a tab stop from the hash table. */
891         g_hash_table_remove(m_tabstops, GINT_TO_POINTER(2 * column + 1));
892     }
893 }
894 
895 /* Check if we have a tabstop at a given position. */
get_tabstop(int column)896 bool VteTerminalPrivate::get_tabstop(int column)
897 {
898     if (m_tabstops != NULL) {
899         auto hash = g_hash_table_lookup(m_tabstops, GINT_TO_POINTER(2 * column + 1));
900         return hash != nullptr;
901     }
902 
903     return false;
904 }
905 
906 /* Reset the set of tab stops to the default. */
set_tabstop(int column)907 void VteTerminalPrivate::set_tabstop(int column)
908 {
909     if (m_tabstops != NULL) {
910         /* Just set a non-NULL pointer for this column number. */
911         g_hash_table_insert(m_tabstops, GINT_TO_POINTER(2 * column + 1), m_terminal);
912     }
913 }
914 
915 /* Reset the set of tab stops to the default. */
set_default_tabstops()916 void VteTerminalPrivate::set_default_tabstops()
917 {
918     if (m_tabstops) {
919         g_hash_table_destroy(m_tabstops);
920     }
921     m_tabstops = g_hash_table_new(nullptr, nullptr);
922     for (int i = 0; i <= VTE_TAB_MAX; i += VTE_TAB_WIDTH) {
923         set_tabstop(i);
924     }
925 }
926 
927 /* Clear the cache of the screen contents we keep. */
match_contents_clear()928 void VteTerminalPrivate::match_contents_clear()
929 {
930     match_hilite_clear();
931     if (m_match_contents != nullptr) {
932         g_free(m_match_contents);
933         m_match_contents = nullptr;
934     }
935     if (m_match_attributes != nullptr) {
936         g_array_free(m_match_attributes, TRUE);
937         m_match_attributes = nullptr;
938     }
939 }
940 
match_contents_refresh()941 void VteTerminalPrivate::match_contents_refresh()
942 {
943     match_contents_clear();
944     GArray *array = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
945     auto match_contents = get_text_displayed(true /* wrap */,
946                                                 false /* include trailing whitespace */,
947                                                 array);
948     m_match_contents = g_string_free(match_contents, FALSE);
949     m_match_attributes = array;
950 }
951 
widget_cursor_new(GdkCursorType cursor_type) const952 GdkCursor *VteTerminalPrivate::widget_cursor_new(GdkCursorType cursor_type) const
953 {
954     return gdk_cursor_new_for_display(gtk_widget_get_display(m_widget), cursor_type);
955 }
956 
957 /*
958  * VteTerminalPrivate::view_coords_from_event:
959  * @event: a #GdkEvent
960  *
961  * Translates the event coordinates to view coordinates, by
962  * subtracting the padding and window offset.
963  * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
964  * mean that the event coordinates are outside the usable area
965  * at that side; use view_coords_visible() to check for that.
966  */
view_coords_from_event(GdkEvent const * event) const967 vte::view::coords VteTerminalPrivate::view_coords_from_event(GdkEvent const* event) const
968 {
969     double x, y;
970     if (event == nullptr ||
971         ((reinterpret_cast<GdkEventAny const*>(event))->window != m_event_window) ||
972         !gdk_event_get_coords(event, &x, &y)) {
973         return vte::view::coords(-1, -1);
974     }
975     return vte::view::coords(x - m_padding.left, y - m_padding.top);
976 }
977 
978 /*
979  * VteTerminalPrivate::grid_coords_from_event:
980  * @event: a #GdkEvent
981  *
982  * Translates the event coordinates to view coordinates, by
983  * subtracting the padding and window offset.
984  * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
985  * mean that the event coordinates are outside the usable area
986  * at that side; use grid_coords_visible() to check for that.
987  */
grid_coords_from_event(GdkEvent const * event) const988 vte::grid::coords VteTerminalPrivate::grid_coords_from_event(GdkEvent const* event) const
989 {
990     return grid_coords_from_view_coords(view_coords_from_event(event));
991 }
992 
993 /*
994  * VteTerminalPrivate::confined_grid_coords_from_event:
995  * @event: a #GdkEvent
996  *
997  * Like grid_coords_from_event(), but also confines the coordinates
998  * to an actual cell in the visible area.
999  */
confined_grid_coords_from_event(GdkEvent const * event) const1000 vte::grid::coords VteTerminalPrivate::confined_grid_coords_from_event(GdkEvent const* event) const
1001 {
1002     auto pos = view_coords_from_event(event);
1003     return confined_grid_coords_from_view_coords(pos);
1004 }
1005 
1006 /*
1007  * VteTerminalPrivate::grid_coords_from_view_coords:
1008  * @pos: the view coordinates
1009  *
1010  * Translates view coordinates to grid coordinates. If the view coordinates point to
1011  * cells that are not visible, may return any value < 0 or >= m_column_count, and
1012  * < first_displayed_row() or > last_displayed_row(), resp.
1013  */
grid_coords_from_view_coords(vte::view::coords const & pos) const1014 vte::grid::coords VteTerminalPrivate::grid_coords_from_view_coords(vte::view::coords const& pos) const
1015 {
1016     vte::grid::column_t col;
1017     if (pos.x >= 0 && pos.x < m_view_usable_extents.width()) {
1018         col = pos.x / m_cell_width;
1019     } else if (pos.x < 0) {
1020         col = -1;
1021     } else {
1022         col = m_column_count;
1023     }
1024 
1025     vte::grid::row_t row = pixel_to_row(pos.y);
1026 
1027     return vte::grid::coords(row, col);
1028 }
1029 
1030 /*
1031  * VteTerminalPrivate::confined_grid_coords_from_view_coords:
1032  * @pos: the view coordinates
1033  *
1034  * Like grid_coords_from_view_coords(), but also confines the coordinates
1035  * to an actual cell in the visible area.
1036  */
confined_grid_coords_from_view_coords(vte::view::coords const & pos) const1037 vte::grid::coords VteTerminalPrivate::confined_grid_coords_from_view_coords(vte::view::coords const& pos) const
1038 {
1039     auto rowcol = grid_coords_from_view_coords(pos);
1040     return confine_grid_coords(rowcol);
1041 }
1042 
1043 /*
1044  * VteTerminalPrivate::view_coords_from_grid_coords:
1045  * @rowcol: the grid coordinates
1046  *
1047  * Translates grid coordinates to view coordinates. If the view coordinates are
1048  * outside the usable area, may return any value < 0 or >= m_usable_view_extents.
1049  *
1050  * Returns: %true if the coordinates are inside the usable area
1051  */
view_coords_from_grid_coords(vte::grid::coords const & rowcol) const1052 vte::view::coords VteTerminalPrivate::view_coords_from_grid_coords(vte::grid::coords const& rowcol) const
1053 {
1054     return vte::view::coords(rowcol.column() * m_cell_width, row_to_pixel(rowcol.row()));
1055 }
1056 
view_coords_visible(vte::view::coords const & pos) const1057 bool VteTerminalPrivate::view_coords_visible(vte::view::coords const& pos) const
1058 {
1059     return pos.x >= 0 && pos.x < m_view_usable_extents.width() &&
1060             pos.y >= 0 && pos.y < m_view_usable_extents.height();
1061 }
1062 
grid_coords_visible(vte::grid::coords const & rowcol) const1063 bool VteTerminalPrivate::grid_coords_visible(vte::grid::coords const& rowcol) const
1064 {
1065     return rowcol.column() >= 0 &&
1066             rowcol.column() < m_column_count &&
1067             rowcol.row() >= first_displayed_row() &&
1068             rowcol.row() <= last_displayed_row();
1069 }
1070 
confine_grid_coords(vte::grid::coords const & rowcol) const1071 vte::grid::coords VteTerminalPrivate::confine_grid_coords(vte::grid::coords const& rowcol) const
1072 {
1073     /* Confine clicks to the nearest actual cell. This is especially useful for
1074         * fullscreen vte so that you can click on the very edge of the screen.
1075         */
1076     auto first_row = first_displayed_row();
1077     auto last_row = last_displayed_row();
1078 
1079     return vte::grid::coords(CLAMP(rowcol.row(), first_row, last_row),
1080                                 CLAMP(rowcol.column(), 0, m_column_count - 1));
1081 }
1082 
rowcol_from_event(GdkEvent * event,long * column,long * row)1083 bool VteTerminalPrivate::rowcol_from_event(GdkEvent *event, long *column, long *row)
1084 {
1085     auto rowcol = grid_coords_from_event(event);
1086     if (!grid_coords_visible(rowcol)) {
1087         return false;
1088     }
1089 
1090     *column = rowcol.column();
1091     *row = rowcol.row();
1092     return true;
1093 }
1094 
hyperlink_check(GdkEvent * event)1095 char *VteTerminalPrivate::hyperlink_check(GdkEvent *event)
1096 {
1097     long col, row;
1098     const char *hyperlink;
1099     const char *separator;
1100 
1101     if (!m_allow_hyperlink || !rowcol_from_event(event, &col, &row)) {
1102         return NULL;
1103     }
1104 
1105     _vte_ring_get_hyperlink_at_position(m_screen->row_data, row, col, false, &hyperlink);
1106 
1107     if (hyperlink != NULL) {
1108         /* URI is after the first semicolon */
1109         separator = strchr(hyperlink, ';');
1110         g_assert(separator != NULL);
1111         hyperlink = separator + 1;
1112     }
1113 
1114     _vte_debug_print (VTE_DEBUG_HYPERLINK,
1115                         "hyperlink_check: \"%s\"\n",
1116                         hyperlink);
1117 
1118     return g_strdup(hyperlink);
1119 }
1120 
1121 
1122 /* Emit an adjustment changed signal on our adjustment object. */
emit_adjustment_changed()1123 void VteTerminalPrivate::emit_adjustment_changed()
1124 {
1125     if (m_adjustment_changed_pending) {
1126         bool changed = false;
1127         gdouble current, v;
1128 
1129         g_object_freeze_notify (G_OBJECT(m_vadjustment));
1130 
1131         v = _vte_ring_delta (m_screen->row_data);
1132         current = gtk_adjustment_get_lower(m_vadjustment);
1133         if (!_vte_double_equal(current, v)) {
1134             _vte_debug_print(VTE_DEBUG_ADJ,
1135                     "Changing lower bound from %.0f to %f\n",
1136                      current, v);
1137             gtk_adjustment_set_lower(m_vadjustment, v);
1138             changed = true;
1139         }
1140 
1141         v = m_screen->insert_delta + m_row_count;
1142         current = gtk_adjustment_get_upper(m_vadjustment);
1143         if (!_vte_double_equal(current, v)) {
1144             _vte_debug_print(VTE_DEBUG_ADJ,
1145                     "Changing upper bound from %.0f to %f\n",
1146                      current, v);
1147             gtk_adjustment_set_upper(m_vadjustment, v);
1148             changed = true;
1149         }
1150 
1151         /* The step increment should always be one. */
1152         v = gtk_adjustment_get_step_increment(m_vadjustment);
1153         if (!_vte_double_equal(v, 1)) {
1154             _vte_debug_print(VTE_DEBUG_ADJ,
1155                     "Changing step increment from %.0lf to 1\n", v);
1156             gtk_adjustment_set_step_increment(m_vadjustment, 1);
1157             changed = true;
1158         }
1159 
1160         /* Set the number of rows the user sees to the number of rows the
1161          * user sees. */
1162         v = gtk_adjustment_get_page_size(m_vadjustment);
1163         if (!_vte_double_equal(v, m_row_count)) {
1164             _vte_debug_print(VTE_DEBUG_ADJ,
1165                     "Changing page size from %.0f to %ld\n",
1166                      v, m_row_count);
1167             gtk_adjustment_set_page_size(m_vadjustment,
1168                              m_row_count);
1169             changed = true;
1170         }
1171 
1172         /* Clicking in the empty area should scroll one screen, so set the
1173          * page size to the number of visible rows. */
1174         v = gtk_adjustment_get_page_increment(m_vadjustment);
1175         if (!_vte_double_equal(v, m_row_count)) {
1176             _vte_debug_print(VTE_DEBUG_ADJ,
1177                     "Changing page increment from "
1178                     "%.0f to %ld\n",
1179                     v, m_row_count);
1180             gtk_adjustment_set_page_increment(m_vadjustment,
1181                               m_row_count);
1182             changed = true;
1183         }
1184 
1185         g_object_thaw_notify (G_OBJECT (m_vadjustment));
1186 
1187         if (changed) {
1188             _vte_debug_print(VTE_DEBUG_SIGNALS,
1189                     "Emitting adjustment_changed.\n");
1190         }
1191         m_adjustment_changed_pending = FALSE;
1192     }
1193     if (m_adjustment_value_changed_pending) {
1194         double v, delta;
1195         _vte_debug_print(VTE_DEBUG_SIGNALS,
1196                 "Emitting adjustment_value_changed.\n");
1197         m_adjustment_value_changed_pending = FALSE;
1198         v = gtk_adjustment_get_value(m_vadjustment);
1199         if (!_vte_double_equal(v, m_screen->scroll_delta)) {
1200             /* this little dance is so that the scroll_delta is
1201              * updated immediately, but we still handled scrolling
1202              * via the adjustment - e.g. user interaction with the
1203              * scrollbar
1204              */
1205             delta = m_screen->scroll_delta;
1206             m_screen->scroll_delta = v;
1207             gtk_adjustment_set_value(m_vadjustment, delta);
1208         }
1209     }
1210 }
1211 
1212 /* Queue an adjustment-changed signal to be delivered when convenient. */
1213 /* FIXMEchpe this has just one caller, fold it into the call site */
queue_adjustment_changed()1214 void VteTerminalPrivate::queue_adjustment_changed()
1215 {
1216     m_adjustment_changed_pending = true;
1217     add_update_timeout(this);
1218 }
1219 
queue_adjustment_value_changed(double v)1220 void VteTerminalPrivate::queue_adjustment_value_changed(double v)
1221 {
1222     if (!_vte_double_equal(v, m_screen->scroll_delta)) {
1223         _vte_debug_print(VTE_DEBUG_ADJ,
1224                             "Adjustment value changed to %f\n",
1225                             v);
1226         m_screen->scroll_delta = v;
1227         m_adjustment_value_changed_pending = true;
1228         add_update_timeout(this);
1229     }
1230 }
1231 
queue_adjustment_value_changed_clamped(double v)1232 void VteTerminalPrivate::queue_adjustment_value_changed_clamped(double v)
1233 {
1234     double lower = gtk_adjustment_get_lower(m_vadjustment);
1235     double upper = gtk_adjustment_get_upper(m_vadjustment);
1236 
1237     v = CLAMP(v, lower, MAX (lower, upper - m_row_count));
1238 
1239     queue_adjustment_value_changed(v);
1240 }
1241 
adjust_adjustments()1242 void VteTerminalPrivate::adjust_adjustments()
1243 {
1244     g_assert(m_screen != nullptr);
1245 
1246     queue_adjustment_changed();
1247 
1248     /* The lower value should be the first row in the buffer. */
1249     long delta = _vte_ring_delta(m_screen->row_data);
1250     /* Snap the insert delta and the cursor position to be in the visible
1251      * area.  Leave the scrolling delta alone because it will be updated
1252      * when the adjustment changes. */
1253     m_screen->insert_delta = MAX(m_screen->insert_delta, delta);
1254     m_screen->cursor.row = MAX(m_screen->cursor.row,
1255                                 m_screen->insert_delta);
1256 
1257     if (m_screen->scroll_delta > m_screen->insert_delta) {
1258         queue_adjustment_value_changed(m_screen->insert_delta);
1259     }
1260 }
1261 
1262 /* Update the adjustment field of the widget.  This function should be called
1263  * whenever we add rows to or remove rows from the history or switch screens. */
adjust_adjustments_full()1264 void VteTerminalPrivate::adjust_adjustments_full()
1265 {
1266     g_assert(m_screen != NULL);
1267     g_assert(m_screen->row_data != NULL);
1268 
1269     adjust_adjustments();
1270     queue_adjustment_changed();
1271 }
1272 
1273 /* Scroll a fixed number of lines up or down in the current screen. */
scroll_lines(long lines)1274 void VteTerminalPrivate::scroll_lines(long lines)
1275 {
1276     double destination;
1277     _vte_debug_print(VTE_DEBUG_ADJ, "Scrolling %ld lines.\n", lines);
1278     /* Calculate the ideal position where we want to be before clamping. */
1279     destination = m_screen->scroll_delta;
1280     /* Snap to whole cell offset. */
1281     if (lines > 0) {
1282         destination = floor(destination);
1283     } else if (lines < 0) {
1284         destination = ceil(destination);
1285     }
1286     destination += lines;
1287     /* Tell the scrollbar to adjust itself. */
1288     queue_adjustment_value_changed_clamped(destination);
1289 }
1290 
1291 /* Scroll so that the scroll delta is the minimum value. */
maybe_scroll_to_top()1292 void VteTerminalPrivate::maybe_scroll_to_top()
1293 {
1294     queue_adjustment_value_changed(_vte_ring_delta(m_screen->row_data));
1295 }
1296 
maybe_scroll_to_bottom()1297 void VteTerminalPrivate::maybe_scroll_to_bottom()
1298 {
1299     queue_adjustment_value_changed(m_screen->insert_delta);
1300     _vte_debug_print(VTE_DEBUG_ADJ,
1301                         "Snapping to bottom of screen\n");
1302 }
1303 
1304 /*
1305  * VteTerminalPrivate::set_encoding:
1306  * @codeset: (allow-none): a valid #GIConv target, or %NULL to use UTF-8
1307  *
1308  * Changes the encoding the terminal will expect data from the child to
1309  * be encoded with.  For certain terminal types, applications executing in the
1310  * terminal can change the encoding. If @codeset is %NULL, it uses "UTF-8".
1311  *
1312  * Returns: %true if the encoding could be changed to the specified one
1313  */
set_encoding(char const * codeset)1314 bool VteTerminalPrivate::set_encoding(char const* codeset)
1315 {
1316     VteConv conv;
1317 
1318     GObject *object = G_OBJECT(m_terminal);
1319 
1320     if (codeset == NULL) {
1321         codeset = "UTF-8";
1322     }
1323     if ((m_encoding != nullptr) && g_str_equal(codeset, m_encoding)) {
1324         /* Nothing to do! */
1325         return true;
1326     }
1327 
1328     /* Open new conversions. */
1329     conv = _vte_conv_open(codeset, "UTF-8");
1330     if (conv == VTE_INVALID_CONV) {
1331         return false;
1332     }
1333 
1334     auto old_codeset = m_encoding;
1335 
1336     g_object_freeze_notify(object);
1337 
1338     if (m_outgoing_conv != VTE_INVALID_CONV) {
1339         _vte_conv_close(m_outgoing_conv);
1340     }
1341     m_outgoing_conv = conv;
1342 
1343     /* Set the terminal's encoding to the new value. */
1344     m_encoding = g_intern_string(codeset);
1345 
1346     /* Convert any buffered output bytes. */
1347     if ((_vte_byte_array_length(m_outgoing) > 0) && (old_codeset != nullptr)) {
1348         char *obuf1, *obuf2;
1349         gsize bytes_written;
1350 
1351         /* Convert back to UTF-8. */
1352         obuf1 = g_convert((char *)m_outgoing->data,
1353                             _vte_byte_array_length(m_outgoing),
1354                             "UTF-8",
1355                             old_codeset,
1356                             NULL,
1357                             &bytes_written,
1358                             NULL);
1359         if (obuf1 != NULL) {
1360             /* Convert to the new encoding. */
1361             obuf2 = g_convert(obuf1,
1362                                 bytes_written,
1363                                 codeset,
1364                                 "UTF-8",
1365                                 NULL,
1366                                 &bytes_written,
1367                                 NULL);
1368             if (obuf2 != NULL) {
1369                 _vte_byte_array_clear(m_outgoing);
1370                 _vte_byte_array_append(m_outgoing, obuf2, bytes_written);
1371                 g_free(obuf2);
1372             }
1373             g_free(obuf1);
1374         }
1375     }
1376 
1377     /* Set the encoding for incoming text. */
1378     _vte_iso2022_state_set_codeset(m_iso2022, m_encoding);
1379 
1380     _vte_debug_print(VTE_DEBUG_IO,
1381                         "Set terminal encoding to `%s'.\n",
1382                         m_encoding);
1383     _vte_debug_print(VTE_DEBUG_SIGNALS,
1384                         "Emitting `encoding-changed'.\n");
1385     g_signal_emit(object, signals[SIGNAL_ENCODING_CHANGED], 0);
1386     g_object_notify_by_pspec(object, pspecs[PROP_ENCODING]);
1387 
1388     g_object_thaw_notify(object);
1389 
1390     return true;
1391 }
1392 
set_cjk_ambiguous_width(int width)1393 bool VteTerminalPrivate::set_cjk_ambiguous_width(int width)
1394 {
1395     g_assert(width == 1 || width == 2);
1396 
1397     if (m_utf8_ambiguous_width == width) {
1398         return false;
1399     }
1400 
1401     m_utf8_ambiguous_width = width;
1402     return true;
1403 }
1404 
1405 /* FIXMEchpe replace this with a method on VteRing */
insert_rows(guint cnt)1406 VteRowData *VteTerminalPrivate::insert_rows (guint cnt)
1407 {
1408     VteRowData *row;
1409     do {
1410         row = ring_append(false);
1411     } while(--cnt);
1412     return row;
1413 }
1414 
1415 /* Make sure we have enough rows and columns to hold data at the current
1416  * cursor position. */
ensure_row()1417 VteRowData *VteTerminalPrivate::ensure_row()
1418 {
1419     VteRowData *row;
1420 
1421     /* Figure out how many rows we need to add. */
1422         //FIXMEchpe use long, not int
1423     int delta = m_screen->cursor.row - _vte_ring_next(m_screen->row_data) + 1;
1424     if (delta > 0) {
1425         row = insert_rows(delta);
1426         adjust_adjustments();
1427     } else {
1428         /* Find the row the cursor is in. */
1429         row = _vte_ring_index_writable(m_screen->row_data, m_screen->cursor.row);
1430     }
1431     g_assert(row != NULL);
1432 
1433     return row;
1434 }
1435 
ensure_cursor()1436 VteRowData *VteTerminalPrivate::ensure_cursor()
1437 {
1438     VteRowData *row = ensure_row();
1439     _vte_row_data_fill(row, &basic_cell, m_screen->cursor.col);
1440 
1441     return row;
1442 }
1443 
1444 /* Update the insert delta so that the screen which includes it also
1445  * includes the end of the buffer. */
update_insert_delta()1446 void VteTerminalPrivate::update_insert_delta()
1447 {
1448     /* The total number of lines.  Add one to the cursor offset
1449      * because it's zero-based. */
1450     auto rows = _vte_ring_next(m_screen->row_data);
1451     auto delta = m_screen->cursor.row - rows + 1;
1452     if (G_UNLIKELY (delta > 0)) {
1453         insert_rows(delta);
1454         rows = _vte_ring_next(m_screen->row_data);
1455     }
1456 
1457     /* Make sure that the bottom row is visible, and that it's in
1458      * the buffer (even if it's empty).  This usually causes the
1459      * top row to become a history-only row. */
1460     delta = m_screen->insert_delta;
1461     delta = MIN(delta, rows - m_row_count);
1462     delta = MAX(delta, m_screen->cursor.row - (m_row_count - 1));
1463     delta = MAX(delta, _vte_ring_delta(m_screen->row_data));
1464 
1465     /* Adjust the insert delta and scroll if needed. */
1466     if (delta != m_screen->insert_delta) {
1467         m_screen->insert_delta = delta;
1468         adjust_adjustments();
1469     }
1470 }
1471 
1472 /* Apply the desired mouse pointer, based on certain member variables. */
apply_mouse_cursor()1473 void VteTerminalPrivate::apply_mouse_cursor()
1474 {
1475     if (!widget_realized()) {
1476         return;
1477     }
1478 
1479    /* Show the cursor if over the widget and not autohidden, this is obvious.
1480     * Also show the cursor if outside the widget regardless of the autohidden state, so that if a popover is opened
1481     * and then the cursor returns (which doesn't trigger enter/motion events), it is visible.
1482     * That is, only hide the cursor if it's over the widget and is autohidden.
1483     * See bug 789390 and bug 789536 comment 6 for details. */
1484     if (!(m_mouse_autohide && m_mouse_cursor_autohidden && m_mouse_cursor_over_widget)) {
1485         if (m_hyperlink_hover_idx != 0) {
1486             _vte_debug_print(VTE_DEBUG_CURSOR, "Setting hyperlink mouse cursor.\n");
1487             gdk_window_set_cursor(m_event_window, m_mouse_hyperlink_cursor);
1488         } else if (m_mouse_tracking_mode) {
1489             _vte_debug_print(VTE_DEBUG_CURSOR, "Setting mousing cursor.\n");
1490             gdk_window_set_cursor(m_event_window, m_mouse_mousing_cursor);
1491         } else {
1492             _vte_debug_print(VTE_DEBUG_CURSOR, "Setting default mouse cursor.\n");
1493             gdk_window_set_cursor(m_event_window, m_mouse_default_cursor);
1494         }
1495     } else {
1496         _vte_debug_print(VTE_DEBUG_CURSOR, "Setting to invisible cursor.\n");
1497         gdk_window_set_cursor(m_event_window, m_mouse_inviso_cursor);
1498     }
1499 }
1500 
1501 /* Show or hide the pointer if autohiding is enabled. */
set_pointer_autohidden(bool autohidden)1502 void VteTerminalPrivate::set_pointer_autohidden(bool autohidden)
1503 {
1504     if (autohidden == m_mouse_cursor_autohidden) {
1505         return;
1506     }
1507 
1508     m_mouse_cursor_autohidden = autohidden;
1509 
1510     if (m_mouse_autohide) {
1511         hyperlink_hilite_update();
1512         apply_mouse_cursor();
1513     }
1514 }
1515 
1516 /*
1517  * Get the actually used color from the palette.
1518  * The return value can be NULL only if entry is one of VTE_CURSOR_BG,
1519  * VTE_CURSOR_FG, VTE_HIGHLIGHT_BG or VTE_HIGHLIGHT_FG.
1520  */
get_color(int entry) const1521 vte::color::rgb const* VteTerminalPrivate::get_color(int entry) const
1522 {
1523     VtePaletteColor const* palette_color = &m_palette[entry];
1524     guint source;
1525     for (source = 0; source < G_N_ELEMENTS(palette_color->sources); source++) {
1526         if (palette_color->sources[source].is_set) {
1527             return &palette_color->sources[source].color;
1528         }
1529     }
1530     return nullptr;
1531 }
1532 
1533 /* Set up a palette entry with a more-or-less match for the requested color. */
set_color(int entry,int source,vte::color::rgb const & proposed)1534 void VteTerminalPrivate::set_color(int entry, int source, vte::color::rgb const& proposed)
1535 {
1536     g_assert(entry >= 0 && entry < VTE_PALETTE_SIZE);
1537 
1538     VtePaletteColor *palette_color = &m_palette[entry];
1539 
1540     _vte_debug_print(VTE_DEBUG_MISC,
1541                         "Set %s color[%d] to (%04x,%04x,%04x).\n",
1542                         source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
1543                         entry, proposed.red, proposed.green, proposed.blue);
1544 
1545     if (palette_color->sources[source].is_set &&
1546         palette_color->sources[source].color == proposed) {
1547         return;
1548     }
1549     palette_color->sources[source].is_set = TRUE;
1550     palette_color->sources[source].color = proposed;
1551 
1552     /* If we're not realized yet, there's nothing else to do. */
1553     if (!widget_realized()) {
1554         return;
1555     }
1556 
1557     /* and redraw */
1558     if (entry == VTE_CURSOR_BG || entry == VTE_CURSOR_FG) {
1559         invalidate_cursor_once();
1560     } else {
1561         invalidate_all();
1562     }
1563 }
1564 
reset_color(int entry,int source)1565 void VteTerminalPrivate::reset_color(int entry, int source)
1566 {
1567     g_assert(entry >= 0 && entry < VTE_PALETTE_SIZE);
1568 
1569     VtePaletteColor *palette_color = &m_palette[entry];
1570 
1571     _vte_debug_print(VTE_DEBUG_MISC,
1572                         "Reset %s color[%d].\n",
1573                         source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
1574                         entry);
1575 
1576     if (!palette_color->sources[source].is_set) {
1577         return;
1578     }
1579     palette_color->sources[source].is_set = FALSE;
1580 
1581     /* If we're not realized yet, there's nothing else to do. */
1582     if (!widget_realized()) {
1583         return;
1584     }
1585 
1586     /* and redraw */
1587     if (entry == VTE_CURSOR_BG || entry == VTE_CURSOR_FG) {
1588         invalidate_cursor_once();
1589     } else {
1590         invalidate_all();
1591     }
1592 }
1593 
set_background_alpha(double alpha)1594 bool VteTerminalPrivate::set_background_alpha(double alpha)
1595 {
1596     g_assert(alpha >= 0. && alpha <= 1.);
1597 
1598     if (_vte_double_equal(alpha, m_background_alpha)) {
1599         return false;
1600     }
1601 
1602     _vte_debug_print(VTE_DEBUG_MISC, "Setting background alpha to %.3f\n", alpha);
1603     m_background_alpha = alpha;
1604 
1605     invalidate_all();
1606 
1607     return true;
1608 }
1609 
set_colors_default()1610 void VteTerminalPrivate::set_colors_default()
1611 {
1612     set_colors(nullptr, nullptr, nullptr, 0);
1613 }
1614 
1615 /*
1616  * VteTerminalPrivate::set_colors:
1617  * @terminal: a #VteTerminal
1618  * @foreground: (allow-none): the new foreground color, or %NULL
1619  * @background: (allow-none): the new background color, or %NULL
1620  * @palette: (array length=palette_size zero-terminated=0): the color palette
1621  * @palette_size: the number of entries in @palette
1622  *
1623  * @palette specifies the new values for the 256 palette colors: 8 standard colors,
1624  * their 8 bright counterparts, 6x6x6 color cube, and 24 grayscale colors.
1625  * Omitted entries will default to a hardcoded value.
1626  *
1627  * @palette_size must be 0, 8, 16, 232 or 256.
1628  *
1629  * If @foreground is %NULL and @palette_size is greater than 0, the new foreground
1630  * color is taken from @palette[7].  If @background is %NULL and @palette_size is
1631  * greater than 0, the new background color is taken from @palette[0].
1632  */
set_colors(vte::color::rgb const * foreground,vte::color::rgb const * background,vte::color::rgb const * new_palette,gsize palette_size)1633 void VteTerminalPrivate::set_colors(vte::color::rgb const* foreground,
1634                                     vte::color::rgb const* background,
1635                                     vte::color::rgb const* new_palette,
1636                                     gsize palette_size)
1637 {
1638     _vte_debug_print(VTE_DEBUG_MISC,
1639             "Set color palette [%" G_GSIZE_FORMAT " elements].\n",
1640             palette_size);
1641 
1642     /* Accept NULL as the default foreground and background colors if we
1643      * got a palette. */
1644     if ((foreground == NULL) && (palette_size >= 8)) {
1645         foreground = &new_palette[7];
1646     }
1647     if ((background == NULL) && (palette_size >= 8)) {
1648         background = &new_palette[0];
1649     }
1650 
1651     /* Initialize each item in the palette if we got any entries to work
1652      * with. */
1653     for (gsize i = 0; i < G_N_ELEMENTS(m_palette); i++) {
1654         vte::color::rgb color;
1655         bool unset = false;
1656 
1657         if (i < 16) {
1658             color.blue = (i & 4) ? 0xc000 : 0;
1659             color.green = (i & 2) ? 0xc000 : 0;
1660             color.red = (i & 1) ? 0xc000 : 0;
1661             if (i > 7) {
1662                 color.blue += 0x3fff;
1663                 color.green += 0x3fff;
1664                 color.red += 0x3fff;
1665             }
1666         } else if (i < 232) {
1667             int j = i - 16;
1668             int r = j / 36, g = (j / 6) % 6, b = j % 6;
1669             int red =   (r == 0) ? 0 : r * 40 + 55;
1670             int green = (g == 0) ? 0 : g * 40 + 55;
1671             int blue =  (b == 0) ? 0 : b * 40 + 55;
1672             color.red   = red | red << 8  ;
1673             color.green = green | green << 8;
1674             color.blue  = blue | blue << 8;
1675         } else if (i < 256) {
1676             int shade = 8 + (i - 232) * 10;
1677             color.red = color.green = color.blue = shade | shade << 8;
1678         } else switch (i) {
1679             case VTE_DEFAULT_BG:
1680                 if (background) {
1681                     color = *background;
1682                 } else {
1683                     color.red = 0;
1684                     color.blue = 0;
1685                     color.green = 0;
1686                 }
1687                 break;
1688             case VTE_DEFAULT_FG:
1689                 if (foreground) {
1690                     color = *foreground;
1691                 } else {
1692                     color.red = 0xc000;
1693                     color.blue = 0xc000;
1694                     color.green = 0xc000;
1695                 }
1696                 break;
1697             case VTE_BOLD_FG:
1698                 unset = true;
1699                 break;
1700             case VTE_HIGHLIGHT_BG:
1701                 unset = true;
1702                 break;
1703             case VTE_HIGHLIGHT_FG:
1704                 unset = true;
1705                 break;
1706             case VTE_CURSOR_BG:
1707                 unset = true;
1708                 break;
1709             case VTE_CURSOR_FG:
1710                 unset = true;
1711                 break;
1712             }
1713 
1714         /* Override from the supplied palette if there is one. */
1715         if (i < palette_size) {
1716             color = new_palette[i];
1717         }
1718 
1719         /* Set up the color entry. */
1720         if (unset) {
1721             reset_color(i, VTE_COLOR_SOURCE_API);
1722         } else {
1723             set_color(i, VTE_COLOR_SOURCE_API, color);
1724         }
1725     }
1726 }
1727 
1728 /*
1729  * VteTerminalPrivate::set_color_bold:
1730  * @bold: (allow-none): the new bold color or %NULL
1731  *
1732  * Sets the color used to draw bold text in the default foreground color.
1733  * If @bold is %NULL then the default color is used.
1734  */
set_color_bold(vte::color::rgb const & color)1735 void VteTerminalPrivate::set_color_bold(vte::color::rgb const& color)
1736 {
1737     _vte_debug_print(VTE_DEBUG_MISC,
1738                         "Set %s color to (%04x,%04x,%04x).\n", "bold",
1739                         color.red, color.green, color.blue);
1740     set_color(VTE_BOLD_FG, VTE_COLOR_SOURCE_API, color);
1741 }
1742 
reset_color_bold()1743 void VteTerminalPrivate::reset_color_bold()
1744 {
1745     _vte_debug_print(VTE_DEBUG_MISC,
1746                         "Reset %s color.\n", "bold");
1747     reset_color(VTE_BOLD_FG, VTE_COLOR_SOURCE_API);
1748 }
1749 
1750 /*
1751  * VteTerminalPrivate::set_color_foreground:
1752  * @foreground: the new foreground color
1753  *
1754  * Sets the foreground color used to draw normal text.
1755  */
set_color_foreground(vte::color::rgb const & color)1756 void VteTerminalPrivate::set_color_foreground(vte::color::rgb const& color)
1757 {
1758     _vte_debug_print(VTE_DEBUG_MISC,
1759                         "Set %s color to (%04x,%04x,%04x).\n", "foreground",
1760                         color.red, color.green, color.blue);
1761     set_color(VTE_DEFAULT_FG, VTE_COLOR_SOURCE_API, color);
1762 }
1763 
1764 /*
1765  * VteTerminalPrivate::set_color_background:
1766  * @background: the new background color
1767  *
1768  * Sets the background color for text which does not have a specific background
1769  * color assigned.  Only has effect when no background image is set and when
1770  * the terminal is not transparent.
1771  */
set_color_background(vte::color::rgb const & color)1772 void VteTerminalPrivate::set_color_background(vte::color::rgb const& color)
1773 {
1774     _vte_debug_print(VTE_DEBUG_MISC,
1775                         "Set %s color to (%04x,%04x,%04x).\n", "background",
1776                         color.red, color.green, color.blue);
1777     set_color(VTE_DEFAULT_BG, VTE_COLOR_SOURCE_API, color);
1778 }
1779 
1780 /*
1781  * VteTerminalPrivate::set_color_cursor_background:
1782  * @cursor_background: (allow-none): the new color to use for the text cursor, or %NULL
1783  *
1784  * Sets the background color for text which is under the cursor.  If %NULL, text
1785  * under the cursor will be drawn with foreground and background colors
1786  * reversed.
1787  */
set_color_cursor_background(vte::color::rgb const & color)1788 void VteTerminalPrivate::set_color_cursor_background(vte::color::rgb const& color)
1789 {
1790     _vte_debug_print(VTE_DEBUG_MISC,
1791                         "Set %s color to (%04x,%04x,%04x).\n", "cursor background",
1792                         color.red, color.green, color.blue);
1793     set_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_API, color);
1794 }
1795 
reset_color_cursor_background()1796 void VteTerminalPrivate::reset_color_cursor_background()
1797 {
1798     _vte_debug_print(VTE_DEBUG_MISC,
1799                         "Reset %s color.\n", "cursor background");
1800     reset_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_API);
1801 }
1802 
1803 /*
1804  * VteTerminalPrivate::set_color_cursor_foreground:
1805  * @cursor_foreground: (allow-none): the new color to use for the text cursor, or %NULL
1806  *
1807  * Sets the foreground color for text which is under the cursor.  If %NULL, text
1808  * under the cursor will be drawn with foreground and background colors
1809  * reversed.
1810  */
set_color_cursor_foreground(vte::color::rgb const & color)1811 void VteTerminalPrivate::set_color_cursor_foreground(vte::color::rgb const& color)
1812 {
1813     _vte_debug_print(VTE_DEBUG_MISC,
1814                         "Set %s color to (%04x,%04x,%04x).\n", "cursor foreground",
1815                         color.red, color.green, color.blue);
1816     set_color(VTE_CURSOR_FG, VTE_COLOR_SOURCE_API, color);
1817 }
1818 
reset_color_cursor_foreground()1819 void VteTerminalPrivate::reset_color_cursor_foreground()
1820 {
1821     _vte_debug_print(VTE_DEBUG_MISC,
1822                         "Reset %s color.\n", "cursor foreground");
1823     reset_color(VTE_CURSOR_FG, VTE_COLOR_SOURCE_API);
1824 }
1825 
1826 /*
1827  * VteTerminalPrivate::set_color_highlight_background:
1828  * @highlight_background: (allow-none): the new color to use for highlighted text, or %NULL
1829  *
1830  * Sets the background color for text which is highlighted.  If %NULL,
1831  * it is unset.  If neither highlight background nor highlight foreground are set,
1832  * highlighted text (which is usually highlighted because it is selected) will
1833  * be drawn with foreground and background colors reversed.
1834  */
set_color_highlight_background(vte::color::rgb const & color)1835 void VteTerminalPrivate::set_color_highlight_background(vte::color::rgb const& color)
1836 {
1837     _vte_debug_print(VTE_DEBUG_MISC,
1838                         "Set %s color to (%04x,%04x,%04x).\n", "highlight background",
1839                         color.red, color.green, color.blue);
1840     set_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_API, color);
1841 }
1842 
reset_color_highlight_background()1843 void VteTerminalPrivate::reset_color_highlight_background()
1844 {
1845     _vte_debug_print(VTE_DEBUG_MISC,
1846                         "Reset %s color.\n", "highlight background");
1847     reset_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_API);
1848 }
1849 
1850 /*
1851  * VteTerminalPrivate::set_color_highlight_foreground:
1852  * @highlight_foreground: (allow-none): the new color to use for highlighted text, or %NULL
1853  *
1854  * Sets the foreground color for text which is highlighted.  If %NULL,
1855  * it is unset.  If neither highlight background nor highlight foreground are set,
1856  * highlighted text (which is usually highlighted because it is selected) will
1857  * be drawn with foreground and background colors reversed.
1858  */
set_color_highlight_foreground(vte::color::rgb const & color)1859 void VteTerminalPrivate::set_color_highlight_foreground(vte::color::rgb const& color)
1860 {
1861     _vte_debug_print(VTE_DEBUG_MISC,
1862                         "Set %s color to (%04x,%04x,%04x).\n", "highlight foreground",
1863                         color.red, color.green, color.blue);
1864     set_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_API, color);
1865 }
1866 
reset_color_highlight_foreground()1867 void VteTerminalPrivate::reset_color_highlight_foreground()
1868 {
1869     _vte_debug_print(VTE_DEBUG_MISC,
1870                         "Reset %s color.\n", "highlight foreground");
1871     reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_API);
1872 }
1873 
1874 /*
1875  * VteTerminalPrivate::cleanup_fragments:
1876  * @start: the starting column, inclusive
1877  * @end: the end column, exclusive
1878  *
1879  * Needs to be called before modifying the contents in the cursor's row,
1880  * between the two given columns.  Cleans up TAB and CJK fragments to the
1881  * left of @start and to the right of @end.  If a CJK is split in half,
1882  * the remaining half is replaced by a space.  If a TAB at @start is split,
1883  * it is replaced by spaces.  If a TAB at @end is split, it is replaced by
1884  * a shorter TAB.  @start and @end can be equal if characters will be
1885  * inserted at the location rather than overwritten.
1886  *
1887  * The area between @start and @end is not cleaned up, hence the whole row
1888  * can be left in an inconsistent state.  It is expected that the caller
1889  * will fill up that range afterwards, resulting in a consistent row again.
1890  *
1891  * Invalidates the cells that visually change outside of the range,
1892  * because the caller can't reasonably be expected to take care of this.
1893  */
cleanup_fragments(long start,long end)1894 void VteTerminalPrivate::cleanup_fragments(long start, long end)
1895 {
1896     VteRowData *row = ensure_row();
1897     const VteCell *cell_start;
1898     VteCell *cell_end, *cell_col;
1899     gboolean cell_start_is_fragment;
1900     long col;
1901 
1902     g_assert(end >= start);
1903 
1904     /* Remember whether the cell at start is a fragment.  We'll need to know it when
1905         * handling the left hand side, but handling the right hand side first might
1906         * overwrite it if start == end (inserting to the middle of a character). */
1907     cell_start = _vte_row_data_get (row, start);
1908     cell_start_is_fragment = cell_start != NULL && cell_start->attr.fragment();
1909 
1910     /* On the right hand side, try to replace a TAB by a shorter TAB if we can.
1911         * This requires that the TAB on the left (which might be the same TAB) is
1912         * not yet converted to spaces, so start on the right hand side. */
1913     cell_end = _vte_row_data_get_writable (row, end);
1914     if (G_UNLIKELY (cell_end != NULL && cell_end->attr.fragment())) {
1915         col = end;
1916         do {
1917             col--;
1918             g_assert(col >= 0);  /* The first cell can't be a fragment. */
1919             cell_col = _vte_row_data_get_writable (row, col);
1920         } while (cell_col->attr.fragment());
1921         if (cell_col->c == '\t') {
1922             _vte_debug_print(VTE_DEBUG_MISC,
1923                                 "Replacing right part of TAB with a shorter one at %ld (%ld cells) => %ld (%ld cells)\n",
1924                                 col, (long) cell_col->attr.columns(), end, (long) cell_col->attr.columns() - (end - col));
1925             cell_end->c = '\t';
1926             cell_end->attr.set_fragment(false);
1927             g_assert(cell_col->attr.columns() > end - col);
1928             cell_end->attr.set_columns(cell_col->attr.columns() - (end - col));
1929         } else {
1930             _vte_debug_print(VTE_DEBUG_MISC,
1931                                 "Cleaning CJK right half at %ld\n",
1932                                 end);
1933             g_assert(end - col == 1 && cell_col->attr.columns() == 2);
1934             cell_end->c = ' ';
1935             cell_end->attr.set_fragment(false);
1936             cell_end->attr.set_columns(1);
1937             invalidate_cells(end, 1, m_screen->cursor.row, 1);
1938         }
1939     }
1940 
1941     /* Handle the left hand side.  Converting longer TABs to shorter ones probably
1942         * wouldn't make that much sense here, so instead convert to spaces. */
1943     if (G_UNLIKELY (cell_start_is_fragment)) {
1944         gboolean keep_going = TRUE;
1945         col = start;
1946         do {
1947             col--;
1948             g_assert(col >= 0);  /* The first cell can't be a fragment. */
1949             cell_col = _vte_row_data_get_writable (row, col);
1950             if (!cell_col->attr.fragment()) {
1951                 if (cell_col->c == '\t') {
1952                     _vte_debug_print(VTE_DEBUG_MISC,
1953                                         "Replacing left part of TAB with spaces at %ld (%ld => %ld cells)\n",
1954                                         col, (long)cell_col->attr.columns(), start - col);
1955                     /* nothing to do here */
1956                 } else {
1957                     _vte_debug_print(VTE_DEBUG_MISC,
1958                                         "Cleaning CJK left half at %ld\n",
1959                                         col);
1960                     g_assert(start - col == 1);
1961                     invalidate_cells(col, 1, m_screen->cursor.row, 1);
1962                 }
1963                 keep_going = FALSE;
1964             }
1965             cell_col->c = ' ';
1966             cell_col->attr.set_fragment(false);
1967             cell_col->attr.set_columns(1);
1968         } while (keep_going);
1969     }
1970 }
1971 
1972 /* Cursor down, with scrolling. */
cursor_down(bool explicit_sequence)1973 void VteTerminalPrivate::cursor_down(bool explicit_sequence)
1974 {
1975     long start, end;
1976 
1977     if (m_scrolling_restricted) {
1978         start = m_screen->insert_delta + m_scrolling_region.start;
1979         end = m_screen->insert_delta + m_scrolling_region.end;
1980     } else {
1981         start = m_screen->insert_delta;
1982         end = start + m_row_count - 1;
1983     }
1984     if (m_screen->cursor.row == end) {
1985         if (m_scrolling_restricted) {
1986             if (start == m_screen->insert_delta) {
1987                 /* Scroll this line into the scrollback
1988                  * buffer by inserting a line at the next
1989                  * line and scrolling the area up. */
1990                 m_screen->insert_delta++;
1991                 m_screen->cursor.row++;
1992                 /* update start and end, as they are relative
1993                  * to insert_delta. */
1994                 start++;
1995                 end++;
1996                 ring_insert(m_screen->cursor.row, false);
1997                 /* Force the areas below the region to be
1998                  * redrawn -- they've moved. */
1999                 scroll_region(start, end - start + 1, 1);
2000                 /* Force scroll. */
2001                 adjust_adjustments();
2002             } else {
2003                 /* If we're at the bottom of the scrolling
2004                  * region, add a line at the top to scroll the
2005                  * bottom off. */
2006                 ring_remove(start);
2007                 ring_insert(end, true);
2008                 /* Update the display. */
2009                 scroll_region(start, end - start + 1, -1);
2010                 invalidate_cells(0, m_column_count, end - 2, 2);
2011             }
2012         } else {
2013             /* Scroll up with history. */
2014             m_screen->cursor.row++;
2015             update_insert_delta();
2016         }
2017 
2018        /* Handle bce (background color erase), however, diverge from xterm:
2019         * only fill the new row with the background color if scrolling
2020         * happens due to an explicit escape sequence, not due to autowrapping.
2021         * See bug 754596 for details. */
2022         bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);
2023 
2024         if (explicit_sequence && not_default_bg) {
2025             VteRowData *rowdata = ensure_row();
2026             _vte_row_data_fill (rowdata, &m_fill_defaults, m_column_count);
2027         }
2028     } else {
2029         /* Otherwise, just move the cursor down. */
2030         m_screen->cursor.row++;
2031     }
2032 }
2033 
2034 /* Drop the scrollback. */
drop_scrollback()2035 void VteTerminalPrivate::drop_scrollback()
2036 {
2037     /* Only for normal screen; alternate screen doesn't have a scrollback. */
2038     _vte_ring_drop_scrollback (m_normal_screen.row_data, m_normal_screen.insert_delta);
2039 
2040     if (m_screen == &m_normal_screen) {
2041         queue_adjustment_value_changed(m_normal_screen.insert_delta);
2042         adjust_adjustments_full();
2043     }
2044 }
2045 
2046 /* Restore cursor on a screen. */
restore_cursor(VteScreen * screen__)2047 void VteTerminalPrivate::restore_cursor(VteScreen *screen__)
2048 {
2049     screen__->cursor.col = screen__->saved.cursor.col;
2050     screen__->cursor.row = screen__->insert_delta + CLAMP(screen__->saved.cursor.row,
2051                                                             0, m_row_count - 1);
2052 
2053     m_reverse_mode = screen__->saved.reverse_mode;
2054     m_origin_mode = screen__->saved.origin_mode;
2055     m_sendrecv_mode = screen__->saved.sendrecv_mode;
2056     m_insert_mode = screen__->saved.insert_mode;
2057     m_linefeed_mode = screen__->saved.linefeed_mode;
2058     m_defaults = screen__->saved.defaults;
2059     m_color_defaults = screen__->saved.color_defaults;
2060     m_fill_defaults = screen__->saved.fill_defaults;
2061     m_character_replacements[0] = screen__->saved.character_replacements[0];
2062     m_character_replacements[1] = screen__->saved.character_replacements[1];
2063     m_character_replacement = screen__->saved.character_replacement;
2064 }
2065 
2066 /* Save cursor on a screen__. */
save_cursor(VteScreen * screen__)2067 void VteTerminalPrivate::save_cursor(VteScreen *screen__)
2068 {
2069     screen__->saved.cursor.col = screen__->cursor.col;
2070     screen__->saved.cursor.row = screen__->cursor.row - screen__->insert_delta;
2071 
2072     screen__->saved.reverse_mode = m_reverse_mode;
2073     screen__->saved.origin_mode = m_origin_mode;
2074     screen__->saved.sendrecv_mode = m_sendrecv_mode;
2075     screen__->saved.insert_mode = m_insert_mode;
2076     screen__->saved.linefeed_mode = m_linefeed_mode;
2077     screen__->saved.defaults = m_defaults;
2078     screen__->saved.color_defaults = m_color_defaults;
2079     screen__->saved.fill_defaults = m_fill_defaults;
2080     screen__->saved.character_replacements[0] = m_character_replacements[0];
2081     screen__->saved.character_replacements[1] = m_character_replacements[1];
2082     screen__->saved.character_replacement = m_character_replacement;
2083 }
2084 
2085 /* Insert a single character into the stored data array. */
insert_char(gunichar c,bool insert,bool invalidate_now)2086 bool VteTerminalPrivate::insert_char(gunichar c,
2087                                         bool insert,
2088                                         bool invalidate_now)
2089 {
2090     VteCellAttr attr;
2091     VteRowData *row;
2092     long col;
2093     int columns, i;
2094     bool line_wrapped = false; /* cursor moved before char inserted */
2095     gunichar c_unmapped = c;
2096 
2097     /* DEC Special Character and Line Drawing Set.  VT100 and higher (per XTerm docs). */
2098     static gunichar line_drawing_map[31] = {
2099         0x25c6,  /* ` => diamond */
2100         0x2592,  /* a => checkerboard */
2101         0x2409,  /* b => HT symbol */
2102         0x240c,  /* c => FF symbol */
2103         0x240d,  /* d => CR symbol */
2104         0x240a,  /* e => LF symbol */
2105         0x00b0,  /* f => degree */
2106         0x00b1,  /* g => plus/minus */
2107         0x2424,  /* h => NL symbol */
2108         0x240b,  /* i => VT symbol */
2109         0x2518,  /* j => downright corner */
2110         0x2510,  /* k => upright corner */
2111         0x250c,  /* l => upleft corner */
2112         0x2514,  /* m => downleft corner */
2113         0x253c,  /* n => cross */
2114         0x23ba,  /* o => scan line 1/9 */
2115         0x23bb,  /* p => scan line 3/9 */
2116         0x2500,  /* q => horizontal line (also scan line 5/9) */
2117         0x23bc,  /* r => scan line 7/9 */
2118         0x23bd,  /* s => scan line 9/9 */
2119         0x251c,  /* t => left t */
2120         0x2524,  /* u => right t */
2121         0x2534,  /* v => bottom t */
2122         0x252c,  /* w => top t */
2123         0x2502,  /* x => vertical line */
2124         0x2264,  /* y => <= */
2125         0x2265,  /* z => >= */
2126         0x03c0,  /* { => pi */
2127         0x2260,  /* | => not equal */
2128         0x00a3,  /* } => pound currency sign */
2129         0x00b7,  /* ~ => bullet */
2130     };
2131 
2132     insert |= m_insert_mode;
2133     invalidate_now |= insert;
2134 
2135     /* If we've enabled the special drawing set, map the characters to
2136      * Unicode. */
2137     if (G_UNLIKELY (*m_character_replacement == VTE_CHARACTER_REPLACEMENT_LINE_DRAWING)) {
2138         if (c >= 96 && c <= 126) {
2139                 c = line_drawing_map[c - 96];
2140         }
2141     } else if (G_UNLIKELY (*m_character_replacement == VTE_CHARACTER_REPLACEMENT_BRITISH)) {
2142         if (G_UNLIKELY (c == '#')) {
2143                 c = 0x00a3;  /* pound sign */
2144         }
2145     }
2146 
2147     /* Figure out how many columns this character should occupy. */
2148     columns = _vte_unichar_width(c, m_utf8_ambiguous_width);
2149 
2150     /* If we're autowrapping here, do it. */
2151         col = m_screen->cursor.col;
2152     if (G_UNLIKELY (columns && col + columns > m_column_count)) {
2153         if (m_autowrap) {
2154             _vte_debug_print(VTE_DEBUG_ADJ, "Autowrapping before character\n");
2155             /* Wrap. */
2156             /* XXX clear to the end of line */
2157             col = m_screen->cursor.col = 0;
2158             /* Mark this line as soft-wrapped. */
2159             row = ensure_row();
2160             row->attr.soft_wrapped = 1;
2161             cursor_down(false);
2162         } else {
2163             /* Don't wrap, stay at the rightmost column. */
2164             col = m_screen->cursor.col =
2165             m_column_count - columns;
2166         }
2167         line_wrapped = true;
2168     }
2169 
2170     _vte_debug_print(VTE_DEBUG_PARSE,
2171             "Inserting %ld '%c' (colors %" G_GUINT64_FORMAT ") (%ld+%d, %ld), delta = %ld; ",
2172             (long)c, c < 256 ? c : ' ',
2173             m_color_defaults.attr.colors(),
2174             col, columns, (long)m_screen->cursor.row,
2175             (long)m_screen->insert_delta);
2176 
2177     if (G_UNLIKELY (columns == 0)) {
2178 
2179         /* It's a combining mark */
2180 
2181         long row_num;
2182         VteCell *cell;
2183 
2184         _vte_debug_print(VTE_DEBUG_PARSE, "combining U+%04X", c);
2185 
2186         row_num = m_screen->cursor.row;
2187         row = NULL;
2188         if (G_UNLIKELY (col == 0)) {
2189             /* We are at first column.  See if the previous line softwrapped.
2190              * If it did, move there.  Otherwise skip inserting. */
2191 
2192             if (G_LIKELY (row_num > 0)) {
2193                 row_num--;
2194                 row = find_row_data_writable(row_num);
2195 
2196                 if (row) {
2197                     if (!row->attr.soft_wrapped) {
2198                         row = NULL;
2199                     } else {
2200                         col = _vte_row_data_length (row);
2201                     }
2202                 }
2203             }
2204         } else {
2205             row = find_row_data_writable(row_num);
2206         }
2207 
2208         if (G_UNLIKELY (!row || !col)) {
2209             goto not_inserted;
2210         }
2211 
2212         /* Combine it on the previous cell */
2213 
2214         col--;
2215         cell = _vte_row_data_get_writable (row, col);
2216 
2217         if (G_UNLIKELY (!cell)) {
2218             goto not_inserted;
2219         }
2220 
2221         /* Find the previous cell */
2222         while (cell && cell->attr.fragment() && col > 0) {
2223             cell = _vte_row_data_get_writable (row, --col);
2224         }
2225         if (G_UNLIKELY (!cell || cell->c == '\t')) {
2226             goto not_inserted;
2227         }
2228 
2229         /* Combine the new character on top of the cell string */
2230         c = _vte_unistr_append_unichar (cell->c, c);
2231 
2232         /* And set it */
2233         columns = cell->attr.columns();
2234         for (i = 0; i < columns; i++) {
2235             cell = _vte_row_data_get_writable (row, col++);
2236             cell->c = c;
2237         }
2238 
2239         /* Always invalidate since we put the mark on the *previous* cell
2240          * and the higher level code doesn't know this. */
2241         invalidate_cells(col - columns, columns, row_num, 1);
2242 
2243         goto done;
2244     } else {
2245         m_last_graphic_character = c_unmapped;
2246     }
2247 
2248     /* Make sure we have enough rows to hold this data. */
2249     row = ensure_cursor();
2250     g_assert(row != NULL);
2251 
2252     if (insert) {
2253         cleanup_fragments(col, col);
2254         for (i = 0; i < columns; i++) {
2255             _vte_row_data_insert (row, col + i, &m_color_defaults);
2256         }
2257     } else {
2258         cleanup_fragments(col, col + columns);
2259         _vte_row_data_fill (row, &basic_cell, col + columns);
2260     }
2261 
2262     attr = m_defaults.attr;
2263     attr.copy_colors(m_color_defaults.attr);
2264     attr.set_columns(columns);
2265 
2266     {
2267         VteCell *pcell = _vte_row_data_get_writable (row, col);
2268         pcell->c = c;
2269         pcell->attr = attr;
2270         col++;
2271     }
2272 
2273     /* insert wide-char fragments */
2274     attr.set_fragment(true);
2275     for (i = 1; i < columns; i++) {
2276         VteCell *pcell = _vte_row_data_get_writable (row, col);
2277         pcell->c = c;
2278         pcell->attr = attr;
2279         col++;
2280     }
2281     if (_vte_row_data_length (row) > m_column_count) {
2282         cleanup_fragments(m_column_count, _vte_row_data_length (row));
2283     }
2284     _vte_row_data_shrink (row, m_column_count);
2285 
2286     /* Signal that this part of the window needs drawing. */
2287     if (G_UNLIKELY (invalidate_now)) {
2288         invalidate_cells(col - columns,
2289                             insert ? m_column_count : columns,
2290                             m_screen->cursor.row, 1);
2291     }
2292 
2293     m_screen->cursor.col = col;
2294 
2295 done:
2296     /* We added text, so make a note of it. */
2297     m_text_inserted_flag = TRUE;
2298 
2299 not_inserted:
2300     _vte_debug_print(VTE_DEBUG_ADJ|VTE_DEBUG_PARSE,
2301                         "insertion delta => %ld.\n",
2302                         (long)m_screen->insert_delta);
2303     return line_wrapped;
2304 }
2305 
2306 /* Reset the input method context. */
im_reset()2307 void VteTerminalPrivate::im_reset()
2308 {
2309     if (widget_realized() && m_im_context) {
2310         gtk_im_context_reset(m_im_context);
2311     }
2312 
2313     if (m_im_preedit) {
2314         g_free(m_im_preedit);
2315         m_im_preedit = nullptr;
2316     }
2317     if (m_im_preedit_attrs) {
2318         pango_attr_list_unref(m_im_preedit_attrs);
2319         m_im_preedit_attrs = nullptr;
2320     }
2321 }
2322 
2323 /* Process incoming data, first converting it to unicode characters, and then
2324  * processing control sequences. */
process_incoming()2325 void VteTerminalPrivate::process_incoming()
2326 {
2327     VteVisualPosition saved_cursor;
2328     gboolean saved_cursor_visible;
2329     VteCursorStyle saved_cursor_style;
2330     GdkPoint bbox_topleft, bbox_bottomright;
2331     gunichar *wbuf, c;
2332     long wcount, start;
2333     gboolean leftovers, modified, bottom, again;
2334     gboolean invalidated_text;
2335     gboolean in_scroll_region;
2336     GArray *unichars;
2337     struct _vte_incoming_chunk *chunk, *next_chunk, *achunk = NULL;
2338 
2339     _vte_debug_print(VTE_DEBUG_IO,
2340             "Handler processing %" G_GSIZE_FORMAT " bytes over %" G_GSIZE_FORMAT " chunks + %d bytes pending.\n",
2341             _vte_incoming_chunks_length(m_incoming),
2342             _vte_incoming_chunks_count(m_incoming),
2343             m_pending->len);
2344     _vte_debug_print (VTE_DEBUG_WORK, "(");
2345 
2346     auto previous_screen = m_screen;
2347 
2348     bottom = m_screen->insert_delta == (long)m_screen->scroll_delta;
2349 
2350     auto top_row = first_displayed_row();
2351     auto bottom_row = last_displayed_row();
2352 
2353     /* Save the current cursor position. */
2354     saved_cursor = m_screen->cursor;
2355     saved_cursor_visible = m_cursor_visible;
2356     saved_cursor_style = m_cursor_style;
2357 
2358     in_scroll_region = m_scrolling_restricted
2359         && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
2360         && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
2361 
2362     /* We should only be called when there's data to process. */
2363     g_assert(m_incoming || (m_pending->len > 0));
2364 
2365     /* Convert the data into unicode characters. */
2366     unichars = m_pending;
2367     for (chunk = _vte_incoming_chunks_reverse (m_incoming);
2368             chunk != NULL;
2369             chunk = next_chunk) {
2370         gsize processed;
2371         next_chunk = chunk->next;
2372         if (chunk->len == 0) {
2373             goto skip_chunk;
2374         }
2375         processed = _vte_iso2022_process(m_iso2022, chunk->data, chunk->len, unichars);
2376         if (G_UNLIKELY (processed != chunk->len)) {
2377             /* shuffle the data about */
2378             g_memmove (chunk->data, chunk->data + processed, chunk->len - processed);
2379             chunk->len = chunk->len - processed;
2380             processed = sizeof (chunk->data) - chunk->len;
2381             if (processed != 0 && next_chunk !=  NULL) {
2382                 if (next_chunk->len <= processed) {
2383                     /* consume it entirely */
2384                     memcpy (chunk->data + chunk->len,
2385                             next_chunk->data,
2386                             next_chunk->len);
2387                     chunk->len += next_chunk->len;
2388                     chunk->next = next_chunk->next;
2389                     release_chunk (next_chunk);
2390                 } else {
2391                     /* next few bytes */
2392                     memcpy (chunk->data + chunk->len,
2393                             next_chunk->data,
2394                             processed);
2395                     chunk->len += processed;
2396                     g_memmove (next_chunk->data,
2397                                 next_chunk->data + processed,
2398                                 next_chunk->len - processed);
2399                     next_chunk->len -= processed;
2400                 }
2401                 next_chunk = chunk; /* repeat */
2402             } else {
2403                 break;
2404             }
2405         } else {
2406 skip_chunk:
2407             /* cache the last chunk */
2408             if (achunk) {
2409                 release_chunk (achunk);
2410             }
2411             achunk = chunk;
2412         }
2413     }
2414     if (achunk) {
2415         if (chunk != NULL) {
2416             release_chunk (achunk);
2417         } else {
2418             chunk = achunk;
2419             chunk->next = NULL;
2420             chunk->len = 0;
2421         }
2422     }
2423     m_incoming = chunk;
2424 
2425     /* Compute the number of unicode characters we got. */
2426     wbuf = &g_array_index(unichars, gunichar, 0);
2427     wcount = unichars->len;
2428 
2429     /* Try initial substrings. */
2430     start = 0;
2431     modified = leftovers = again = FALSE;
2432     invalidated_text = FALSE;
2433 
2434     bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
2435     bbox_topleft.x = bbox_topleft.y = G_MAXINT;
2436 
2437     while (start < wcount && !leftovers) {
2438         const gunichar *next;
2439         vte::parser::Params params{nullptr};
2440 
2441         /* Try to match any control sequences. */
2442         sequence_handler_t handler = nullptr;
2443         auto match_result = _vte_matcher_match(m_matcher,
2444                                                        &wbuf[start],
2445                                                        wcount - start,
2446                                                        &handler,
2447                                                        &next,
2448                                                        &params.m_values);
2449         switch (match_result) {
2450         /* We're in one of three possible situations now.
2451          * First, the match returned a handler, and next
2452          * points to the first character which isn't part of this
2453          * sequence. */
2454             case VTE_MATCHER_RESULT_MATCH: {
2455                 _VTE_DEBUG_IF(VTE_DEBUG_PARSE)
2456                 params.print();
2457 
2458                 /* Call the sequence handler */
2459                 (this->*handler)(params);
2460 
2461                 m_last_graphic_character = 0;
2462 
2463                 /* Skip over the proper number of unicode chars. */
2464                 start = (next - wbuf);
2465                 modified = TRUE;
2466 
2467                 // FIXME m_screen may be != previous_screen, check for that!
2468 
2469                 gboolean new_in_scroll_region = m_scrolling_restricted
2470                     && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
2471                     && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
2472 
2473                 /* delta may have changed from sequence. */
2474                 top_row = first_displayed_row();
2475                 bottom_row = last_displayed_row();
2476 
2477                /* if we have moved greatly during the sequence handler, or moved
2478                 * into a scroll_region from outside it, restart the bbox.
2479                 */
2480                 if (invalidated_text &&
2481                     ((new_in_scroll_region && !in_scroll_region) ||
2482                                          (m_screen->cursor.col > bbox_bottomright.x + VTE_CELL_BBOX_SLACK ||
2483                                           m_screen->cursor.col < bbox_topleft.x - VTE_CELL_BBOX_SLACK     ||
2484                                           m_screen->cursor.row > bbox_bottomright.y + VTE_CELL_BBOX_SLACK ||
2485                                           m_screen->cursor.row < bbox_topleft.y - VTE_CELL_BBOX_SLACK))) {
2486                     /* Clip off any part of the box which isn't already on-screen. */
2487                     bbox_topleft.x = MAX(bbox_topleft.x, 0);
2488                     bbox_topleft.y = MAX(bbox_topleft.y, top_row);
2489                     bbox_bottomright.x = MIN(bbox_bottomright.x, m_column_count);
2490                     /* lazily apply the +1 to the cursor_row */
2491                     bbox_bottomright.y = MIN(bbox_bottomright.y + 1, bottom_row + 1);
2492 
2493                     invalidate_cells(bbox_topleft.x,
2494                                         bbox_bottomright.x - bbox_topleft.x,
2495                                         bbox_topleft.y,
2496                                         bbox_bottomright.y - bbox_topleft.y);
2497 
2498                     invalidated_text = FALSE;
2499                     bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
2500                     bbox_topleft.x = bbox_topleft.y = G_MAXINT;
2501                 }
2502 
2503                 in_scroll_region = new_in_scroll_region;
2504 
2505                 break;
2506             }
2507            /* Second, we have no match, and next points to the very
2508             * next character in the buffer.  Insert the character which
2509             * we're currently examining into the screen. */
2510             case VTE_MATCHER_RESULT_NO_MATCH: {
2511                 c = wbuf[start];
2512                 /* If it's a control character, permute the order, per
2513                 * vttest. */
2514                 if ((c != *next) &&
2515                     ((*next & 0x1f) == *next) &&
2516                                 //FIXMEchpe what about C1 controls
2517                     (start + 1 < next - wbuf)) {
2518                     const gunichar *tnext = NULL;
2519                     gunichar ctrl;
2520                     int i;
2521                    /* We don't want to permute it if it's another
2522                     * control sequence, so check if it is. */
2523                     sequence_handler_t thandler;
2524                     _vte_matcher_match(m_matcher,
2525                                         next,
2526                                         wcount - (next - wbuf),
2527                                         &thandler,
2528                                         &tnext,
2529                                         NULL);
2530                    /* We only do this for non-control-sequence
2531                     * characters and random garbage. */
2532                     if (tnext == next + 1) {
2533                         /* Save the control character. */
2534                         ctrl = *next;
2535                        /* Move everything before it up a
2536                         * slot.  */
2537                         /* FIXMEchpe memmove! */
2538                         for (i = next - wbuf; i > start; i--) {
2539                             wbuf[i] = wbuf[i - 1];
2540                         }
2541                         /* Move the control character to the
2542                         * front. */
2543                         wbuf[i] = ctrl;
2544                         goto next_match;
2545                     }
2546                 }
2547                 _VTE_DEBUG_IF(VTE_DEBUG_PARSE) {
2548                     if (c > 255) {
2549                         g_printerr("U+%04lx\n", (long) c);
2550                     } else {
2551                         if (c > 127) {
2552                             g_printerr("%ld = ", (long) c);
2553                         }
2554                         if (c < 32) {
2555                             g_printerr("^%c\n", c + 64);
2556                         } else {
2557                             g_printerr("`%c'\n", c);
2558                         }
2559                     }
2560                 }
2561 
2562                 bbox_topleft.x = MIN(bbox_topleft.x, m_screen->cursor.col);
2563                 bbox_topleft.y = MIN(bbox_topleft.y, m_screen->cursor.row);
2564 
2565                 /* Insert the character. */
2566                 /* FIXMEchpe should not use UNLIKELY here */
2567                 if (G_UNLIKELY(insert_char(c, false, false))) {
2568                     /* line wrapped, correct bbox */
2569                     if (invalidated_text &&
2570                         (m_screen->cursor.col > bbox_bottomright.x + VTE_CELL_BBOX_SLACK    ||
2571                          m_screen->cursor.col < bbox_topleft.x - VTE_CELL_BBOX_SLACK    ||
2572                          m_screen->cursor.row > bbox_bottomright.y + VTE_CELL_BBOX_SLACK    ||
2573                          m_screen->cursor.row < bbox_topleft.y - VTE_CELL_BBOX_SLACK)) {
2574                         /* Clip off any part of the box which isn't already on-screen. */
2575                         bbox_topleft.x = MAX(bbox_topleft.x, 0);
2576                         bbox_topleft.y = MAX(bbox_topleft.y, top_row);
2577                         bbox_bottomright.x = MIN(bbox_bottomright.x, m_column_count);
2578                         /* lazily apply the +1 to the cursor_row */
2579                         bbox_bottomright.y = MIN(bbox_bottomright.y + 1, bottom_row + 1);
2580 
2581                         invalidate_cells(bbox_topleft.x,
2582                                             bbox_bottomright.x - bbox_topleft.x,
2583                                             bbox_topleft.y,
2584                                             bbox_bottomright.y - bbox_topleft.y);
2585                         bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
2586                         bbox_topleft.x = bbox_topleft.y = G_MAXINT;
2587                     }
2588                     bbox_topleft.x = MIN(bbox_topleft.x, 0);
2589                     bbox_topleft.y = MIN(bbox_topleft.y, m_screen->cursor.row);
2590                 }
2591                /* Add the cells over which we have moved to the region
2592                 * which we need to refresh for the user. */
2593                 bbox_bottomright.x = MAX(bbox_bottomright.x, m_screen->cursor.col);
2594                /* cursor.row + 1 (defer until inv.) */
2595                 bbox_bottomright.y = MAX(bbox_bottomright.y, m_screen->cursor.row);
2596                 invalidated_text = TRUE;
2597 
2598                 /* We *don't* emit flush pending signals here. */
2599                 modified = TRUE;
2600                 start++;
2601 
2602                 break;
2603             }
2604             case VTE_MATCHER_RESULT_PARTIAL: {
2605                /* Case three: the read broke in the middle of a
2606                 * control sequence, so we're undecided with no more
2607                 * data to consult. If we have data following the
2608                 * middle of the sequence, then it's just garbage data,
2609                 * and for compatibility, we should discard it. */
2610                 if (wbuf + wcount > next) {
2611                     _vte_debug_print(VTE_DEBUG_PARSE,
2612                                         "Invalid control "
2613                                         "sequence, discarding %ld "
2614                                         "characters.\n",
2615                                         (long)(next - (wbuf + start)));
2616                     /* Discard. */
2617                     start = next - wbuf + 1;
2618                 } else {
2619                     /* Pause processing here and wait for more
2620                     * data before continuing. */
2621                     leftovers = TRUE;
2622                 }
2623 
2624                 break;
2625             }
2626         }
2627 
2628 #ifdef VTE_DEBUG
2629         /* Some safety checks: ensure the visible parts of the buffer
2630          * are all in the buffer. */
2631         g_assert_cmpint(m_screen->insert_delta, >=, _vte_ring_delta(m_screen->row_data));
2632 
2633         /* The cursor shouldn't be above or below the addressable
2634          * part of the display buffer. */
2635         g_assert_cmpint(m_screen->cursor.row, >=, m_screen->insert_delta);
2636 #endif
2637 
2638 next_match:
2639         /* Free any parameters we don't care about any more. */
2640         params.recycle(m_matcher);
2641     }
2642 
2643     /* Remove most of the processed characters. */
2644     if (start < wcount) {
2645         g_array_remove_range(m_pending, 0, start);
2646     } else {
2647         g_array_set_size(m_pending, 0);
2648         /* If we're out of data, we needn't pause to let the
2649          * controlling application respond to incoming data, because
2650          * the main loop is already going to do that. */
2651     }
2652 
2653     if (modified) {
2654         /* Keep the cursor on-screen if we scroll on output, or if
2655          * we're currently at the bottom of the buffer. */
2656         update_insert_delta();
2657         if (m_scroll_on_output || bottom) {
2658             maybe_scroll_to_bottom();
2659         }
2660         /* Deselect the current selection if its contents are changed
2661          * by this insertion. */
2662         if (m_has_selection) {
2663             /* FIXMEchpe: this is atrocious */
2664             auto selection = get_selected_text();
2665             if ((selection == nullptr) ||
2666                 (m_selection[VTE_SELECTION_PRIMARY] == nullptr) ||
2667                 (strcmp(selection->str, m_selection[VTE_SELECTION_PRIMARY]->str) != 0)) {
2668                 deselect_all();
2669             }
2670             if (selection) {
2671                 g_string_free(selection, TRUE);
2672             }
2673         }
2674     }
2675 
2676     if (modified || (m_screen != previous_screen)) {
2677         /* Signal that the visible contents changed. */
2678         queue_contents_changed();
2679     }
2680 
2681     emit_pending_signals();
2682 
2683     if (invalidated_text) {
2684         /* Clip off any part of the box which isn't already on-screen. */
2685         bbox_topleft.x = MAX(bbox_topleft.x, 0);
2686         bbox_topleft.y = MAX(bbox_topleft.y, top_row);
2687         bbox_bottomright.x = MIN(bbox_bottomright.x, m_column_count);
2688         /* lazily apply the +1 to the cursor_row */
2689         bbox_bottomright.y = MIN(bbox_bottomright.y + 1, bottom_row + 1);
2690 
2691         invalidate_cells(bbox_topleft.x,
2692                             bbox_bottomright.x - bbox_topleft.x,
2693                             bbox_topleft.y,
2694                             bbox_bottomright.y - bbox_topleft.y);
2695     }
2696 
2697     /* FIXMEchpe: also need to take into account if the number of columns the cursor
2698      * occupies has changed due to the cell it's on being changed... */
2699     if ((saved_cursor.col != m_screen->cursor.col) ||
2700         (saved_cursor.row != m_screen->cursor.row)) {
2701         /* invalidate the old and new cursor positions */
2702         if (saved_cursor_visible) {
2703             invalidate_cell(saved_cursor.col, saved_cursor.row);
2704         }
2705         invalidate_cursor_once();
2706         check_cursor_blink();
2707         /* Signal that the cursor moved. */
2708         queue_cursor_moved();
2709     } else if ((saved_cursor_visible != m_cursor_visible) ||
2710                 (saved_cursor_style != m_cursor_style)) {
2711         invalidate_cell(saved_cursor.col, saved_cursor.row);
2712         check_cursor_blink();
2713     }
2714 
2715     /* Tell the input method where the cursor is. */
2716     im_update_cursor();
2717 
2718     /* After processing some data, do a hyperlink GC. The multiplier is totally arbitrary, feel free to fine tune. */
2719     _vte_ring_hyperlink_maybe_gc(m_screen->row_data, wcount * 4);
2720 
2721     _vte_debug_print (VTE_DEBUG_WORK, ")");
2722     _vte_debug_print (VTE_DEBUG_IO,
2723             "%ld chars and %ld bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",
2724             (long) unichars->len,
2725             (long) _vte_incoming_chunks_length(m_incoming),
2726             _vte_incoming_chunks_count(m_incoming));
2727 }
2728 
feed_chunks(struct _vte_incoming_chunk * chunks)2729 void VteTerminalPrivate::feed_chunks(struct _vte_incoming_chunk *chunks)
2730 {
2731     struct _vte_incoming_chunk *last;
2732 
2733     _vte_debug_print(VTE_DEBUG_IO, "Feed %" G_GSIZE_FORMAT " bytes, in %" G_GSIZE_FORMAT " chunks.\n",
2734             _vte_incoming_chunks_length(chunks),
2735             _vte_incoming_chunks_count(chunks));
2736 
2737     for (last = chunks; last->next != NULL; last = last->next) ;
2738     last->next = m_incoming;
2739     m_incoming = chunks;
2740 }
2741 
2742 /*
2743  * VteTerminalPrivate::feed:
2744  * @data: (array length=length) (element-type guint8): a string in the terminal's current encoding
2745  * @length: the length of the string, or -1 to use the full length or a nul-terminated string
2746  *
2747  * Interprets @data as if it were data received from a child process.  This
2748  * can either be used to drive the terminal without a child process, or just
2749  * to mess with your users.
2750  */
feed(char const * data,gssize length,bool start_processing_)2751 void VteTerminalPrivate::feed(char const* data,
2752                                 gssize length,
2753                                 bool start_processing_)
2754 {
2755     g_assert(length == 0 || data != nullptr);
2756 
2757     if (length == -1) {
2758         length = strlen(data);
2759     }
2760 
2761     /* If we have data, modify the incoming buffer. */
2762     if (length > 0) {
2763         struct _vte_incoming_chunk *chunk;
2764         if (m_incoming &&
2765                 (gsize)length < sizeof (m_incoming->data) - m_incoming->len) {
2766             chunk = m_incoming;
2767         } else {
2768             chunk = get_chunk ();
2769             feed_chunks(chunk);
2770         }
2771         do { /* break the incoming data into chunks */
2772             gsize rem = sizeof (chunk->data) - chunk->len;
2773             gsize len = (gsize) length < rem ? (gsize) length : rem;
2774             memcpy (chunk->data + chunk->len, data, len);
2775             chunk->len += len;
2776             length -= len;
2777             if (length == 0) {
2778                 break;
2779             }
2780             data += len;
2781 
2782             chunk = get_chunk ();
2783             feed_chunks(chunk);
2784         } while (1);
2785 
2786         if (start_processing_) {
2787             start_processing();
2788         }
2789     }
2790 }
2791 
2792 #if 0 /* FIXME: remove */
2793 bool
2794 VteTerminalPrivate::pty_io_write(GIOChannel *channel,
2795                                  GIOCondition condition)
2796 {
2797     gssize count;
2798     int fd;
2799     gboolean leave_open;
2800 
2801     fd = g_io_channel_unix_get_fd(channel);
2802 
2803     count = write(fd, m_outgoing->data,
2804               _vte_byte_array_length(m_outgoing));
2805     if (count != -1) {
2806         _VTE_DEBUG_IF (VTE_DEBUG_IO) {
2807             gssize i;
2808             for (i = 0; i < count; i++) {
2809                 g_printerr("Wrote %c%c\n",
2810                     ((guint8)m_outgoing->data[i]) >= 32 ?
2811                     ' ' : '^',
2812                     ((guint8)m_outgoing->data[i]) >= 32 ?
2813                     m_outgoing->data[i] :
2814                     ((guint8)m_outgoing->data[i])  + 64);
2815             }
2816         }
2817         _vte_byte_array_consume(m_outgoing, count);
2818     }
2819 
2820     if (_vte_byte_array_length(m_outgoing) == 0) {
2821         leave_open = FALSE;
2822     } else {
2823         leave_open = TRUE;
2824     }
2825 
2826     return leave_open;
2827 }
2828 #endif
2829 
im_commit(char const * text)2830 void VteTerminalPrivate::im_commit(char const* text)
2831 {
2832     _vte_debug_print(VTE_DEBUG_EVENTS,
2833             "Input method committed `%s'.\n", text);
2834 
2835     /* feed_child_using_modes(text, -1); */ /* FIXME: removed */
2836 
2837     /* Committed text was committed because the user pressed a key, so
2838      * we need to obey the scroll-on-keystroke setting. */
2839     if (m_scroll_on_keystroke) {
2840         maybe_scroll_to_bottom();
2841     }
2842 }
2843 
2844 /* We've started pre-editing. */
vte_terminal_im_preedit_start_cb(GtkIMContext * im_context,VteTerminalPrivate * that)2845 static void vte_terminal_im_preedit_start_cb(GtkIMContext *im_context,
2846                                                 VteTerminalPrivate *that)
2847 {
2848     that->im_preedit_start();
2849 }
2850 
im_preedit_start()2851 void VteTerminalPrivate::im_preedit_start()
2852 {
2853     _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit started.\n");
2854     m_im_preedit_active = true;
2855 }
2856 
2857 /* We've stopped pre-editing. */
vte_terminal_im_preedit_end_cb(GtkIMContext * im_context,VteTerminalPrivate * that)2858 static void vte_terminal_im_preedit_end_cb(GtkIMContext *im_context,
2859                                             VteTerminalPrivate *that)
2860 {
2861     that->im_preedit_end();
2862 }
2863 
im_preedit_end()2864 void VteTerminalPrivate::im_preedit_end()
2865 {
2866     _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit ended.\n");
2867     m_im_preedit_active = false;
2868 }
2869 
2870 /* The pre-edit string changed. */
vte_terminal_im_preedit_changed_cb(GtkIMContext * im_context,VteTerminalPrivate * that)2871 static void vte_terminal_im_preedit_changed_cb(GtkIMContext *im_context,
2872                                                 VteTerminalPrivate *that)
2873 {
2874     that->im_preedit_changed();
2875 }
2876 
im_preedit_changed()2877 void VteTerminalPrivate::im_preedit_changed()
2878 {
2879     gchar *str;
2880     PangoAttrList *attrs;
2881     gint cursorpos;
2882 
2883     gtk_im_context_get_preedit_string(m_im_context, &str, &attrs, &cursorpos);
2884     _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit changed (%s,%d).\n", str, cursorpos);
2885 
2886     /* Queue the area where the current preedit string is being displayed
2887      * for repainting. */
2888     invalidate_cursor_once();
2889 
2890     g_free(m_im_preedit);
2891     m_im_preedit = str;
2892 
2893     if (m_im_preedit_attrs != NULL) {
2894         pango_attr_list_unref(m_im_preedit_attrs);
2895     }
2896     m_im_preedit_attrs = attrs;
2897 
2898     m_im_preedit_cursor = cursorpos;
2899 
2900     /* Invalidate again with the new cursor position */
2901     invalidate_cursor_once();
2902 
2903     /* And tell the input method where the cursor is on the screen */
2904     im_update_cursor();
2905 }
2906 
vte_terminal_im_retrieve_surrounding_cb(GtkIMContext * im_context,VteTerminalPrivate * that)2907 static gboolean vte_terminal_im_retrieve_surrounding_cb(GtkIMContext *im_context,
2908                                                         VteTerminalPrivate *that)
2909 {
2910     return that->im_retrieve_surrounding();
2911 }
2912 
im_retrieve_surrounding()2913 bool VteTerminalPrivate::im_retrieve_surrounding()
2914 {
2915     /* FIXME: implement this! Bug #726191 */
2916     _vte_debug_print(VTE_DEBUG_EVENTS, "Input method retrieve-surrounding.\n");
2917     return false;
2918 }
2919 
vte_terminal_im_delete_surrounding_cb(GtkIMContext * im_context,int offset,int n_chars,VteTerminalPrivate * that)2920 static gboolean vte_terminal_im_delete_surrounding_cb(GtkIMContext *im_context,
2921                                                         int offset,
2922                                                         int n_chars,
2923                                                         VteTerminalPrivate *that)
2924 {
2925     return that->im_delete_surrounding(offset, n_chars);
2926 }
2927 
im_delete_surrounding(int offset,int n_chars)2928 bool VteTerminalPrivate::im_delete_surrounding(int offset, int n_chars)
2929 {
2930     /* FIXME: implement this! Bug #726191 */
2931     _vte_debug_print(VTE_DEBUG_EVENTS,
2932                         "Input method delete-surrounding offset %d n-chars %d.\n",
2933                         offset, n_chars);
2934     return false;
2935 }
2936 
im_update_cursor()2937 void VteTerminalPrivate::im_update_cursor()
2938 {
2939     if (!widget_realized()) {
2940         return;
2941     }
2942 
2943     cairo_rectangle_int_t rect;
2944     rect.x = m_screen->cursor.col * m_cell_width + m_padding.left +
2945                 get_preedit_width(false) * m_cell_width;
2946     rect.width = m_cell_width; /* FIXMEchpe: if columns > 1 ? */
2947     rect.y = row_to_pixel(m_screen->cursor.row) + m_padding.top;
2948     rect.height = m_cell_height;
2949     gtk_im_context_set_cursor_location(m_im_context, &rect);
2950 }
2951 
widget_style_updated()2952 void VteTerminalPrivate::widget_style_updated()
2953 {
2954     set_font_desc(m_unscaled_font_desc);
2955 
2956     auto context = gtk_widget_get_style_context(m_widget);
2957     GtkBorder new_padding;
2958     gtk_style_context_get_padding(context, gtk_style_context_get_state(context),
2959                                     &new_padding);
2960     if (memcmp(&new_padding, &m_padding, sizeof(GtkBorder)) != 0) {
2961         _vte_debug_print(VTE_DEBUG_MISC | VTE_DEBUG_WIDGET_SIZE,
2962                             "Setting padding to (%d,%d,%d,%d)\n",
2963                             new_padding.left, new_padding.right,
2964                             new_padding.top, new_padding.bottom);
2965 
2966         m_padding = new_padding;
2967         update_view_extents();
2968         gtk_widget_queue_resize(m_widget);
2969     } else {
2970         _vte_debug_print(VTE_DEBUG_MISC | VTE_DEBUG_WIDGET_SIZE,
2971                             "Keeping padding the same at (%d,%d,%d,%d)\n",
2972                             new_padding.left, new_padding.right,
2973                             new_padding.top, new_padding.bottom);
2974 
2975     }
2976 
2977     float aspect;
2978     gtk_widget_style_get(m_widget, "cursor-aspect-ratio", &aspect, nullptr);
2979     if (!_vte_double_equal(aspect, m_cursor_aspect_ratio)) {
2980         m_cursor_aspect_ratio = aspect;
2981         invalidate_cursor_once();
2982     }
2983 
2984 }
2985 
add_cursor_timeout()2986 void VteTerminalPrivate::add_cursor_timeout()
2987 {
2988     if (m_cursor_blink_tag) {
2989         return; /* already added */
2990     }
2991 
2992     m_cursor_blink_time = 0;
2993     m_cursor_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
2994                                                 m_cursor_blink_cycle,
2995                                                 (GSourceFunc)invalidate_cursor_periodic_cb,
2996                                                 this,
2997                                                 NULL);
2998 }
2999 
remove_cursor_timeout()3000 void VteTerminalPrivate::remove_cursor_timeout()
3001 {
3002     if (m_cursor_blink_tag == 0) {
3003         return; /* already removed */
3004     }
3005 
3006     g_source_remove(m_cursor_blink_tag);
3007     m_cursor_blink_tag = 0;
3008     if (!m_cursor_blink_state) {
3009         invalidate_cursor_once();
3010         m_cursor_blink_state = true;
3011     }
3012 }
3013 
3014 /* Activates / disactivates the cursor blink timer to reduce wakeups */
check_cursor_blink()3015 void VteTerminalPrivate::check_cursor_blink()
3016 {
3017     if (m_has_focus &&
3018         m_cursor_blinks &&
3019         m_cursor_visible) {
3020         add_cursor_timeout();
3021     } else {
3022         remove_cursor_timeout();
3023     }
3024 }
3025 
remove_text_blink_timeout()3026 void VteTerminalPrivate::remove_text_blink_timeout()
3027 {
3028     if (m_text_blink_tag == 0) {
3029         return;
3030     }
3031 
3032     g_source_remove (m_text_blink_tag);
3033     m_text_blink_tag = 0;
3034 }
3035 
beep()3036 void VteTerminalPrivate::beep()
3037 {
3038     if (m_audible_bell) {
3039         GdkWindow *window = gtk_widget_get_window(m_widget);
3040         gdk_window_beep(window);
3041     }
3042 }
3043 
translate_ctrlkey(GdkEventKey * event)3044 guint VteTerminalPrivate::translate_ctrlkey(GdkEventKey *event)
3045 {
3046     guint keyval;
3047     GdkKeymap *keymap;
3048     unsigned int i;
3049 
3050     if (event->keyval < 128) {
3051         return event->keyval;
3052     }
3053     keymap = gdk_keymap_get_for_display(gdk_window_get_display (event->window));
3054 
3055     /* Try groups in order to find one mapping the key to ASCII */
3056     for (i = 0; i < 4; i++) {
3057         GdkModifierType consumed_modifiers;
3058 
3059         gdk_keymap_translate_keyboard_state (keymap,
3060                                                 event->hardware_keycode,
3061                                                 (GdkModifierType)event->state,
3062                                                 i,
3063                                                 &keyval, NULL, NULL, &consumed_modifiers);
3064         if (keyval < 128) {
3065             _vte_debug_print (VTE_DEBUG_EVENTS,
3066                     "ctrl+Key, group=%d de-grouped into keyval=0x%x\n",
3067                     event->group, keyval);
3068             return keyval;
3069         }
3070     }
3071 
3072     return event->keyval;
3073 }
3074 
read_modifiers(GdkEvent * event)3075 void VteTerminalPrivate::read_modifiers(GdkEvent *event)
3076 {
3077     GdkKeymap *keymap;
3078     GdkModifierType mods;
3079     guint mask;
3080 
3081     /* Read the modifiers. */
3082     if (!gdk_event_get_state((GdkEvent*)event, &mods)) {
3083         return;
3084     }
3085 
3086     keymap = gdk_keymap_get_for_display(gdk_window_get_display(((GdkEventAny*)event)->window));
3087 
3088     gdk_keymap_add_virtual_modifiers (keymap, &mods);
3089 
3090     mask = (guint)mods;
3091 #if 1
3092     /* HACK! Treat ALT as META; see bug #663779. */
3093     if (mask & GDK_MOD1_MASK) {
3094             mask |= VTE_META_MASK;
3095     }
3096 #endif
3097 
3098     m_modifiers = mask;
3099 }
3100 
widget_key_press(GdkEventKey * event)3101 bool VteTerminalPrivate::widget_key_press(GdkEventKey *event)
3102 {
3103     char *normal = NULL;
3104     gssize normal_length = 0;
3105     int i;
3106     gboolean scrolled = FALSE, steal = FALSE, modifier = FALSE, handled,
3107          /*suppress_meta_esc = FALSE,*/ add_modifiers = FALSE;
3108     guint keyval = 0;
3109     gunichar keychar = 0;
3110     char keybuf[VTE_UTF8_BPC];
3111 
3112     /* If it's a keypress, record that we got the event, in case the
3113      * input method takes the event from us. */
3114     if (event->type == GDK_KEY_PRESS) {
3115         /* Store a copy of the key. */
3116         keyval = event->keyval;
3117         read_modifiers((GdkEvent*)event);
3118 
3119         /* FIXMEchpe? */
3120         if (m_cursor_blink_tag != 0) {
3121             remove_cursor_timeout();
3122             add_cursor_timeout();
3123         }
3124 
3125         /* Determine if this is just a modifier key. */
3126         modifier = _vte_keymap_key_is_modifier(keyval);
3127 
3128         /* Unless it's a modifier key, hide the pointer. */
3129         if (!modifier) {
3130             set_pointer_autohidden(true);
3131         }
3132 
3133         _vte_debug_print(VTE_DEBUG_EVENTS,
3134                 "Keypress, modifiers=0x%x, "
3135                 "keyval=0x%x, raw string=`%s'.\n",
3136                 m_modifiers,
3137                 keyval, event->string);
3138 
3139         /* We steal many keypad keys here. */
3140         if (!m_im_preedit_active) {
3141             switch (keyval) {
3142                 case GDK_KEY_KP_Add:
3143                 case GDK_KEY_KP_Subtract:
3144                 case GDK_KEY_KP_Multiply:
3145                 case GDK_KEY_KP_Divide:
3146                 case GDK_KEY_KP_Enter:
3147                     steal = TRUE;
3148                     break;
3149                 default:
3150                     break;
3151             }
3152             if (m_modifiers & VTE_META_MASK) {
3153                 steal = TRUE;
3154             }
3155             switch (keyval) {
3156                 case GDK_KEY_ISO_Lock:
3157                 case GDK_KEY_ISO_Level2_Latch:
3158                 case GDK_KEY_ISO_Level3_Shift:
3159                 case GDK_KEY_ISO_Level3_Latch:
3160                 case GDK_KEY_ISO_Level3_Lock:
3161                 case GDK_KEY_ISO_Level5_Shift:
3162                 case GDK_KEY_ISO_Level5_Latch:
3163                 case GDK_KEY_ISO_Level5_Lock:
3164                 case GDK_KEY_ISO_Group_Shift:
3165                 case GDK_KEY_ISO_Group_Latch:
3166                 case GDK_KEY_ISO_Group_Lock:
3167                 case GDK_KEY_ISO_Next_Group:
3168                 case GDK_KEY_ISO_Next_Group_Lock:
3169                 case GDK_KEY_ISO_Prev_Group:
3170                 case GDK_KEY_ISO_Prev_Group_Lock:
3171                 case GDK_KEY_ISO_First_Group:
3172                 case GDK_KEY_ISO_First_Group_Lock:
3173                 case GDK_KEY_ISO_Last_Group:
3174                 case GDK_KEY_ISO_Last_Group_Lock:
3175                 case GDK_KEY_Multi_key:
3176                 case GDK_KEY_Codeinput:
3177                 case GDK_KEY_SingleCandidate:
3178                 case GDK_KEY_MultipleCandidate:
3179                 case GDK_KEY_PreviousCandidate:
3180                 case GDK_KEY_Kanji:
3181                 case GDK_KEY_Muhenkan:
3182                 case GDK_KEY_Henkan_Mode:
3183                 /* case GDK_KEY_Henkan: is GDK_KEY_Henkan_Mode */
3184                 case GDK_KEY_Romaji:
3185                 case GDK_KEY_Hiragana:
3186                 case GDK_KEY_Katakana:
3187                 case GDK_KEY_Hiragana_Katakana:
3188                 case GDK_KEY_Zenkaku:
3189                 case GDK_KEY_Hankaku:
3190                 case GDK_KEY_Zenkaku_Hankaku:
3191                 case GDK_KEY_Touroku:
3192                 case GDK_KEY_Massyo:
3193                 case GDK_KEY_Kana_Lock:
3194                 case GDK_KEY_Kana_Shift:
3195                 case GDK_KEY_Eisu_Shift:
3196                 case GDK_KEY_Eisu_toggle:
3197                 /* case GDK_KEY_Kanji_Bangou: is GDK_KEY_Codeinput */
3198                 /* case GDK_KEY_Zen_Koho: is GDK_KEY_MultipleCandidate */
3199                 /* case GDK_KEY_Mae_Koho: is GDK_KEY_PreviousCandidate */
3200                 /* case GDK_KEY_kana_switch: is GDK_KEY_ISO_Group_Shift */
3201                 case GDK_KEY_Hangul:
3202                 case GDK_KEY_Hangul_Start:
3203                 case GDK_KEY_Hangul_End:
3204                 case GDK_KEY_Hangul_Hanja:
3205                 case GDK_KEY_Hangul_Jamo:
3206                 case GDK_KEY_Hangul_Romaja:
3207                 /* case GDK_KEY_Hangul_Codeinput: is GDK_KEY_Codeinput */
3208                 case GDK_KEY_Hangul_Jeonja:
3209                 case GDK_KEY_Hangul_Banja:
3210                 case GDK_KEY_Hangul_PreHanja:
3211                 case GDK_KEY_Hangul_PostHanja:
3212                 /* case GDK_KEY_Hangul_SingleCandidate: is GDK_KEY_SingleCandidate */
3213                 /* case GDK_KEY_Hangul_MultipleCandidate: is GDK_KEY_MultipleCandidate */
3214                 /* case GDK_KEY_Hangul_PreviousCandidate: is GDK_KEY_PreviousCandidate */
3215                 case GDK_KEY_Hangul_Special:
3216                 /* case GDK_KEY_Hangul_switch: is GDK_KEY_ISO_Group_Shift */
3217 
3218                     steal = FALSE;
3219                     break;
3220                 default:
3221                     break;
3222             }
3223         }
3224     }
3225 
3226     /* Let the input method at this one first. */
3227     if (!steal && m_input_enabled) {
3228         if (m_im_context && gtk_im_context_filter_keypress(m_im_context, event)) {
3229             _vte_debug_print(VTE_DEBUG_EVENTS, "Keypress taken by IM.\n");
3230             return true;
3231         }
3232     }
3233 
3234     /* Now figure out what to send to the child. */
3235     if ((event->type == GDK_KEY_PRESS) && !modifier) {
3236         handled = FALSE;
3237         /* Map the key to a sequence name if we can. */
3238         switch (keyval) {
3239             case GDK_KEY_BackSpace:
3240                 switch (m_backspace_binding) {
3241                     case VTE_ERASE_ASCII_BACKSPACE:
3242                         normal = g_strdup("");
3243                         normal_length = 1;
3244                         /*suppress_meta_esc = FALSE;*/
3245                         break;
3246                     case VTE_ERASE_ASCII_DELETE:
3247                         normal = g_strdup("");
3248                         normal_length = 1;
3249                         /*suppress_meta_esc = FALSE;*/
3250                         break;
3251                     case VTE_ERASE_DELETE_SEQUENCE:
3252                         normal = g_strdup("\e[3~");
3253                         normal_length = 4;
3254                         add_modifiers = TRUE;
3255                         /*suppress_meta_esc = TRUE;*/
3256                         break;
3257                     case VTE_ERASE_TTY:
3258 #if 0
3259                         if (m_pty != nullptr &&
3260                             tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1)
3261                         {
3262                             normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
3263                             normal_length = 1;
3264                         }
3265                         suppress_meta_esc = FALSE;
3266 #endif
3267                         break;
3268                     case VTE_ERASE_AUTO:
3269                     default:
3270 #ifndef _POSIX_VDISABLE
3271 #define _POSIX_VDISABLE '\0'
3272 #endif
3273 
3274 #if 0
3275                         if (m_pty != nullptr &&
3276                             tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1 &&
3277                             tio.c_cc[VERASE] != _POSIX_VDISABLE)
3278                         {
3279                             normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
3280                             normal_length = 1;
3281                         }
3282                         else
3283 #endif
3284                         {
3285                             normal = g_strdup("");
3286                             normal_length = 1;
3287                             /*suppress_meta_esc = FALSE;*/
3288                         }
3289                         /*suppress_meta_esc = FALSE;*/
3290                         break;
3291                 }
3292                 /* Toggle ^H vs ^? if Ctrl is pressed */
3293                 if (normal_length == 1 && m_modifiers & GDK_CONTROL_MASK) {
3294                     if (normal[0] == '\010') {
3295                         normal[0] = '\177';
3296                     } else if (normal[0] == '\177') {
3297                         normal[0] = '\010';
3298                     }
3299                 }
3300                 handled = TRUE;
3301                 break;
3302             case GDK_KEY_KP_Delete:
3303             case GDK_KEY_Delete:
3304                 switch (m_delete_binding) {
3305                     case VTE_ERASE_ASCII_BACKSPACE:
3306                         normal = g_strdup("\010");
3307                         normal_length = 1;
3308                         break;
3309                     case VTE_ERASE_ASCII_DELETE:
3310                         normal = g_strdup("\177");
3311                         normal_length = 1;
3312                         break;
3313                     case VTE_ERASE_TTY:
3314 #if 0
3315                         if (m_pty != nullptr &&
3316                             tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1)
3317                         {
3318                             normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
3319                             normal_length = 1;
3320                         }
3321 #endif
3322                         /*suppress_meta_esc = FALSE;*/
3323                         break;
3324                     case VTE_ERASE_DELETE_SEQUENCE:
3325                     case VTE_ERASE_AUTO:
3326                         default:
3327                             normal = g_strdup("\e[3~");
3328                             normal_length = 4;
3329                             add_modifiers = TRUE;
3330                             break;
3331                 }
3332                 handled = TRUE;
3333                 /* FIXMEchpe: why? this overrides the FALSE set above? */
3334                 /*suppress_meta_esc = TRUE;*/
3335                 break;
3336             case GDK_KEY_KP_Insert:
3337             case GDK_KEY_Insert:
3338                 if (m_modifiers & GDK_SHIFT_MASK) {
3339                     if (m_modifiers & GDK_CONTROL_MASK) {
3340                         emit_paste_clipboard();
3341                         handled = TRUE;
3342                         /*suppress_meta_esc = TRUE;*/
3343                     } else {
3344                         widget_paste(GDK_SELECTION_PRIMARY);
3345                         handled = TRUE;
3346                         /*suppress_meta_esc = TRUE;*/
3347                     }
3348                 } else if (m_modifiers & GDK_CONTROL_MASK) {
3349                     emit_copy_clipboard();
3350                     handled = TRUE;
3351                     /*suppress_meta_esc = TRUE;*/
3352                 }
3353                 break;
3354             /* Keypad/motion keys. */
3355             case GDK_KEY_KP_Up:
3356             case GDK_KEY_Up:
3357                 if (m_screen == &m_normal_screen &&
3358                     m_modifiers & GDK_CONTROL_MASK &&
3359                     m_modifiers & GDK_SHIFT_MASK) {
3360                     scroll_lines(-1);
3361                     scrolled = TRUE;
3362                     handled = TRUE;
3363                     /*suppress_meta_esc = TRUE;*/
3364                 }
3365                 break;
3366             case GDK_KEY_KP_Down:
3367             case GDK_KEY_Down:
3368                 if (m_screen == &m_normal_screen &&
3369                     m_modifiers & GDK_CONTROL_MASK &&
3370                     m_modifiers & GDK_SHIFT_MASK) {
3371                     scroll_lines(1);
3372                     scrolled = TRUE;
3373                     handled = TRUE;
3374                     /*suppress_meta_esc = TRUE;*/
3375                 }
3376                 break;
3377             case GDK_KEY_KP_Page_Up:
3378             case GDK_KEY_Page_Up:
3379                 if (m_screen == &m_normal_screen &&
3380                     m_modifiers & GDK_SHIFT_MASK) {
3381                     scroll_pages(-1);
3382                     scrolled = TRUE;
3383                     handled = TRUE;
3384                     /*suppress_meta_esc = TRUE;*/
3385                 }
3386                 break;
3387             case GDK_KEY_KP_Page_Down:
3388             case GDK_KEY_Page_Down:
3389                 if (m_screen == &m_normal_screen &&
3390                     m_modifiers & GDK_SHIFT_MASK) {
3391                     scroll_pages(1);
3392                     scrolled = TRUE;
3393                     handled = TRUE;
3394                     /*suppress_meta_esc = TRUE;*/
3395                 }
3396                 break;
3397             case GDK_KEY_KP_Home:
3398             case GDK_KEY_Home:
3399                 if (m_screen == &m_normal_screen &&
3400                     m_modifiers & GDK_SHIFT_MASK) {
3401                     maybe_scroll_to_top();
3402                     scrolled = TRUE;
3403                     handled = TRUE;
3404                 }
3405                 break;
3406             case GDK_KEY_KP_End:
3407             case GDK_KEY_End:
3408                 if (m_screen == &m_normal_screen &&
3409                     m_modifiers & GDK_SHIFT_MASK) {
3410                     maybe_scroll_to_bottom();
3411                     scrolled = TRUE;
3412                     handled = TRUE;
3413                 }
3414                 break;
3415             /* Let Shift +/- tweak the font, like XTerm does. */
3416             case GDK_KEY_KP_Add:
3417             case GDK_KEY_KP_Subtract:
3418                 if (m_modifiers & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
3419                     switch (keyval) {
3420                         case GDK_KEY_KP_Add:
3421                             emit_increase_font_size();
3422                             handled = TRUE;
3423                             /*suppress_meta_esc = TRUE;*/
3424                             break;
3425                         case GDK_KEY_KP_Subtract:
3426                             emit_decrease_font_size();
3427                             handled = TRUE;
3428                             /*suppress_meta_esc = TRUE;*/
3429                             break;
3430                     }
3431                 }
3432                 break;
3433             default:
3434                 break;
3435         }
3436         /* If the above switch statement didn't do the job, try mapping
3437          * it to a literal or capability name. */
3438         if (handled == FALSE) {
3439             _vte_keymap_map(keyval, m_modifiers,
3440                     m_cursor_mode == VTE_KEYMODE_APPLICATION,
3441                     m_keypad_mode == VTE_KEYMODE_APPLICATION,
3442                     &normal,
3443                     &normal_length);
3444             /* If we found something this way, suppress
3445              * escape-on-meta. */
3446             if (normal != NULL && normal_length > 0) {
3447                 /*suppress_meta_esc = TRUE;*/
3448             }
3449         }
3450 
3451         /* Shall we do this here or earlier?  See bug 375112 and bug 589557 */
3452         if (m_modifiers & GDK_CONTROL_MASK) {
3453             keyval = translate_ctrlkey(event);
3454         }
3455 
3456         /* If we didn't manage to do anything, try to salvage a
3457          * printable string. */
3458         if (handled == FALSE && normal == NULL) {
3459 
3460             /* Convert the keyval to a gunichar. */
3461             keychar = gdk_keyval_to_unicode(keyval);
3462             normal_length = 0;
3463             if (keychar != 0) {
3464                 /* Convert the gunichar to a string. */
3465                 normal_length = g_unichar_to_utf8(keychar, keybuf);
3466                 if (normal_length != 0) {
3467                     normal = (char *)g_malloc(normal_length + 1);
3468                     memcpy(normal, keybuf, normal_length);
3469                     normal[normal_length] = '\0';
3470                 } else {
3471                     normal = NULL;
3472                 }
3473             }
3474             if ((normal != NULL) && (m_modifiers & GDK_CONTROL_MASK)) {
3475                 /* Replace characters which have "control"
3476                  * counterparts with those counterparts. */
3477                 for (i = 0; i < normal_length; i++) {
3478                     if ((((guint8)normal[i]) >= 0x40) &&
3479                         (((guint8)normal[i]) <  0x80)) {
3480                         normal[i] &= (~(0x60));
3481                     }
3482                 }
3483             }
3484             _VTE_DEBUG_IF (VTE_DEBUG_EVENTS) {
3485                 if (normal) g_printerr(
3486                         "Keypress, modifiers=0x%x, "
3487                         "keyval=0x%x, cooked string=`%s'.\n",
3488                         m_modifiers,
3489                         keyval, normal);
3490             }
3491         }
3492         /* If we got normal characters, send them to the child. */
3493         if (normal != NULL) {
3494             if (add_modifiers) {
3495                 _vte_keymap_key_add_key_modifiers(keyval,
3496                                                     m_modifiers,
3497                                                     m_cursor_mode == VTE_KEYMODE_APPLICATION,
3498                                                     &normal,
3499                                                     &normal_length);
3500             }
3501 #if 0
3502             if (m_meta_sends_escape &&
3503                 !suppress_meta_esc &&
3504                 (normal_length > 0) &&
3505                 (m_modifiers & VTE_META_MASK)) {
3506                 feed_child(_VTE_CAP_ESC, 1);
3507             }
3508 #endif
3509 #if 0
3510             if (normal_length > 0) {
3511                 feed_child_using_modes(normal, normal_length);
3512             }
3513 #endif
3514             g_free(normal);
3515         }
3516         /* Keep the cursor on-screen. */
3517         if (!scrolled && !modifier &&
3518             m_scroll_on_keystroke) {
3519             maybe_scroll_to_bottom();
3520         }
3521         return true;
3522     }
3523     return false;
3524 }
3525 
widget_key_release(GdkEventKey * event)3526 bool VteTerminalPrivate::widget_key_release(GdkEventKey *event)
3527 {
3528     read_modifiers((GdkEvent*)event);
3529 
3530     if (m_input_enabled &&
3531             m_im_context &&
3532             gtk_im_context_filter_keypress(m_im_context, event)) {
3533         return true;
3534     }
3535 
3536     return false;
3537 }
3538 
compare_unichar_p(const void * u1p,const void * u2p)3539 static int compare_unichar_p(const void *u1p, const void *u2p)
3540 {
3541     const gunichar u1 = *(gunichar*)u1p;
3542     const gunichar u2 = *(gunichar*)u2p;
3543     return u1 < u2 ? -1 : u1 > u2 ? 1 : 0;
3544 }
3545 
3546 static const guint8 word_char_by_category[] = {
3547     [G_UNICODE_CONTROL]             = 2,
3548     [G_UNICODE_FORMAT]              = 2,
3549     [G_UNICODE_UNASSIGNED]          = 2,
3550     [G_UNICODE_PRIVATE_USE]         = 0,
3551     [G_UNICODE_SURROGATE]           = 2,
3552     [G_UNICODE_LOWERCASE_LETTER]    = 1,
3553     [G_UNICODE_MODIFIER_LETTER]     = 1,
3554     [G_UNICODE_OTHER_LETTER]        = 1,
3555     [G_UNICODE_TITLECASE_LETTER]    = 1,
3556     [G_UNICODE_UPPERCASE_LETTER]    = 1,
3557     [G_UNICODE_SPACING_MARK]        = 0,
3558     [G_UNICODE_ENCLOSING_MARK]      = 0,
3559     [G_UNICODE_NON_SPACING_MARK]    = 0,
3560     [G_UNICODE_DECIMAL_NUMBER]      = 1,
3561     [G_UNICODE_LETTER_NUMBER]       = 1,
3562     [G_UNICODE_OTHER_NUMBER]        = 1,
3563     [G_UNICODE_CONNECT_PUNCTUATION] = 0,
3564     [G_UNICODE_DASH_PUNCTUATION]    = 0,
3565     [G_UNICODE_CLOSE_PUNCTUATION]   = 0,
3566     [G_UNICODE_FINAL_PUNCTUATION]   = 0,
3567     [G_UNICODE_INITIAL_PUNCTUATION] = 0,
3568     [G_UNICODE_OTHER_PUNCTUATION]   = 0,
3569     [G_UNICODE_OPEN_PUNCTUATION]    = 0,
3570     [G_UNICODE_CURRENCY_SYMBOL]     = 0,
3571     [G_UNICODE_MODIFIER_SYMBOL]     = 0,
3572     [G_UNICODE_MATH_SYMBOL]         = 0,
3573     [G_UNICODE_OTHER_SYMBOL]        = 0,
3574     [G_UNICODE_LINE_SEPARATOR]      = 2,
3575     [G_UNICODE_PARAGRAPH_SEPARATOR] = 2,
3576     [G_UNICODE_SPACE_SEPARATOR]     = 2,
3577 };
3578 
3579 /*
3580  * VteTerminalPrivate::is_word_char:
3581  * @c: a candidate Unicode code point
3582  *
3583  * Checks if a particular character is considered to be part of a word or not.
3584  *
3585  * Returns: %TRUE if the character is considered to be part of a word
3586  */
is_word_char(gunichar c) const3587 bool VteTerminalPrivate::is_word_char(gunichar c) const
3588 {
3589     const guint8 v = word_char_by_category[g_unichar_type(c)];
3590 
3591     if (v) {
3592         return v == 1;
3593     }
3594 
3595     /* Do we have an exception? */
3596     return bsearch(&c,
3597                     m_word_char_exceptions,
3598                     m_word_char_exceptions_len,
3599                     sizeof(gunichar),
3600                     compare_unichar_p) != NULL;
3601 }
3602 
3603 /* Check if the characters in the two given locations are in the same class
3604  * (word vs. non-word characters). */
is_same_class(vte::grid::column_t acol,vte::grid::row_t arow,vte::grid::column_t bcol,vte::grid::row_t brow) const3605 bool VteTerminalPrivate::is_same_class(vte::grid::column_t acol,
3606                                         vte::grid::row_t arow,
3607                                         vte::grid::column_t bcol,
3608                                         vte::grid::row_t brow) const
3609 {
3610     VteCell const* pcell = nullptr;
3611     bool word_char;
3612     if ((pcell = find_charcell(acol, arow)) != nullptr && pcell->c != 0) {
3613         word_char = is_word_char(_vte_unistr_get_base(pcell->c));
3614 
3615         /* Lets not group non-wordchars together (bug #25290) */
3616         if (!word_char) {
3617             return false;
3618         }
3619 
3620         pcell = find_charcell(bcol, brow);
3621         if (pcell == NULL || pcell->c == 0) {
3622             return false;
3623         }
3624         if (word_char != is_word_char(_vte_unistr_get_base(pcell->c))) {
3625             return false;
3626         }
3627         return true;
3628     }
3629     return false;
3630 }
3631 
3632 /* Check if we soft-wrapped on the given line. */
3633 /* FIXMEchpe replace this with a method on VteRing */
line_is_wrappable(vte::grid::row_t row) const3634 bool VteTerminalPrivate::line_is_wrappable(vte::grid::row_t row) const
3635 {
3636     VteRowData const* rowdata = find_row_data(row);
3637     return rowdata && rowdata->attr.soft_wrapped;
3638 }
3639 
3640 /* Check if the given point is in the region between the two points */
vte_cell_is_between(glong col,glong row,glong acol,glong arow,glong bcol,glong brow)3641 static gboolean vte_cell_is_between(glong col, glong row,
3642                                 glong acol, glong arow, glong bcol, glong brow)
3643 {
3644     /* Negative between never allowed. */
3645     if ((arow > brow) || ((arow == brow) && (acol > bcol))) {
3646         return FALSE;
3647     }
3648     /* Degenerate span? */
3649     if ((row == arow) && (row == brow) && (col == acol) && (col == bcol)) {
3650         return TRUE;
3651     }
3652     /* A cell is between two points if it's on a line after the
3653      * specified area starts, or before the line where it ends,
3654      * or any of the lines in between. */
3655     if ((row > arow) && (row < brow)) {
3656         return TRUE;
3657     }
3658     /* It's also between the two points if they're on the same row
3659      * the cell lies between the start and end columns. */
3660     if ((row == arow) && (row == brow)) {
3661         if (col >= acol) {
3662             if (col < bcol) {
3663                 return TRUE;
3664             } else {
3665                 if (col == bcol) {
3666                     return TRUE;
3667                 } else {
3668                     return FALSE;
3669                 }
3670             }
3671         } else {
3672             return FALSE;
3673         }
3674     }
3675     /* It's also "between" if it's on the line where the area starts and
3676      * at or after the start column, or on the line where the area ends and
3677      * before the end column. */
3678     if ((row == arow) && (col >= acol)) {
3679         return TRUE;
3680     } else {
3681         if (row == brow) {
3682             if (col < bcol) {
3683                 return TRUE;
3684             } else {
3685                 if (col == bcol) {
3686                     return TRUE;
3687                 } else {
3688                     return FALSE;
3689                 }
3690             }
3691         } else {
3692             return FALSE;
3693         }
3694     }
3695     return FALSE;
3696 }
3697 
3698 /* Check if a cell is selected or not. */
3699 /* FIXMEchpe: replace this by just using vte::grid::span for selection and then this simply becomes .contains() */
cell_is_selected(vte::grid::column_t col,vte::grid::row_t row) const3700 bool VteTerminalPrivate::cell_is_selected(vte::grid::column_t col,
3701                                             vte::grid::row_t row) const
3702 {
3703     /* If there's nothing selected, it's an easy question to answer. */
3704     if (!m_has_selection) {
3705         return false;
3706     }
3707 
3708     /* If the selection is obviously bogus, then it's also very easy. */
3709     auto const& ss = m_selection_start;
3710     auto const& se = m_selection_end;
3711     if ((ss.row < 0) || (se.row < 0)) {
3712         return false;
3713     }
3714 
3715     /* Limit selection in block mode. */
3716     if (m_selection_block_mode) {
3717         if (col < ss.col || col > se.col) {
3718             return false;
3719         }
3720     }
3721 
3722     /* Now it boils down to whether or not the point is between the
3723      * begin and endpoint of the selection. */
3724     return vte_cell_is_between(col, row, ss.col, ss.row, se.col, se.row);
3725 }
3726 
widget_paste_received(char const * text)3727 void VteTerminalPrivate::widget_paste_received(char const* text)
3728 {
3729     gchar *paste, *p;
3730     gsize run;
3731     unsigned char c;
3732 
3733     if (text == nullptr) {
3734         return;
3735     }
3736 
3737     gsize len = strlen(text);
3738     _vte_debug_print(VTE_DEBUG_SELECTION,
3739                         "Pasting %" G_GSIZE_FORMAT " UTF-8 bytes.\n", len);
3740     /* FIXMEchpe this cannot happen ever */
3741     if (!g_utf8_validate(text, len, NULL)) {
3742         g_warning("Paste not valid UTF-8, dropping.");
3743         return;
3744     }
3745 
3746    /* Convert newlines to carriage returns, which more software
3747     * is able to cope with (cough, pico, cough).
3748     * Filter out control chars except HT, CR (even stricter than xterm).
3749     * Also filter out C1 controls: U+0080 (0xC2 0x80) - U+009F (0xC2 0x9F). */
3750     p = paste = (gchar *) g_malloc(len + 1);
3751     while (p != nullptr && text[0] != '\0') {
3752         run = strcspn(text, "\x01\x02\x03\x04\x05\x06\x07"
3753                         "\x08\x0A\x0B\x0C\x0E\x0F"
3754                         "\x10\x11\x12\x13\x14\x15\x16\x17"
3755                         "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
3756                         "\x7F\xC2");
3757         memcpy(p, text, run);
3758         p += run;
3759         text += run;
3760         switch (text[0]) {
3761             case '\x00':
3762                 break;
3763             case '\x0A':
3764                 *p = '\x0D';
3765                 p++;
3766                 text++;
3767                 break;
3768             case '\xC2':
3769                 c = text[1];
3770                 if (c >= 0x80 && c <= 0x9F) {
3771                     /* Skip both bytes of a C1 */
3772                     text += 2;
3773                 } else {
3774                     /* Move along, nothing to see here */
3775                     *p = '\xC2';
3776                     p++;
3777                     text++;
3778                 }
3779                 break;
3780             default:
3781                 /* Swallow this byte */
3782                 text++;
3783                 break;
3784         }
3785     }
3786 #if 0
3787     if (m_bracketed_paste_mode) {
3788         feed_child("\e[200~", -1);
3789     }
3790     /* FIXMEchpe add a way to avoid the extra string copy done here */
3791     feed_child(paste, p - paste);
3792     if (m_bracketed_paste_mode) {
3793             feed_child("\e[201~", -1);
3794     }
3795 #endif
3796     g_free(paste);
3797 }
3798 
feed_mouse_event(vte::grid::coords const & rowcol,int button,bool is_drag,bool is_release)3799 bool VteTerminalPrivate::feed_mouse_event(vte::grid::coords const& rowcol /* confined */,
3800                                             int button,
3801                                             bool is_drag,
3802                                             bool is_release)
3803 {
3804     unsigned char cb = 0;
3805 
3806     char buf[LINE_MAX];
3807     /*gint len = 0;*/
3808 
3809     /* Don't send events on scrollback contents: bug 755187. */
3810     if (grid_coords_in_scrollback(rowcol)) {
3811         return false;
3812     }
3813 
3814     /* Make coordinates 1-based. */
3815     auto cx = rowcol.column() + 1;
3816     auto cy = rowcol.row() - m_screen->insert_delta + 1;
3817 
3818     /* Encode the button information in cb. */
3819     switch (button) {
3820         case 0:                 /* No button, just dragging. */
3821             cb = 3;
3822             break;
3823         case 1:         /* Left. */
3824             cb = 0;
3825             break;
3826         case 2:         /* Middle. */
3827             cb = 1;
3828             break;
3829         case 3:         /* Right. */
3830             cb = 2;
3831             break;
3832         case 4:
3833             cb = 64;    /* Scroll up. */
3834             break;
3835         case 5:
3836             cb = 65;    /* Scroll down. */
3837             break;
3838     }
3839 
3840     /* With the exception of the 1006 mode, button release is also encoded here. */
3841     /* Note that if multiple extensions are enabled, the 1006 is used, so it's okay to check for only that. */
3842     if (is_release && !m_mouse_xterm_extension) {
3843         cb = 3;
3844     }
3845 
3846     /* Encode the modifiers. */
3847     if (m_modifiers & GDK_SHIFT_MASK) {
3848         cb |= 4;
3849     }
3850     if (m_modifiers & VTE_META_MASK) {
3851         cb |= 8;
3852     }
3853     if (m_modifiers & GDK_CONTROL_MASK) {
3854         cb |= 16;
3855     }
3856 
3857     /* Encode a drag event. */
3858     if (is_drag) {
3859         cb |= 32;
3860     }
3861 
3862     /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
3863     if (m_mouse_xterm_extension) {
3864         /* xterm's extended mode (1006) */
3865         /*len =*/ g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "<%d;%ld;%ld%c", cb, cx, cy, is_release ? 'm' : 'M');
3866     } else if (m_mouse_urxvt_extension) {
3867         /* urxvt's extended mode (1015) */
3868         /*len =*/ g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "%d;%ld;%ldM", 32 + cb, cx, cy);
3869     } else if (cx <= 231 && cy <= 231) {
3870         /* legacy mode */
3871         /*len =*/ g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "M%c%c%c", 32 + cb, 32 + (guchar)cx, 32 + (guchar)cy);
3872     }
3873 #if 0
3874     /* Send event direct to the child, this is binary not text data */
3875     feed_child_binary((guint8*) buf, len);
3876 #endif
3877         return true;
3878 }
3879 
feed_focus_event(bool in)3880 void VteTerminalPrivate::feed_focus_event(bool in)
3881 {
3882 #if 0
3883     char buf[8];
3884     gsize len;
3885 
3886     len = g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "%c", in ? 'I' : 'O');
3887     feed_child_binary((guint8 *)buf, len);
3888 #endif
3889 }
3890 
feed_focus_event_initial()3891 void VteTerminalPrivate::feed_focus_event_initial()
3892 {
3893     /* We immediately send the terminal a focus event, since otherwise
3894         * it has no way to know the current status.
3895         */
3896     feed_focus_event(gtk_widget_has_focus(m_widget));
3897 }
3898 
maybe_feed_focus_event(bool in)3899 void VteTerminalPrivate::maybe_feed_focus_event(bool in)
3900 {
3901     if (m_focus_tracking_mode) {
3902             feed_focus_event(in);
3903     }
3904 }
3905 
3906 /*
3907  * VteTerminalPrivate::maybe_send_mouse_button:
3908  * @terminal:
3909  * @event:
3910  *
3911  * Sends a mouse button click or release notification to the application,
3912  * if the terminal is in mouse tracking mode.
3913  *
3914  * Returns: %TRUE iff the event was consumed
3915  */
maybe_send_mouse_button(vte::grid::coords const & unconfined_rowcol,GdkEventType event_type,int event_button)3916 bool VteTerminalPrivate::maybe_send_mouse_button(vte::grid::coords const& unconfined_rowcol,
3917                                                     GdkEventType event_type,
3918                                                     int event_button)
3919 {
3920     switch (event_type) {
3921         case GDK_BUTTON_PRESS:
3922             if (m_mouse_tracking_mode < MOUSE_TRACKING_SEND_XY_ON_CLICK) {
3923                 return false;
3924             }
3925             break;
3926         case GDK_BUTTON_RELEASE:
3927         {
3928             if (m_mouse_tracking_mode < MOUSE_TRACKING_SEND_XY_ON_BUTTON) {
3929                 return false;
3930             }
3931             break;
3932         }
3933         default:
3934             return false;
3935             break;
3936     }
3937 
3938     auto rowcol = confine_grid_coords(unconfined_rowcol);
3939     return feed_mouse_event(rowcol, event_button, false /* not drag */, event_type == GDK_BUTTON_RELEASE);
3940 }
3941 
3942 /*
3943  * VteTerminalPrivate::maybe_send_mouse_drag:
3944  * @terminal:
3945  * @event:
3946  *
3947  * Sends a mouse motion notification to the application,
3948  * if the terminal is in mouse tracking mode.
3949  *
3950  * Returns: %TRUE iff the event was consumed
3951  */
maybe_send_mouse_drag(vte::grid::coords const & unconfined_rowcol,GdkEventType event_type)3952 bool VteTerminalPrivate::maybe_send_mouse_drag(vte::grid::coords const& unconfined_rowcol,
3953                                                 GdkEventType event_type)
3954 {
3955     auto rowcol = confine_grid_coords(unconfined_rowcol);
3956 
3957     /* First determine if we even want to send notification. */
3958     switch (event_type) {
3959         case GDK_MOTION_NOTIFY:
3960             if (m_mouse_tracking_mode < MOUSE_TRACKING_CELL_MOTION_TRACKING) {
3961                 return false;
3962             }
3963 
3964             if (m_mouse_tracking_mode < MOUSE_TRACKING_ALL_MOTION_TRACKING) {
3965 
3966                 if (m_mouse_pressed_buttons == 0) {
3967                     return false;
3968                 }
3969                /* The xterm doc is not clear as to whether
3970                 * all-tracking also sends degenerate same-cell events;
3971                 * we don't.
3972                 */
3973                 if (rowcol == confined_grid_coords_from_view_coords(m_mouse_last_position)) {
3974                     return false;
3975                 }
3976             }
3977             break;
3978         default:
3979             return false;
3980             break;
3981     }
3982 
3983     /* As per xterm, report the leftmost pressed button - if any. */
3984     int button;
3985     if (m_mouse_pressed_buttons & 1) {
3986         button = 1;
3987     } else if (m_mouse_pressed_buttons & 2) {
3988         button = 2;
3989     } else if (m_mouse_pressed_buttons & 4) {
3990         button = 3;
3991     } else {
3992         button = 0;
3993     }
3994 
3995     return feed_mouse_event(rowcol, button, true /* drag */, false /* not release */);
3996 }
3997 
3998 /*
3999  * VteTerminalPrivate::hyperlink_invalidate_and_get_bbox
4000  *
4001  * Invalidates cells belonging to the non-zero hyperlink idx, in order to
4002  * stop highlighting the previously hovered hyperlink or start highlighting
4003  * the new one. Optionally stores the coordinates of the bounding box.
4004  */
hyperlink_invalidate_and_get_bbox(hyperlink_idx_t idx,GdkRectangle * bbox)4005 void VteTerminalPrivate::hyperlink_invalidate_and_get_bbox(hyperlink_idx_t idx, GdkRectangle *bbox)
4006 {
4007     auto first_row = first_displayed_row();
4008     auto end_row = last_displayed_row() + 1;
4009     vte::grid::row_t row, top = LONG_MAX, bottom = -1;
4010     vte::grid::column_t col, left = LONG_MAX, right = -1;
4011     const VteRowData *rowdata;
4012 
4013     g_assert (idx != 0);
4014 
4015     for (row = first_row; row < end_row; row++) {
4016         rowdata = _vte_ring_index(m_screen->row_data, row);
4017         if (rowdata != NULL) {
4018             for (col = 0; col < rowdata->len; col++) {
4019                 if (G_UNLIKELY (rowdata->cells[col].attr.hyperlink_idx == idx)) {
4020                     invalidate_cells(col, 1, row, 1);
4021                     top = MIN(top, row);
4022                     bottom = MAX(bottom, row);
4023                     left = MIN(left, col);
4024                     right = MAX(right, col);
4025                 }
4026             }
4027         }
4028     }
4029 
4030     if (bbox == NULL) {
4031         return;
4032     }
4033 
4034     /* If bbox != NULL, we're looking for the new hovered hyperlink which always has onscreen bits. */
4035     g_assert (top != LONG_MAX && bottom != -1 && left != LONG_MAX && right != -1);
4036 
4037     auto allocation = get_allocated_rect();
4038     bbox->x = allocation.x + m_padding.left + left * m_cell_width;
4039     bbox->y = allocation.y + m_padding.top + row_to_pixel(top);
4040     bbox->width = (right - left + 1) * m_cell_width;
4041     bbox->height = (bottom - top + 1) * m_cell_height;
4042     _vte_debug_print (VTE_DEBUG_HYPERLINK,
4043                         "Hyperlink bounding box: x=%d y=%d w=%d h=%d\n",
4044                         bbox->x, bbox->y, bbox->width, bbox->height);
4045 }
4046 
4047 /*
4048  * VteTerminalPrivate::hyperlink_hilite_update:
4049  *
4050  * Checks the coordinates for hyperlink. Updates m_hyperlink_hover_idx
4051  * and m_hyperlink_hover_uri, and schedules to update the highlighting.
4052  */
hyperlink_hilite_update()4053 void VteTerminalPrivate::hyperlink_hilite_update()
4054 {
4055     const VteRowData *rowdata;
4056     bool do_check_hilite;
4057     vte::grid::coords rowcol;
4058     hyperlink_idx_t new_hyperlink_hover_idx = 0;
4059     GdkRectangle bbox;
4060     const char *separator;
4061 
4062     if (!m_allow_hyperlink) {
4063         return;
4064     }
4065 
4066     _vte_debug_print (VTE_DEBUG_HYPERLINK,
4067                         "hyperlink_hilite_update\n");
4068 
4069     /* m_mouse_last_position contains the current position, see bug 789536 comment 24. */
4070     auto pos = m_mouse_last_position;
4071 
4072     /* Whether there's any chance we'd highlight something */
4073     do_check_hilite = view_coords_visible(pos) &&
4074                         m_mouse_cursor_over_widget &&
4075                         !(m_mouse_autohide && m_mouse_cursor_autohidden) &&
4076                         !m_selecting;
4077     if (do_check_hilite) {
4078         rowcol = grid_coords_from_view_coords(pos);
4079         rowdata = find_row_data(rowcol.row());
4080         if (rowdata && rowcol.column() < rowdata->len) {
4081             new_hyperlink_hover_idx = rowdata->cells[rowcol.column()].attr.hyperlink_idx;
4082         }
4083     }
4084 
4085     if (new_hyperlink_hover_idx == m_hyperlink_hover_idx) {
4086         _vte_debug_print (VTE_DEBUG_HYPERLINK,
4087                             "hyperlink did not change\n");
4088         return;
4089     }
4090 
4091     /* Invalidate cells of the old hyperlink. */
4092     if (m_hyperlink_hover_idx != 0) {
4093         hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, NULL);
4094     }
4095 
4096     /* This might be different from new_hyperlink_hover_idx. If in the stream, that one contains
4097         * the pseudo idx VTE_HYPERLINK_IDX_TARGET_IN_STREAM and now a real idx is allocated.
4098         * Plus, the ring's internal belief of the hovered hyperlink is also updated. */
4099     if (do_check_hilite) {
4100         m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, rowcol.row(), rowcol.column(), true, &m_hyperlink_hover_uri);
4101     } else {
4102         m_hyperlink_hover_idx = 0;
4103         m_hyperlink_hover_uri = nullptr;
4104     }
4105 
4106     /* Invalidate cells of the new hyperlink. Get the bounding box. */
4107     if (m_hyperlink_hover_idx != 0) {
4108         /* URI is after the first semicolon */
4109         separator = strchr(m_hyperlink_hover_uri, ';');
4110         g_assert(separator != NULL);
4111         m_hyperlink_hover_uri = separator + 1;
4112 
4113         hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, &bbox);
4114         g_assert(bbox.width > 0 && bbox.height > 0);
4115     }
4116     _vte_debug_print(VTE_DEBUG_HYPERLINK,
4117                         "Hover idx: %d \"%s\"\n",
4118                         m_hyperlink_hover_idx,
4119                         m_hyperlink_hover_uri);
4120 
4121     /* Underlining hyperlinks has precedence over regex matches. So when the hovered hyperlink changes,
4122         * the regex match might need to become or stop being underlined. */
4123     invalidate_match_span();
4124 
4125     apply_mouse_cursor();
4126 
4127     emit_hyperlink_hover_uri_changed(m_hyperlink_hover_idx != 0 ? &bbox : NULL);
4128 }
4129 
4130 /*
4131  * VteTerminalPrivate::match_hilite_clear:
4132  *
4133  * Reset match variables and invalidate the old match region if highlighted.
4134  */
match_hilite_clear()4135 void VteTerminalPrivate::match_hilite_clear()
4136 {
4137     invalidate_match_span();
4138 
4139     m_match_span.clear();
4140     m_match_tag = -1;
4141 
4142     if (m_match != nullptr) {
4143         g_free (m_match);
4144         m_match = nullptr;
4145     }
4146 }
4147 
invalidate_match_span()4148 void VteTerminalPrivate::invalidate_match_span()
4149 {
4150     _vte_debug_print(VTE_DEBUG_EVENTS,
4151                         "Invalidating match span %s\n", m_match_span.to_string());
4152     invalidate(m_match_span);
4153 }
4154 
4155 /* Note that the clipboard has cleared. */
clipboard_clear_cb(GtkClipboard * clipboard,gpointer user_data)4156 static void clipboard_clear_cb(GtkClipboard *clipboard, gpointer user_data)
4157 {
4158     VteTerminalPrivate *that = reinterpret_cast<VteTerminalPrivate*>(user_data);
4159     that->widget_clipboard_cleared(clipboard);
4160 }
4161 
widget_clipboard_cleared(GtkClipboard * clipboard_)4162 void VteTerminalPrivate::widget_clipboard_cleared(GtkClipboard *clipboard_)
4163 {
4164     if (m_changing_selection) {
4165         return;
4166     }
4167 
4168     if (clipboard_ == m_clipboard[VTE_SELECTION_PRIMARY]) {
4169         if (m_selection_owned[VTE_SELECTION_PRIMARY] && m_has_selection) {
4170             _vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n");
4171             deselect_all();
4172         }
4173         m_selection_owned[VTE_SELECTION_PRIMARY] = false;
4174     } else if (clipboard_ == m_clipboard[VTE_SELECTION_CLIPBOARD]) {
4175         m_selection_owned[VTE_SELECTION_CLIPBOARD] = false;
4176     }
4177 }
4178 
4179 /* Supply the selected text to the clipboard. */
clipboard_copy_cb(GtkClipboard * clipboard,GtkSelectionData * data,guint info,gpointer user_data)4180 static void clipboard_copy_cb(GtkClipboard *clipboard,
4181                                 GtkSelectionData *data,
4182                                 guint info,
4183                                 gpointer user_data)
4184 {
4185     VteTerminalPrivate *that = reinterpret_cast<VteTerminalPrivate*>(user_data);
4186     that->widget_clipboard_requested(clipboard, data, info);
4187 }
4188 
text_to_utf16_mozilla(GString * text,gsize * len_ptr)4189 static char* text_to_utf16_mozilla(GString* text,
4190                       gsize* len_ptr)
4191 {
4192    /* Use g_convert() instead of g_utf8_to_utf16() since the former
4193     * adds a BOM which Mozilla requires for text/html format.
4194     */
4195     return g_convert(text->str, text->len,
4196                         "UTF-16", /* conver to UTF-16 */
4197                         "UTF-8", /* convert from UTF-8 */
4198                         nullptr /* out bytes_read */,
4199                         len_ptr,
4200                         nullptr);
4201 }
4202 
widget_clipboard_requested(GtkClipboard * target_clipboard,GtkSelectionData * data,guint info)4203 void VteTerminalPrivate::widget_clipboard_requested(GtkClipboard *target_clipboard,
4204                                                     GtkSelectionData *data,
4205                                                     guint info)
4206 {
4207     for (auto sel = 0; sel < LAST_VTE_SELECTION; sel++) {
4208         if (target_clipboard == m_clipboard[sel] && m_selection[sel] != nullptr) {
4209             _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) {
4210                 int i;
4211                 g_printerr("Setting selection %d (%" G_GSIZE_FORMAT " UTF-8 bytes.) for target %s\n",
4212                             sel, m_selection[sel]->len, gdk_atom_name(gtk_selection_data_get_target(data)));
4213                 char const* selection_text = m_selection[sel]->str;
4214                 for (i = 0; selection_text[i] != '\0'; i++) {
4215                     g_printerr("0x%04x ", selection_text[i]);
4216                     if ((i & 0x7) == 0x7) {
4217                         g_printerr("\n");
4218                     }
4219                 }
4220                 g_printerr("\n");
4221             }
4222             if (info == VTE_TARGET_TEXT) {
4223                 gtk_selection_data_set_text(data,
4224                                             m_selection[sel]->str,
4225                                             m_selection[sel]->len);
4226             } else if (info == VTE_TARGET_HTML) {
4227                 gsize len;
4228                 auto selection = text_to_utf16_mozilla(m_selection[sel], &len);
4229                 /* FIXMEchpe this makes yet another copy of the data... :( */
4230                 if (selection) {
4231                     gtk_selection_data_set(data,
4232                                             gdk_atom_intern_static_string("text/html"),
4233                                             16,
4234                                             (const guchar *)selection,
4235                                             len);
4236                 }
4237                 g_free(selection);
4238             } else {
4239                 /* Not reached */
4240             }
4241         }
4242     }
4243 }
4244 
4245 /* Convert the internal color code (either index or RGB) into RGB. */
4246 template <unsigned int redbits,
4247           unsigned int greenbits,
4248           unsigned int bluebits>
rgb_from_index(guint index,vte::color::rgb & color) const4249 void VteTerminalPrivate::rgb_from_index(guint index,
4250                                         vte::color::rgb& color) const
4251 {
4252     bool dim = false;
4253     if (!(index & VTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)) && (index & VTE_DIM_COLOR)) {
4254         index &= ~VTE_DIM_COLOR;
4255         dim = true;
4256     }
4257 
4258     if (index >= VTE_LEGACY_COLORS_OFFSET && index < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_FULL_COLOR_SET_SIZE) {
4259         index -= VTE_LEGACY_COLORS_OFFSET;
4260     }
4261     if (index < VTE_PALETTE_SIZE) {
4262         color = *get_color(index);
4263         if (dim) {
4264             /* magic formula taken from xterm */
4265             color.red = color.red * 2 / 3;
4266             color.green = color.green * 2 / 3;
4267             color.blue = color.blue * 2 / 3;
4268         }
4269     } else if (index & VTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)) {
4270         color.red   = VTE_RGB_COLOR_GET_COMPONENT(index, greenbits + bluebits, redbits) * 0x101U;
4271         color.green = VTE_RGB_COLOR_GET_COMPONENT(index, bluebits, greenbits) * 0x101U;
4272         color.blue  = VTE_RGB_COLOR_GET_COMPONENT(index, 0, bluebits) * 0x101U;
4273     } else {
4274         g_assert_not_reached();
4275     }
4276 }
4277 
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,bool include_trailing_spaces,GArray * attributes)4278 GString* VteTerminalPrivate::get_text(vte::grid::row_t start_row,
4279                                         vte::grid::column_t start_col,
4280                                         vte::grid::row_t end_row,
4281                                         vte::grid::column_t end_col,
4282                                         bool block,
4283                                         bool wrap,
4284                                         bool include_trailing_spaces,
4285                                         GArray *attributes)
4286 {
4287     const VteCell *pcell = NULL;
4288     GString *string;
4289     struct _VteCharAttributes attr;
4290     vte::color::rgb fore, back;
4291 
4292     if (attributes) {
4293         g_array_set_size (attributes, 0);
4294     }
4295 
4296     string = g_string_new(NULL);
4297     memset(&attr, 0, sizeof(attr));
4298 
4299     if (start_col < 0) {
4300             start_col = 0;
4301     }
4302 
4303     vte::grid::column_t next_first_column = block ? start_col : 0;
4304     vte::grid::column_t col = start_col;
4305     vte::grid::row_t row;
4306     for (row = start_row; row < end_row + 1; row++, col = next_first_column) {
4307         VteRowData const* row_data = find_row_data(row);
4308         gsize last_empty, last_nonempty;
4309         vte::grid::column_t last_emptycol, last_nonemptycol;
4310         vte::grid::column_t line_last_column = (block || row == end_row) ? end_col : G_MAXLONG;
4311 
4312         last_empty = last_nonempty = string->len;
4313         last_emptycol = last_nonemptycol = -1;
4314 
4315         attr.row = row;
4316         attr.column = col;
4317         pcell = NULL;
4318         if (row_data != NULL) {
4319             while (col <= line_last_column && (pcell = _vte_row_data_get (row_data, col))) {
4320 
4321                 attr.column = col;
4322 
4323                 /* If it's not part of a multi-column character,
4324                  * and passes the selection criterion, add it to
4325                  * the selection. */
4326                 if (!pcell->attr.fragment()) {
4327                     /* Store the attributes of this character. */
4328                     /* FIXMEchpe shouldn't this use determine_colors? */
4329                     uint32_t fg, bg, dc;
4330                     vte_color_triple_get(pcell->attr.colors(), &fg, &bg, &dc);
4331                     rgb_from_index<8, 8, 8>(fg, fore);
4332                     rgb_from_index<8, 8, 8>(bg, back);
4333                     attr.fore.red = fore.red;
4334                     attr.fore.green = fore.green;
4335                     attr.fore.blue = fore.blue;
4336                     attr.back.red = back.red;
4337                     attr.back.green = back.green;
4338                     attr.back.blue = back.blue;
4339                     attr.underline = (pcell->attr.underline() == 1);
4340                     attr.strikethrough = pcell->attr.strikethrough();
4341 
4342                     /* Store the cell string */
4343                     if (pcell->c == 0) {
4344                         g_string_append_c (string, ' ');
4345                         last_empty = string->len;
4346                         last_emptycol = col;
4347                     } else {
4348                         _vte_unistr_append_to_string (pcell->c, string);
4349                         last_nonempty = string->len;
4350                         last_nonemptycol = col;
4351                     }
4352 
4353                     /* If we added text to the string, record its
4354                      * attributes, one per byte. */
4355                     if (attributes) {
4356                         vte_g_array_fill(attributes,
4357                                 &attr, string->len);
4358                     }
4359                 }
4360 
4361                 col++;
4362             }
4363         }
4364 
4365            /* If the last thing we saw was a empty, and we stopped at the
4366         * right edge of the range, trim the trailing spaces
4367         * off of the line. */
4368         if (!include_trailing_spaces && last_empty > last_nonempty) {
4369 
4370             col = last_emptycol + 1;
4371 
4372             if (row_data != NULL) {
4373                 while ((pcell = _vte_row_data_get (row_data, col))) {
4374                     col++;
4375 
4376                     if (pcell->attr.fragment()) {
4377                         continue;
4378                     }
4379 
4380                     if (pcell->c != 0) {
4381                         break;
4382                     }
4383                 }
4384             }
4385             if (pcell == NULL) {
4386                 g_string_truncate(string, last_nonempty);
4387                 if (attributes) {
4388                     g_array_set_size(attributes, string->len);
4389                 }
4390                 attr.column = last_nonemptycol;
4391             }
4392         }
4393 
4394         /* Adjust column, in case we want to append a newline */
4395         /* FIXMEchpe MIN ? */
4396         attr.column = MAX(m_column_count, attr.column + 1);
4397 
4398         /* Add a newline in block mode. */
4399         if (block) {
4400             string = g_string_append_c(string, '\n');
4401         }
4402         /* Else, if the last visible column on this line was in range and
4403          * not soft-wrapped, append a newline. */
4404         else if (row < end_row) {
4405             /* If we didn't softwrap, add a newline. */
4406             /* XXX need to clear row->soft_wrap on deletion! */
4407             if (!line_is_wrappable(row)) {
4408                 string = g_string_append_c(string, '\n');
4409             }
4410         }
4411 
4412         /* Make sure that the attributes array is as long as the string. */
4413         if (attributes) {
4414             vte_g_array_fill (attributes, &attr, string->len);
4415         }
4416     }
4417 
4418     /* Sanity check. */
4419     if (attributes != nullptr) {
4420         g_assert_cmpuint(string->len, ==, attributes->len);
4421     }
4422 
4423     return string;
4424 }
4425 
get_text_displayed(bool wrap,bool include_trailing_spaces,GArray * attributes)4426 GString* VteTerminalPrivate::get_text_displayed(bool wrap,
4427                                                 bool include_trailing_spaces,
4428                                                 GArray *attributes)
4429 {
4430     return get_text(first_displayed_row(), 0,
4431                     last_displayed_row() + 1, -1,
4432                     false /* block */, wrap, include_trailing_spaces,
4433                     attributes);
4434 }
4435 
4436 /* This is distinct from just using first/last_displayed_row since a11y
4437  * doesn't know about sub-row displays.
4438  */
get_text_displayed_a11y(bool wrap,bool include_trailing_spaces,GArray * attributes)4439 GString* VteTerminalPrivate::get_text_displayed_a11y(bool wrap,
4440                                             bool include_trailing_spaces,
4441                                             GArray *attributes)
4442 {
4443     return get_text(m_screen->scroll_delta, 0,
4444                     m_screen->scroll_delta + m_row_count - 1 + 1, -1,
4445                     false /* block */, wrap, include_trailing_spaces,
4446                     attributes);
4447 }
4448 
get_selected_text(GArray * attributes)4449 GString* VteTerminalPrivate::get_selected_text(GArray *attributes)
4450 {
4451     return get_text(m_selection_start.row,
4452                         m_selection_start.col,
4453                         m_selection_end.row,
4454                         m_selection_end.col,
4455                         m_selection_block_mode,
4456                         true /* wrap */,
4457                         false /* include trailing whitespace */,
4458                         attributes);
4459 }
4460 
4461 #ifdef VTE_DEBUG
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)4462 unsigned int VteTerminalPrivate::checksum_area(vte::grid::row_t start_row,
4463                                                 vte::grid::column_t start_col,
4464                                                 vte::grid::row_t end_row,
4465                                                 vte::grid::column_t end_col)
4466 {
4467     unsigned int checksum = 0;
4468 
4469     auto text = get_text(start_row, start_col, end_row, end_col,
4470                             true /* block */, false /* wrap */,
4471                             true /* trailing whitespace */,
4472                             nullptr /* not interested in attributes */);
4473     if (text == nullptr) {
4474         return checksum;
4475     }
4476 
4477     char const* end = (char const*)text->str + text->len;
4478     for (char const *p = text->str; p < end; p = g_utf8_next_char(p)) {
4479         auto const c = g_utf8_get_char(p);
4480         if (c == '\n') {
4481             continue;
4482         }
4483         checksum += c;
4484     }
4485     g_string_free(text, true);
4486 
4487     return checksum & 0xffff;
4488 }
4489 #endif /* VTE_DEBUG */
4490 
4491 /*
4492  * Compares the visual attributes of a VteCellAttr for equality, but ignores
4493  * attributes that tend to change from character to character or are otherwise
4494  * strange (in particular: fragment, columns).
4495  */
4496 /* FIXMEchpe: make VteCellAttr a class with operator== */
vte_terminal_cellattr_equal(VteCellAttr const * attr1,VteCellAttr const * attr2)4497 static bool vte_terminal_cellattr_equal(VteCellAttr const* attr1,
4498                             VteCellAttr const* attr2)
4499 {
4500     /* FIXMEchpe why exclude DIM here? */
4501     return (((attr1->attr ^ attr2->attr) & VTE_ATTR_ALL_MASK) == 0 &&
4502                 attr1->colors()       == attr2->colors()   &&
4503                 attr1->hyperlink_idx  == attr2->hyperlink_idx);
4504 }
4505 
4506 /*
4507  * Wraps a given string according to the VteCellAttr in HTML tags. Used
4508  * old-style HTML (and not CSS) for better compatibility with, for example,
4509  * evolution's mail editor component.
4510  */
cellattr_to_html(VteCellAttr const * attr,char const * text) const4511 char *VteTerminalPrivate::cellattr_to_html(VteCellAttr const* attr,
4512                                             char const* text) const
4513 {
4514     GString *string;
4515     guint fore, back, deco;
4516 
4517     string = g_string_new(text);
4518 
4519     determine_colors(attr, false, false, &fore, &back, &deco);
4520 
4521     if (attr->bold()) {
4522         g_string_prepend(string, "<b>");
4523         g_string_append(string, "</b>");
4524     }
4525     if (attr->italic()) {
4526         g_string_prepend(string, "<i>");
4527         g_string_append(string, "</i>");
4528     }
4529     /* <u> should be inside <font> so that it inherits its color by default */
4530     if (attr->underline() != 0) {
4531         static const char styles[][7] = {"", "single", "double", "wavy"};
4532         char *tag, *colorattr;
4533 
4534         if (deco != VTE_DEFAULT_FG) {
4535             vte::color::rgb color;
4536 
4537             rgb_from_index<4, 5, 4>(deco, color);
4538             colorattr = g_strdup_printf(";text-decoration-color:#%02X%02X%02X",
4539                                         color.red >> 8,
4540                                         color.green >> 8,
4541                                         color.blue >> 8);
4542         } else {
4543             colorattr = g_strdup("");
4544         }
4545 
4546         tag = g_strdup_printf("<u style=\"text-decoration-style:%s%s\">",
4547                                 styles[attr->underline()],
4548                                 colorattr);
4549         g_string_prepend(string, tag);
4550         g_free(tag);
4551         g_free(colorattr);
4552         g_string_append(string, "</u>");
4553     }
4554     if (fore != VTE_DEFAULT_FG || attr->reverse()) {
4555         vte::color::rgb color;
4556         char *tag;
4557 
4558         rgb_from_index<8, 8, 8>(fore, color);
4559         tag = g_strdup_printf("<font color=\"#%02X%02X%02X\">",
4560                                 color.red >> 8,
4561                                 color.green >> 8,
4562                                 color.blue >> 8);
4563         g_string_prepend(string, tag);
4564         g_free(tag);
4565         g_string_append(string, "</font>");
4566     }
4567     if (back != VTE_DEFAULT_BG || attr->reverse()) {
4568         vte::color::rgb color;
4569         char *tag;
4570 
4571         rgb_from_index<8, 8, 8>(back, color);
4572         tag = g_strdup_printf("<span style=\"background-color:#%02X%02X%02X\">",
4573                                 color.red >> 8,
4574                                 color.green >> 8,
4575                                 color.blue >> 8);
4576         g_string_prepend(string, tag);
4577         g_free(tag);
4578         g_string_append(string, "</span>");
4579     }
4580     if (attr->strikethrough()) {
4581         g_string_prepend(string, "<strike>");
4582         g_string_append(string, "</strike>");
4583     }
4584     if (attr->overline()) {
4585         g_string_prepend(string, "<span style=\"text-decoration-line:overline\">");
4586         g_string_append(string, "</span>");
4587     }
4588     if (attr->blink()) {
4589         g_string_prepend(string, "<blink>");
4590         g_string_append(string, "</blink>");
4591     }
4592     /* reverse and invisible are not supported */
4593 
4594     return g_string_free(string, FALSE);
4595 }
4596 
4597 /*
4598  * Similar to find_charcell(), but takes a VteCharAttribute for
4599  * indexing and returns the VteCellAttr.
4600  */
char_to_cell_attr(VteCharAttributes const * attr) const4601 VteCellAttr const* VteTerminalPrivate::char_to_cell_attr(VteCharAttributes const* attr) const
4602 {
4603     VteCell const* cell = find_charcell(attr->column, attr->row);
4604     if (cell) {
4605         return &cell->attr;
4606     }
4607     return nullptr;
4608 }
4609 
4610 /*
4611  * VteTerminalPrivate::attributes_to_html:
4612  * @text: A string as returned by the vte_terminal_get_* family of functions.
4613  * @attrs: (array) (element-type Vte.CharAttributes): text attributes, as created by vte_terminal_get_*
4614  *
4615  * Marks the given text up according to the given attributes, using HTML <span>
4616  * commands, and wraps the string in a <pre> element. The attributes have to be
4617  * "fresh" in the sense that the terminal must not have changed since they were
4618  * obtained using the vte_terminal_get* function.
4619  *
4620  * Returns: (transfer full): a newly allocated text string, or %NULL.
4621  */
attributes_to_html(GString * text_string,GArray * attrs)4622 GString* VteTerminalPrivate::attributes_to_html(GString* text_string, GArray* attrs)
4623 {
4624     GString *string;
4625     guint from,to;
4626     const VteCellAttr *attr;
4627     char *escaped, *marked;
4628 
4629     char const* text = text_string->str;
4630     auto len = text_string->len;
4631     g_assert_cmpuint(len, ==, attrs->len);
4632 
4633     /* Initial size fits perfectly if the text has no attributes and no
4634      * characters that need to be escaped
4635      */
4636     string = g_string_sized_new (len + 11);
4637 
4638     g_string_append(string, "<pre>");
4639     /* Find streches with equal attributes. Newlines are treated specially,
4640      * so that the <span> do not cover multiple lines.
4641      */
4642     from = to = 0;
4643     while (text[from] != '\0') {
4644         g_assert(from == to);
4645         if (text[from] == '\n') {
4646             g_string_append_c(string, '\n');
4647             from = ++to;
4648         } else {
4649             attr = char_to_cell_attr(
4650                 &g_array_index(attrs, VteCharAttributes, from));
4651             while (text[to] != '\0' && text[to] != '\n' &&
4652                     vte_terminal_cellattr_equal(attr, char_to_cell_attr(
4653                         &g_array_index(attrs, VteCharAttributes, to)))) {
4654                 to++;
4655             }
4656             escaped = g_markup_escape_text(text + from, to - from);
4657             marked = cellattr_to_html(attr, escaped);
4658             g_string_append(string, marked);
4659             g_free(escaped);
4660             g_free(marked);
4661             from = to;
4662         }
4663     }
4664     g_string_append(string, "</pre>");
4665 
4666     return string;
4667 }
4668 
targets_for_format(VteFormat format,int * n_targets)4669 static GtkTargetEntry* targets_for_format(VteFormat format, int *n_targets)
4670 {
4671     switch (format) {
4672         case VTE_FORMAT_TEXT:
4673         {
4674             static GtkTargetEntry *text_targets = nullptr;
4675             static int n_text_targets;
4676 
4677             if (text_targets == nullptr) {
4678                 auto list = gtk_target_list_new (nullptr, 0);
4679                 gtk_target_list_add_text_targets (list, VTE_TARGET_TEXT);
4680 
4681                 text_targets = gtk_target_table_new_from_list (list, &n_text_targets);
4682                 gtk_target_list_unref (list);
4683             }
4684 
4685             *n_targets = n_text_targets;
4686             return text_targets;
4687         }
4688 
4689         case VTE_FORMAT_HTML:
4690         {
4691             static GtkTargetEntry *html_targets = nullptr;
4692             static int n_html_targets;
4693 
4694             if (html_targets == nullptr) {
4695                 auto list = gtk_target_list_new (nullptr, 0);
4696                 gtk_target_list_add_text_targets (list, VTE_TARGET_TEXT);
4697                 gtk_target_list_add (list,
4698                                         gdk_atom_intern_static_string("text/html"),
4699                                         0,
4700                                         VTE_TARGET_HTML);
4701 
4702                 html_targets = gtk_target_table_new_from_list (list, &n_html_targets);
4703                 gtk_target_list_unref (list);
4704             }
4705 
4706             *n_targets = n_html_targets;
4707             return html_targets;
4708         }
4709         default:
4710             g_assert_not_reached();
4711     }
4712 }
4713 
4714 /* Place the selected text onto the clipboard.  Do this asynchronously so that
4715  * we get notified when the selection we placed on the clipboard is replaced. */
widget_copy(VteSelection sel,VteFormat format)4716 void VteTerminalPrivate::widget_copy(VteSelection sel, VteFormat format)
4717 {
4718     /* Only put HTML on the CLIPBOARD, not PRIMARY */
4719     g_assert(sel == VTE_SELECTION_CLIPBOARD || format == VTE_FORMAT_TEXT);
4720 
4721     /* Chuck old selected text and retrieve the newly-selected text. */
4722     GArray *attributes = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
4723     auto selection = get_selected_text(attributes);
4724 
4725     if (m_selection[sel]) {
4726         g_string_free(m_selection[sel], TRUE);
4727         m_selection[sel] = nullptr;
4728     }
4729 
4730     if (selection == nullptr) {
4731         g_array_free(attributes, TRUE);
4732         m_has_selection = FALSE;
4733         m_selection_owned[sel] = false;
4734         return;
4735     }
4736 
4737     if (format == VTE_FORMAT_HTML) {
4738         m_selection[sel] = attributes_to_html(selection, attributes);
4739         g_string_free(selection, TRUE);
4740     } else {
4741         m_selection[sel] = selection;
4742     }
4743 
4744     g_array_free (attributes, TRUE);
4745 
4746     if (sel == VTE_SELECTION_PRIMARY) {
4747         m_has_selection = TRUE;
4748     }
4749 
4750     /* Place the text on the clipboard. */
4751     _vte_debug_print(VTE_DEBUG_SELECTION, "Assuming ownership of selection.\n");
4752 
4753     int n_targets;
4754     auto targets = targets_for_format(format, &n_targets);
4755 
4756     m_changing_selection = true;
4757     gtk_clipboard_set_with_data(m_clipboard[sel],
4758                                 targets,
4759                                 n_targets,
4760                                 clipboard_copy_cb,
4761                                 clipboard_clear_cb,
4762                                 this);
4763     m_changing_selection = false;
4764 
4765     gtk_clipboard_set_can_store(m_clipboard[sel], nullptr, 0);
4766     m_selection_owned[sel] = true;
4767     m_selection_format[sel] = format;
4768 }
4769 
4770 /* Paste from the given clipboard. */
widget_paste(GdkAtom board)4771 void VteTerminalPrivate::widget_paste(GdkAtom board)
4772 {
4773     if (!m_input_enabled) {
4774         return;
4775     }
4776 
4777     auto clip = gtk_clipboard_get_for_display(gtk_widget_get_display(m_widget), board);
4778     if (!clip) {
4779         return;
4780     }
4781 
4782     _vte_debug_print(VTE_DEBUG_SELECTION, "Requesting clipboard contents.\n");
4783 
4784     m_paste_request.request_text(clip, &VteTerminalPrivate::widget_paste_received, this);
4785 }
4786 
invalidate_selection()4787 void VteTerminalPrivate::invalidate_selection()
4788 {
4789     invalidate_region(m_selection_start.col,
4790                         m_selection_end.col,
4791                         m_selection_start.row,
4792                         m_selection_end.row,
4793                         m_selection_block_mode);
4794 }
4795 
4796 /* Confine coordinates into the visible area. Padding is already subtracted. */
confine_coordinates(long * xp,long * yp)4797 void VteTerminalPrivate::confine_coordinates(long *xp, long *yp)
4798 {
4799     long x = *xp;
4800     long y = *yp;
4801     long y_stop;
4802 
4803     /* Allow to use the bottom extra padding only if there's content there. */
4804     y_stop = MIN(m_view_usable_extents.height(),
4805                     row_to_pixel(m_screen->insert_delta + m_row_count));
4806 
4807     if (y < 0) {
4808         y = 0;
4809         if (!m_selection_block_mode) {
4810             x = 0;
4811         }
4812     } else if (y >= y_stop) {
4813         y = y_stop - 1;
4814         if (!m_selection_block_mode) {
4815             x = m_column_count * m_cell_width - 1;
4816         }
4817     }
4818     if (x < 0) {
4819         x = 0;
4820     } else if (x >= m_column_count * m_cell_width) {
4821         x = m_column_count * m_cell_width - 1;
4822     }
4823 
4824     *xp = x;
4825     *yp = y;
4826 }
4827 
4828 /* Start selection at the location of the event. */
start_selection(long x,long y,enum vte_selection_type type)4829 void VteTerminalPrivate::start_selection(long x, long y, enum vte_selection_type type)
4830 {
4831     if (m_selection_block_mode) {
4832         type = selection_type_char;
4833     }
4834 
4835     /* Confine coordinates into the visible area. (#563024, #722635c7) */
4836     confine_coordinates(&x, &y);
4837 
4838     /* Record that we have the selection, and where it started. */
4839     m_has_selection = TRUE;
4840     m_selection_last.x = x;
4841     m_selection_last.y = scroll_delta_pixel() + y;
4842 
4843     /* Decide whether or not to restart on the next drag. */
4844     switch (type) {
4845         case selection_type_char:
4846             /* Restart selection once we register a drag. */
4847             m_selecting_restart = TRUE;
4848             m_has_selection = FALSE;
4849             m_selecting_had_delta = FALSE;
4850 
4851             m_selection_origin = m_selection_last;
4852             break;
4853         case selection_type_word:
4854         case selection_type_line:
4855             /* Mark the newly-selected areas now. */
4856             m_selecting_restart = FALSE;
4857             m_has_selection = FALSE;
4858             m_selecting_had_delta = FALSE;
4859             break;
4860     }
4861 
4862     /* Record the selection type. */
4863     m_selection_type = type;
4864     m_selecting = TRUE;
4865     m_selecting_after_threshold = FALSE;
4866 
4867     _vte_debug_print(VTE_DEBUG_SELECTION,
4868                          "Selection started at (%ld,%ld).\n",
4869                          m_selection_start.col,
4870                          m_selection_start.row);
4871 
4872         /* Take care of updating the display. */
4873         extend_selection(x, y, false, true);
4874 #if 0 /* FIXME: removed */
4875     /* Temporarily stop caring about input from the child. */
4876     disconnect_pty_read();
4877 #endif
4878 }
4879 
maybe_end_selection()4880 bool VteTerminalPrivate::maybe_end_selection()
4881 {
4882     if (m_selecting) {
4883         /* Copy only if something was selected. */
4884         if (m_has_selection &&
4885             !m_selecting_restart &&
4886             m_selecting_had_delta) {
4887             widget_copy(VTE_SELECTION_PRIMARY, VTE_FORMAT_TEXT);
4888             emit_selection_changed();
4889         }
4890         m_selecting = false;
4891 #if 0 /* FIXME: removed */
4892         /* Reconnect to input from the child if we paused it. */
4893         connect_pty_read();
4894 #endif
4895         return true;
4896     }
4897 
4898     if (m_selecting_after_threshold) {
4899         return true;
4900     }
4901 
4902     return false;
4903 }
4904 
math_div(long a,long b)4905 static long math_div (long a, long b)
4906 {
4907     if (G_LIKELY (a >= 0)) {
4908         return a / b;
4909     } else {
4910         return (a / b) - 1;
4911     }
4912 }
4913 
4914 /* Helper */
extend_selection_expand()4915 void VteTerminalPrivate::extend_selection_expand()
4916 {
4917     long i, j;
4918     const VteCell *cell;
4919     VteVisualPosition *sc, *ec;
4920 
4921     if (m_selection_block_mode) {
4922         return;
4923     }
4924 
4925     sc = &m_selection_start;
4926     ec = &m_selection_end;
4927 
4928     /* Extend the selection to handle end-of-line cases, word, and line
4929      * selection.  We do this here because calculating it once is cheaper
4930      * than recalculating for each cell as we render it. */
4931 
4932     /* Handle end-of-line at the start-cell. */
4933     VteRowData const* rowdata = find_row_data(sc->row);
4934     if (rowdata != NULL) {
4935         /* Find the last non-empty character on the first line. */
4936         for (i = _vte_row_data_length (rowdata); i > 0; i--) {
4937             cell = _vte_row_data_get (rowdata, i - 1);
4938             if (cell->attr.fragment() || cell->c != 0) {
4939                 break;
4940             }
4941         }
4942     } else {
4943         i = 0;
4944     }
4945     if (sc->col > i) {
4946         if (m_selection_type == selection_type_char) {
4947            /* If the start point is neither over the used cells, nor over the first
4948             * unused one, then move it to the next line. This way you can still start
4949             * selecting at the newline character by clicking over the first unused cell.
4950             * See bug 725909. */
4951             sc->col = -1;
4952             sc->row++;
4953         } else if (m_selection_type == selection_type_word) {
4954             sc->col = i;
4955         }
4956     }
4957     sc->col = find_start_column(sc->col, sc->row);
4958 
4959     /* Handle end-of-line at the end-cell. */
4960     rowdata = find_row_data(ec->row);
4961     if (rowdata != NULL) {
4962         /* Find the last non-empty character on the last line. */
4963         for (i = _vte_row_data_length (rowdata); i > 0; i--) {
4964             cell = _vte_row_data_get (rowdata, i - 1);
4965             if (cell->attr.fragment() || cell->c != 0) {
4966                 break;
4967             }
4968         }
4969         /* If the end point is to its right, then extend the
4970          * endpoint to the beginning of the next row. */
4971         if (ec->col >= i) {
4972             ec->col = -1;
4973             ec->row++;
4974         }
4975     } else {
4976         /* Snap to the beginning of the next line, only if
4977          * selecting anything of this row. */
4978         if (ec->col >= 0) {
4979             ec->col = -1;
4980             ec->row++;
4981         }
4982     }
4983     ec->col = find_end_column(ec->col, ec->row);
4984 
4985     /* Now extend again based on selection type. */
4986     switch (m_selection_type) {
4987         case selection_type_char:
4988             /* Nothing more to do. */
4989             break;
4990         case selection_type_word:
4991             /* Keep selecting to the left as long as the next character we
4992             * look at is of the same class as the current start point. */
4993             i = sc->col;
4994             j = sc->row;
4995             while (_vte_ring_contains (m_screen->row_data, j)) {
4996                 /* Get the data for the row we're looking at. */
4997                 rowdata = _vte_ring_index(m_screen->row_data, j);
4998                 if (rowdata == NULL) {
4999                     break;
5000                 }
5001                 /* Back up. */
5002                 for (i = (j == sc->row) ? sc->col : m_column_count; i > 0; i--) {
5003                     if (is_same_class(i - 1, j, i, j)) {
5004                         sc->col = i - 1;
5005                         sc->row = j;
5006                     } else {
5007                         break;
5008                     }
5009                 }
5010                 if (i > 0) {
5011                     /* We hit a stopping point, so stop. */
5012                     break;
5013                 } else {
5014                     if (line_is_wrappable(j - 1) && is_same_class(m_column_count - 1,
5015                                                                     j - 1,
5016                                                                     0,
5017                                                                     j)) {
5018                         /* Move on to the previous line. */
5019                         j--;
5020                         sc->col = m_column_count - 1;
5021                         sc->row = j;
5022                     } else {
5023                         break;
5024                     }
5025                 }
5026             }
5027             /* Keep selecting to the right as long as the next character we
5028             * look at is of the same class as the current end point. */
5029             i = ec->col;
5030             j = ec->row;
5031             while (_vte_ring_contains (m_screen->row_data, j)) {
5032                 /* Get the data for the row we're looking at. */
5033                 rowdata = _vte_ring_index(m_screen->row_data, j);
5034                 if (rowdata == NULL) {
5035                     break;
5036                 }
5037                 /* Move forward. */
5038                 for (i = (j == ec->row) ? ec->col : 0; i < m_column_count - 1; i++) {
5039                     if (is_same_class(i, j, i + 1, j)) {
5040                         ec->col = i + 1;
5041                         ec->row = j;
5042                     } else {
5043                         break;
5044                     }
5045                 }
5046                 if (i < m_column_count - 1) {
5047                     /* We hit a stopping point, so stop. */
5048                     break;
5049                 } else {
5050                     if (line_is_wrappable(j) && is_same_class(
5051                                                                 m_column_count - 1,
5052                                                                 j,
5053                                                                 0,
5054                                                                 j + 1)) {
5055                         /* Move on to the next line. */
5056                         j++;
5057                         ec->col = 0;
5058                         ec->row = j;
5059                     } else {
5060                         break;
5061                     }
5062                 }
5063             }
5064             break;
5065         case selection_type_line:
5066             /* Extend the selection to the beginning of the start line. */
5067             sc->col = 0;
5068             /* Now back up as far as we can go. */
5069             j = sc->row;
5070             while (_vte_ring_contains (m_screen->row_data, j - 1) && line_is_wrappable(j - 1)) {
5071                 j--;
5072                 sc->row = j;
5073             }
5074             /* And move forward as far as we can go. */
5075             if (ec->col < 0) {
5076                /* If triple clicking on an unused area, ec already points
5077                 * to the beginning of the next line after the second click.
5078                 * Go back to the actual row we're at. See bug 725909. */
5079                 ec->row--;
5080             }
5081             j = ec->row;
5082             while (_vte_ring_contains (m_screen->row_data, j) &&
5083                 line_is_wrappable(j)) {
5084                 j++;
5085                 ec->row = j;
5086             }
5087             /* Make sure we include all of the last line by extending
5088             * to the beginning of the next line. */
5089             ec->row++;
5090             ec->col = -1;
5091             break;
5092     }
5093 }
5094 
5095 /* Extend selection to include the given event coordinates. */
extend_selection(long x,long y,bool always_grow,bool force)5096 void VteTerminalPrivate::extend_selection(long x, long y, bool always_grow, bool force)
5097 {
5098     long residual;
5099     long row;
5100     vte::view::coords *origin, *last, *start, *end;
5101     VteVisualPosition old_start, old_end, *sc, *ec, *so, *eo;
5102     gboolean invalidate_selected = FALSE;
5103     gboolean had_selection;
5104 
5105     /* Confine coordinates into the visible area. (#563024, #722635c7) */
5106     confine_coordinates(&x, &y);
5107 
5108     old_start = m_selection_start;
5109     old_end = m_selection_end;
5110     so = &old_start;
5111     eo = &old_end;
5112 
5113     /* If we're restarting on a drag, then mark this as the start of
5114      * the selected block. */
5115     if (m_selecting_restart) {
5116         deselect_all();
5117         invalidate_selected = TRUE;
5118         _vte_debug_print(VTE_DEBUG_SELECTION,
5119                 "Selection delayed start at (%ld,%ld).\n",
5120                 m_selection_origin.x / m_cell_width,
5121                 m_selection_origin.y / m_cell_height);
5122     }
5123 
5124     /* Recognize that we've got a selected block. */
5125     had_selection = m_has_selection;
5126     m_has_selection = TRUE;
5127     m_selecting_had_delta = TRUE;
5128     m_selecting_restart = FALSE;
5129 
5130     /* If we're not in always-grow mode, update the last location of
5131      * the selection. */
5132     last = &m_selection_last;
5133 
5134     /* Map the origin and last selected points to a start and end. */
5135     origin = &m_selection_origin;
5136     if (m_selection_block_mode) {
5137         last->x = x;
5138         last->y = scroll_delta_pixel() + y;
5139 
5140         /* We don't support always_grow in block mode */
5141         if (always_grow) {
5142             invalidate_selection();
5143         }
5144 
5145         if (origin->y <= last->y) {
5146             /* The origin point is "before" the last point. */
5147             start = origin;
5148             end = last;
5149         } else {
5150             /* The last point is "before" the origin point. */
5151             start = last;
5152             end = origin;
5153         }
5154     } else {
5155         if (!always_grow) {
5156             last->x = x;
5157             last->y = scroll_delta_pixel() + y;
5158         }
5159 
5160         if ((origin->y / m_cell_height < last->y / m_cell_height) ||
5161             ((origin->y / m_cell_height == last->y / m_cell_height) &&
5162              (origin->x / m_cell_width < last->x / m_cell_width ))) {
5163             /* The origin point is "before" the last point. */
5164             start = origin;
5165             end = last;
5166         } else {
5167             /* The last point is "before" the origin point. */
5168             start = last;
5169             end = origin;
5170         }
5171 
5172         /* Extend the selection by moving whichever end of the selection is
5173          * closer to the new point. */
5174         if (always_grow) {
5175             /* New endpoint is before existing selection. */
5176                         row = pixel_to_row(y);
5177             if ((row < start->y / m_cell_height) ||
5178                 ((row == start->y / m_cell_height) &&
5179                  (x / m_cell_width < start->x / m_cell_width))) {
5180                 start->x = x;
5181                 start->y = scroll_delta_pixel() + y;
5182             } else {
5183                 /* New endpoint is after existing selection. */
5184                 end->x = x;
5185                 end->y = scroll_delta_pixel() + y;
5186             }
5187         }
5188     }
5189 
5190 #if 0
5191     _vte_debug_print(VTE_DEBUG_SELECTION,
5192             "Selection is (%ld,%ld) to (%ld,%ld).\n",
5193             start->x, start->y, end->x, end->y);
5194 #endif
5195 
5196     /* Recalculate the selection area in terms of cell positions. */
5197 
5198     sc = &m_selection_start;
5199     ec = &m_selection_end;
5200 
5201     sc->row = MAX (0, start->y / m_cell_height);
5202     ec->row = MAX (0, end->y   / m_cell_height);
5203 
5204     /* Sort x using row cell coordinates */
5205     if ((m_selection_block_mode || sc->row == ec->row) && (start->x > end->x)) {
5206                 vte::view::coords *tmp;
5207         tmp = start;
5208         start = end;
5209         end = tmp;
5210     }
5211 
5212     /* We want to be more lenient on the user with their column selection.
5213      * We round to the closest logical position (positions are located between
5214      * cells).  But we don't want to fully round.  So we divide the cell
5215      * width into three parts.  The side parts round to their nearest
5216      * position.  The middle part is always inclusive in the selection.
5217      *
5218      * math_div and no MAX, to allow selecting no cells in the line,
5219      * ie. ec->col = -1, which is essentially equal to copying the
5220      * newline from previous line but no chars from current line. */
5221     residual = (m_cell_width + 1) / 3;
5222     sc->col = math_div (start->x + residual, m_cell_width);
5223     ec->col = math_div (end->x - residual, m_cell_width);
5224 
5225     extend_selection_expand();
5226 
5227     if (!invalidate_selected && !force &&
5228         0 == memcmp (sc, so, sizeof (*sc)) &&
5229         0 == memcmp (ec, eo, sizeof (*ec))) {
5230         /* No change */
5231         return;
5232     }
5233 
5234     /* Invalidate */
5235 
5236     if (had_selection) {
5237 
5238         if (m_selection_block_mode) {
5239             /* Update the selection area diff in block mode. */
5240 
5241             /* The top band */
5242             invalidate_region(
5243                         MIN(sc->col, so->col),
5244                         MAX(ec->col, eo->col),
5245                         MIN(sc->row, so->row),
5246                         MAX(sc->row, so->row) - 1,
5247                         true);
5248             /* The bottom band */
5249             invalidate_region(
5250                         MIN(sc->col, so->col),
5251                         MAX(ec->col, eo->col),
5252                         MIN(ec->row, eo->row) + 1,
5253                         MAX(ec->row, eo->row),
5254                         true);
5255             /* The left band */
5256             invalidate_region(
5257                         MIN(sc->col, so->col),
5258                         MAX(sc->col, so->col) - 1 + (VTE_TAB_WIDTH_MAX - 1),
5259                         MIN(sc->row, so->row),
5260                         MAX(ec->row, eo->row),
5261                         true);
5262             /* The right band */
5263             invalidate_region(
5264                         MIN(ec->col, eo->col) + 1,
5265                         MAX(ec->col, eo->col) + (VTE_TAB_WIDTH_MAX - 1),
5266                         MIN(sc->row, so->row),
5267                         MAX(ec->row, eo->row),
5268                         true);
5269         } else {
5270             /* Update the selection area diff in non-block mode. */
5271 
5272             /* The before band */
5273             if (sc->row < so->row) {
5274                 invalidate_region(
5275                             sc->col, so->col - 1,
5276                             sc->row, so->row,
5277                             false);
5278             } else if (sc->row > so->row) {
5279                 invalidate_region(
5280                             so->col, sc->col - 1,
5281                             so->row, sc->row,
5282                             false);
5283             } else {
5284                 invalidate_region(
5285                             MIN(sc->col, so->col), MAX(sc->col, so->col) - 1,
5286                             sc->row, sc->row,
5287                             true);
5288             }
5289 
5290             /* The after band */
5291             if (ec->row < eo->row) {
5292                 invalidate_region(
5293                             ec->col + 1, eo->col,
5294                             ec->row, eo->row,
5295                             false);
5296             } else if (ec->row > eo->row) {
5297                 invalidate_region(
5298                             eo->col + 1, ec->col,
5299                             eo->row, ec->row,
5300                             false);
5301             } else {
5302                 invalidate_region(
5303                             MIN(ec->col, eo->col) + 1, MAX(ec->col, eo->col),
5304                             ec->row, ec->row,
5305                             true);
5306             }
5307         }
5308     }
5309 
5310     if (invalidate_selected || !had_selection) {
5311         _vte_debug_print(VTE_DEBUG_SELECTION, "Invalidating selection.");
5312         invalidate_selection();
5313     }
5314 
5315     _vte_debug_print(VTE_DEBUG_SELECTION,
5316             "Selection changed to "
5317             "(%ld,%ld) to (%ld,%ld).\n",
5318             sc->col, sc->row, ec->col, ec->row);
5319 }
5320 
5321 /*
5322  * VteTerminalPrivate::select_all:
5323  *
5324  * Selects all text within the terminal (including the scrollback buffer).
5325  */
select_all()5326 void VteTerminalPrivate::select_all()
5327 {
5328     deselect_all();
5329 
5330     m_has_selection = TRUE;
5331     m_selecting_had_delta = TRUE;
5332     m_selecting_restart = FALSE;
5333 
5334     m_selection_start.row = _vte_ring_delta (m_screen->row_data);
5335     m_selection_start.col = 0;
5336     m_selection_end.row = _vte_ring_next (m_screen->row_data);
5337     m_selection_end.col = -1;
5338 
5339     _vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n");
5340 
5341     widget_copy(VTE_SELECTION_PRIMARY, VTE_FORMAT_TEXT);
5342     emit_selection_changed();
5343 
5344     invalidate_all();
5345 }
5346 
5347 /* Autoscroll a bit. */
vte_terminal_autoscroll_cb(VteTerminalPrivate * that)5348 static gboolean vte_terminal_autoscroll_cb(VteTerminalPrivate *that)
5349 {
5350     return that->autoscroll() ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
5351 }
5352 
5353 /*
5354  * VteTerminalPrivate::autoscroll():
5355  *
5356  * Returns: %true to continue autoscrolling, %false to stop
5357  */
autoscroll()5358 bool VteTerminalPrivate::autoscroll()
5359 {
5360     bool extend = false;
5361     long x, y, xmax, ymax;
5362     glong adj;
5363 
5364     /* Provide an immediate effect for mouse wigglers. */
5365     if (m_mouse_last_position.y < 0) {
5366         if (m_vadjustment) {
5367             /* Try to scroll up by one line. */
5368             adj = m_screen->scroll_delta - 1;
5369             queue_adjustment_value_changed_clamped(adj);
5370             extend = true;
5371         }
5372         _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling down.\n");
5373     }
5374     if (m_mouse_last_position.y >= m_view_usable_extents.height()) {
5375         if (m_vadjustment) {
5376             /* Try to scroll up by one line. */
5377             adj = m_screen->scroll_delta + 1;
5378             queue_adjustment_value_changed_clamped(adj);
5379             extend = true;
5380         }
5381         _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling up.\n");
5382     }
5383     if (extend) {
5384         /* FIXMEchpe use confine_view_coords here */
5385         /* Don't select off-screen areas.  That just confuses people. */
5386         xmax = m_column_count * m_cell_width;
5387         ymax = m_row_count * m_cell_height;
5388 
5389         x = CLAMP(m_mouse_last_position.x, 0, xmax);
5390         y = CLAMP(m_mouse_last_position.y, 0, ymax);
5391         /* If we clamped the Y, mess with the X to get the entire
5392          * lines. */
5393         if (m_mouse_last_position.y < 0 && !m_selection_block_mode) {
5394             x = 0;
5395         }
5396         if (m_mouse_last_position.y >= ymax && !m_selection_block_mode) {
5397             x = m_column_count * m_cell_width;
5398         }
5399         /* Extend selection to cover the newly-scrolled area. */
5400         extend_selection(x, y, false, true);
5401     } else {
5402         /* Stop autoscrolling. */
5403         m_mouse_autoscroll_tag = 0;
5404     }
5405     return (m_mouse_autoscroll_tag != 0);
5406 }
5407 
5408 /* Start autoscroll. */
start_autoscroll()5409 void VteTerminalPrivate::start_autoscroll()
5410 {
5411     if (m_mouse_autoscroll_tag != 0) {
5412         return;
5413     }
5414 
5415     m_mouse_autoscroll_tag =
5416             g_timeout_add_full(G_PRIORITY_LOW,
5417                                 666 / m_row_count, /* FIXME WTF? */
5418                                 (GSourceFunc)vte_terminal_autoscroll_cb,
5419                                 this,
5420                                 NULL);/* FIXME make sure m_mouse_autoscroll_tag is nulled! */
5421 }
5422 
5423 /* Stop autoscroll. */
stop_autoscroll()5424 void VteTerminalPrivate::stop_autoscroll()
5425 {
5426     if (m_mouse_autoscroll_tag == 0) {
5427         return;
5428     }
5429 
5430     g_source_remove(m_mouse_autoscroll_tag);
5431     m_mouse_autoscroll_tag = 0;
5432 }
5433 
widget_motion_notify(GdkEventMotion * event)5434 bool VteTerminalPrivate::widget_motion_notify(GdkEventMotion *event)
5435 {
5436     bool handled = false;
5437 
5438     GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
5439     auto pos = view_coords_from_event(base_event);
5440     auto rowcol = grid_coords_from_view_coords(pos);
5441 
5442     _vte_debug_print(VTE_DEBUG_EVENTS,
5443                          "Motion notify %s %s\n",
5444                          pos.to_string(), rowcol.to_string());
5445 
5446     read_modifiers(base_event);
5447 
5448     switch (event->type) {
5449         case GDK_MOTION_NOTIFY:
5450             if (m_selecting_after_threshold) {
5451                 if (!gtk_drag_check_threshold (m_widget,
5452                                 m_mouse_last_position.x,
5453                                 m_mouse_last_position.y,
5454                                 pos.x, pos.y)) {
5455                     return true;
5456                 }
5457 
5458                 start_selection(m_mouse_last_position.x, m_mouse_last_position.y,
5459                                             selection_type_char);
5460             }
5461 
5462             if (m_selecting && (m_mouse_handled_buttons & 1) != 0) {
5463                 _vte_debug_print(VTE_DEBUG_EVENTS, "Mousing drag 1.\n");
5464                 extend_selection(pos.x, pos.y, false, false);
5465 
5466                 /* Start scrolling if we need to. */
5467                 if (pos.y < 0 || pos.y >= m_view_usable_extents.height()) {
5468                     /* Give mouse wigglers something. */
5469                     stop_autoscroll();
5470                     autoscroll();
5471                     /* Start a timed autoscroll if we're not doing it
5472                     * already. */
5473                     start_autoscroll();
5474                 }
5475 
5476                 handled = true;
5477             }
5478 
5479             if (!handled && m_input_enabled) {
5480                 maybe_send_mouse_drag(rowcol, event->type);
5481             }
5482             break;
5483         default:
5484             break;
5485     }
5486 
5487     if (pos != m_mouse_last_position) {
5488         m_mouse_last_position = pos;
5489 
5490         set_pointer_autohidden(false);
5491         hyperlink_hilite_update();
5492     }
5493 
5494     return handled;
5495 }
5496 
widget_button_press(GdkEventButton * event)5497 bool VteTerminalPrivate::widget_button_press(GdkEventButton *event)
5498 {
5499     bool handled = false;
5500     gboolean start_selecting = FALSE, extend_selecting = FALSE;
5501 
5502     GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
5503     auto pos = view_coords_from_event(base_event);
5504     auto rowcol = grid_coords_from_view_coords(pos);
5505 
5506     read_modifiers(base_event);
5507 
5508     switch (event->type) {
5509         case GDK_BUTTON_PRESS:
5510             _vte_debug_print(VTE_DEBUG_EVENTS,
5511                                     "Button %d single-click at %s\n",
5512                                     event->button,
5513                                     rowcol.to_string());
5514             /* Handle this event ourselves. */
5515             switch (event->button) {
5516                 case 1:
5517                     _vte_debug_print(VTE_DEBUG_EVENTS, "Handling click ourselves.\n");
5518                     /* Grab focus. */
5519                     if (!gtk_widget_has_focus(m_widget)) {
5520                         gtk_widget_grab_focus(m_widget);
5521                     }
5522 
5523                     /* If we're in event mode, and the user held down the
5524                     * shift key, we start selecting. */
5525                     if (m_mouse_tracking_mode) {
5526                         if (m_modifiers & GDK_SHIFT_MASK) {
5527                             start_selecting = TRUE;
5528                         }
5529                     } else {
5530                         /* If the user hit shift, then extend the
5531                         * selection instead. */
5532                         if ((m_modifiers & GDK_SHIFT_MASK) &&
5533                             (m_has_selection ||
5534                             m_selecting_restart) &&
5535                             !cell_is_selected(rowcol.column(), rowcol.row())) {
5536                             extend_selecting = TRUE;
5537                         } else {
5538                             start_selecting = TRUE;
5539                         }
5540                     }
5541                     if (start_selecting) {
5542                         deselect_all();
5543                         m_selecting_after_threshold = TRUE;
5544                         m_selection_block_mode = !!(m_modifiers & GDK_CONTROL_MASK);
5545                         handled = true;
5546                     }
5547                     if (extend_selecting) {
5548                         extend_selection(pos.x, pos.y, !m_selecting_restart, true);
5549                         /* The whole selection code needs to be
5550                         * rewritten.  For now, put this here to
5551                         * fix bug 614658 */
5552                         m_selecting = TRUE;
5553                         handled = true;
5554                     }
5555                     break;
5556                 /* Paste if the user pressed shift or we're not sending events
5557                 * to the app. */
5558                 case 2:
5559                     if ((m_modifiers & GDK_SHIFT_MASK) ||
5560                         !m_mouse_tracking_mode) {
5561                         gboolean do_paste;
5562 
5563                         g_object_get (gtk_widget_get_settings(m_widget),
5564                                     "gtk-enable-primary-paste",
5565                                     &do_paste, nullptr);
5566                         if (do_paste) {
5567                             widget_paste(GDK_SELECTION_PRIMARY);
5568                         }
5569                         handled = do_paste;
5570                     }
5571                     break;
5572                 case 3:
5573                 default:
5574                     break;
5575             }
5576             if (event->button >= 1 && event->button <= 3) {
5577                 if (handled) {
5578                     m_mouse_handled_buttons |= (1 << (event->button - 1));
5579                 } else {
5580                     m_mouse_handled_buttons &= ~(1 << (event->button - 1));
5581                 }
5582             }
5583             /* If we haven't done anything yet, try sending the mouse
5584             * event to the app. */
5585             if (handled == FALSE) {
5586                 handled = maybe_send_mouse_button(rowcol, event->type, event->button);
5587             }
5588             break;
5589         case GDK_2BUTTON_PRESS:
5590             _vte_debug_print(VTE_DEBUG_EVENTS,
5591                                     "Button %d double-click at %s\n",
5592                                     event->button,
5593                                     rowcol.to_string());
5594             switch (event->button) {
5595                 case 1:
5596                     if (m_selecting_after_threshold) {
5597                         start_selection(pos.x, pos.y, selection_type_char);
5598                         handled = true;
5599                     }
5600                     if ((m_mouse_handled_buttons & 1) != 0) {
5601                         start_selection(pos.x, pos.y, selection_type_word);
5602                         handled = true;
5603                     }
5604                     break;
5605                 case 2:
5606                 case 3:
5607                 default:
5608                     break;
5609             }
5610             break;
5611         case GDK_3BUTTON_PRESS:
5612             _vte_debug_print(VTE_DEBUG_EVENTS,
5613                                     "Button %d triple-click at %s\n",
5614                                     event->button,
5615                                     rowcol.to_string());
5616             switch (event->button) {
5617                 case 1:
5618                     if ((m_mouse_handled_buttons & 1) != 0) {
5619                         start_selection(pos.x, pos.y, selection_type_line);
5620                         handled = true;
5621                     }
5622                     break;
5623                 case 2:
5624                 case 3:
5625                 default:
5626                     break;
5627             }
5628         default:
5629             break;
5630     }
5631 
5632     /* Save the pointer state for later use. */
5633     if (event->button >= 1 && event->button <= 3) {
5634         m_mouse_pressed_buttons |= (1 << (event->button - 1));
5635     }
5636 
5637     m_mouse_last_position = pos;
5638 
5639     set_pointer_autohidden(false);
5640     hyperlink_hilite_update();
5641 
5642     return handled;
5643 }
5644 
widget_button_release(GdkEventButton * event)5645 bool VteTerminalPrivate::widget_button_release(GdkEventButton *event)
5646 {
5647     bool handled = false;
5648 
5649     GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
5650     auto pos = view_coords_from_event(base_event);
5651     auto rowcol = grid_coords_from_view_coords(pos);
5652 
5653     stop_autoscroll();
5654 
5655     read_modifiers(base_event);
5656 
5657     switch (event->type) {
5658         case GDK_BUTTON_RELEASE:
5659             _vte_debug_print(VTE_DEBUG_EVENTS,
5660                                     "Button %d released at %s\n",
5661                                     event->button, rowcol.to_string());
5662             switch (event->button) {
5663                 case 1:
5664                     if ((m_mouse_handled_buttons & 1) != 0) {
5665                             handled = maybe_end_selection();
5666                     }
5667                     break;
5668                 case 2:
5669                     handled = (m_mouse_handled_buttons & 2) != 0;
5670                     m_mouse_handled_buttons &= ~2;
5671                     break;
5672                 case 3:
5673                 default:
5674                     break;
5675             }
5676             if (!handled && m_input_enabled) {
5677                 handled = maybe_send_mouse_button(rowcol, event->type, event->button);
5678             }
5679             break;
5680         default:
5681             break;
5682     }
5683 
5684     /* Save the pointer state for later use. */
5685     if (event->button >= 1 && event->button <= 3) {
5686             m_mouse_pressed_buttons &= ~(1 << (event->button - 1));
5687     }
5688 
5689     m_mouse_last_position = pos;
5690     m_selecting_after_threshold = false;
5691 
5692     set_pointer_autohidden(false);
5693     hyperlink_hilite_update();
5694 
5695     return handled;
5696 }
5697 
widget_focus_in(GdkEventFocus * event)5698 void VteTerminalPrivate::widget_focus_in(GdkEventFocus *event)
5699 {
5700     _vte_debug_print(VTE_DEBUG_EVENTS, "Focus in.\n");
5701 
5702     gtk_widget_grab_focus(m_widget);
5703 
5704     /* Read the keyboard modifiers, though they're probably garbage. */
5705     read_modifiers((GdkEvent*)event);
5706 
5707     /* We only have an IM context when we're realized, and there's not much
5708      * point to painting the cursor if we don't have a window. */
5709     if (widget_realized()) {
5710         m_cursor_blink_state = TRUE;
5711         m_has_focus = TRUE;
5712 
5713        /* If blinking gets enabled now, do a full repaint.
5714         * If blinking gets disabled, only repaint if there's blinking stuff present
5715         * (we could further optimize by checking its current phase). */
5716         if (m_text_blink_mode == VTE_TEXT_BLINK_FOCUSED ||
5717             (m_text_blink_mode == VTE_TEXT_BLINK_UNFOCUSED && m_text_blink_tag != 0)) {
5718             invalidate_all();
5719         }
5720 
5721         check_cursor_blink();
5722 
5723         gtk_im_context_focus_in(m_im_context);
5724         invalidate_cursor_once();
5725         maybe_feed_focus_event(true);
5726     }
5727 }
5728 
widget_focus_out(GdkEventFocus * event)5729 void VteTerminalPrivate::widget_focus_out(GdkEventFocus *event)
5730 {
5731     _vte_debug_print(VTE_DEBUG_EVENTS, "Focus out.\n");
5732 
5733     /* Read the keyboard modifiers, though they're probably garbage. */
5734     read_modifiers((GdkEvent*)event);
5735 
5736     /* We only have an IM context when we're realized, and there's not much
5737      * point to painting ourselves if we don't have a window. */
5738     if (widget_realized()) {
5739         maybe_feed_focus_event(false);
5740 
5741         maybe_end_selection();
5742 
5743         /* If blinking gets enabled now, do a full repaint.
5744             * If blinking gets disabled, only repaint if there's blinking stuff present
5745             * (we could further optimize by checking its current phase). */
5746         if (m_text_blink_mode == VTE_TEXT_BLINK_UNFOCUSED ||
5747             (m_text_blink_mode == VTE_TEXT_BLINK_FOCUSED && m_text_blink_tag != 0)) {
5748             invalidate_all();
5749         }
5750 
5751         gtk_im_context_focus_out(m_im_context);
5752         invalidate_cursor_once();
5753 
5754         m_mouse_pressed_buttons = 0;
5755         m_mouse_handled_buttons = 0;
5756     }
5757 
5758     m_has_focus = false;
5759     check_cursor_blink();
5760 }
5761 
widget_enter(GdkEventCrossing * event)5762 void VteTerminalPrivate::widget_enter(GdkEventCrossing *event)
5763 {
5764     GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
5765     auto pos = view_coords_from_event(base_event);
5766 
5767     _vte_debug_print(VTE_DEBUG_EVENTS, "Enter at %s\n", pos.to_string());
5768 
5769     m_mouse_cursor_over_widget = TRUE;
5770     m_mouse_last_position = pos;
5771 
5772     set_pointer_autohidden(false);
5773     hyperlink_hilite_update();
5774     apply_mouse_cursor();
5775 }
5776 
widget_leave(GdkEventCrossing * event)5777 void VteTerminalPrivate::widget_leave(GdkEventCrossing *event)
5778 {
5779     GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
5780     auto pos = view_coords_from_event(base_event);
5781 
5782     _vte_debug_print(VTE_DEBUG_EVENTS, "Leave at %s\n", pos.to_string());
5783 
5784     m_mouse_cursor_over_widget = FALSE;
5785     m_mouse_last_position = pos;
5786 
5787     hyperlink_hilite_update();
5788     apply_mouse_cursor();
5789 }
5790 
visibility_state_str(GdkVisibilityState state)5791 static G_GNUC_UNUSED inline const char *visibility_state_str(GdkVisibilityState state)
5792 {
5793     switch(state) {
5794         case GDK_VISIBILITY_FULLY_OBSCURED:
5795             return "fully-obscured";
5796         case GDK_VISIBILITY_UNOBSCURED:
5797             return "unobscured";
5798         default:
5799             return "partial";
5800     }
5801 }
5802 
5803 /* Apply the changed metrics, and queue a resize if need be.
5804  *
5805  * The cell's height consists of 4 parts, from top to bottom:
5806  * - char_spacing.top: half of the extra line spacing,
5807  * - char_ascent: the font's ascent,
5808  * - char_descent: the font's descent,
5809  * - char_spacing.bottom: the other half of the extra line spacing.
5810  * Extra line spacing is typically 0, beef up cell_height_scale to get actual pixels
5811  * here. Similarly, increase cell_width_scale to get nonzero char_spacing.{left,right}.
5812  */
apply_font_metrics(int cell_width,int cell_height,int char_ascent,int char_descent,GtkBorder char_spacing)5813 void VteTerminalPrivate::apply_font_metrics(int cell_width,
5814                                             int cell_height,
5815                                             int char_ascent,
5816                                             int char_descent,
5817                                             GtkBorder char_spacing)
5818 {
5819     int char_height;
5820     bool resize = false, cresize = false;
5821 
5822     /* Sanity check for broken font changes. */
5823     cell_width = MAX(cell_width, 1);
5824     cell_height = MAX(cell_height, 2);
5825     char_ascent = MAX(char_ascent, 1);
5826     char_descent = MAX(char_descent, 1);
5827 
5828     /* For convenience only. */
5829     char_height = char_ascent + char_descent;
5830 
5831     /* Change settings, and keep track of when we've changed anything. */
5832     if (cell_width != m_cell_width) {
5833         resize = cresize = true;
5834         m_cell_width = cell_width;
5835     }
5836     if (cell_height != m_cell_height) {
5837         resize = cresize = true;
5838         m_cell_height = cell_height;
5839     }
5840     if (char_ascent != m_char_ascent) {
5841         resize = true;
5842         m_char_ascent = char_ascent;
5843     }
5844     if (char_descent != m_char_descent) {
5845         resize = true;
5846         m_char_descent = char_descent;
5847     }
5848     if (memcmp(&char_spacing, &m_char_padding, sizeof(GtkBorder)) != 0) {
5849         resize = true;
5850         m_char_padding = char_spacing;
5851     }
5852     m_line_thickness = MAX (MIN (char_descent / 2, char_height / 14), 1);
5853     /* FIXME take these from pango_font_metrics_get_{underline,strikethrough}_{position,thickness} */
5854     m_underline_thickness = m_line_thickness;
5855     m_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - m_underline_thickness);
5856     m_double_underline_thickness = m_line_thickness;
5857     /* FIXME make sure this doesn't reach the baseline (switch to thinner lines, or one thicker line in that case) */
5858     m_double_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - 3 * m_double_underline_thickness);
5859     m_undercurl_thickness = m_line_thickness;
5860     m_undercurl_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - _vte_draw_get_undercurl_height(cell_width, m_undercurl_thickness));
5861     m_strikethrough_thickness = m_line_thickness;
5862     m_strikethrough_position = char_spacing.top + char_ascent - char_height / 4;
5863     m_overline_thickness = m_line_thickness;
5864     m_overline_position = char_spacing.top;  /* FIXME */
5865     m_regex_underline_thickness = 1;  /* FIXME */
5866     m_regex_underline_position = char_spacing.top + char_height - m_regex_underline_thickness;  /* FIXME */
5867 
5868     /* Queue a resize if anything's changed. */
5869     if (resize) {
5870         if (widget_realized()) {
5871             gtk_widget_queue_resize_no_redraw(m_widget);
5872         }
5873     }
5874     /* Emit a signal that the font changed. */
5875     if (cresize) {
5876         emit_char_size_changed(m_cell_width, m_cell_height);
5877     }
5878     /* Repaint. */
5879     invalidate_all();
5880 }
5881 
ensure_font()5882 void VteTerminalPrivate::ensure_font()
5883 {
5884     if (m_draw != NULL) {
5885         /* Load default fonts, if no fonts have been loaded. */
5886         if (!m_has_fonts) {
5887             set_font_desc(m_unscaled_font_desc);
5888         }
5889         if (m_fontdirty) {
5890             int cell_width, cell_height;
5891             int char_ascent, char_descent;
5892             GtkBorder char_spacing;
5893             m_fontdirty = FALSE;
5894             _vte_draw_set_text_font (m_draw, m_widget, m_fontdesc,
5895                                      m_cell_width_scale, m_cell_height_scale);
5896             _vte_draw_get_text_metrics (m_draw,
5897                                         &cell_width, &cell_height,
5898                                         &char_ascent, &char_descent,
5899                                         &char_spacing);
5900             apply_font_metrics(cell_width, cell_height,
5901                                 char_ascent, char_descent,
5902                                 char_spacing);
5903         }
5904     }
5905 }
5906 
update_font()5907 void VteTerminalPrivate::update_font()
5908 {
5909     /* We'll get called again later */
5910     if (m_unscaled_font_desc == nullptr) {
5911         return;
5912     }
5913 
5914     auto desc = pango_font_description_copy(m_unscaled_font_desc);
5915 
5916     double size = pango_font_description_get_size(desc);
5917     if (pango_font_description_get_size_is_absolute(desc)) {
5918         pango_font_description_set_absolute_size(desc, m_font_scale * size);
5919     } else {
5920         pango_font_description_set_size(desc, m_font_scale * size);
5921     }
5922 
5923     if (m_fontdesc) {
5924         pango_font_description_free(m_fontdesc);
5925     }
5926     m_fontdesc = desc;
5927 
5928     m_fontdirty = TRUE;
5929     m_has_fonts = TRUE;
5930 
5931     /* Set the drawing font. */
5932     if (widget_realized()) {
5933         ensure_font();
5934     }
5935 }
5936 
5937 /*
5938  * VteTerminalPrivate::set_font_desc:
5939  * @font_desc: (allow-none): a #PangoFontDescription for the desired font, or %nullptr
5940  *
5941  * Sets the font used for rendering all text displayed by the terminal,
5942  * overriding any fonts set using gtk_widget_modify_font().  The terminal
5943  * will immediately attempt to load the desired font, retrieve its
5944  * metrics, and attempt to resize itself to keep the same number of rows
5945  * and columns.  The font scale is applied to the specified font.
5946  */
set_font_desc(PangoFontDescription const * font_desc)5947 bool VteTerminalPrivate::set_font_desc(PangoFontDescription const* font_desc)
5948 {
5949     /* Create an owned font description. */
5950     PangoFontDescription *desc;
5951 
5952     auto context = gtk_widget_get_style_context(m_widget);
5953     gtk_style_context_save(context);
5954     gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
5955     gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font", &desc, nullptr);
5956     gtk_style_context_restore(context);
5957 
5958     pango_font_description_set_family_static (desc, "monospace");
5959     if (font_desc != nullptr) {
5960         pango_font_description_merge (desc, font_desc, TRUE);
5961         _VTE_DEBUG_IF(VTE_DEBUG_MISC) {
5962             if (desc) {
5963                 char *tmp;
5964                 tmp = pango_font_description_to_string(desc);
5965                 g_printerr("Using pango font \"%s\".\n", tmp);
5966                 g_free (tmp);
5967             }
5968         }
5969     } else {
5970         _vte_debug_print(VTE_DEBUG_MISC,
5971                 "Using default monospace font.\n");
5972     }
5973 
5974     bool same_desc = m_unscaled_font_desc &&
5975             pango_font_description_equal(m_unscaled_font_desc, desc);
5976 
5977     /* Note that we proceed to recreating the font even if the description
5978      * are the same.  This is because maybe screen
5979      * font options were changed, or new fonts installed.  Those will be
5980      * detected at font creation time and respected.
5981      */
5982 
5983     /* Free the old font description and save the new one. */
5984     if (m_unscaled_font_desc != nullptr) {
5985         pango_font_description_free(m_unscaled_font_desc);
5986     }
5987 
5988     m_unscaled_font_desc = desc /* adopted */;
5989 
5990     update_font();
5991 
5992     return !same_desc;
5993 }
5994 
set_font_scale(gdouble scale)5995 bool VteTerminalPrivate::set_font_scale(gdouble scale)
5996 {
5997     /* FIXME: compare old and new scale in pixel space */
5998     if (_vte_double_equal(scale, m_font_scale)) {
5999         return false;
6000     }
6001 
6002     m_font_scale = scale;
6003     update_font();
6004 
6005     return true;
6006 }
6007 
set_cell_width_scale(double scale)6008 bool VteTerminalPrivate::set_cell_width_scale(double scale)
6009 {
6010     /* FIXME: compare old and new scale in pixel space */
6011     if (_vte_double_equal(scale, m_cell_width_scale)) {
6012         return false;
6013     }
6014 
6015     m_cell_width_scale = scale;
6016     /* Set the drawing font. */
6017     m_fontdirty = TRUE;
6018     if (widget_realized()) {
6019         ensure_font();
6020     }
6021 
6022     return true;
6023 }
6024 
set_cell_height_scale(double scale)6025 bool VteTerminalPrivate::set_cell_height_scale(double scale)
6026 {
6027     /* FIXME: compare old and new scale in pixel space */
6028     if (_vte_double_equal(scale, m_cell_height_scale)) {
6029         return false;
6030     }
6031 
6032     m_cell_height_scale = scale;
6033     /* Set the drawing font. */
6034     m_fontdirty = TRUE;
6035     if (widget_realized()) {
6036         ensure_font();
6037     }
6038 
6039     return true;
6040 }
6041 
6042 /* Resize the given screen (normal or alternate) of the terminal. */
screen_set_size(VteScreen * screen_,long old_columns,long old_rows,bool do_rewrap)6043 void VteTerminalPrivate::screen_set_size(VteScreen *screen_,
6044                                             long old_columns,
6045                                             long old_rows,
6046                                             bool do_rewrap)
6047 {
6048     VteRing *ring = screen_->row_data;
6049     VteVisualPosition cursor_saved_absolute;
6050     VteVisualPosition below_viewport;
6051     VteVisualPosition below_current_paragraph;
6052     VteVisualPosition *markers[7];
6053     gboolean was_scrolled_to_top = ((long) ceil(screen_->scroll_delta) == _vte_ring_delta(ring));
6054     gboolean was_scrolled_to_bottom = ((long) screen_->scroll_delta == screen_->insert_delta);
6055     glong old_top_lines;
6056     double new_scroll_delta;
6057 
6058     if (m_selection_block_mode && do_rewrap && old_columns != m_column_count) {
6059         deselect_all();
6060     }
6061 
6062     _vte_debug_print(VTE_DEBUG_RESIZE,
6063             "Resizing %s screen_\n"
6064             "Old  insert_delta=%ld  scroll_delta=%f\n"
6065                         "     cursor (absolute)  row=%ld  col=%ld\n"
6066             "     cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n",
6067             screen_ == &m_normal_screen ? "normal" : "alternate",
6068             screen_->insert_delta, screen_->scroll_delta,
6069             screen_->cursor.row, screen_->cursor.col,
6070             screen_->saved.cursor.row, screen_->saved.cursor.col);
6071 
6072     cursor_saved_absolute.row = screen_->saved.cursor.row + screen_->insert_delta;
6073     cursor_saved_absolute.col = screen_->saved.cursor.col;
6074     below_viewport.row = screen_->scroll_delta + old_rows;
6075     below_viewport.col = 0;
6076     below_current_paragraph.row = screen_->cursor.row + 1;
6077     while (below_current_paragraph.row < _vte_ring_next(ring)
6078         && _vte_ring_index(ring, below_current_paragraph.row - 1)->attr.soft_wrapped) {
6079         below_current_paragraph.row++;
6080     }
6081     below_current_paragraph.col = 0;
6082     memset(&markers, 0, sizeof(markers));
6083     markers[0] = &cursor_saved_absolute;
6084     markers[1] = &below_viewport;
6085     markers[2] = &below_current_paragraph;
6086     markers[3] = &screen_->cursor;
6087     if (m_has_selection) {
6088         /* selection_end is inclusive, make it non-inclusive, see bug 722635. */
6089         m_selection_end.col++;
6090         markers[4] = &m_selection_start;
6091         markers[5] = &m_selection_end;
6092     }
6093 
6094     old_top_lines = below_current_paragraph.row - screen_->insert_delta;
6095 
6096     if (do_rewrap && old_columns != m_column_count) {
6097         _vte_ring_rewrap(ring, m_column_count, markers);
6098     }
6099 
6100     if (_vte_ring_length(ring) > m_row_count) {
6101         /* The content won't fit without scrollbars. Before figuring out the position, we might need to
6102            drop some lines from the ring if the cursor is not at the bottom, as XTerm does. See bug 708213.
6103            This code is really tricky, see ../doc/rewrap.txt for details! */
6104         glong new_top_lines, drop1, drop2, drop3, drop;
6105         screen_->insert_delta = _vte_ring_next(ring) - m_row_count;
6106         new_top_lines = below_current_paragraph.row - screen_->insert_delta;
6107         drop1 = _vte_ring_length(ring) - m_row_count;
6108         drop2 = _vte_ring_next(ring) - below_current_paragraph.row;
6109         drop3 = old_top_lines - new_top_lines;
6110         drop = MIN(MIN(drop1, drop2), drop3);
6111         if (drop > 0) {
6112             int new_ring_next = screen_->insert_delta + m_row_count - drop;
6113             _vte_debug_print(VTE_DEBUG_RESIZE,
6114                     "Dropping %ld [== MIN(%ld, %ld, %ld)] rows at the bottom\n",
6115                     drop, drop1, drop2, drop3);
6116             _vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
6117         }
6118     }
6119 
6120     if (m_has_selection) {
6121         /* Make selection_end inclusive again, see above. */
6122         m_selection_end.col--;
6123     }
6124 
6125     /* Figure out new insert and scroll deltas */
6126     if (_vte_ring_length(ring) <= m_row_count) {
6127         /* Everything fits without scrollbars. Align at top. */
6128         screen_->insert_delta = _vte_ring_delta(ring);
6129         new_scroll_delta = screen_->insert_delta;
6130         _vte_debug_print(VTE_DEBUG_RESIZE, "Everything fits without scrollbars\n");
6131     } else {
6132         /* Scrollbar required. Can't afford unused lines at bottom. */
6133         screen_->insert_delta = _vte_ring_next(ring) - m_row_count;
6134         if (was_scrolled_to_bottom) {
6135             /* Was scrolled to bottom, keep this way. */
6136             new_scroll_delta = screen_->insert_delta;
6137             _vte_debug_print(VTE_DEBUG_RESIZE, "Scroll to bottom\n");
6138         } else if (was_scrolled_to_top) {
6139             /* Was scrolled to top, keep this way. Not sure if this special case is worth it. */
6140             new_scroll_delta = _vte_ring_delta(ring);
6141             _vte_debug_print(VTE_DEBUG_RESIZE, "Scroll to top\n");
6142         } else {
6143             /* Try to scroll so that the bottom visible row stays.
6144                More precisely, the character below the bottom left corner stays in that
6145                (invisible) row.
6146                So if the bottom of the screen_ was at a hard line break then that hard
6147                line break will stay there.
6148                TODO: What would be the best behavior if the bottom of the screen_ is a
6149                soft line break, i.e. only a partial line is visible at the bottom? */
6150             new_scroll_delta = below_viewport.row - m_row_count;
6151             /* Keep the old fractional part. */
6152             new_scroll_delta += screen_->scroll_delta - floor(screen_->scroll_delta);
6153             _vte_debug_print(VTE_DEBUG_RESIZE, "Scroll so bottom row stays\n");
6154         }
6155     }
6156 
6157     /* Don't clamp, they'll be clamped when restored. Until then remember off-screen_ values
6158        since they might become on-screen_ again on subsequent resizes. */
6159         screen_->saved.cursor.row = cursor_saved_absolute.row - screen_->insert_delta;
6160         screen_->saved.cursor.col = cursor_saved_absolute.col;
6161 
6162     _vte_debug_print(VTE_DEBUG_RESIZE,
6163             "New  insert_delta=%ld  scroll_delta=%f\n"
6164                         "     cursor (absolute)  row=%ld  col=%ld\n"
6165             "     cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n\n",
6166             screen_->insert_delta, new_scroll_delta,
6167                         screen_->cursor.row, screen_->cursor.col,
6168                         screen_->saved.cursor.row, screen_->saved.cursor.col);
6169 
6170     if (screen_ == m_screen) {
6171         queue_adjustment_value_changed(new_scroll_delta);
6172     } else {
6173         screen_->scroll_delta = new_scroll_delta;
6174     }
6175 }
6176 
set_size(long columns,long rows)6177 void VteTerminalPrivate::set_size(long columns, long rows)
6178 {
6179     glong old_columns, old_rows;
6180 
6181     _vte_debug_print(VTE_DEBUG_RESIZE,
6182             "Setting PTY size to %ldx%ld.\n",
6183             columns, rows);
6184 
6185     old_rows = m_row_count;
6186     old_columns = m_column_count;
6187     {
6188         m_row_count = rows;
6189         m_column_count = columns;
6190     }
6191     if (old_rows != m_row_count || old_columns != m_column_count) {
6192         m_scrolling_restricted = FALSE;
6193 
6194         _vte_ring_set_visible_rows(m_normal_screen.row_data, m_row_count);
6195         _vte_ring_set_visible_rows(m_alternate_screen.row_data, m_row_count);
6196 
6197         /* Resize the normal screen and (if rewrapping is enabled) rewrap it even if the alternate screen is visible: bug 415277 */
6198         screen_set_size(&m_normal_screen, old_columns, old_rows, m_rewrap_on_resize);
6199         /* Resize the alternate screen if it's the current one, but never rewrap it: bug 336238 comment 60 */
6200         if (m_screen == &m_alternate_screen) {
6201             screen_set_size(&m_alternate_screen, old_columns, old_rows, false);
6202         }
6203 
6204         /* Ensure scrollback buffers cover the screen. */
6205         set_scrollback_lines(m_scrollback_lines);
6206 
6207         /* Ensure the cursor is valid */
6208         m_screen->cursor.row = CLAMP (m_screen->cursor.row,
6209                                         _vte_ring_delta (m_screen->row_data),
6210                                         MAX (_vte_ring_delta (m_screen->row_data),
6211                                             _vte_ring_next (m_screen->row_data) - 1));
6212 
6213         adjust_adjustments_full();
6214         gtk_widget_queue_resize_no_redraw(m_widget);
6215         /* Our visible text changed. */
6216         emit_text_modified();
6217     }
6218 }
6219 
6220 /* Redraw the widget. */
vte_terminal_vadjustment_value_changed_cb(VteTerminalPrivate * that)6221 static void vte_terminal_vadjustment_value_changed_cb(VteTerminalPrivate *that)
6222 {
6223     that->vadjustment_value_changed();
6224 }
6225 
vadjustment_value_changed()6226 void VteTerminalPrivate::vadjustment_value_changed()
6227 {
6228     /* Read the new adjustment value and save the difference. */
6229     double adj = gtk_adjustment_get_value(m_vadjustment);
6230     double dy = adj - m_screen->scroll_delta;
6231     m_screen->scroll_delta = adj;
6232 
6233     /* Sanity checks. */
6234     if (G_UNLIKELY(!widget_realized())) {
6235             return;
6236     }
6237 
6238         /* FIXME: do this check in pixel space */
6239     if (!_vte_double_equal(dy, 0)) {
6240         _vte_debug_print(VTE_DEBUG_ADJ,
6241                 "Scrolling by %f\n", dy);
6242                 invalidate_all();
6243         emit_text_scrolled(dy);
6244         queue_contents_changed();
6245     } else {
6246         _vte_debug_print(VTE_DEBUG_ADJ, "Not scrolling\n");
6247     }
6248 }
6249 
widget_set_hadjustment(GtkAdjustment * adjustment)6250 void VteTerminalPrivate::widget_set_hadjustment(GtkAdjustment *adjustment)
6251 {
6252     if (adjustment == m_hadjustment) {
6253         return;
6254     }
6255 
6256     if (m_hadjustment) {
6257         g_object_unref (m_hadjustment);
6258     }
6259 
6260     m_hadjustment = adjustment ? (GtkAdjustment *)g_object_ref_sink(adjustment) : nullptr;
6261 }
6262 
widget_set_vadjustment(GtkAdjustment * adjustment)6263 void VteTerminalPrivate::widget_set_vadjustment(GtkAdjustment *adjustment)
6264 {
6265     if (adjustment != nullptr && adjustment == m_vadjustment) {
6266         return;
6267     }
6268     if (adjustment == nullptr && m_vadjustment != nullptr) {
6269         return;
6270     }
6271 
6272     if (adjustment == nullptr) {
6273         adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
6274     }
6275 
6276     /* Add a reference to the new adjustment object. */
6277     g_object_ref_sink(adjustment);
6278     /* Get rid of the old adjustment object. */
6279     if (m_vadjustment != nullptr) {
6280         /* Disconnect our signal handlers from this object. */
6281         g_signal_handlers_disconnect_by_func(m_vadjustment,
6282                              (void*)vte_terminal_vadjustment_value_changed_cb,
6283                              this);
6284         g_object_unref(m_vadjustment);
6285     }
6286 
6287     /* Set the new adjustment object. */
6288     m_vadjustment = adjustment;
6289 
6290     /* We care about the offset, not the top or bottom. */
6291     g_signal_connect_swapped(m_vadjustment,
6292                  "value-changed",
6293                  G_CALLBACK(vte_terminal_vadjustment_value_changed_cb),
6294                  this);
6295 }
6296 
VteTerminalPrivate(VteTerminal * t)6297 VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) :
6298         m_terminal(t),
6299         m_widget(&t->widget)
6300 {
6301     /* Inits allocation to 1x1 @ -1,-1 */
6302     cairo_rectangle_int_t allocation;
6303     gtk_widget_get_allocation(m_widget, &allocation);
6304     set_allocated_rect(allocation);
6305 
6306     int i;
6307     GdkDisplay *display;
6308 
6309     /* NOTE! We allocated zeroed memory, just fill in non-zero stuff. */
6310 
6311     gtk_widget_set_can_focus(m_widget, TRUE);
6312 
6313     /* We do our own redrawing. */
6314     /* FIXMEchpe still necessary? */
6315     gtk_widget_set_redraw_on_allocate(m_widget, FALSE);
6316 
6317     m_invalidated_all = false;
6318     m_update_rects = g_array_sized_new(FALSE /* zero terminated */,
6319                                         FALSE /* clear */,
6320                                         sizeof(cairo_rectangle_int_t),
6321                                         32 /* preallocated size */);
6322 
6323     /* Set an adjustment for the application to use to control scrolling. */
6324     m_vadjustment = nullptr;
6325     m_hadjustment = nullptr;
6326 
6327     /* GtkScrollable */
6328     m_hscroll_policy = GTK_SCROLL_NATURAL;
6329     m_vscroll_policy = GTK_SCROLL_NATURAL;
6330 
6331     widget_set_hadjustment(nullptr);
6332     widget_set_vadjustment(nullptr);
6333 
6334     /* Set up dummy metrics, value != 0 to avoid division by 0 */
6335     m_cell_width = 1;
6336     m_cell_height = 1;
6337     m_char_ascent = 1;
6338     m_char_descent = 1;
6339     m_char_padding = {0, 0, 0, 0};
6340     m_line_thickness = 1;
6341     m_underline_position = 1;
6342     m_underline_thickness = 1;
6343     m_double_underline_position = 1;
6344     m_double_underline_thickness = 1;
6345     m_undercurl_position = 1.;
6346     m_undercurl_thickness = 1.;
6347     m_strikethrough_position = 1;
6348     m_strikethrough_thickness = 1;
6349     m_overline_position = 1;
6350     m_overline_thickness = 1;
6351     m_regex_underline_position = 1;
6352     m_regex_underline_thickness = 1;
6353 
6354     m_row_count = VTE_ROWS;
6355     m_column_count = VTE_COLUMNS;
6356 
6357     /* Initialize the screens and histories. */
6358     _vte_ring_init (m_alternate_screen.row_data, m_row_count, FALSE);
6359     m_screen = &m_alternate_screen;
6360     _vte_ring_init (m_normal_screen.row_data, VTE_SCROLLBACK_INIT, TRUE);
6361     m_screen = &m_normal_screen;
6362 
6363     reset_default_attributes(true);
6364 
6365     /* Initialize charset modes. */
6366     m_character_replacements[0] = VTE_CHARACTER_REPLACEMENT_NONE;
6367     m_character_replacements[1] = VTE_CHARACTER_REPLACEMENT_NONE;
6368     m_character_replacement = &m_character_replacements[0];
6369 
6370     /* Set up the desired palette. */
6371     set_colors_default();
6372     for (i = 0; i < VTE_PALETTE_SIZE; i++) {
6373         m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
6374     }
6375 
6376     /* Set up I/O encodings. */
6377     m_utf8_ambiguous_width = VTE_DEFAULT_UTF8_AMBIGUOUS_WIDTH;
6378     m_iso2022 = _vte_iso2022_state_new(m_encoding);
6379     m_incoming = nullptr;
6380     m_pending = g_array_new(FALSE, TRUE, sizeof(gunichar));
6381     m_max_input_bytes = VTE_MAX_INPUT_READ;
6382     m_cursor_blink_tag = 0;
6383     m_text_blink_tag = 0;
6384     m_outgoing = _vte_byte_array_new();
6385     m_outgoing_conv = VTE_INVALID_CONV;
6386     m_conv_buffer = _vte_byte_array_new();
6387     set_encoding(nullptr /* UTF-8 */);
6388     g_assert_cmpstr(m_encoding, ==, "UTF-8");
6389     m_last_graphic_character = 0;
6390 
6391         /* Set up the emulation. */
6392     m_keypad_mode = VTE_KEYMODE_NORMAL;
6393     m_cursor_mode = VTE_KEYMODE_NORMAL;
6394     m_autowrap = TRUE;
6395     m_sendrecv_mode = TRUE;
6396     m_dec_saved = g_hash_table_new(NULL, NULL);
6397     m_matcher = _vte_matcher_new();
6398     /* Scrolling options. */
6399     m_scroll_on_keystroke = TRUE;
6400     m_alternate_screen_scroll = TRUE;
6401     m_scrollback_lines = -1; /* force update in vte_terminal_set_scrollback_lines */
6402     set_scrollback_lines(VTE_SCROLLBACK_INIT);
6403 
6404     /* Selection info. */
6405     display = gtk_widget_get_display(m_widget);
6406     m_clipboard[VTE_SELECTION_PRIMARY] = gtk_clipboard_get_for_display(display, GDK_SELECTION_PRIMARY);
6407     m_clipboard[VTE_SELECTION_CLIPBOARD] = gtk_clipboard_get_for_display(display, GDK_SELECTION_CLIPBOARD);
6408     m_selection_owned[VTE_SELECTION_PRIMARY] = false;
6409     m_selection_owned[VTE_SELECTION_CLIPBOARD] = false;
6410 
6411     /* Miscellaneous options. */
6412     set_backspace_binding(VTE_ERASE_AUTO);
6413     set_delete_binding(VTE_ERASE_AUTO);
6414     m_meta_sends_escape = TRUE;
6415     m_audible_bell = TRUE;
6416     m_text_blink_mode = VTE_TEXT_BLINK_ALWAYS;
6417     m_allow_bold = TRUE;
6418     m_bold_is_bright = TRUE;
6419     m_deccolm_mode = FALSE;
6420     m_rewrap_on_resize = TRUE;
6421     set_default_tabstops();
6422 
6423     m_input_enabled = TRUE;
6424 
6425     /* Cursor shape. */
6426     m_cursor_shape = VTE_CURSOR_SHAPE_BLOCK;
6427     m_cursor_aspect_ratio = 0.04;
6428 
6429     /* Cursor blinking. */
6430     m_cursor_visible = TRUE;
6431     m_cursor_blink_timeout = 500;
6432     m_cursor_blinks = FALSE;
6433     m_cursor_blink_mode = VTE_CURSOR_BLINK_SYSTEM;
6434 
6435     /* DECSCUSR cursor style (shape and blinking possibly overridden
6436      * via escape sequence) */
6437     m_cursor_style = VTE_CURSOR_STYLE_TERMINAL_DEFAULT;
6438 
6439     /* Initialize the saved cursor. */
6440     save_cursor(&m_normal_screen);
6441     save_cursor(&m_alternate_screen);
6442 
6443     /* Rendering data */
6444     m_draw = _vte_draw_new();
6445 
6446     /* Set up background information. */
6447     m_background_alpha = 1.;
6448 
6449     /* Word chars */
6450     set_word_char_exceptions(WORD_CHAR_EXCEPTIONS_DEFAULT);
6451 
6452     /* Selection */
6453     m_selection_block_mode = FALSE;
6454     m_unscaled_font_desc = nullptr;
6455     m_fontdesc = nullptr;
6456     m_font_scale = 1.;
6457     m_cell_width_scale = 1.;
6458     m_cell_height_scale = 1.;
6459     m_has_fonts = FALSE;
6460 
6461     /* Hyperlink */
6462     m_allow_hyperlink = FALSE;
6463     m_hyperlink_auto_id = 0;
6464 
6465     /* Mouse */
6466     m_mouse_last_position = vte::view::coords(-1, -1);
6467 
6468     m_padding = default_padding;
6469     update_view_extents();
6470 
6471 #ifdef VTE_DEBUG
6472     if (g_test_mode) {
6473         static char const warning[] = "\e[1m\e[31mWARNING:\e[39m Test mode enabled.\e[0m\n\e[G";
6474         feed(warning, strlen(warning), false);
6475     }
6476 #endif
6477 }
6478 
widget_constructed()6479 void VteTerminalPrivate::widget_constructed()
6480 {
6481    /* Set the style as early as possible, before GTK+ starts
6482     * invoking various callbacks. This is needed in order to
6483     * compute the initial geometry correctly in presence of
6484     * non-default padding, see bug 787710. */
6485     widget_style_updated();
6486 }
6487 
widget_get_preferred_width(int * minimum_width,int * natural_width)6488 void VteTerminalPrivate::widget_get_preferred_width(int *minimum_width,
6489                                                int *natural_width)
6490 {
6491     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_width()\n");
6492 
6493     ensure_font();
6494 
6495 #if 0
6496     refresh_size(); /* FIXME */
6497 #endif
6498     *minimum_width = m_cell_width * 1;
6499     *natural_width = m_cell_width * m_column_count;
6500 
6501     *minimum_width += m_padding.left +
6502                           m_padding.right;
6503     *natural_width += m_padding.left +
6504                           m_padding.right;
6505 
6506     _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
6507             "[Terminal %p] minimum_width=%d, natural_width=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",
6508             m_terminal,
6509             *minimum_width, *natural_width,
6510             m_column_count,
6511             m_row_count,
6512             m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
6513 }
6514 
widget_get_preferred_height(int * minimum_height,int * natural_height)6515 void VteTerminalPrivate::widget_get_preferred_height(int *minimum_height,
6516                                                         int *natural_height)
6517 {
6518     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_height()\n");
6519 
6520     ensure_font();
6521 
6522 #if 0
6523     refresh_size(); /* FIXME */
6524 #endif
6525 
6526     *minimum_height = m_cell_height * 1;
6527     *natural_height = m_cell_height * m_row_count;
6528 
6529     *minimum_height += m_padding.top +
6530                m_padding.bottom;
6531     *natural_height += m_padding.top +
6532                m_padding.bottom;
6533 
6534     _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
6535             "[Terminal %p] minimum_height=%d, natural_height=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",
6536             m_terminal,
6537             *minimum_height, *natural_height,
6538             m_column_count,
6539             m_row_count,
6540             m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
6541 }
6542 
widget_size_allocate(GtkAllocation * allocation)6543 void VteTerminalPrivate::widget_size_allocate(GtkAllocation *allocation)
6544 {
6545     glong width, height;
6546     gboolean repaint, update_scrollback;
6547 
6548     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_size_allocate()\n");
6549 
6550     width = (allocation->width - (m_padding.left + m_padding.right)) / m_cell_width;
6551     height = (allocation->height - (m_padding.top + m_padding.bottom)) / m_cell_height;
6552     width = MAX(width, 1);
6553     height = MAX(height, 1);
6554 
6555     _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
6556             "[Terminal %p] Sizing window to %dx%d (%ldx%ld, padding %d,%d;%d,%d).\n",
6557             m_terminal,
6558             allocation->width, allocation->height,
6559             width, height,
6560             m_padding.left, m_padding.right, m_padding.top, m_padding.bottom);
6561 
6562     auto current_allocation = get_allocated_rect();
6563 
6564     repaint = current_allocation.width != allocation->width
6565             || current_allocation.height != allocation->height;
6566     update_scrollback = current_allocation.height != allocation->height;
6567 
6568     /* Set our allocation to match the structure. */
6569     gtk_widget_set_allocation(m_widget, allocation);
6570     set_allocated_rect(*allocation);
6571 
6572     if (width != m_column_count
6573      || height != m_row_count
6574      || update_scrollback) {
6575         /* Set the size of the pseudo-terminal. */
6576         set_size(width, height);
6577 
6578         /* Notify viewers that the contents have changed. */
6579         queue_contents_changed();
6580     }
6581 
6582     /* Resize the GDK window. */
6583     if (widget_realized()) {
6584         gdk_window_move_resize(m_event_window,
6585                                 allocation->x,
6586                                 allocation->y,
6587                                 allocation->width,
6588                                 allocation->height);
6589         /* Force a repaint if we were resized. */
6590         if (repaint) {
6591             reset_update_rects();
6592             invalidate_all();
6593         }
6594     }
6595 }
6596 
widget_unrealize()6597 void VteTerminalPrivate::widget_unrealize()
6598 {
6599     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_unrealize()\n");
6600 
6601     /* Deallocate the cursors. */
6602     m_mouse_cursor_over_widget = FALSE;
6603     g_object_unref(m_mouse_default_cursor);
6604     m_mouse_default_cursor = NULL;
6605     g_object_unref(m_mouse_mousing_cursor);
6606     m_mouse_mousing_cursor = NULL;
6607     g_object_unref(m_mouse_hyperlink_cursor);
6608     m_mouse_hyperlink_cursor = NULL;
6609     g_object_unref(m_mouse_inviso_cursor);
6610     m_mouse_inviso_cursor = NULL;
6611 
6612     match_hilite_clear();
6613 
6614     /* Shut down input methods. */
6615     if (m_im_context != nullptr) {
6616         g_signal_handlers_disconnect_matched(m_im_context, G_SIGNAL_MATCH_DATA,
6617                                                     0, 0, NULL, NULL, this);
6618         im_reset();
6619         gtk_im_context_set_client_window(m_im_context, NULL);
6620         g_object_unref(m_im_context);
6621         m_im_context = nullptr;
6622     }
6623     m_im_preedit_active = FALSE;
6624     if (m_im_preedit != nullptr) {
6625         g_free(m_im_preedit);
6626         m_im_preedit = NULL;
6627     }
6628     if (m_im_preedit_attrs != NULL) {
6629         pango_attr_list_unref(m_im_preedit_attrs);
6630         m_im_preedit_attrs = NULL;
6631     }
6632     m_im_preedit_cursor = 0;
6633 
6634     /* Clean up our draw structure. */
6635     if (m_draw != NULL) {
6636         _vte_draw_free(m_draw);
6637         m_draw = NULL;
6638     }
6639     m_fontdirty = TRUE;
6640 
6641     /* Unmap the widget if it hasn't been already. */
6642     /* FIXMEchpe this can't happen */
6643     if (gtk_widget_get_mapped(m_widget)) {
6644         gtk_widget_unmap(m_widget);
6645     }
6646 
6647     /* Remove the cursor blink timeout function. */
6648     remove_cursor_timeout();
6649 
6650     /* Remove the contents blink timeout function. */
6651     remove_text_blink_timeout();
6652 
6653     /* Cancel any pending redraws. */
6654     remove_update_timeout(this);
6655 
6656     /* Cancel any pending signals */
6657     m_contents_changed_pending = FALSE;
6658     m_cursor_moved_pending = FALSE;
6659     m_text_modified_flag = FALSE;
6660     m_text_inserted_flag = FALSE;
6661     m_text_deleted_flag = FALSE;
6662 
6663     /* Clear modifiers. */
6664     m_modifiers = 0;
6665 
6666     /* Destroy the even window */
6667     gtk_widget_unregister_window(m_widget, m_event_window);
6668     gdk_window_destroy(m_event_window);
6669     m_event_window = nullptr;
6670 }
6671 
vte_terminal_settings_notify_cb(GtkSettings * settings,GParamSpec * pspec,VteTerminalPrivate * that)6672 static void vte_terminal_settings_notify_cb (GtkSettings *settings,
6673                                                 GParamSpec *pspec,
6674                                                 VteTerminalPrivate *that)
6675 {
6676     that->widget_settings_notify();
6677 }
6678 
widget_settings_notify()6679 void VteTerminalPrivate::widget_settings_notify()
6680 {
6681     gboolean blink;
6682     int blink_time = 1000;
6683     int blink_timeout = G_MAXINT;
6684 
6685     g_object_get(gtk_widget_get_settings(m_widget),
6686                     "gtk-cursor-blink", &blink,
6687                     "gtk-cursor-blink-time", &blink_time,
6688                     "gtk-cursor-blink-timeout", &blink_timeout,
6689                     nullptr);
6690 
6691     _vte_debug_print(VTE_DEBUG_MISC,
6692                         "Cursor blinking settings: blink=%d time=%d timeout=%d\n",
6693                         blink, blink_time, blink_timeout);
6694 
6695     m_cursor_blink_cycle = blink_time / 2;
6696     m_cursor_blink_timeout = blink_timeout;
6697 
6698     update_cursor_blinks();
6699 
6700     /* Misuse gtk-cursor-blink-time for text blinking as well. This might change in the future. */
6701     m_text_blink_cycle = m_cursor_blink_cycle;
6702     if (m_text_blink_tag != 0) {
6703        /* The current phase might have changed, and an already installed
6704         * timer to blink might fire too late. So remove the timer and
6705         * repaint the contents (which will install a correct new timer). */
6706         remove_text_blink_timeout();
6707         invalidate_all();
6708     }
6709 }
6710 
widget_screen_changed(GdkScreen * previous_screen)6711 void VteTerminalPrivate::widget_screen_changed (GdkScreen *previous_screen)
6712 {
6713     GtkSettings *settings;
6714 
6715     auto gdk_screen = gtk_widget_get_screen (m_widget);
6716     if (previous_screen != NULL &&
6717         (gdk_screen != previous_screen || gdk_screen == NULL)) {
6718         settings = gtk_settings_get_for_screen (previous_screen);
6719         g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
6720                                                 0, 0, NULL, NULL, this);
6721     }
6722 
6723     if (gdk_screen == previous_screen || gdk_screen == nullptr)
6724             return;
6725 
6726     widget_settings_notify();
6727 
6728     settings = gtk_widget_get_settings(m_widget);
6729     g_signal_connect (settings, "notify::gtk-cursor-blink",
6730                         G_CALLBACK (vte_terminal_settings_notify_cb), this);
6731     g_signal_connect (settings, "notify::gtk-cursor-blink-time",
6732                         G_CALLBACK (vte_terminal_settings_notify_cb), this);
6733     g_signal_connect (settings, "notify::gtk-cursor-blink-timeout",
6734                         G_CALLBACK (vte_terminal_settings_notify_cb), this);
6735 }
6736 
~VteTerminalPrivate()6737 VteTerminalPrivate::~VteTerminalPrivate()
6738 {
6739     int sel;
6740 
6741     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_finalize()\n");
6742 
6743     /* Free the draw structure. */
6744     if (m_draw != NULL) {
6745         _vte_draw_free(m_draw);
6746     }
6747 
6748     /* The NLS maps. */
6749     _vte_iso2022_state_free(m_iso2022);
6750 
6751     /* Free the font description. */
6752     if (m_unscaled_font_desc != NULL) {
6753             pango_font_description_free(m_unscaled_font_desc);
6754     }
6755     if (m_fontdesc != NULL) {
6756         pango_font_description_free(m_fontdesc);
6757     }
6758 
6759     /* Free matching data. */
6760     if (m_match_attributes != NULL) {
6761         g_array_free(m_match_attributes, TRUE);
6762     }
6763     g_free(m_match_contents);
6764 
6765     if (m_search_attrs) {
6766         g_array_free (m_search_attrs, TRUE);
6767     }
6768 
6769     /* Disconnect from autoscroll requests. */
6770     stop_autoscroll();
6771 
6772     /* Cancel pending adjustment change notifications. */
6773     m_adjustment_changed_pending = FALSE;
6774 
6775     /* Tabstop information. */
6776     if (m_tabstops) {
6777         g_hash_table_destroy(m_tabstops);
6778     }
6779 
6780     /* Free any selected text, but if we currently own the selection,
6781      * throw the text onto the clipboard without an owner so that it
6782      * doesn't just disappear. */
6783     for (sel = VTE_SELECTION_PRIMARY; sel < LAST_VTE_SELECTION; sel++) {
6784         if (m_selection[sel] != nullptr) {
6785             if (m_selection_owned[sel]) {
6786                 /* FIXMEchpe we should check m_selection_format[sel]
6787                  * and also put text/html on if it's VTE_FORMAT_HTML */
6788                 gtk_clipboard_set_text(m_clipboard[sel],
6789                                         m_selection[sel]->str,
6790                                         m_selection[sel]->len);
6791             }
6792             g_string_free(m_selection[sel], TRUE);
6793             m_selection[sel] = nullptr;
6794         }
6795     }
6796 
6797     /* Clear the output histories. */
6798     _vte_ring_fini(m_normal_screen.row_data);
6799     _vte_ring_fini(m_alternate_screen.row_data);
6800 
6801     /* Free conversion descriptors. */
6802     if (m_outgoing_conv != VTE_INVALID_CONV) {
6803         _vte_conv_close(m_outgoing_conv);
6804         m_outgoing_conv = VTE_INVALID_CONV;
6805     }
6806     /* Stop processing input. */
6807     stop_processing(this);
6808 
6809     /* Discard any pending data. */
6810     _vte_incoming_chunks_release(m_incoming);
6811     _vte_byte_array_free(m_outgoing);
6812     g_array_free(m_pending, TRUE);
6813     _vte_byte_array_free(m_conv_buffer);
6814 
6815     /* Remove hash tables. */
6816     if (m_dec_saved != NULL) {
6817         g_hash_table_destroy(m_dec_saved);
6818     }
6819 
6820     /* Clean up emulation structures. */
6821     if (m_matcher != NULL) {
6822         _vte_matcher_free(m_matcher);
6823     }
6824 
6825     remove_update_timeout(this);
6826 
6827     /* discard title updates */
6828     g_free(m_window_title);
6829     g_free(m_window_title_changed);
6830     g_free(m_icon_title_changed);
6831     g_free(m_current_directory_uri_changed);
6832     g_free(m_current_directory_uri);
6833     g_free(m_current_file_uri_changed);
6834     g_free(m_current_file_uri);
6835 
6836     /* Word char exceptions */
6837     g_free(m_word_char_exceptions_string);
6838     g_free(m_word_char_exceptions);
6839 
6840     /* Free public-facing data. */
6841     g_free(m_icon_title);
6842     if (m_vadjustment != NULL) {
6843         /* Disconnect our signal handlers from this object. */
6844         g_signal_handlers_disconnect_by_func(m_vadjustment,
6845                              (void*)vte_terminal_vadjustment_value_changed_cb,
6846                              this);
6847         g_object_unref(m_vadjustment);
6848     }
6849 
6850     g_signal_handlers_disconnect_matched (gtk_widget_get_settings(m_widget), G_SIGNAL_MATCH_DATA,
6851                                             0, 0, NULL, NULL, this);
6852 
6853     /* Update rects */
6854     g_array_free(m_update_rects, TRUE /* free segment */);
6855 }
6856 
widget_realize()6857 void VteTerminalPrivate::widget_realize()
6858 {
6859     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_realize()\n");
6860 
6861     auto allocation = get_allocated_rect();
6862 
6863     /* Create the stock cursors. */
6864     m_mouse_cursor_over_widget = FALSE;  /* We'll receive an enter_notify_event if the window appears under the cursor. */
6865     m_mouse_default_cursor = widget_cursor_new(VTE_DEFAULT_CURSOR);
6866     m_mouse_mousing_cursor = widget_cursor_new(VTE_MOUSING_CURSOR);
6867     if (_vte_debug_on(VTE_DEBUG_HYPERLINK)) {
6868         /* Differ from the standard regex match cursor in debug mode. */
6869         m_mouse_hyperlink_cursor = widget_cursor_new(VTE_HYPERLINK_CURSOR_DEBUG);
6870     } else {
6871         m_mouse_hyperlink_cursor = widget_cursor_new(VTE_HYPERLINK_CURSOR);
6872     }
6873     m_mouse_inviso_cursor = widget_cursor_new(GDK_BLANK_CURSOR);
6874 
6875     /* Create a GDK window for the widget. */
6876     GdkWindowAttr attributes;
6877     attributes.window_type = GDK_WINDOW_CHILD;
6878     attributes.x = allocation.x;
6879     attributes.y = allocation.y;
6880     attributes.width = allocation.width;
6881     attributes.height = allocation.height;
6882     attributes.wclass = GDK_INPUT_ONLY;
6883     attributes.visual = gtk_widget_get_visual(m_widget);
6884     attributes.event_mask = gtk_widget_get_events(m_widget) |
6885                             GDK_EXPOSURE_MASK |
6886                             GDK_FOCUS_CHANGE_MASK |
6887                             GDK_SMOOTH_SCROLL_MASK |
6888                             GDK_SCROLL_MASK |
6889                             GDK_BUTTON_PRESS_MASK |
6890                             GDK_BUTTON_RELEASE_MASK |
6891                             GDK_POINTER_MOTION_MASK |
6892                             GDK_BUTTON1_MOTION_MASK |
6893                             GDK_ENTER_NOTIFY_MASK |
6894                             GDK_LEAVE_NOTIFY_MASK |
6895                             GDK_KEY_PRESS_MASK |
6896                             GDK_KEY_RELEASE_MASK;
6897     attributes.cursor = m_mouse_default_cursor;
6898     guint attributes_mask = GDK_WA_X |
6899                             GDK_WA_Y |
6900                             (attributes.visual ? GDK_WA_VISUAL : 0) |
6901                             GDK_WA_CURSOR;
6902 
6903     m_event_window = gdk_window_new(gtk_widget_get_parent_window (m_widget),
6904                                         &attributes, attributes_mask);
6905     gtk_widget_register_window(m_widget, m_event_window);
6906 
6907     /* Create rendering data if this is a re-realise */
6908     if (m_draw == NULL) {
6909         m_draw = _vte_draw_new();
6910     }
6911 
6912         // FIXMEchpe this shouldn't ever be true:
6913     if (m_im_context != nullptr) {
6914         g_signal_handlers_disconnect_matched(m_im_context, G_SIGNAL_MATCH_DATA,
6915                                                     0, 0, NULL, NULL, this);
6916         im_reset();
6917         gtk_im_context_set_client_window(m_im_context, nullptr);
6918         g_object_unref(m_im_context);
6919         m_im_context = nullptr;
6920     }
6921     m_im_preedit_active = FALSE;
6922     m_im_context = gtk_im_multicontext_new();
6923     gtk_im_context_set_client_window(m_im_context, m_event_window);
6924 /* FIXME
6925     g_signal_connect(m_im_context, "commit",
6926              G_CALLBACK(vte_terminal_im_commit_cb), this);
6927 */
6928     g_signal_connect(m_im_context, "preedit-start",
6929              G_CALLBACK(vte_terminal_im_preedit_start_cb), this);
6930     g_signal_connect(m_im_context, "preedit-changed",
6931              G_CALLBACK(vte_terminal_im_preedit_changed_cb), this);
6932     g_signal_connect(m_im_context, "preedit-end",
6933              G_CALLBACK(vte_terminal_im_preedit_end_cb), this);
6934     g_signal_connect(m_im_context, "retrieve-surrounding",
6935              G_CALLBACK(vte_terminal_im_retrieve_surrounding_cb), this);
6936     g_signal_connect(m_im_context, "delete-surrounding",
6937              G_CALLBACK(vte_terminal_im_delete_surrounding_cb), this);
6938     gtk_im_context_set_use_preedit(m_im_context, TRUE);
6939 
6940     /* Clear modifiers. */
6941     m_modifiers = 0;
6942 
6943     ensure_font();
6944 }
6945 
widget_map()6946 void VteTerminalPrivate::widget_map()
6947 {
6948     if (m_event_window) {
6949         gdk_window_show_unraised(m_event_window);
6950     }
6951 }
6952 
widget_unmap()6953 void VteTerminalPrivate::widget_unmap()
6954 {
6955     if (m_event_window) {
6956         gdk_window_hide(m_event_window);
6957     }
6958 }
6959 
swap(guint * a,guint * b)6960 static inline void swap (guint *a, guint *b)
6961 {
6962     guint tmp;
6963     tmp = *a, *a = *b, *b = tmp;
6964 }
6965 
6966 /* FIXMEchpe probably @attr should be passed by ref */
determine_colors(VteCellAttr const * attr,bool is_selected,bool is_cursor,guint * pfore,guint * pback,guint * pdeco) const6967 void VteTerminalPrivate::determine_colors(VteCellAttr const* attr,
6968                                             bool is_selected,
6969                                             bool is_cursor,
6970                                             guint *pfore,
6971                                             guint *pback,
6972                                             guint *pdeco) const
6973 {
6974     guint fore, back, deco;
6975 
6976     g_assert(attr);
6977 
6978     /* Start with cell colors */
6979     vte_color_triple_get(attr->colors(), &fore, &back, &deco);
6980 
6981     /* Reverse-mode switches default fore and back colors */
6982     if (G_UNLIKELY (m_reverse_mode)) {
6983         if (fore == VTE_DEFAULT_FG) {
6984             fore = VTE_DEFAULT_BG;
6985         }
6986         if (back == VTE_DEFAULT_BG) {
6987             back = VTE_DEFAULT_FG;
6988         }
6989     }
6990 
6991     /* Handle bold by using set bold color or brightening */
6992     if (attr->bold()) {
6993         if (fore == VTE_DEFAULT_FG && get_color(VTE_BOLD_FG) != NULL) {
6994             fore = VTE_BOLD_FG;
6995         } else if (m_bold_is_bright &&
6996                     fore >= VTE_LEGACY_COLORS_OFFSET &&
6997                     fore < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) {
6998             fore += VTE_COLOR_BRIGHT_OFFSET;
6999         }
7000     }
7001 
7002    /* Handle dim colors.  Only apply to palette colors, dimming direct RGB wouldn't make sense.
7003     * Apply to the foreground color only, but do this before handling reverse/highlight so that
7004     * those can be used to dim the background instead. */
7005     if (attr->dim() && !(fore & VTE_RGB_COLOR_MASK(8, 8, 8))) {
7006         fore |= VTE_DIM_COLOR;
7007     }
7008 
7009     /* Reverse cell? */
7010     if (attr->reverse()) {
7011         swap (&fore, &back);
7012     }
7013 
7014     /* Selection: use hightlight back/fore, or inverse */
7015     if (is_selected) {
7016         /* XXX what if hightlight back is same color as current back? */
7017         bool do_swap = true;
7018         if (get_color(VTE_HIGHLIGHT_BG) != NULL) {
7019             back = VTE_HIGHLIGHT_BG;
7020             do_swap = false;
7021         }
7022         if (get_color(VTE_HIGHLIGHT_FG) != NULL) {
7023             fore = VTE_HIGHLIGHT_FG;
7024             do_swap = false;
7025         }
7026         if (do_swap) {
7027             swap (&fore, &back);
7028         }
7029     }
7030 
7031     /* Cursor: use cursor back, or inverse */
7032     if (is_cursor) {
7033         /* XXX what if cursor back is same color as current back? */
7034         bool do_swap = true;
7035         if (get_color(VTE_CURSOR_BG) != NULL) {
7036             back = VTE_CURSOR_BG;
7037             do_swap = false;
7038         }
7039         if (get_color(VTE_CURSOR_FG) != NULL) {
7040             fore = VTE_CURSOR_FG;
7041             do_swap = false;
7042         }
7043         if (do_swap) {
7044             swap (&fore, &back);
7045         }
7046     }
7047 
7048    /* Invisible? */
7049    /* FIXME: This is dead code, this is not where we actually handle invisibile.
7050     * Instead, draw_cells() is not called from draw_rows().
7051     * That is required for the foreground to be transparent if so is the background. */
7052     if (attr->invisible()) {
7053         fore = back;
7054         deco = VTE_DEFAULT_FG;
7055     }
7056 
7057     *pfore = fore;
7058     *pback = back;
7059     *pdeco = deco;
7060 }
7061 
determine_colors(VteCell const * cell,bool highlight,guint * fore,guint * back,guint * deco) const7062 void VteTerminalPrivate::determine_colors(VteCell const* cell,
7063                                             bool highlight,
7064                                             guint *fore,
7065                                             guint *back,
7066                                             guint *deco) const
7067 {
7068     determine_colors(cell ? &cell->attr : &basic_cell.attr,
7069                         highlight, false /* not cursor */,
7070                         fore, back, deco);
7071 }
7072 
determine_cursor_colors(VteCell const * cell,bool highlight,guint * fore,guint * back,guint * deco) const7073 void VteTerminalPrivate::determine_cursor_colors(VteCell const* cell,
7074                                                     bool highlight,
7075                                                     guint *fore,
7076                                                     guint *back,
7077                                                     guint *deco) const
7078 {
7079     determine_colors(cell ? &cell->attr : &basic_cell.attr,
7080                         highlight, true /* cursor */,
7081                         fore, back, deco);
7082 }
7083 
invalidate_text_blink_cb(VteTerminalPrivate * that)7084 static gboolean invalidate_text_blink_cb(VteTerminalPrivate *that)
7085 {
7086     that->m_text_blink_tag = 0;
7087     that->invalidate_all();
7088     return G_SOURCE_REMOVE;
7089 }
7090 
7091 /* Draw a string of characters with similar attributes. */
draw_cells(struct _vte_draw_text_request * 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)7092 void VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
7093                                     gssize n,
7094                                     uint32_t fore,
7095                                     uint32_t back,
7096                                     uint32_t deco,
7097                                     bool clear,
7098                                     bool draw_default_bg,
7099                                     uint32_t attr,
7100                                     bool hyperlink,
7101                                     bool hilite,
7102                                     int column_width,
7103                                     int row_height)
7104 {
7105     int i, x, y;
7106     gint columns = 0;
7107     vte::color::rgb fg, bg, dc;
7108 
7109     g_assert(n > 0);
7110 #if 0
7111     _VTE_DEBUG_IF(VTE_DEBUG_CELLS) {
7112         GString *str = g_string_new (NULL);
7113         gchar *tmp;
7114         for (i = 0; i < n; i++) {
7115             g_string_append_unichar (str, items[i].c);
7116         }
7117         tmp = g_string_free (str, FALSE);
7118                 g_printerr ("draw_cells('%s', fore=%d, back=%d, deco=%d, bold=%d,"
7119                                 " ul=%d, strike=%d, ol=%d, blink=%d,"
7120                                 " hyperlink=%d, hilite=%d, boxed=%d)\n",
7121                                 tmp, fore, back, deco, bold,
7122                                 underline, strikethrough, overline, blink,
7123                                 hyperlink, hilite, boxed);
7124         g_free (tmp);
7125     }
7126 #endif
7127 
7128     auto bold = (attr & VTE_ATTR_BOLD) != 0;
7129     rgb_from_index<8, 8, 8>(fore, fg);
7130     rgb_from_index<8, 8, 8>(back, bg);
7131     /* FIXMEchpe defer resolving deco color until we actually need to draw an underline? */
7132     if (deco == VTE_DEFAULT_FG) {
7133         dc = fg;
7134     } else {
7135         rgb_from_index<4, 5, 4>(deco, dc);
7136     }
7137 
7138     i = 0;
7139     do {
7140         columns = 0;
7141         x = items[i].x;
7142         y = items[i].y;
7143         for (; i < n && items[i].y == y; i++) {
7144             columns += items[i].columns;
7145         }
7146         if (clear && (draw_default_bg || back != VTE_DEFAULT_BG)) {
7147             gint bold_offset = (bold && !_vte_draw_has_bold(m_draw, VTE_DRAW_BOLD)) ? 1 : 0;
7148             _vte_draw_fill_rectangle(m_draw, x, y,
7149                     columns * column_width + bold_offset, row_height,
7150                     &bg, VTE_DRAW_OPAQUE);
7151         }
7152     } while (i < n);
7153 
7154     if (attr & VTE_ATTR_BLINK) {
7155        /* Notify the caller that cells with the "blink" attribute were encountered (regardless of
7156         * whether they're actually painted or skipped now), so that the caller can set up a timer
7157         * to make them blink if it wishes to. */
7158         m_text_to_blink = true;
7159 
7160        /* This is for the "off" state of blinking text. Invisible text could also be handled here,
7161         * but it's not, it's handled outside by not even calling this method.
7162         * Setting fg = bg and painting the text would not work for two reasons: it'd be opaque
7163         * even if the background is translucent, and this method can be called with a continuous
7164         * run of identical fg, yet different bg colored cells. So we simply bail out. */
7165         if (!m_text_blink_state) {
7166             return;
7167         }
7168     }
7169 
7170    /* Draw whatever SFX are required. Do this before drawing the letters,
7171     * so that if the descent of a letter crosses an underline of a different color,
7172     * it's the letter's color that wins. Other kinds of decorations always have the
7173     * same color as the text, so the order is irrelevant there. */
7174     if ((attr & (VTE_ATTR_UNDERLINE_MASK |
7175                     VTE_ATTR_STRIKETHROUGH_MASK |
7176                     VTE_ATTR_OVERLINE_MASK |
7177                     VTE_ATTR_BOXED_MASK)) |
7178          hyperlink | hilite) {
7179         i = 0;
7180         do {
7181             x = items[i].x;
7182             y = items[i].y;
7183             for (columns = 0; i < n && items[i].y == y; i++) {
7184                 columns += items[i].columns;
7185             }
7186             switch (vte_attr_get_value(attr, VTE_ATTR_UNDERLINE_VALUE_MASK, VTE_ATTR_UNDERLINE_SHIFT)) {
7187                 case 1:
7188                     _vte_draw_draw_line(m_draw,
7189                                         x,
7190                                         y + m_underline_position,
7191                                         x + (columns * column_width) - 1,
7192                                         y + m_underline_position + m_underline_thickness - 1,
7193                                         VTE_LINE_WIDTH,
7194                                         &dc, VTE_DRAW_OPAQUE);
7195                     break;
7196                 case 2:
7197                     _vte_draw_draw_line(m_draw,
7198                                         x,
7199                                         y + m_double_underline_position,
7200                                         x + (columns * column_width) - 1,
7201                                         y + m_double_underline_position + m_double_underline_thickness - 1,
7202                                         VTE_LINE_WIDTH,
7203                                         &dc, VTE_DRAW_OPAQUE);
7204                     _vte_draw_draw_line(m_draw,
7205                                         x,
7206                                         y + m_double_underline_position + 2 * m_double_underline_thickness,
7207                                         x + (columns * column_width) - 1,
7208                                         y + m_double_underline_position + 3 * m_double_underline_thickness - 1,
7209                                         VTE_LINE_WIDTH,
7210                                         &dc, VTE_DRAW_OPAQUE);
7211                     break;
7212                 case 3:
7213                     _vte_draw_draw_undercurl(m_draw,
7214                                                 x,
7215                                                 y + m_undercurl_position,
7216                                                 m_undercurl_thickness,
7217                                                 columns,
7218                                                 &dc, VTE_DRAW_OPAQUE);
7219                     break;
7220             }
7221             if (attr & VTE_ATTR_STRIKETHROUGH) {
7222                 _vte_draw_draw_line(m_draw,
7223                                     x,
7224                                     y + m_strikethrough_position,
7225                                     x + (columns * column_width) - 1,
7226                                     y + m_strikethrough_position + m_strikethrough_thickness - 1,
7227                                     VTE_LINE_WIDTH,
7228                                     &fg, VTE_DRAW_OPAQUE);
7229             }
7230             if (attr & VTE_ATTR_OVERLINE) {
7231                 _vte_draw_draw_line(m_draw,
7232                                     x,
7233                                     y + m_overline_position,
7234                                     x + (columns * column_width) - 1,
7235                                     y + m_overline_position + m_overline_thickness - 1,
7236                                     VTE_LINE_WIDTH,
7237                                     &fg, VTE_DRAW_OPAQUE);
7238             }
7239             if (hilite) {
7240                 _vte_draw_draw_line(m_draw,
7241                                     x,
7242                                     y + m_regex_underline_position,
7243                                     x + (columns * column_width) - 1,
7244                                     y + m_regex_underline_position + m_regex_underline_thickness - 1,
7245                                     VTE_LINE_WIDTH,
7246                                     &fg, VTE_DRAW_OPAQUE);
7247             } else if (hyperlink) {
7248                 for (double j = 1.0 / 6.0; j < columns; j += 0.5) {
7249                     _vte_draw_fill_rectangle(m_draw,
7250                                                 x + j * column_width,
7251                                                 y + m_regex_underline_position,
7252                                                 MAX(column_width / 6.0, 1.0),
7253                                                 m_regex_underline_thickness,
7254                                                 &fg, VTE_DRAW_OPAQUE);
7255                 }
7256             }
7257             if (attr & VTE_ATTR_BOXED) {
7258                 _vte_draw_draw_rectangle(m_draw, x, y,
7259                                             MAX(0, (columns * column_width)),
7260                                             MAX(0, row_height),
7261                                             &fg, VTE_DRAW_OPAQUE);
7262             }
7263         } while (i < n);
7264     }
7265 
7266     _vte_draw_text(m_draw, items, n, &fg, VTE_DRAW_OPAQUE,
7267                     _vte_draw_get_style(attr & VTE_ATTR_BOLD,
7268                                         attr & VTE_ATTR_ITALIC));
7269 }
7270 
7271 /* FIXME: we don't have a way to tell GTK+ what the default text attributes
7272  * should be, so for now at least it's assuming white-on-black is the norm and
7273  * is using "black-on-white" to signify "inverse".  Pick up on that state and
7274  * fix things.  Do this here, so that if we suddenly get red-on-black, we'll do
7275  * the right thing. */
fudge_pango_colors(GSList * attributes,VteCell * cells,gsize n)7276 void VteTerminalPrivate::fudge_pango_colors(GSList *attributes,
7277                                             VteCell *cells,
7278                                             gsize n)
7279 {
7280     gsize i, sumlen = 0;
7281     struct _fudge_cell_props{
7282         gboolean saw_fg, saw_bg;
7283         vte::color::rgb fg, bg;
7284         guint index;
7285     }*props = g_newa (struct _fudge_cell_props, n);
7286 
7287     for (i = 0; i < n; i++) {
7288         gchar ubuf[7];
7289         gint len = g_unichar_to_utf8 (cells[i].c, ubuf);
7290         props[i].index = sumlen;
7291         props[i].saw_fg = props[i].saw_bg = FALSE;
7292         sumlen += len;
7293     }
7294 
7295     while (attributes != NULL) {
7296         PangoAttribute *attr = (PangoAttribute *)attributes->data;
7297         PangoAttrColor *color;
7298         switch (attr->klass->type) {
7299             case PANGO_ATTR_FOREGROUND:
7300                 for (i = 0; i < n; i++) {
7301                     if (props[i].index < attr->start_index) {
7302                         continue;
7303                     }
7304                     if (props[i].index >= attr->end_index) {
7305                         break;
7306                     }
7307                     props[i].saw_fg = TRUE;
7308                     color = (PangoAttrColor*) attr;
7309                     props[i].fg = color->color;
7310                 }
7311                 break;
7312             case PANGO_ATTR_BACKGROUND:
7313                 for (i = 0; i < n; i++) {
7314                     if (props[i].index < attr->start_index) {
7315                         continue;
7316                     }
7317                     if (props[i].index >= attr->end_index) {
7318                         break;
7319                     }
7320                     props[i].saw_bg = TRUE;
7321                     color = (PangoAttrColor*) attr;
7322                     props[i].bg = color->color;
7323                 }
7324                 break;
7325             default:
7326                 break;
7327         }
7328         attributes = g_slist_next(attributes);
7329     }
7330 
7331     for (i = 0; i < n; i++) {
7332         if (props[i].saw_fg && props[i].saw_bg &&
7333             (props[i].fg.red == 0xffff) &&
7334             (props[i].fg.green == 0xffff) &&
7335             (props[i].fg.blue == 0xffff) &&
7336             (props[i].bg.red == 0) &&
7337             (props[i].bg.green == 0) &&
7338             (props[i].bg.blue == 0)) {
7339             cells[i].attr.copy_colors(m_color_defaults.attr);
7340             cells[i].attr.set_reverse(true);
7341         }
7342     }
7343 }
7344 
7345 /* Apply the attribute given in the PangoAttribute to the list of cells. */
apply_pango_attr(PangoAttribute * attr,VteCell * cells,gsize n_cells)7346 void VteTerminalPrivate::apply_pango_attr(PangoAttribute *attr,
7347                                             VteCell *cells,
7348                                             gsize n_cells)
7349 {
7350     guint i, ival;
7351     PangoAttrInt *attrint;
7352     PangoAttrColor *attrcolor;
7353 
7354     switch (attr->klass->type) {
7355         case PANGO_ATTR_FOREGROUND:
7356         case PANGO_ATTR_BACKGROUND:
7357             attrcolor = (PangoAttrColor*) attr;
7358             ival = VTE_RGB_COLOR(8, 8, 8,
7359                                 ((attrcolor->color.red & 0xFF00) >> 8),
7360                                 ((attrcolor->color.green & 0xFF00) >> 8),
7361                                 ((attrcolor->color.blue & 0xFF00) >> 8));
7362             for (i = attr->start_index;
7363                 i < attr->end_index && i < n_cells;
7364                 i++) {
7365                 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
7366                     cells[i].attr.set_fore(ival);
7367                 }
7368                 if (attr->klass->type == PANGO_ATTR_BACKGROUND) {
7369                     cells[i].attr.set_back(ival);
7370                 }
7371             }
7372             break;
7373         case PANGO_ATTR_UNDERLINE_COLOR:
7374             attrcolor = (PangoAttrColor*) attr;
7375             ival = VTE_RGB_COLOR(4, 5, 4,
7376                                 ((attrcolor->color.red & 0xFF00) >> 8),
7377                                 ((attrcolor->color.green & 0xFF00) >> 8),
7378                                 ((attrcolor->color.blue & 0xFF00) >> 8));
7379             for (i = attr->start_index;
7380                 i < attr->end_index && i < n_cells;
7381                 i++) {
7382                 if (attr->klass->type == PANGO_ATTR_UNDERLINE) {
7383                     cells[i].attr.set_deco(ival);
7384                 }
7385             }
7386             break;
7387         case PANGO_ATTR_STRIKETHROUGH:
7388             attrint = (PangoAttrInt*) attr;
7389             ival = attrint->value;
7390             for (i = attr->start_index;
7391                 (i < attr->end_index) && (i < n_cells);
7392                 i++) {
7393                 cells[i].attr.set_strikethrough(ival != FALSE);
7394             }
7395             break;
7396         case PANGO_ATTR_UNDERLINE:
7397             attrint = (PangoAttrInt*) attr;
7398             ival = attrint->value;
7399             for (i = attr->start_index;
7400                 (i < attr->end_index) && (i < n_cells);
7401                 i++) {
7402                 unsigned int underline = 0;
7403                 switch (ival) {
7404                     case PANGO_UNDERLINE_SINGLE:
7405                         underline = 1;
7406                         break;
7407                     case PANGO_UNDERLINE_DOUBLE:
7408                         underline = 2;
7409                         break;
7410                     case PANGO_UNDERLINE_ERROR:
7411                         underline = 3; /* wavy */
7412                         break;
7413                     case PANGO_UNDERLINE_NONE:
7414                     case PANGO_UNDERLINE_LOW: /* FIXME */
7415                         underline = 0;
7416                         break;
7417                 }
7418                 cells[i].attr.set_underline(underline);
7419             }
7420             break;
7421         case PANGO_ATTR_WEIGHT:
7422             attrint = (PangoAttrInt*) attr;
7423             ival = attrint->value;
7424             for (i = attr->start_index;
7425                 (i < attr->end_index) && (i < n_cells);
7426                 i++) {
7427                 cells[i].attr.set_bold(ival >= PANGO_WEIGHT_BOLD);
7428             }
7429             break;
7430         case PANGO_ATTR_STYLE:
7431             attrint = (PangoAttrInt*) attr;
7432             ival = attrint->value;
7433             for (i = attr->start_index;
7434                 (i < attr->end_index) && (i < n_cells);
7435                 i++) {
7436                 cells[i].attr.set_italic(ival != PANGO_STYLE_NORMAL);
7437             }
7438             break;
7439         default:
7440             break;
7441     }
7442 }
7443 
7444 /** \brief  Function to avoid function pointer madness
7445  *
7446  * \param[in]   attr    pango atrributes
7447  * \param[in]   unused  unused param, used to provide a GFunc() prototype
7448  */
pango_attr_destroy_glue(PangoAttribute * attr,void * unused)7449 static void pango_attr_destroy_glue(PangoAttribute *attr, void *unused)
7450 {
7451     pango_attribute_destroy(attr);
7452 }
7453 
7454 
7455 /* Convert a PangoAttrList and a location in that list to settings in a
7456  * charcell structure.  The cells array is assumed to contain enough items
7457  * so that all ranges in the attribute list can be mapped into the array, which
7458  * typically means that the cell array should have the same length as the
7459  * string (byte-wise) which the attributes describe. */
translate_pango_cells(PangoAttrList * attrs,VteCell * cells,gsize n_cells)7460 void VteTerminalPrivate::translate_pango_cells(PangoAttrList *attrs,
7461                                                 VteCell *cells,
7462                                                 gsize n_cells)
7463 {
7464     PangoAttribute *attr;
7465     PangoAttrIterator *attriter;
7466     GSList *list, *listiter;
7467     guint i;
7468 
7469     for (i = 0; i < n_cells; i++) {
7470         cells[i] = m_fill_defaults;
7471     }
7472 
7473     attriter = pango_attr_list_get_iterator(attrs);
7474     if (attriter != NULL) {
7475         do {
7476             list = pango_attr_iterator_get_attrs(attriter);
7477             if (list != NULL) {
7478                 for (listiter = list;
7479                      listiter != NULL;
7480                      listiter = g_slist_next(listiter)) {
7481                     attr = (PangoAttribute *)listiter->data;
7482                     apply_pango_attr(attr, cells, n_cells);
7483                 }
7484                 attr = (PangoAttribute *)list->data;
7485                 fudge_pango_colors(
7486                                  list,
7487                                  cells +
7488                                  attr->start_index,
7489                                  MIN(n_cells, attr->end_index) -
7490                                  attr->start_index);
7491                 g_slist_foreach(list, (GFunc)pango_attr_destroy_glue, nullptr);
7492                 g_slist_free(list);
7493             }
7494         } while (pango_attr_iterator_next(attriter) == TRUE);
7495         pango_attr_iterator_destroy(attriter);
7496     }
7497 }
7498 
7499 /* Draw the listed items using the given attributes.  Tricky because the
7500  * attribute string is indexed by byte in the UTF-8 representation of the string
7501  * of characters.  Because we draw a character at a time, this is slower. */
draw_cells_with_attributes(struct _vte_draw_text_request * items,gssize n,PangoAttrList * attrs,bool draw_default_bg,gint column_width,gint height)7502 void VteTerminalPrivate::draw_cells_with_attributes(struct _vte_draw_text_request *items,
7503                                                     gssize n,
7504                                                     PangoAttrList *attrs,
7505                                                     bool draw_default_bg,
7506                                                     gint column_width,
7507                                                     gint height)
7508 {
7509     int i, j, cell_count;
7510     VteCell *cells;
7511     char scratch_buf[VTE_UTF8_BPC];
7512     guint fore, back, deco;
7513 
7514     /* Note: since this function is only called with the pre-edit text,
7515      * all the items contain gunichar only, not vteunistr. */
7516     /* FIXMEchpe is that really true for all input methods? */
7517 
7518     uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
7519 
7520     for (i = 0, cell_count = 0; i < n; i++) {
7521         cell_count += g_unichar_to_utf8(items[i].c, scratch_buf);
7522     }
7523     cells = g_new(VteCell, cell_count);
7524     translate_pango_cells(attrs, cells, cell_count);
7525     for (i = 0, j = 0; i < n; i++) {
7526         determine_colors(&cells[j], false, &fore, &back, &deco);
7527         draw_cells(items + i, 1,
7528                     fore,
7529                     back,
7530                     deco,
7531                     TRUE, draw_default_bg,
7532                     cells[j].attr.attr & attr_mask,
7533                     m_allow_hyperlink && cells[j].attr.hyperlink_idx != 0,
7534                     FALSE, column_width, height);
7535         j += g_unichar_to_utf8(items[i].c, scratch_buf);
7536     }
7537     g_free(cells);
7538 }
7539 
7540 
7541 /* Paint the contents of a given row at the given location.  Take advantage
7542  * of multiple-draw APIs by finding runs of characters with identical
7543  * attributes and bundling them together. */
draw_rows(VteScreen * screen_,vte::grid::row_t start_row,vte::grid::row_t end_row,vte::grid::column_t start_column,vte::grid::column_t end_column,gint start_x,gint start_y,gint column_width,gint row_height)7544 void VteTerminalPrivate::draw_rows(VteScreen *screen_,
7545                                     vte::grid::row_t start_row,
7546                                     vte::grid::row_t end_row,
7547                                     vte::grid::column_t start_column,
7548                                     vte::grid::column_t end_column,
7549                                     gint start_x,
7550                                     gint start_y,
7551                                     gint column_width,
7552                                     gint row_height)
7553 {
7554     struct _vte_draw_text_request items[4*VTE_DRAW_MAX_LENGTH];
7555     vte::grid::row_t row, rows;
7556     vte::grid::column_t i, j;
7557     long x, y;
7558     guint fore, nfore, back, nback, deco, ndeco;
7559     gboolean hyperlink, nhyperlink, hilite, nhilite, selected, nselected;
7560     guint item_count;
7561     const VteCell *cell;
7562     VteRowData const* row_data;
7563 
7564     uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
7565 
7566     /* adjust for the absolute start of row */
7567     start_x -= start_column * column_width;
7568 
7569     /* clear the background */
7570     x = start_x;
7571     y = start_y;
7572     row = start_row;
7573     rows = end_row - start_row;
7574     do {
7575         row_data = find_row_data(row);
7576         /* Back up in case this is a multicolumn character,
7577          * making the drawing area a little wider. */
7578         i = start_column;
7579         if (row_data != NULL) {
7580             cell = _vte_row_data_get (row_data, i);
7581             if (cell != NULL) {
7582                 while (cell->attr.fragment() && i > 0) {
7583                     cell = _vte_row_data_get (row_data, --i);
7584                 }
7585             }
7586             /* Walk the line. */
7587             do {
7588                 /* Get the character cell's contents. */
7589                 cell = _vte_row_data_get (row_data, i);
7590                 /* Find the colors for this cell. */
7591                 selected = cell_is_selected(i, row);
7592                 determine_colors(cell, selected, &fore, &back, &deco);
7593 
7594                 bool bold = cell && cell->attr.bold();
7595                 j = i + (cell ? cell->attr.columns() : 1);
7596 
7597                 while (j < end_column){
7598                     /* Retrieve the cell. */
7599                     cell = _vte_row_data_get (row_data, j);
7600                     /* Don't render fragments of multicolumn characters
7601                      * which have the same attributes as the initial
7602                      * portions. */
7603                     if (cell != NULL && cell->attr.fragment()) {
7604                         j++;
7605                         continue;
7606                     }
7607                     /* Resolve attributes to colors where possible and
7608                      * compare visual attributes to the first character
7609                      * in this chunk. */
7610                     selected = cell_is_selected(j, row);
7611                     determine_colors(cell, selected, &nfore, &nback, &ndeco);
7612                     if (nback != back) {
7613                         break;
7614                     }
7615                     bold = cell && cell->attr.bold();
7616                     j += cell ? cell->attr.columns() : 1;
7617                 }
7618                 if (back != VTE_DEFAULT_BG) {
7619                     vte::color::rgb bg;
7620                     gint bold_offset = (bold && !_vte_draw_has_bold(m_draw, VTE_DRAW_BOLD)) ? 1 : 0;
7621                     rgb_from_index<8, 8, 8>(back, bg);
7622                     _vte_draw_fill_rectangle (
7623                             m_draw,
7624                             x + i * column_width,
7625                             y,
7626                             (j - i) * column_width + bold_offset,
7627                             row_height,
7628                             &bg, VTE_DRAW_OPAQUE);
7629                 }
7630                 /* We'll need to continue at the first cell which didn't
7631                  * match the first one in this set. */
7632                 i = j;
7633             } while (i < end_column);
7634         } else {
7635             do {
7636                 selected = cell_is_selected(i, row);
7637                 j = i + 1;
7638                 while (j < end_column){
7639                     nselected = cell_is_selected(j, row);
7640                     if (nselected != selected) {
7641                         break;
7642                     }
7643                     j++;
7644                 }
7645                 determine_colors(nullptr, selected, &fore, &back, &deco);
7646                 if (back != VTE_DEFAULT_BG) {
7647                     vte::color::rgb bg;
7648                     rgb_from_index<8, 8, 8>(back, bg);
7649                     _vte_draw_fill_rectangle (m_draw,
7650                                                 x + i *column_width,
7651                                                 y,
7652                                                 (j - i)  * column_width,
7653                                                 row_height,
7654                                                 &bg, VTE_DRAW_OPAQUE);
7655                 }
7656                 i = j;
7657             } while (i < end_column);
7658         }
7659         row++;
7660         y += row_height;
7661     } while (--rows);
7662 
7663 
7664     /* render the text */
7665     y = start_y;
7666     row = start_row;
7667     rows = end_row - start_row;
7668     item_count = 1;
7669     do {
7670         row_data = find_row_data(row);
7671         if (row_data == NULL) {
7672             goto fg_skip_row;
7673         }
7674         /* Back up in case this is a multicolumn character,
7675          * making the drawing area a little wider. */
7676         i = start_column;
7677         cell = _vte_row_data_get (row_data, i);
7678         if (cell == NULL) {
7679             goto fg_skip_row;
7680         }
7681         while (cell->attr.fragment() && i > 0) {
7682             cell = _vte_row_data_get (row_data, --i);
7683         }
7684 
7685         /* Walk the line. */
7686         do {
7687             /* Get the character cell's contents. */
7688             cell = _vte_row_data_get (row_data, i);
7689             if (cell == NULL) {
7690                 goto fg_skip_row;
7691             }
7692             while (cell->c == 0 || cell->attr.invisible() ||
7693                     (cell->c == ' ' &&
7694                                          cell->attr.has_none(VTE_ATTR_UNDERLINE_MASK |
7695                                                              VTE_ATTR_STRIKETHROUGH_MASK |
7696                                                              VTE_ATTR_OVERLINE_MASK) &&
7697                                          (!m_allow_hyperlink || cell->attr.hyperlink_idx == 0)) ||
7698                                cell->attr.fragment()) {
7699                 if (++i >= end_column) {
7700                     goto fg_skip_row;
7701                 }
7702                 cell = _vte_row_data_get (row_data, i);
7703                 if (cell == NULL) {
7704                     goto fg_skip_row;
7705                 }
7706             }
7707             /* Find the colors for this cell. */
7708             selected = cell_is_selected(i, row);
7709             determine_colors(cell, selected, &fore, &back, &deco);
7710 
7711             uint32_t const attr = cell->attr.attr;
7712 
7713             hyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0);
7714             hilite = (cell->attr.hyperlink_idx != 0 && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) ||
7715                         (m_hyperlink_hover_idx == 0 && m_match != nullptr && m_match_span.contains(row, i));
7716 
7717             items[0].c = cell->c;
7718             items[0].columns = cell->attr.columns();
7719             items[0].x = start_x + i * column_width;
7720             items[0].y = y;
7721             j = i + items[0].columns;
7722 
7723             /* Now find out how many cells have the same attributes. */
7724             do {
7725                 while (j < end_column && item_count < G_N_ELEMENTS(items)) {
7726                     /* Retrieve the cell. */
7727                     cell = _vte_row_data_get (row_data, j);
7728                     if (cell == NULL) {
7729                         goto fg_next_row;
7730                     }
7731                     /* Ignore the attributes on a fragment, the attributes
7732                         * of the preceding character cell should apply. */
7733                     if (cell->attr.fragment()) {
7734                         j++;
7735                         continue;
7736                     }
7737                     if (cell->c == 0){
7738                         /* only break the run if we
7739                          * are drawing attributes
7740                          */
7741                         if ((attr & (VTE_ATTR_UNDERLINE_MASK |
7742                                         VTE_ATTR_STRIKETHROUGH_MASK |
7743                                         VTE_ATTR_OVERLINE_MASK)) |
7744                             hyperlink | hilite) {
7745                             break;
7746                         } else {
7747                             j++;
7748                             continue;
7749                         }
7750                     }
7751                     /* Resolve attributes to colors where possible and
7752                      * compare visual attributes to the first character
7753                      * in this chunk. */
7754                     selected = cell_is_selected(j, row);
7755                     determine_colors(cell, selected, &nfore, &nback, &ndeco);
7756                     if (nfore != fore) {
7757                         break;
7758                     }
7759                     if (ndeco != deco) {
7760                         break;
7761                     }
7762 
7763                     /* Bold, italic, underline, strikethrough,
7764                      * overline, blink, or invisible differ;
7765                      * break the run.
7766                      */
7767                     if ((cell->attr.attr ^ attr) & (VTE_ATTR_BOLD_MASK |
7768                                                     VTE_ATTR_ITALIC_MASK |
7769                                                     VTE_ATTR_UNDERLINE_MASK |
7770                                                     VTE_ATTR_STRIKETHROUGH_MASK |
7771                                                     VTE_ATTR_OVERLINE_MASK |
7772                                                     VTE_ATTR_BLINK_MASK |
7773                                                     VTE_ATTR_INVISIBLE_MASK)) {
7774                         break;
7775                     }
7776 
7777                     nhyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0);
7778                     if (nhyperlink != hyperlink) {
7779                         break;
7780                     }
7781                     /* Break up matched/not-matched text. */
7782                     nhilite = (cell->attr.hyperlink_idx != 0 && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) ||
7783                                 (m_hyperlink_hover_idx == 0 && m_match != nullptr && m_match_span.contains(row, j));
7784                     if (nhilite != hilite) {
7785                         break;
7786                     }
7787                     /* Add this cell to the draw list. */
7788                     items[item_count].c = cell->c;
7789                     items[item_count].columns = cell->attr.columns();
7790                     items[item_count].x = start_x + j * column_width;
7791                     items[item_count].y = y;
7792                     j +=  items[item_count].columns;
7793                     item_count++;
7794                 }
7795                 /* have we encountered a state change? */
7796                 if (j < end_column) {
7797                     break;
7798                 }
7799 fg_next_row:
7800                 /* is this the last column, on the last row? */
7801                 do {
7802                     do {
7803                         if (!--rows) {
7804                             goto fg_draw;
7805                         }
7806 
7807                         /* restart on the next row */
7808                         row++;
7809                         y += row_height;
7810                         row_data = find_row_data(row);
7811                     } while (row_data == NULL);
7812 
7813                     /* Back up in case this is a
7814                      * multicolumn character, making the drawing
7815                      * area a little wider. */
7816                     j = start_column;
7817                     cell = _vte_row_data_get (row_data, j);
7818                 } while (cell == NULL);
7819                 while (cell->attr.fragment() && j > 0) {
7820                     cell = _vte_row_data_get (row_data, --j);
7821                 }
7822             } while (TRUE);
7823 fg_draw:
7824             /* Draw the cells. */
7825             draw_cells(items, item_count, fore, back, deco, FALSE, FALSE,
7826                         attr & attr_mask, hyperlink, hilite, column_width, row_height);
7827             item_count = 1;
7828             /* We'll need to continue at the first cell which didn't
7829              * match the first one in this set. */
7830             i = j;
7831             if (!rows) {
7832                 goto fg_out;
7833             }
7834         } while (i < end_column);
7835 fg_skip_row:
7836         row++;
7837         y += row_height;
7838     } while (--rows);
7839 fg_out:
7840     return;
7841 }
7842 
expand_rectangle(cairo_rectangle_int_t & rect) const7843 void VteTerminalPrivate::expand_rectangle(cairo_rectangle_int_t& rect) const
7844 {
7845     /* increase the paint by one pixel on all sides to force the
7846      * inclusion of neighbouring cells */
7847     vte::grid::row_t row = pixel_to_row(MAX(0, rect.y - 1));
7848     /* Both the value given by MIN() and row_stop are exclusive.
7849         * _vte_terminal_pixel_to_row expects an actual value corresponding
7850         * to the bottom visible pixel, hence the - 1 + 1 magic. */
7851     vte::grid::row_t row_stop = pixel_to_row(MIN(rect.height + rect.y + 1, m_view_usable_extents.height()) - 1) + 1;
7852     if (row_stop <= row) {
7853             return;
7854     }
7855 
7856     vte::grid::column_t col = MAX(0, (rect.x - 1) / m_cell_width);
7857     vte::grid::column_t col_stop = MIN(howmany(rect.width + rect.x + 1, m_cell_width), m_column_count);
7858     if (col_stop <= col) {
7859             return;
7860     }
7861 #if VTE_DEBUG
7862     cairo_rectangle_int_t old_rect = rect;
7863 #endif
7864     rect.x = col * m_cell_width;
7865     rect.width = (col_stop - col) * m_cell_width;
7866     rect.y = row_to_pixel(row);
7867     rect.height = (row_stop - row) * m_cell_height;
7868 
7869     _vte_debug_print (VTE_DEBUG_UPDATES,
7870                         "expand_rectangle"
7871                         " (%d,%d)x(%d,%d) pixels,"
7872                         " (%ld,%ld)x(%ld,%ld) cells"
7873                         " [(%d,%d)x(%d,%d) pixels]\n",
7874                         old_rect.x, old_rect.y, old_rect.width, old_rect.height,
7875                         col, row, col_stop - col, row_stop - row,
7876                         rect.x, rect.y, rect.width, rect.height);
7877 }
7878 
paint_area(GdkRectangle const * area)7879 void VteTerminalPrivate::paint_area(GdkRectangle const* area)
7880 {
7881     vte::grid::row_t row, row_stop;
7882     vte::grid::column_t col, col_stop;
7883 
7884     row = pixel_to_row(MAX(0, area->y));
7885     /* Both the value given by MIN() and row_stop are exclusive.
7886         * _vte_terminal_pixel_to_row expects an actual value corresponding
7887         * to the bottom visible pixel, hence the - 1 + 1 magic. */
7888     row_stop = pixel_to_row(MIN(area->height + area->y,
7889                                 get_allocated_height() - m_padding.top - m_padding.bottom) - 1) + 1;
7890     if (row_stop <= row) {
7891         return;
7892     }
7893     col = MAX(0, area->x / m_cell_width);
7894     col_stop = MIN((area->width + area->x) / m_cell_width,
7895                m_column_count);
7896     if (col_stop <= col) {
7897         return;
7898     }
7899     _vte_debug_print (VTE_DEBUG_UPDATES,
7900             "paint_area"
7901             "   (%d,%d)x(%d,%d) pixels,"
7902             " (%ld,%ld)x(%ld,%ld) cells"
7903             " [(%ld,%ld)x(%ld,%ld) pixels]\n",
7904             area->x, area->y, area->width, area->height,
7905             col, row, col_stop - col, row_stop - row,
7906             col * m_cell_width,
7907             row * m_cell_height,
7908             (col_stop - col) * m_cell_width,
7909             (row_stop - row) * m_cell_height);
7910 
7911     /* Now we're ready to draw the text.  Iterate over the rows we
7912      * need to draw. */
7913     draw_rows(m_screen,
7914                 row, row_stop,
7915                 col, col_stop,
7916                 col * m_cell_width,
7917                 row_to_pixel(row),
7918                 m_cell_width,
7919                 m_cell_height);
7920 }
7921 
paint_cursor()7922 void VteTerminalPrivate::paint_cursor()
7923 {
7924     struct _vte_draw_text_request item;
7925         vte::grid::row_t drow;
7926         vte::grid::column_t col;
7927         int width, height, cursor_width;
7928         guint style = 0;
7929         guint fore, back, deco;
7930     vte::color::rgb bg;
7931     int x, y;
7932     gboolean blink, selected, focus;
7933 
7934     if (!m_cursor_visible) {
7935         return;
7936     }
7937 
7938     if (m_im_preedit_active) {
7939         return;
7940     }
7941 
7942     col = m_screen->cursor.col;
7943     drow = m_screen->cursor.row;
7944     width = m_cell_width;
7945     height = m_cell_height;
7946 
7947     /* TODOegmont: clamp on rows? tricky... */
7948     if (CLAMP(col, 0, m_column_count - 1) != col) {
7949         return;
7950     }
7951 
7952     focus = m_has_focus;
7953     blink = m_cursor_blink_state;
7954 
7955     if (focus && !blink) {
7956         return;
7957     }
7958 
7959    /* Find the first cell of the character "under" the cursor.
7960     * This is for CJK.  For TAB, paint the cursor where it really is. */
7961     auto cell = find_charcell(col, drow);
7962         while (cell != NULL && cell->attr.fragment() && cell->c != '\t' && col > 0) {
7963         col--;
7964         cell = find_charcell(col, drow);
7965     }
7966 
7967     /* Draw the cursor. */
7968     item.c = (cell && cell->c) ? cell->c : ' ';
7969     item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns() : 1;
7970     item.x = col * width;
7971     item.y = row_to_pixel(drow);
7972     if (cell && cell->c != 0) {
7973         style = _vte_draw_get_style(cell->attr.bold(), cell->attr.italic());
7974     }
7975 
7976     selected = cell_is_selected(col, drow);
7977         determine_cursor_colors(cell, selected, &fore, &back, &deco);
7978         rgb_from_index<8, 8, 8>(back, bg);
7979 
7980     x = item.x;
7981     y = item.y;
7982 
7983     switch (decscusr_cursor_shape()) {
7984 
7985         case VTE_CURSOR_SHAPE_IBEAM:
7986         {
7987            /* Draw at the very left of the cell (before the spacing), even in case of CJK.
7988             * IMO (egmont) not overrunning the letter improves readability, vertical movement
7989             * looks good (no zigzag even when a somewhat wider glyph that starts filling up
7990             * the left spacing, or CJK that begins further to the right is encountered),
7991             * and also this is where it looks good if background colors change, including
7992             * Shift+arrows highlighting experience in some editors. As per the behavior of
7993             * word processors, don't increase the height by the line spacing. */
7994             int stem_width;
7995 
7996             stem_width = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
7997             stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, m_cell_width);
7998 
7999             _vte_draw_fill_rectangle(m_draw,
8000                                         x, y + m_char_padding.top, stem_width, m_char_ascent + m_char_descent,
8001                                         &bg, VTE_DRAW_OPAQUE);
8002             break;
8003         }
8004 
8005         case VTE_CURSOR_SHAPE_UNDERLINE:
8006         {
8007            /* The width is at least the overall width of the cell (or two cells) minus the two
8008             * half spacings on the two edges. That is, underlines under a CJK are more than twice
8009             * as wide as narrow characters in case of letter spacing. Plus, if necessary, the width
8010             * is increased to span under the entire glyph. Vertical position is not affected by
8011             * line spacing. */
8012 
8013             int line_height, left, right;
8014 
8015             /* use height (not width) so underline and ibeam will
8016              * be equally visible */
8017             line_height = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
8018             line_height = CLAMP (line_height, VTE_LINE_WIDTH, m_char_ascent + m_char_descent);
8019 
8020             left = m_char_padding.left;
8021             right = item.columns * m_cell_width - m_char_padding.right;
8022 
8023             if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
8024                 int l, r;
8025                 _vte_draw_get_char_edges (m_draw, cell->c, cell->attr.columns(), style, &l, &r);
8026                 left = MIN(left, l);
8027                 right = MAX(right, r);
8028             }
8029 
8030             _vte_draw_fill_rectangle(m_draw,
8031                                         x + left, y + m_cell_height - m_char_padding.bottom - line_height,
8032                                         right - left, line_height,
8033                                         &bg, VTE_DRAW_OPAQUE);
8034             break;
8035         }
8036 
8037         case VTE_CURSOR_SHAPE_BLOCK:
8038            /* Include the spacings in the cursor, see bug 781479 comments 39-44.
8039             * Make the cursor even wider if the glyph is wider. */
8040 
8041             cursor_width = item.columns * width;
8042             if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
8043                 int r;
8044                 _vte_draw_get_char_edges (m_draw, cell->c, cell->attr.columns(), style, NULL, &r);
8045                 cursor_width = MAX(cursor_width, r);
8046             }
8047 
8048             uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
8049 
8050             if (focus) {
8051                 /* just reverse the character under the cursor */
8052                 _vte_draw_fill_rectangle(m_draw, x, y, cursor_width, height,
8053                                             &bg, VTE_DRAW_OPAQUE);
8054 
8055                 if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
8056                     draw_cells(&item, 1,
8057                                 fore, back, deco, TRUE, FALSE,
8058                                 cell->attr.attr & attr_mask,
8059                                 m_allow_hyperlink && cell->attr.hyperlink_idx != 0,
8060                                 FALSE,
8061                                 width,
8062                                 height);
8063                 }
8064 
8065             } else {
8066                 /* draw a box around the character */
8067                 _vte_draw_draw_rectangle(m_draw,
8068                                             x - VTE_LINE_WIDTH,
8069                                             y - VTE_LINE_WIDTH,
8070                                             cursor_width + 2*VTE_LINE_WIDTH,
8071                                             height + 2*VTE_LINE_WIDTH,
8072                                             &bg, VTE_DRAW_OPAQUE);
8073             }
8074 
8075             break;
8076     }
8077 }
8078 
paint_im_preedit_string()8079 void VteTerminalPrivate::paint_im_preedit_string()
8080 {
8081     int col, columns;
8082     long width, height;
8083     int i, len;
8084 
8085     if (!m_im_preedit) {
8086         return;
8087     }
8088 
8089     /* Keep local copies of rendering information. */
8090     width = m_cell_width;
8091     height = m_cell_height;
8092 
8093     /* Find out how many columns the pre-edit string takes up. */
8094     columns = get_preedit_width(false);
8095     len = get_preedit_length(false);
8096 
8097     /* If the pre-edit string won't fit on the screen if we start
8098      * drawing it at the cursor's position, move it left. */
8099     col = m_screen->cursor.col;
8100     if (col + columns > m_column_count) {
8101         col = MAX(0, m_column_count - columns);
8102     }
8103 
8104     /* Draw the preedit string, boxed. */
8105     if (len > 0) {
8106         struct _vte_draw_text_request *items;
8107         const char *preedit = m_im_preedit;
8108         int preedit_cursor;
8109 
8110         items = g_new(struct _vte_draw_text_request, len);
8111         for (i = columns = 0; i < len; i++) {
8112             items[i].c = g_utf8_get_char(preedit);
8113             items[i].columns = _vte_unichar_width(items[i].c, m_utf8_ambiguous_width);
8114             items[i].x = (col + columns) * width;
8115             items[i].y = row_to_pixel(m_screen->cursor.row);
8116             columns += items[i].columns;
8117             preedit = g_utf8_next_char(preedit);
8118         }
8119         if (G_LIKELY(m_clear_background)) {
8120             _vte_draw_clear(m_draw,
8121                             col * width,
8122                             row_to_pixel(m_screen->cursor.row),
8123                             width * columns,
8124                             height,
8125                             get_color(VTE_DEFAULT_BG), m_background_alpha);
8126         }
8127         draw_cells_with_attributes(
8128                                     items, len,
8129                                     m_im_preedit_attrs,
8130                                     TRUE,
8131                                     width, height);
8132         preedit_cursor = m_im_preedit_cursor;
8133 
8134         if (preedit_cursor >= 0 && preedit_cursor < len) {
8135             uint32_t fore, back, deco;
8136             vte_color_triple_get(m_color_defaults.attr.colors(), &fore, &back, &deco);
8137 
8138             /* Cursored letter in reverse. */
8139             draw_cells(
8140                         &items[preedit_cursor], 1,
8141                                                 fore, back, deco,
8142                                                 TRUE,  /* clear */
8143                                                 TRUE,  /* draw_default_bg */
8144                                                 VTE_ATTR_NONE | VTE_ATTR_BOXED,
8145                                                 FALSE, /* hyperlink */
8146                                                 FALSE, /* hilite */
8147                         width, height);
8148         }
8149         g_free(items);
8150     }
8151 }
8152 
widget_draw(cairo_t * cr)8153 void VteTerminalPrivate::widget_draw(cairo_t *cr)
8154 {
8155     cairo_rectangle_int_t clip_rect;
8156     cairo_region_t *region;
8157     int allocated_width, allocated_height;
8158     int extra_area_for_cursor;
8159     bool text_blink_enabled_now;
8160     gint64 now = 0;
8161 
8162     if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect)) {
8163         return;
8164     }
8165 
8166     _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_draw()\n");
8167     _vte_debug_print (VTE_DEBUG_WORK, "+");
8168     _vte_debug_print (VTE_DEBUG_UPDATES, "Draw (%d,%d)x(%d,%d)\n",
8169                         clip_rect.x, clip_rect.y,
8170                         clip_rect.width, clip_rect.height);
8171 
8172     region = vte_cairo_get_clip_region (cr);
8173     if (region == NULL) {
8174         return;
8175     }
8176 
8177     allocated_width = get_allocated_width();
8178     allocated_height = get_allocated_height();
8179 
8180     /* Designate the start of the drawing operation and clear the area. */
8181     _vte_draw_set_cairo(m_draw, cr);
8182 
8183     if (G_LIKELY(m_clear_background)) {
8184         _vte_draw_clear (m_draw, 0, 0,
8185                             allocated_width, allocated_height,
8186                             get_color(VTE_DEFAULT_BG), m_background_alpha);
8187     }
8188 
8189     /* Clip vertically, for the sake of smooth scrolling. We want the top and bottom paddings to be unused.
8190      * Don't clip horizontally so that antialiasing can legally overflow to the right padding. */
8191     cairo_save(cr);
8192     cairo_rectangle(cr, 0, m_padding.top, allocated_width, allocated_height - m_padding.top - m_padding.bottom);
8193     cairo_clip(cr);
8194 
8195     cairo_translate(cr, m_padding.left, m_padding.top);
8196 
8197     /* Transform to view coordinates */
8198     cairo_region_translate(region, -m_padding.left, -m_padding.top);
8199 
8200     cairo_rectangle_int_t *rectangles;
8201     int n, n_rectangles;
8202     n_rectangles = cairo_region_num_rectangles (region);
8203     rectangles = g_new(cairo_rectangle_int_t, n_rectangles);
8204     for (n = 0; n < n_rectangles; n++) {
8205         cairo_region_get_rectangle (region, n, &rectangles[n]);
8206     }
8207 
8208     /* don't bother to enlarge an invalidate all */
8209     if (!(n_rectangles == 1
8210           && rectangles[0].width == allocated_width
8211           && rectangles[0].height == allocated_height)) {
8212         cairo_region_t *rr = cairo_region_create ();
8213        /* Expand the rectangles so that they cover whole cells,
8214         * to avoid overlapping XY bands.
8215         */
8216         for (n = 0; n < n_rectangles; n++) {
8217             expand_rectangle(rectangles[n]);
8218             cairo_region_union_rectangle(rr, &rectangles[n]);
8219         }
8220         g_free(rectangles);
8221 
8222         n_rectangles = cairo_region_num_rectangles (rr);
8223         rectangles = g_new (cairo_rectangle_int_t, n_rectangles);
8224         for (n = 0; n < n_rectangles; n++) {
8225             cairo_region_get_rectangle(rr, n, &rectangles[n]);
8226         }
8227         cairo_region_destroy(rr);
8228     }
8229 
8230     /* Whether blinking text should be visible now */
8231     m_text_blink_state = true;
8232     text_blink_enabled_now = m_text_blink_mode & (m_has_focus ? VTE_TEXT_BLINK_FOCUSED : VTE_TEXT_BLINK_UNFOCUSED);
8233     if (text_blink_enabled_now) {
8234         now = g_get_monotonic_time() / 1000;
8235         if (now % (m_text_blink_cycle * 2) >= m_text_blink_cycle) {
8236             m_text_blink_state = false;
8237         }
8238     }
8239     /* Painting will flip this if it encounters any cell with blink attribute */
8240     m_text_to_blink = false;
8241 
8242     /* and now paint them */
8243     for (n = 0; n < n_rectangles; n++) {
8244         paint_area(&rectangles[n]);
8245     }
8246     g_free (rectangles);
8247 
8248     paint_im_preedit_string();
8249 
8250     cairo_restore(cr);
8251 
8252     /* Re-clip, allowing 1 more pixel row for the outline cursor. */
8253     /* TODOegmont: It's really ugly to do it here. */
8254     cairo_save(cr);
8255     extra_area_for_cursor = (decscusr_cursor_shape() == VTE_CURSOR_SHAPE_BLOCK && !m_has_focus) ? 1 : 0;
8256     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);
8257     cairo_clip(cr);
8258 
8259     cairo_translate(cr, m_padding.left, m_padding.top);
8260 
8261     paint_cursor();
8262 
8263     cairo_restore(cr);
8264 
8265     /* Done with various structures. */
8266     _vte_draw_set_cairo(m_draw, NULL);
8267 
8268     cairo_region_destroy (region);
8269 
8270    /* If painting encountered any cell with blink attribute, we might need to set up a timer.
8271     * Blinking is implemented using a one-shot (not repeating) timer that keeps getting reinstalled
8272     * here as long as blinking cells are encountered during (re)painting. This way there's no need
8273     * for an explicit step to stop the timer when blinking cells are no longer present, this happens
8274     * implicitly by the timer not getting reinstalled anymore (often after a final unnecessary but
8275     * harmless repaint). */
8276     if (G_UNLIKELY (m_text_to_blink && text_blink_enabled_now && m_text_blink_tag == 0)) {
8277         m_text_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
8278                                                 m_text_blink_cycle - now % m_text_blink_cycle,
8279                                                 (GSourceFunc)invalidate_text_blink_cb,
8280                                                 this,
8281                                                 NULL);
8282     }
8283 
8284     m_invalidated_all = FALSE;
8285 }
8286 
8287 /* Handle an expose event by painting the exposed area. */
vte_cairo_get_clip_region(cairo_t * cr)8288 static cairo_region_t *vte_cairo_get_clip_region (cairo_t *cr)
8289 {
8290     cairo_rectangle_list_t *list;
8291     cairo_region_t *region;
8292     int i;
8293 
8294     list = cairo_copy_clip_rectangle_list (cr);
8295     if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) {
8296         cairo_rectangle_int_t clip_rect;
8297 
8298         cairo_rectangle_list_destroy (list);
8299 
8300         if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect)) {
8301             return NULL;
8302         }
8303         return cairo_region_create_rectangle (&clip_rect);
8304     }
8305 
8306 
8307     region = cairo_region_create ();
8308     for (i = list->num_rectangles - 1; i >= 0; --i) {
8309         cairo_rectangle_t *rect = &list->rectangles[i];
8310         cairo_rectangle_int_t clip_rect;
8311 
8312         clip_rect.x = floor (rect->x);
8313         clip_rect.y = floor (rect->y);
8314         clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x;
8315         clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y;
8316 
8317         if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS) {
8318             cairo_region_destroy (region);
8319             region = NULL;
8320             break;
8321         }
8322     }
8323 
8324     cairo_rectangle_list_destroy (list);
8325     return region;
8326 }
8327 
widget_scroll(GdkEventScroll * event)8328 void VteTerminalPrivate::widget_scroll(GdkEventScroll *event)
8329 {
8330     gdouble delta_x, delta_y;
8331     gdouble v;
8332     gint cnt, i;
8333     int button;
8334 
8335     GdkEvent *base_event = reinterpret_cast<GdkEvent*>(event);
8336     auto rowcol = confined_grid_coords_from_event(base_event);
8337 
8338     read_modifiers(base_event);
8339 
8340     switch (event->direction) {
8341         case GDK_SCROLL_UP:
8342             m_mouse_smooth_scroll_delta -= 1.;
8343             _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll up\n");
8344             break;
8345         case GDK_SCROLL_DOWN:
8346             m_mouse_smooth_scroll_delta += 1.;
8347             _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll down\n");
8348             break;
8349         case GDK_SCROLL_SMOOTH:
8350             gdk_event_get_scroll_deltas ((GdkEvent*) event, &delta_x, &delta_y);
8351             m_mouse_smooth_scroll_delta += delta_y;
8352             _vte_debug_print(VTE_DEBUG_EVENTS,
8353                     "Smooth scroll by %f, delta now at %f\n",
8354                     delta_y, m_mouse_smooth_scroll_delta);
8355             break;
8356         default:
8357             break;
8358     }
8359 
8360     /* If we're running a mouse-aware application, map the scroll event
8361      * to a button press on buttons four and five. */
8362     if (m_mouse_tracking_mode) {
8363         cnt = m_mouse_smooth_scroll_delta;
8364         if (cnt == 0) {
8365             return;
8366         }
8367         m_mouse_smooth_scroll_delta -= cnt;
8368         _vte_debug_print(VTE_DEBUG_EVENTS,
8369                 "Scroll application by %d lines, smooth scroll delta set back to %f\n",
8370                 cnt, m_mouse_smooth_scroll_delta);
8371 
8372         button = cnt > 0 ? 5 : 4;
8373         if (cnt < 0) {
8374             cnt = -cnt;
8375         }
8376         for (i = 0; i < cnt; i++) {
8377             /* Encode the parameters and send them to the app. */
8378             feed_mouse_event(rowcol,
8379                                 button,
8380                                 false /* not drag */,
8381                                 false /* not release */);
8382         }
8383         return;
8384     }
8385 
8386     v = MAX (1., ceil (gtk_adjustment_get_page_increment (m_vadjustment) / 10.));
8387     _vte_debug_print(VTE_DEBUG_EVENTS,
8388             "Scroll speed is %d lines per non-smooth scroll unit\n",
8389             (int) v);
8390     if (m_screen == &m_alternate_screen && m_alternate_screen_scroll) {
8391         char *normal;
8392         gssize normal_length;
8393 
8394         cnt = v * m_mouse_smooth_scroll_delta;
8395         if (cnt == 0) {
8396             return;
8397         }
8398         m_mouse_smooth_scroll_delta -= cnt / v;
8399         _vte_debug_print(VTE_DEBUG_EVENTS,
8400                 "Scroll by %d lines, smooth scroll delta set back to %f\n",
8401                 cnt, m_mouse_smooth_scroll_delta);
8402 
8403         /* In the alternate screen there is no scrolling,
8404          * so fake a few cursor keystrokes. */
8405 
8406         _vte_keymap_map (
8407                 cnt > 0 ? GDK_KEY_Down : GDK_KEY_Up,
8408                 m_modifiers,
8409                 m_cursor_mode == VTE_KEYMODE_APPLICATION,
8410                 m_keypad_mode == VTE_KEYMODE_APPLICATION,
8411                 &normal,
8412                 &normal_length);
8413         if (cnt < 0) {
8414             cnt = -cnt;
8415         }
8416 #if 0
8417         for (i = 0; i < cnt; i++) {
8418             feed_child_using_modes(normal, normal_length);
8419         }
8420 #endif
8421         g_free (normal);
8422     } else {
8423         /* Perform a history scroll. */
8424         double dcnt = m_screen->scroll_delta + v * m_mouse_smooth_scroll_delta;
8425         queue_adjustment_value_changed_clamped(dcnt);
8426         m_mouse_smooth_scroll_delta = 0;
8427     }
8428 }
8429 
set_audible_bell(bool setting)8430 bool VteTerminalPrivate::set_audible_bell(bool setting)
8431 {
8432     if (setting == m_audible_bell) {
8433         return false;
8434     }
8435 
8436     m_audible_bell = setting;
8437     return true;
8438 }
8439 
set_text_blink_mode(VteTextBlinkMode setting)8440 bool VteTerminalPrivate::set_text_blink_mode(VteTextBlinkMode setting)
8441 {
8442     if (setting == m_text_blink_mode) {
8443         return false;
8444     }
8445 
8446     m_text_blink_mode = setting;
8447     invalidate_all();
8448 
8449     return true;
8450 }
8451 
set_allow_bold(bool setting)8452 bool VteTerminalPrivate::set_allow_bold(bool setting)
8453 {
8454     if (setting == m_allow_bold) {
8455         return false;
8456     }
8457 
8458     m_allow_bold = setting;
8459     invalidate_all();
8460 
8461     return true;
8462 }
8463 
set_bold_is_bright(bool setting)8464 bool VteTerminalPrivate::set_bold_is_bright(bool setting)
8465 {
8466     if (setting == m_bold_is_bright) {
8467         return false;
8468     }
8469 
8470     m_bold_is_bright = setting;
8471     invalidate_all();
8472 
8473     return true;
8474 }
8475 
set_allow_hyperlink(bool setting)8476 bool VteTerminalPrivate::set_allow_hyperlink(bool setting)
8477 {
8478     if (setting == m_allow_hyperlink) {
8479         return false;
8480     }
8481 
8482     if (setting == false) {
8483         m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, -1, -1, true, NULL);
8484         g_assert (m_hyperlink_hover_idx == 0);
8485         m_hyperlink_hover_uri = NULL;
8486         emit_hyperlink_hover_uri_changed(NULL);  /* FIXME only emit if really changed */
8487         m_defaults.attr.hyperlink_idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, NULL);
8488         g_assert (m_defaults.attr.hyperlink_idx == 0);
8489     }
8490 
8491     m_allow_hyperlink = setting;
8492     invalidate_all();
8493 
8494     return true;
8495 }
8496 
set_scroll_on_output(bool scroll)8497 bool VteTerminalPrivate::set_scroll_on_output(bool scroll)
8498 {
8499     if (scroll == m_scroll_on_output) {
8500         return false;
8501     }
8502 
8503     m_scroll_on_output = scroll;
8504     return true;
8505 }
8506 
set_scroll_on_keystroke(bool scroll)8507 bool VteTerminalPrivate::set_scroll_on_keystroke(bool scroll)
8508 {
8509     if (scroll == m_scroll_on_keystroke) {
8510         return false;
8511     }
8512 
8513     m_scroll_on_keystroke = scroll;
8514     return true;
8515 }
8516 
set_rewrap_on_resize(bool rewrap)8517 bool VteTerminalPrivate::set_rewrap_on_resize(bool rewrap)
8518 {
8519     if (rewrap == m_rewrap_on_resize) {
8520         return false;
8521     }
8522 
8523     m_rewrap_on_resize = rewrap;
8524     return true;
8525 }
8526 
update_cursor_blinks()8527 void VteTerminalPrivate::update_cursor_blinks()
8528 {
8529     bool blink = false;
8530 
8531     switch (decscusr_cursor_blink()) {
8532         case VTE_CURSOR_BLINK_SYSTEM:
8533             gboolean v;
8534             g_object_get(gtk_widget_get_settings(m_widget),
8535                                                     "gtk-cursor-blink",
8536                                                     &v, nullptr);
8537             blink = v != FALSE;
8538             break;
8539         case VTE_CURSOR_BLINK_ON:
8540             blink = true;
8541             break;
8542         case VTE_CURSOR_BLINK_OFF:
8543             blink = false;
8544             break;
8545     }
8546 
8547     if (m_cursor_blinks == blink) {
8548         return;
8549     }
8550 
8551     m_cursor_blinks = blink;
8552     check_cursor_blink();
8553 }
8554 
set_cursor_blink_mode(VteCursorBlinkMode mode)8555 bool VteTerminalPrivate::set_cursor_blink_mode(VteCursorBlinkMode mode)
8556 {
8557     if (mode == m_cursor_blink_mode) {
8558         return false;
8559     }
8560 
8561     m_cursor_blink_mode = mode;
8562     update_cursor_blinks();
8563 
8564     return true;
8565 }
8566 
set_cursor_shape(VteCursorShape shape)8567 bool VteTerminalPrivate::set_cursor_shape(VteCursorShape shape)
8568 {
8569     if (shape == m_cursor_shape) {
8570         return false;
8571     }
8572 
8573     m_cursor_shape = shape;
8574     invalidate_cursor_once();
8575 
8576     return true;
8577 }
8578 
8579 /* DECSCUSR set cursor style */
set_cursor_style(VteCursorStyle style)8580 bool VteTerminalPrivate::set_cursor_style(VteCursorStyle style)
8581 {
8582     if (m_cursor_style == style) {
8583         return false;
8584     }
8585 
8586     m_cursor_style = style;
8587     update_cursor_blinks();
8588     /* and this will also make cursor shape match the DECSCUSR style */
8589     invalidate_cursor_once();
8590 
8591     return true;
8592 }
8593 
8594 /*
8595  * VteTerminalPrivate::decscusr_cursor_blink:
8596  *
8597  * Returns the cursor blink mode set by DECSCUSR. If DECSCUSR was never
8598  * called, or it set the blink mode to terminal default, this returns the
8599  * value set via API or in dconf. Internal use only.
8600  *
8601  * Return value: cursor blink mode
8602  */
decscusr_cursor_blink()8603 VteCursorBlinkMode VteTerminalPrivate::decscusr_cursor_blink()
8604 {
8605     switch (m_cursor_style) {
8606         default:
8607         case VTE_CURSOR_STYLE_TERMINAL_DEFAULT:
8608             return m_cursor_blink_mode;
8609         case VTE_CURSOR_STYLE_BLINK_BLOCK:
8610         case VTE_CURSOR_STYLE_BLINK_UNDERLINE:
8611         case VTE_CURSOR_STYLE_BLINK_IBEAM:
8612             return VTE_CURSOR_BLINK_ON;
8613         case VTE_CURSOR_STYLE_STEADY_BLOCK:
8614         case VTE_CURSOR_STYLE_STEADY_UNDERLINE:
8615         case VTE_CURSOR_STYLE_STEADY_IBEAM:
8616             return VTE_CURSOR_BLINK_OFF;
8617     }
8618 }
8619 
8620 /*
8621  * VteTerminalPrivate::decscusr_cursor_shape:
8622  * @terminal: a #VteTerminal
8623  *
8624  * Returns the cursor shape set by DECSCUSR. If DECSCUSR was never called,
8625  * or it set the cursor shape to terminal default, this returns the value
8626  * set via API. Internal use only.
8627  *
8628  * Return value: cursor shape
8629  */
decscusr_cursor_shape()8630 VteCursorShape VteTerminalPrivate::decscusr_cursor_shape()
8631 {
8632     switch (m_cursor_style) {
8633         default:
8634         case VTE_CURSOR_STYLE_TERMINAL_DEFAULT:
8635             return m_cursor_shape;
8636         case VTE_CURSOR_STYLE_BLINK_BLOCK:
8637         case VTE_CURSOR_STYLE_STEADY_BLOCK:
8638             return VTE_CURSOR_SHAPE_BLOCK;
8639         case VTE_CURSOR_STYLE_BLINK_UNDERLINE:
8640         case VTE_CURSOR_STYLE_STEADY_UNDERLINE:
8641             return VTE_CURSOR_SHAPE_UNDERLINE;
8642         case VTE_CURSOR_STYLE_BLINK_IBEAM:
8643         case VTE_CURSOR_STYLE_STEADY_IBEAM:
8644             return VTE_CURSOR_SHAPE_IBEAM;
8645     }
8646 }
8647 
set_scrollback_lines(long lines)8648 bool VteTerminalPrivate::set_scrollback_lines(long lines)
8649 {
8650     glong low, high, next;
8651     double scroll_delta;
8652     VteScreen *scrn;
8653 
8654     if (lines < 0) {
8655         lines = G_MAXLONG;
8656     }
8657 
8658 #if 0
8659         /* FIXME: this breaks the scrollbar range, bug #562511 */
8660         if (lines == m_scrollback_lines)
8661                 return false;
8662 #endif
8663 
8664     _vte_debug_print (VTE_DEBUG_MISC,
8665             "Setting scrollback lines to %ld\n", lines);
8666 
8667     m_scrollback_lines = lines;
8668 
8669     /* The main screen gets the full scrollback buffer. */
8670     scrn = &m_normal_screen;
8671     lines = MAX (lines, m_row_count);
8672     next = MAX (m_screen->cursor.row + 1,
8673                 _vte_ring_next (scrn->row_data));
8674     _vte_ring_resize (scrn->row_data, lines);
8675     low = _vte_ring_delta (scrn->row_data);
8676     high = lines + MIN (G_MAXLONG - lines, low - m_row_count + 1);
8677     scrn->insert_delta = CLAMP (scrn->insert_delta, low, high);
8678     scrn->scroll_delta = CLAMP (scrn->scroll_delta, low, scrn->insert_delta);
8679     next = MIN (next, scrn->insert_delta + m_row_count);
8680     if (_vte_ring_next (scrn->row_data) > next){
8681         _vte_ring_shrink (scrn->row_data, next - low);
8682     }
8683 
8684     /* The alternate scrn isn't allowed to scroll at all. */
8685     scrn = &m_alternate_screen;
8686     _vte_ring_resize (scrn->row_data, m_row_count);
8687     scrn->scroll_delta = _vte_ring_delta (scrn->row_data);
8688     scrn->insert_delta = _vte_ring_delta (scrn->row_data);
8689     if (_vte_ring_next (scrn->row_data) > scrn->insert_delta + m_row_count){
8690         _vte_ring_shrink (scrn->row_data, m_row_count);
8691     }
8692 
8693     /* Adjust the scrollbar to the new location. */
8694     /* Hack: force a change in scroll_delta even if the value remains, so that
8695        vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 676075. */
8696     scroll_delta = m_screen->scroll_delta;
8697     m_screen->scroll_delta = -1;
8698     queue_adjustment_value_changed(scroll_delta);
8699     adjust_adjustments_full();
8700 
8701     return true;
8702 }
8703 
set_backspace_binding(VteEraseBinding binding)8704 bool VteTerminalPrivate::set_backspace_binding(VteEraseBinding binding)
8705 {
8706     if (binding == m_backspace_binding) {
8707         return false;
8708     }
8709 
8710     m_backspace_binding = binding;
8711     return true;
8712 }
8713 
set_delete_binding(VteEraseBinding binding)8714 bool VteTerminalPrivate::set_delete_binding(VteEraseBinding binding)
8715 {
8716     if (binding == m_delete_binding) {
8717         return false;
8718     }
8719 
8720     m_delete_binding = binding;
8721     return true;
8722 }
8723 
set_mouse_autohide(bool autohide)8724 bool VteTerminalPrivate::set_mouse_autohide(bool autohide)
8725 {
8726     if (autohide == m_mouse_autohide) {
8727         return false;
8728     }
8729 
8730     m_mouse_autohide = autohide;
8731 
8732     if (m_mouse_cursor_autohidden) {
8733         hyperlink_hilite_update();
8734         apply_mouse_cursor();
8735     }
8736     return true;
8737 }
8738 
8739 /*
8740  * VteTerminalPrivate::reset:
8741  * @clear_tabstops: whether to reset tabstops
8742  * @clear_history: whether to empty the terminal's scrollback buffer
8743  *
8744  * Resets as much of the terminal's internal state as possible, discarding any
8745  * unprocessed input data, resetting character attributes, cursor state,
8746  * national character set state, status line, terminal modes (insert/delete),
8747  * selection state, and encoding.
8748  *
8749  */
reset(bool clear_tabstops,bool clear_history,bool from_api)8750 void VteTerminalPrivate::reset(bool clear_tabstops,
8751                                 bool clear_history,
8752                                 bool from_api)
8753 {
8754     if (from_api && !m_input_enabled) {
8755         return;
8756     }
8757 
8758     GObject *object = G_OBJECT(m_terminal);
8759     g_object_freeze_notify(object);
8760 
8761     m_bell_pending = false;
8762 
8763     /* Clear the output buffer. */
8764     _vte_byte_array_clear(m_outgoing);
8765     /* Reset charset substitution state. */
8766     _vte_iso2022_state_free(m_iso2022);
8767     m_iso2022 = _vte_iso2022_state_new(nullptr);
8768     _vte_iso2022_state_set_codeset(m_iso2022, m_encoding);
8769     m_last_graphic_character = 0;
8770     /* Reset keypad/cursor key modes. */
8771     m_keypad_mode = VTE_KEYMODE_NORMAL;
8772     m_cursor_mode = VTE_KEYMODE_NORMAL;
8773     /* Enable autowrap. */
8774     m_autowrap = TRUE;
8775     /* Enable meta-sends-escape. */
8776     m_meta_sends_escape = TRUE;
8777     /* Disable DECCOLM mode. */
8778     m_deccolm_mode = FALSE;
8779     /* Reset saved settings. */
8780     if (m_dec_saved != NULL) {
8781         g_hash_table_destroy(m_dec_saved);
8782         m_dec_saved = g_hash_table_new(NULL, NULL);
8783     }
8784     /* Reset the color palette. Only the 256 indexed colors, not the special ones, as per xterm. */
8785     for (int i = 0; i < 256; i++) {
8786         m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
8787     }
8788     /* Reset the default attributes.  Reset the alternate attribute because
8789      * it's not a real attribute, but we need to treat it as one here. */
8790     reset_default_attributes(true);
8791     /* Reset charset modes. */
8792     m_character_replacements[0] = VTE_CHARACTER_REPLACEMENT_NONE;
8793     m_character_replacements[1] = VTE_CHARACTER_REPLACEMENT_NONE;
8794     m_character_replacement = &m_character_replacements[0];
8795     /* Clear the scrollback buffers and reset the cursors. Switch to normal screen. */
8796     if (clear_history) {
8797         m_screen = &m_normal_screen;
8798         m_normal_screen.scroll_delta = m_normal_screen.insert_delta =
8799                 _vte_ring_reset(m_normal_screen.row_data);
8800         m_normal_screen.cursor.row = m_normal_screen.insert_delta;
8801         m_normal_screen.cursor.col = 0;
8802         m_alternate_screen.scroll_delta = m_alternate_screen.insert_delta =
8803                 _vte_ring_reset(m_alternate_screen.row_data);
8804         m_alternate_screen.cursor.row = m_alternate_screen.insert_delta;
8805         m_alternate_screen.cursor.col = 0;
8806         /* Adjust the scrollbar to the new location. */
8807         /* Hack: force a change in scroll_delta even if the value remains, so that
8808             vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 730599. */
8809         m_screen->scroll_delta = -1;
8810         queue_adjustment_value_changed(m_screen->insert_delta);
8811         adjust_adjustments_full();
8812     }
8813     /* DECSCUSR cursor style */
8814     set_cursor_style(VTE_CURSOR_STYLE_TERMINAL_DEFAULT);
8815     /* Do more stuff we refer to as a "full" reset. */
8816     if (clear_tabstops) {
8817         set_default_tabstops();
8818     }
8819     /* Reset restricted scrolling regions, leave insert mode, make
8820      * the cursor visible again. */
8821     m_scrolling_restricted = FALSE;
8822     m_sendrecv_mode = TRUE;
8823     m_insert_mode = FALSE;
8824     m_linefeed_mode = FALSE;
8825     m_origin_mode = FALSE;
8826     m_reverse_mode = FALSE;
8827     m_cursor_visible = TRUE;
8828     /* For some reason, xterm doesn't reset alternateScroll, but we do. */
8829     m_alternate_screen_scroll = TRUE;
8830     /* Reset the visual bits of selection on hard reset, see bug 789954. */
8831     if (clear_history) {
8832         deselect_all();
8833         m_has_selection = FALSE;
8834         m_selecting = FALSE;
8835         m_selecting_restart = FALSE;
8836         m_selecting_had_delta = FALSE;
8837         memset(&m_selection_origin, 0, sizeof(m_selection_origin));
8838         memset(&m_selection_last, 0, sizeof(m_selection_last));
8839         memset(&m_selection_start, 0, sizeof(m_selection_start));
8840         memset(&m_selection_end, 0, sizeof(m_selection_end));
8841     }
8842 
8843     /* Reset mouse motion events. */
8844     m_mouse_tracking_mode = MOUSE_TRACKING_NONE;
8845     apply_mouse_cursor();
8846     m_mouse_pressed_buttons = 0;
8847     m_mouse_handled_buttons = 0;
8848     m_mouse_xterm_extension = FALSE;
8849     m_mouse_urxvt_extension = FALSE;
8850     m_mouse_smooth_scroll_delta = 0.;
8851     /* Reset focus tracking */
8852     m_focus_tracking_mode = FALSE;
8853     /* Clear modifiers. */
8854     m_modifiers = 0;
8855     /* Reset miscellaneous stuff. */
8856     m_bracketed_paste_mode = FALSE;
8857     /* Reset the saved cursor. */
8858     save_cursor(&m_normal_screen);
8859     save_cursor(&m_alternate_screen);
8860     /* Cause everything to be redrawn (or cleared). */
8861     invalidate_all();
8862 
8863     g_object_thaw_notify(object);
8864 }
8865 
8866 /* We need this bit of glue to ensure that accessible objects will always
8867  * get signals. */
subscribe_accessible_events()8868 void VteTerminalPrivate::subscribe_accessible_events()
8869 {
8870     m_accessible_emit = true;
8871 }
8872 
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)8873 void VteTerminalPrivate::select_text(vte::grid::column_t start_col,
8874                                         vte::grid::row_t start_row,
8875                                         vte::grid::column_t end_col,
8876                                         vte::grid::row_t end_row)
8877 {
8878     deselect_all();
8879 
8880     m_selection_type = selection_type_char;
8881     m_selecting_had_delta = true;
8882     m_selection_start.col = start_col;
8883     m_selection_start.row = start_row;
8884     m_selection_end.col = end_col;
8885     m_selection_end.row = end_row;
8886     widget_copy(VTE_SELECTION_PRIMARY, VTE_FORMAT_TEXT);
8887     emit_selection_changed();
8888 
8889     invalidate_region(MIN (start_col, end_col), MAX (start_col, end_col),
8890                           MIN (start_row, end_row), MAX (start_row, end_row),
8891                           false);
8892 }
8893 
select_empty(vte::grid::column_t col,vte::grid::row_t row)8894 void VteTerminalPrivate::select_empty(vte::grid::column_t col,
8895                                         vte::grid::row_t row)
8896 {
8897         select_text(col, row, col - 1, row);
8898 }
8899 
remove_process_timeout_source(void)8900 static void remove_process_timeout_source(void)
8901 {
8902     if (process_timeout_tag == 0) {
8903         return;
8904     }
8905 
8906     _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing process timeout\n");
8907     g_source_remove (process_timeout_tag);
8908     process_timeout_tag = 0;
8909 }
8910 
add_update_timeout(VteTerminalPrivate * that)8911 static void add_update_timeout(VteTerminalPrivate *that)
8912 {
8913     if (update_timeout_tag == 0) {
8914         _vte_debug_print (VTE_DEBUG_TIMEOUT, "Starting update timeout\n");
8915         update_timeout_tag = g_timeout_add_full (GDK_PRIORITY_REDRAW,
8916                                                     VTE_UPDATE_TIMEOUT,
8917                                                     update_timeout, NULL,
8918                                                     NULL);
8919     }
8920     if (!in_process_timeout) {
8921                 remove_process_timeout_source();
8922     }
8923     if (that->m_active_terminals_link == nullptr) {
8924         _vte_debug_print (VTE_DEBUG_TIMEOUT, "Adding terminal to active list\n");
8925         that->m_active_terminals_link = g_active_terminals =
8926                                     g_list_prepend(g_active_terminals, that);
8927     }
8928 }
8929 
reset_update_rects()8930 void VteTerminalPrivate::reset_update_rects()
8931 {
8932     g_array_set_size(m_update_rects, 0);
8933     m_invalidated_all = FALSE;
8934 }
8935 
remove_from_active_list(VteTerminalPrivate * that)8936 static bool remove_from_active_list(VteTerminalPrivate *that)
8937 {
8938     if (that->m_active_terminals_link == nullptr || that->m_update_rects->len != 0) {
8939         return false;
8940     }
8941 
8942     _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing terminal from active list\n");
8943     g_active_terminals = g_list_delete_link(g_active_terminals, that->m_active_terminals_link);
8944     that->m_active_terminals_link = nullptr;
8945     return true;
8946 }
8947 
stop_processing(VteTerminalPrivate * that)8948 static void stop_processing(VteTerminalPrivate *that)
8949 {
8950     if (!remove_from_active_list(that)) {
8951         return;
8952     }
8953 
8954     if (g_active_terminals != nullptr) {
8955         return;
8956     }
8957 
8958     if (!in_process_timeout) {
8959         remove_process_timeout_source();
8960     }
8961     if (in_update_timeout == FALSE && update_timeout_tag != 0) {
8962         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Removing update timeout\n");
8963         g_source_remove (update_timeout_tag);
8964         update_timeout_tag = 0;
8965     }
8966 }
8967 
remove_update_timeout(VteTerminalPrivate * that)8968 static void remove_update_timeout(VteTerminalPrivate *that)
8969 {
8970     that->reset_update_rects();
8971     stop_processing(that);
8972 }
8973 
add_process_timeout(VteTerminalPrivate * that)8974 static void add_process_timeout(VteTerminalPrivate *that)
8975 {
8976     _vte_debug_print(VTE_DEBUG_TIMEOUT, "Adding terminal to active list\n");
8977     that->m_active_terminals_link = g_active_terminals =
8978                                     g_list_prepend(g_active_terminals, that);
8979     if (update_timeout_tag == 0 && process_timeout_tag == 0) {
8980         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Starting process timeout\n");
8981         process_timeout_tag = g_timeout_add (VTE_DISPLAY_TIMEOUT,
8982                                                 process_timeout, NULL);
8983     }
8984 }
8985 
start_processing()8986 void VteTerminalPrivate::start_processing()
8987 {
8988     if (!is_processing()) {
8989         add_process_timeout(this);
8990     }
8991 }
8992 
emit_pending_signals()8993 void VteTerminalPrivate::emit_pending_signals()
8994 {
8995     GObject *object = G_OBJECT(m_terminal);
8996     g_object_freeze_notify(object);
8997     gboolean really_changed;
8998 
8999     emit_adjustment_changed();
9000 
9001     if (m_window_title_changed) {
9002         really_changed = (g_strcmp0(m_window_title, m_window_title_changed) != 0);
9003         g_free (m_window_title);
9004         m_window_title = m_window_title_changed;
9005         m_window_title_changed = NULL;
9006 
9007         if (really_changed) {
9008             _vte_debug_print(VTE_DEBUG_SIGNALS,
9009                                 "Emitting `window-title-changed'.\n");
9010             g_signal_emit(object, signals[SIGNAL_WINDOW_TITLE_CHANGED], 0);
9011             g_object_notify_by_pspec(object, pspecs[PROP_WINDOW_TITLE]);
9012         }
9013     }
9014 
9015     if (m_icon_title_changed) {
9016         really_changed = (g_strcmp0(m_icon_title, m_icon_title_changed) != 0);
9017         g_free (m_icon_title);
9018         m_icon_title = m_icon_title_changed;
9019         m_icon_title_changed = NULL;
9020 
9021         if (really_changed) {
9022             _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `icon-title-changed'.\n");
9023             g_signal_emit(object, signals[SIGNAL_ICON_TITLE_CHANGED], 0);
9024             g_object_notify_by_pspec(object, pspecs[PROP_ICON_TITLE]);
9025         }
9026     }
9027 
9028     if (m_current_directory_uri_changed) {
9029         really_changed = (g_strcmp0(m_current_directory_uri, m_current_directory_uri_changed) != 0);
9030         g_free (m_current_directory_uri);
9031         m_current_directory_uri = m_current_directory_uri_changed;
9032         m_current_directory_uri_changed = NULL;
9033 
9034         if (really_changed) {
9035             _vte_debug_print(VTE_DEBUG_SIGNALS,
9036                                 "Emitting `current-directory-uri-changed'.\n");
9037             g_signal_emit(object, signals[SIGNAL_CURRENT_DIRECTORY_URI_CHANGED], 0);
9038             g_object_notify_by_pspec(object, pspecs[PROP_CURRENT_DIRECTORY_URI]);
9039         }
9040     }
9041 
9042     if (m_current_file_uri_changed) {
9043         really_changed = (g_strcmp0(m_current_file_uri, m_current_file_uri_changed) != 0);
9044         g_free (m_current_file_uri);
9045         m_current_file_uri = m_current_file_uri_changed;
9046         m_current_file_uri_changed = NULL;
9047 
9048         if (really_changed) {
9049             _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `current-file-uri-changed'.\n");
9050             g_signal_emit(object, signals[SIGNAL_CURRENT_FILE_URI_CHANGED], 0);
9051             g_object_notify_by_pspec(object, pspecs[PROP_CURRENT_FILE_URI]);
9052         }
9053     }
9054 
9055     /* Flush any pending "inserted" signals. */
9056 
9057     if (m_cursor_moved_pending) {
9058         _vte_debug_print(VTE_DEBUG_SIGNALS,
9059                             "Emitting `cursor-moved'.\n");
9060         g_signal_emit(object, signals[SIGNAL_CURSOR_MOVED], 0);
9061         m_cursor_moved_pending = false;
9062     }
9063     if (m_text_modified_flag) {
9064         _vte_debug_print(VTE_DEBUG_SIGNALS,
9065                             "Emitting buffered `text-modified'.\n");
9066         emit_text_modified();
9067         m_text_modified_flag = false;
9068     }
9069     if (m_text_inserted_flag) {
9070         _vte_debug_print(VTE_DEBUG_SIGNALS,
9071                             "Emitting buffered `text-inserted'\n");
9072         emit_text_inserted();
9073         m_text_inserted_flag = false;
9074     }
9075     if (m_text_deleted_flag) {
9076         _vte_debug_print(VTE_DEBUG_SIGNALS,
9077                             "Emitting buffered `text-deleted'\n");
9078         emit_text_deleted();
9079         m_text_deleted_flag = false;
9080     }
9081     if (m_contents_changed_pending) {
9082         /* Update hyperlink and dingus match set. */
9083         match_contents_clear();
9084         if (m_mouse_cursor_over_widget) {
9085             hyperlink_hilite_update();
9086         }
9087 
9088         _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `contents-changed'.\n");
9089         g_signal_emit(m_terminal, signals[SIGNAL_CONTENTS_CHANGED], 0);
9090         m_contents_changed_pending = false;
9091     }
9092     if (m_bell_pending) {
9093         auto const timestamp = g_get_monotonic_time();
9094         if ((timestamp - m_bell_timestamp) >= VTE_BELL_MINIMUM_TIME_DIFFERENCE) {
9095             beep();
9096             emit_bell();
9097 
9098             m_bell_timestamp = timestamp;
9099         }
9100 
9101         m_bell_pending = false;
9102     }
9103 
9104     g_object_thaw_notify(object);
9105 }
9106 
time_process_incoming()9107 void VteTerminalPrivate::time_process_incoming()
9108 {
9109     g_timer_reset(process_timer);
9110     process_incoming();
9111     auto elapsed = g_timer_elapsed(process_timer, NULL) * 1000;
9112     gssize target = VTE_MAX_PROCESS_TIME / elapsed * m_input_bytes;
9113     m_max_input_bytes = (m_max_input_bytes + target) / 2;
9114 }
9115 
process(bool emit_adj_changed)9116 bool VteTerminalPrivate::process(bool emit_adj_changed)
9117 {
9118     bool is_active;
9119     if (emit_adj_changed) {
9120         emit_adjustment_changed();
9121     }
9122     is_active = _vte_incoming_chunks_length(m_incoming) != 0;
9123     if (is_active) {
9124         if (VTE_MAX_PROCESS_TIME) {
9125             time_process_incoming();
9126         } else {
9127             process_incoming();
9128         }
9129         m_input_bytes = 0;
9130     } else {
9131         emit_pending_signals();
9132     }
9133 
9134     return is_active;
9135 }
9136 
9137 /* This function is called after DISPLAY_TIMEOUT ms.
9138  * It makes sure initial output is never delayed by more than DISPLAY_TIMEOUT
9139  */
process_timeout(gpointer data)9140 static gboolean process_timeout (gpointer data)
9141 {
9142     GList *l, *next;
9143     gboolean again;
9144 
9145     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9146     gdk_threads_enter();
9147     G_GNUC_END_IGNORE_DEPRECATIONS;
9148 
9149     in_process_timeout = TRUE;
9150 
9151     _vte_debug_print (VTE_DEBUG_WORK, "<");
9152     _vte_debug_print (VTE_DEBUG_TIMEOUT,
9153                           "Process timeout:  %d active\n",
9154                           g_list_length(g_active_terminals));
9155 
9156     for (l = g_active_terminals; l != NULL; l = next) {
9157         VteTerminalPrivate *that = reinterpret_cast<VteTerminalPrivate*>(l->data);
9158         bool active;
9159 
9160         next = l->next;
9161 
9162         if (l != g_active_terminals) {
9163             _vte_debug_print (VTE_DEBUG_WORK, "T");
9164         }
9165 
9166         /* FIXMEchpe find out why we don't emit_adjustment_changed() here!! */
9167         active = that->process(false);
9168 
9169         if (!active) {
9170             remove_from_active_list(that);
9171         }
9172     }
9173 
9174     _vte_debug_print (VTE_DEBUG_WORK, ">");
9175 
9176     if (g_active_terminals != nullptr && update_timeout_tag == 0) {
9177         again = TRUE;
9178     } else {
9179         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Stopping process timeout\n");
9180         process_timeout_tag = 0;
9181         again = FALSE;
9182     }
9183 
9184     in_process_timeout = FALSE;
9185 
9186     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9187     gdk_threads_leave();
9188     G_GNUC_END_IGNORE_DEPRECATIONS;
9189 
9190     if (again) {
9191         /* Force us to relinquish the CPU as the child is running
9192          * at full tilt and making us run to keep up...
9193          */
9194         g_usleep (0);
9195     } else if (update_timeout_tag == 0) {
9196         /* otherwise free up memory used to capture incoming data */
9197         prune_chunks (10);
9198     }
9199 
9200     return again;
9201 }
9202 
invalidate_dirty_rects_and_process_updates()9203 bool VteTerminalPrivate::invalidate_dirty_rects_and_process_updates()
9204 {
9205     if (G_UNLIKELY(!widget_realized())) {
9206         return false;
9207     }
9208 
9209     if (G_UNLIKELY (!m_update_rects->len)) {
9210         return false;
9211     }
9212 
9213     auto region = cairo_region_create();
9214     auto n_rects = m_update_rects->len;
9215     for (guint i = 0; i < n_rects; i++) {
9216         cairo_rectangle_int_t *rect = &g_array_index(m_update_rects, cairo_rectangle_int_t, i);
9217         cairo_region_union_rectangle(region, rect);
9218     }
9219     g_array_set_size(m_update_rects, 0);
9220     m_invalidated_all = false;
9221 
9222     auto allocation = get_allocated_rect();
9223     cairo_region_translate(region,
9224                             allocation.x + m_padding.left,
9225                             allocation.y + m_padding.top);
9226 
9227     /* and perform the merge with the window visible area */
9228     gtk_widget_queue_draw_region(m_widget, region);
9229     cairo_region_destroy (region);
9230 
9231     return true;
9232 }
9233 
update_repeat_timeout(gpointer data)9234 static gboolean update_repeat_timeout (gpointer data)
9235 {
9236     GList *l, *next;
9237     bool again;
9238 
9239     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9240     gdk_threads_enter();
9241     G_GNUC_END_IGNORE_DEPRECATIONS;
9242 
9243     in_update_timeout = TRUE;
9244 
9245     _vte_debug_print (VTE_DEBUG_WORK, "[");
9246     _vte_debug_print (VTE_DEBUG_TIMEOUT,
9247                           "Repeat timeout:  %d active\n",
9248                           g_list_length(g_active_terminals));
9249 
9250     for (l = g_active_terminals; l != NULL; l = next) {
9251         VteTerminalPrivate *that = reinterpret_cast<VteTerminalPrivate*>(l->data);
9252 
9253         next = l->next;
9254 
9255         if (l != g_active_terminals) {
9256             _vte_debug_print (VTE_DEBUG_WORK, "T");
9257         }
9258 
9259         that->process(true);
9260 
9261         again = that->invalidate_dirty_rects_and_process_updates();
9262         if (!again) {
9263             remove_from_active_list(that);
9264         }
9265     }
9266 
9267     _vte_debug_print (VTE_DEBUG_WORK, "]");
9268 
9269    /* We only stop the timer if no update request was received in this
9270     * past cycle.  Technically, always stop this timer object and maybe
9271     * reinstall a new one because we need to delay by the amount of time
9272     * it took to repaint the screen: bug 730732.
9273     */
9274     if (g_active_terminals == nullptr) {
9275         _vte_debug_print(VTE_DEBUG_TIMEOUT, "Stopping update timeout\n");
9276         update_timeout_tag = 0;
9277         again = false;
9278     } else {
9279         update_timeout_tag =
9280                 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
9281                                     VTE_UPDATE_REPEAT_TIMEOUT,
9282                                     update_repeat_timeout, NULL,
9283                                     NULL);
9284         again = true;
9285     }
9286 
9287     in_update_timeout = FALSE;
9288 
9289     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9290     gdk_threads_leave();
9291     G_GNUC_END_IGNORE_DEPRECATIONS;
9292 
9293     if (again) {
9294         /* Force us to relinquish the CPU as the child is running
9295          * at full tilt and making us run to keep up...
9296          */
9297         g_usleep (0);
9298     } else {
9299         /* otherwise free up memory used to capture incoming data */
9300         prune_chunks (10);
9301     }
9302 
9303     return FALSE;  /* If we need to go again, we already have a new timer for that. */
9304 }
9305 
update_timeout(gpointer data)9306 static gboolean update_timeout (gpointer data)
9307 {
9308     GList *l, *next;
9309 
9310     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9311     gdk_threads_enter();
9312     G_GNUC_END_IGNORE_DEPRECATIONS;
9313 
9314     in_update_timeout = TRUE;
9315 
9316     _vte_debug_print (VTE_DEBUG_WORK, "{");
9317     _vte_debug_print (VTE_DEBUG_TIMEOUT,
9318                           "Update timeout:  %d active\n",
9319                           g_list_length(g_active_terminals));
9320 
9321     remove_process_timeout_source();
9322 
9323     for (l = g_active_terminals; l != NULL; l = next) {
9324         VteTerminalPrivate *that = reinterpret_cast<VteTerminalPrivate*>(l->data);
9325 
9326         next = l->next;
9327 
9328         if (l != g_active_terminals) {
9329             _vte_debug_print (VTE_DEBUG_WORK, "T");
9330         }
9331 
9332         that->process(true);
9333 
9334         that->invalidate_dirty_rects_and_process_updates();
9335     }
9336 
9337     _vte_debug_print (VTE_DEBUG_WORK, "}");
9338 
9339     /* Set a timer such that we do not invalidate for a while. */
9340     /* This limits the number of times we draw to ~40fps. */
9341     update_timeout_tag =
9342         g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
9343                     VTE_UPDATE_REPEAT_TIMEOUT,
9344                     update_repeat_timeout, NULL,
9345                     NULL);
9346     in_update_timeout = FALSE;
9347 
9348     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
9349     gdk_threads_leave();
9350     G_GNUC_END_IGNORE_DEPRECATIONS;
9351 
9352     return FALSE;
9353 }
9354 
write_contents_sync(GOutputStream * stream,VteWriteFlags flags,GCancellable * cancellable,GError ** error)9355 bool VteTerminalPrivate::write_contents_sync (GOutputStream *stream,
9356                                                 VteWriteFlags flags,
9357                                                 GCancellable *cancellable,
9358                                                 GError **error)
9359 {
9360     return _vte_ring_write_contents (m_screen->row_data,
9361                                         stream, flags,
9362                                         cancellable, error);
9363 }
9364 
9365 /*
9366  * Buffer search
9367  */
9368 
9369 /* TODO Add properties & signals */
9370 
9371 
9372 /*
9373  * VteTerminalPrivate::set_input_enabled:
9374  * @enabled: whether to enable user input
9375  *
9376  * Enables or disables user input. When user input is disabled,
9377  * the terminal's child will not receive any key press, or mouse button
9378  * press or motion events sent to it.
9379  *
9380  * Returns: %true iff the setting changed
9381  */
set_input_enabled(bool enabled)9382 bool VteTerminalPrivate::set_input_enabled (bool enabled)
9383 {
9384     if (enabled == m_input_enabled) {
9385         return false;
9386     }
9387 
9388     m_input_enabled = enabled;
9389 
9390     auto context = gtk_widget_get_style_context(m_widget);
9391 
9392     /* FIXME: maybe hide cursor when input disabled, too? */
9393 
9394     if (enabled) {
9395         if (gtk_widget_has_focus(m_widget)) {
9396             gtk_im_context_focus_in(m_im_context);
9397         }
9398 
9399         gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY);
9400     } else {
9401         im_reset();
9402         if (gtk_widget_has_focus(m_widget)) {
9403             gtk_im_context_focus_out(m_im_context);
9404         }
9405         _vte_byte_array_clear(m_outgoing);
9406 
9407         gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY);
9408     }
9409 
9410     return true;
9411 }
9412 
process_word_char_exceptions(char const * str,gunichar ** arrayp,gsize * lenp)9413 bool VteTerminalPrivate::process_word_char_exceptions(char const *str,
9414                                                         gunichar **arrayp,
9415                                                         gsize *lenp)
9416 {
9417     const char *p;
9418     gunichar *array, c;
9419     gsize len, i;
9420 
9421     if (str == NULL) {
9422         str = WORD_CHAR_EXCEPTIONS_DEFAULT;
9423     }
9424 
9425     len = g_utf8_strlen(str, -1);
9426     array = g_new(gunichar, len);
9427     i = 0;
9428 
9429     for (p = str; *p; p = g_utf8_next_char(p)) {
9430         c = g_utf8_get_char(p);
9431 
9432        /* For forward compatibility reasons, we skip
9433         * characters that aren't supposed to be here,
9434         * instead of erroring out.
9435         */
9436         /* '-' must only be used*  at the start of the string */
9437         if (c == (gunichar)'-' && p != str) {
9438             continue;
9439         }
9440         if (!g_unichar_isgraph(c)) {
9441             continue;
9442         }
9443         if (g_unichar_isspace(c)) {
9444             continue;
9445         }
9446         if (g_unichar_isalnum(c)) {
9447             continue;
9448         }
9449 
9450         array[i++] = g_utf8_get_char(p);
9451     }
9452 
9453     g_assert(i <= len);
9454     len = i; /* we may have skipped some characters */
9455 
9456     /* Sort the result since we want to use bsearch on it */
9457     qsort(array, len, sizeof(gunichar), compare_unichar_p);
9458 
9459     /* Check that no character occurs twice */
9460     for (i = 1; i < len; i++) {
9461         if (array[i-1] != array[i]) {
9462             continue;
9463         }
9464 
9465         g_free(array);
9466         return false;
9467     }
9468 
9469 #if 0
9470     /* Debug */
9471     for (i = 0; i < len; i++) {
9472         char utf[7];
9473         c = array[i];
9474         utf[g_unichar_to_utf8(c, utf)] = '\0';
9475         g_printerr("Word char exception: U+%04X %s\n", c, utf);
9476     }
9477 #endif
9478 
9479     *lenp = len;
9480     *arrayp = array;
9481     return true;
9482 }
9483 
9484 /*
9485  * VteTerminalPrivate::set_word_char_exceptions:
9486  * @exceptions: a string of ASCII punctuation characters, or %nullptr
9487  *
9488  * With this function you can provide a set of characters which will
9489  * be considered parts of a word when doing word-wise selection, in
9490  * addition to the default which only considers alphanumeric characters
9491  * part of a word.
9492  *
9493  * The characters in @exceptions must be non-alphanumeric, each character
9494  * must occur only once, and if @exceptions contains the character
9495  * U+002D HYPHEN-MINUS, it must be at the start of the string.
9496  *
9497  * Use %nullptr to reset the set of exception characters to the default.
9498  *
9499  * Returns: %true if the word char exceptions changed
9500  */
set_word_char_exceptions(char const * exceptions)9501 bool VteTerminalPrivate::set_word_char_exceptions(char const* exceptions)
9502 {
9503     gunichar *array;
9504     gsize len;
9505 
9506     if (g_strcmp0(exceptions, m_word_char_exceptions_string) == 0) {
9507         return false;
9508     }
9509 
9510     if (!process_word_char_exceptions(exceptions, &array, &len)) {
9511         return false;
9512     }
9513 
9514     g_free(m_word_char_exceptions_string);
9515     m_word_char_exceptions_string = g_strdup(exceptions);
9516 
9517     g_free(m_word_char_exceptions);
9518     m_word_char_exceptions = array;
9519     m_word_char_exceptions_len = len;
9520 
9521     return true;
9522 }
9523 
set_clear_background(bool setting)9524 void VteTerminalPrivate::set_clear_background(bool setting)
9525 {
9526     if (m_clear_background == setting) {
9527         return;
9528     }
9529 
9530     m_clear_background = setting;
9531     invalidate_all();
9532 }
9533