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 ¶ms.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