1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * Copyright (C) 2001-2004,2009,2010 Red Hat, Inc.
4 * Copyright © 2008, 2009, 2010 Christian Persch
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * SECTION: vte-terminal
23 * @short_description: A terminal widget implementation
24 *
25 * A VteTerminal is a terminal emulator implemented as a GTK3 widget.
26 */
27
28 #include <config.h>
29
30 #include <math.h>
31
32 #include "vte.h"
33 #include "vte-private.h"
34
35 #ifdef HAVE_WCHAR_H
36 #include <wchar.h>
37 #endif
38 #ifdef HAVE_SYS_SYSLIMITS_H
39 #include <sys/syslimits.h>
40 #endif
41 #ifdef HAVE_SYS_WAIT_H
42 #include <sys/wait.h>
43 #endif
44 #include <glib.h>
45 #include <glib/gstdio.h>
46 #include <glib-object.h>
47 #include <gdk/gdk.h>
48 #include <gtk/gtk.h>
49 #include <pango/pango.h>
50 #include "iso2022.h"
51 #include "keymap.h"
52 #include "marshal.h"
53 #include "matcher.h"
54 #include "vteaccess.h"
55 #include "vteint.h"
56 #include "vtepty.h"
57 #include "vtepty-private.h"
58
59 #ifdef HAVE_LOCALE_H
60 #include <locale.h>
61 #endif
62
63 #ifndef HAVE_ROUND
round(double x)64 static inline double round(double x) {
65 if(x - floor(x) < 0.5) {
66 return floor(x);
67 } else {
68 return ceil(x);
69 }
70 }
71 #endif
72
73 #ifndef HAVE_WINT_T
74 typedef gunichar wint_t;
75 #endif
76
77 #if !GLIB_CHECK_VERSION(2, 41, 2)
78 #define G_PARAM_EXPLICIT_NOTIFY 0
79 #endif
80
81 #ifndef howmany
82 #define howmany(x, y) (((x) + ((y) - 1)) / (y))
83 #endif
84
85 #define WORD_CHAR_EXCEPTIONS_DEFAULT "-#%&+,./=?@\\_~\302\267"
86
87 static int _vte_unichar_width(gunichar c, int utf8_ambiguous_width);
88 static void vte_terminal_set_visibility (VteTerminal *terminal, GdkVisibilityState state);
89 static void vte_terminal_paste(VteTerminal *terminal, GdkAtom board);
90 static void vte_terminal_real_copy_clipboard(VteTerminal *terminal);
91 static void vte_terminal_real_paste_clipboard(VteTerminal *terminal);
92 static gboolean vte_terminal_io_read(GIOChannel *channel,
93 GIOCondition condition,
94 VteTerminal *terminal);
95 static gboolean vte_terminal_io_write(GIOChannel *channel,
96 GIOCondition condition,
97 VteTerminal *terminal);
98 static void vte_terminal_match_hilite_clear(VteTerminal *terminal);
99 static void vte_terminal_match_hilite_hide(VteTerminal *terminal);
100 static void vte_terminal_match_hilite_show(VteTerminal *terminal, long x, long y);
101 static void vte_terminal_match_hilite_update(VteTerminal *terminal, long x, long y);
102 static void vte_terminal_match_contents_clear(VteTerminal *terminal);
103 static void vte_terminal_background_update(VteTerminal *data);
104 static void vte_terminal_process_incoming(VteTerminal *terminal);
105 static void vte_terminal_emit_pending_signals(VteTerminal *terminal);
106 static gboolean vte_cell_is_selected(VteTerminal *terminal,
107 glong col, glong row, gpointer data);
108 static void vte_terminal_extend_selection(VteTerminal *terminal, long x, long y,
109 gboolean always_grow, gboolean force);
110 static char *vte_terminal_get_text_range_maybe_wrapped(VteTerminal *terminal,
111 glong start_row,
112 glong start_col,
113 glong end_row,
114 glong end_col,
115 gboolean wrap,
116 VteSelectionFunc is_selected,
117 gpointer data,
118 GArray *attributes,
119 gboolean include_trailing_spaces);
120 static char *vte_terminal_get_text_maybe_wrapped(VteTerminal *terminal,
121 gboolean wrap,
122 VteSelectionFunc is_selected,
123 gpointer data,
124 GArray *attributes,
125 gboolean include_trailing_spaces);
126 static void _vte_terminal_disconnect_pty_read(VteTerminal *terminal);
127 static void _vte_terminal_disconnect_pty_write(VteTerminal *terminal);
128 static void vte_terminal_stop_processing (VteTerminal *terminal);
129
130 static inline gboolean vte_terminal_is_processing (VteTerminal *terminal);
131 static inline void vte_terminal_start_processing (VteTerminal *terminal);
132 static void vte_terminal_add_process_timeout (VteTerminal *terminal);
133 static void add_update_timeout (VteTerminal *terminal);
134 static void remove_update_timeout (VteTerminal *terminal);
135 static void reset_update_regions (VteTerminal *terminal);
136 static void vte_terminal_update_cursor_blinks_internal(VteTerminal *terminal);
137 static void _vte_check_cursor_blink(VteTerminal *terminal);
138 static VteCursorShape _vte_terminal_decscusr_cursor_shape(VteTerminal *terminal);
139 static VteCursorBlinkMode _vte_terminal_decscusr_cursor_blink(VteTerminal *terminal);
140
141 static gboolean process_timeout (gpointer data);
142 static gboolean update_timeout (gpointer data);
143 static cairo_region_t *vte_cairo_get_clip_region (cairo_t *cr);
144
145 enum {
146 COPY_CLIPBOARD,
147 PASTE_CLIPBOARD,
148 LAST_SIGNAL
149 };
150 static guint signals[LAST_SIGNAL];
151
152 enum {
153 PROP_0,
154 PROP_HADJUSTMENT,
155 PROP_VADJUSTMENT,
156 PROP_HSCROLL_POLICY,
157 PROP_VSCROLL_POLICY,
158 PROP_ALLOW_BOLD,
159 PROP_AUDIBLE_BELL,
160 PROP_BACKSPACE_BINDING,
161 PROP_CJK_AMBIGUOUS_WIDTH,
162 PROP_CURSOR_BLINK_MODE,
163 PROP_CURSOR_SHAPE,
164 PROP_CURRENT_DIRECTORY_URI,
165 PROP_CURRENT_FILE_URI,
166 PROP_DELETE_BINDING,
167 PROP_ENCODING,
168 PROP_FONT_DESC,
169 PROP_FONT_SCALE,
170 PROP_ICON_TITLE,
171 PROP_INPUT_ENABLED,
172 PROP_MOUSE_POINTER_AUTOHIDE,
173 PROP_PTY,
174 PROP_REWRAP_ON_RESIZE,
175 PROP_SCROLLBACK_LINES,
176 PROP_SCROLL_ON_KEYSTROKE,
177 PROP_SCROLL_ON_OUTPUT,
178 PROP_WINDOW_TITLE,
179 PROP_WORD_CHAR_EXCEPTIONS
180 };
181
182 /* these static variables are guarded by the GDK mutex */
183 static guint process_timeout_tag = 0;
184 static gboolean in_process_timeout;
185 static guint update_timeout_tag = 0;
186 static gboolean in_update_timeout;
187 static GList *active_terminals;
188 static GTimer *process_timer;
189
190 static const GtkBorder default_padding = { 1, 1, 1, 1 };
191
192 static int
_vte_unichar_width(gunichar c,int utf8_ambiguous_width)193 _vte_unichar_width(gunichar c, int utf8_ambiguous_width)
194 {
195 if (G_LIKELY (c < 0x80))
196 return 1;
197 if (G_UNLIKELY (g_unichar_iszerowidth (c)))
198 return 0;
199 if (G_UNLIKELY (g_unichar_iswide (c)))
200 return 2;
201 if (G_LIKELY (utf8_ambiguous_width == 1))
202 return 1;
203 if (G_UNLIKELY (g_unichar_iswide_cjk (c)))
204 return 2;
205 return 1;
206 }
207
208 /* process incoming data without copying */
209 static struct _vte_incoming_chunk *free_chunks;
210 static struct _vte_incoming_chunk *
get_chunk(void)211 get_chunk (void)
212 {
213 struct _vte_incoming_chunk *chunk = NULL;
214 if (free_chunks) {
215 chunk = free_chunks;
216 free_chunks = free_chunks->next;
217 }
218 if (chunk == NULL) {
219 chunk = g_new (struct _vte_incoming_chunk, 1);
220 }
221 chunk->next = NULL;
222 chunk->len = 0;
223 return chunk;
224 }
225 static void
release_chunk(struct _vte_incoming_chunk * chunk)226 release_chunk (struct _vte_incoming_chunk *chunk)
227 {
228 chunk->next = free_chunks;
229 chunk->len = free_chunks ? free_chunks->len + 1 : 0;
230 free_chunks = chunk;
231 }
232 static void
prune_chunks(guint len)233 prune_chunks (guint len)
234 {
235 struct _vte_incoming_chunk *chunk = NULL;
236 if (len && free_chunks != NULL) {
237 if (free_chunks->len > len) {
238 struct _vte_incoming_chunk *last;
239 chunk = free_chunks;
240 while (free_chunks->len > len) {
241 last = free_chunks;
242 free_chunks = free_chunks->next;
243 }
244 last->next = NULL;
245 }
246 } else {
247 chunk = free_chunks;
248 free_chunks = NULL;
249 }
250 while (chunk != NULL) {
251 struct _vte_incoming_chunk *next = chunk->next;
252 g_free (chunk);
253 chunk = next;
254 }
255 }
256 static void
_vte_incoming_chunks_release(struct _vte_incoming_chunk * chunk)257 _vte_incoming_chunks_release (struct _vte_incoming_chunk *chunk)
258 {
259 while (chunk) {
260 struct _vte_incoming_chunk *next = chunk->next;
261 release_chunk (chunk);
262 chunk = next;
263 }
264 }
265 static gsize
_vte_incoming_chunks_length(struct _vte_incoming_chunk * chunk)266 _vte_incoming_chunks_length (struct _vte_incoming_chunk *chunk)
267 {
268 gsize len = 0;
269 while (chunk) {
270 len += chunk->len;
271 chunk = chunk->next;
272 }
273 return len;
274 }
275 static gsize
_vte_incoming_chunks_count(struct _vte_incoming_chunk * chunk)276 _vte_incoming_chunks_count (struct _vte_incoming_chunk *chunk)
277 {
278 gsize cnt = 0;
279 while (chunk) {
280 cnt ++;
281 chunk = chunk->next;
282 }
283 return cnt;
284 }
285 static struct _vte_incoming_chunk *
_vte_incoming_chunks_reverse(struct _vte_incoming_chunk * chunk)286 _vte_incoming_chunks_reverse(struct _vte_incoming_chunk *chunk)
287 {
288 struct _vte_incoming_chunk *prev = NULL;
289 while (chunk) {
290 struct _vte_incoming_chunk *next = chunk->next;
291 chunk->next = prev;
292 prev = chunk;
293 chunk = next;
294 }
295 return prev;
296 }
297
298 #ifdef VTE_DEBUG
G_DEFINE_TYPE_WITH_CODE(VteTerminal,vte_terminal,GTK_TYPE_WIDGET,g_type_add_class_private (g_define_type_id,sizeof (VteTerminalClassPrivate));G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,NULL)if (_vte_debug_on (VTE_DEBUG_LIFECYCLE)){ g_printerr("vte_terminal_get_type()\\n"); })299 G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET,
300 g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate));
301 G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL)
302 if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) {
303 g_printerr("vte_terminal_get_type()\n");
304 })
305 #else
306 G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET,
307 g_type_add_class_private (g_define_type_id, sizeof (VteTerminalClassPrivate));
308 G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
309 #endif
310
311 static void
312 vte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
313 {
314 if (array->len >= final_size)
315 return;
316
317 final_size -= array->len;
318 do {
319 g_array_append_vals(array, item, 1);
320 } while (--final_size);
321 }
322
323
324 VteRowData *
_vte_terminal_ring_insert(VteTerminal * terminal,glong position,gboolean fill)325 _vte_terminal_ring_insert (VteTerminal *terminal, glong position, gboolean fill)
326 {
327 VteRowData *row;
328 VteRing *ring = terminal->pvt->screen->row_data;
329 while (G_UNLIKELY (_vte_ring_next (ring) < position)) {
330 row = _vte_ring_append (ring);
331 if (terminal->pvt->fill_defaults.attr.back != VTE_DEFAULT_BG)
332 _vte_row_data_fill (row, &terminal->pvt->fill_defaults, terminal->pvt->column_count);
333 }
334 row = _vte_ring_insert (ring, position);
335 if (fill && terminal->pvt->fill_defaults.attr.back != VTE_DEFAULT_BG)
336 _vte_row_data_fill (row, &terminal->pvt->fill_defaults, terminal->pvt->column_count);
337 return row;
338 }
339
340 VteRowData *
_vte_terminal_ring_append(VteTerminal * terminal,gboolean fill)341 _vte_terminal_ring_append (VteTerminal *terminal, gboolean fill)
342 {
343 return _vte_terminal_ring_insert (terminal, _vte_ring_next (terminal->pvt->screen->row_data), fill);
344 }
345
346 void
_vte_terminal_ring_remove(VteTerminal * terminal,glong position)347 _vte_terminal_ring_remove (VteTerminal *terminal, glong position)
348 {
349 _vte_ring_remove (terminal->pvt->screen->row_data, position);
350 }
351
352 /* Reset defaults for character insertion. */
353 void
_vte_terminal_set_default_attributes(VteTerminal * terminal)354 _vte_terminal_set_default_attributes(VteTerminal *terminal)
355 {
356 terminal->pvt->defaults = basic_cell.cell;
357 terminal->pvt->color_defaults = terminal->pvt->defaults;
358 terminal->pvt->fill_defaults = terminal->pvt->defaults;
359 }
360
361 /* Cause certain cells to be repainted. */
362 void
_vte_invalidate_cells(VteTerminal * terminal,glong column_start,gint column_count,glong row_start,gint row_count)363 _vte_invalidate_cells(VteTerminal *terminal,
364 glong column_start, gint column_count,
365 glong row_start, gint row_count)
366 {
367 cairo_rectangle_int_t rect;
368 glong i;
369
370 if (G_UNLIKELY (!gtk_widget_get_realized(&terminal->widget)))
371 return;
372
373 if (!column_count || !row_count) {
374 return;
375 }
376
377 if (terminal->pvt->invalidated_all) {
378 return;
379 }
380
381 _vte_debug_print (VTE_DEBUG_UPDATES,
382 "Invalidating cells at (%ld,%ld+%ld)x(%d,%d).\n",
383 column_start, row_start,
384 (long)terminal->pvt->screen->scroll_delta,
385 column_count, row_count);
386 _vte_debug_print (VTE_DEBUG_WORK, "?");
387
388 /* Subtract the scrolling offset from the row start so that the
389 * resulting rectangle is relative to the visible portion of the
390 * buffer. */
391 row_start -= terminal->pvt->screen->scroll_delta;
392
393 /* Ensure the start of region is on screen */
394 if (column_start > terminal->pvt->column_count ||
395 row_start > terminal->pvt->row_count) {
396 return;
397 }
398
399 /* Clamp the start values to reasonable numbers. */
400 i = row_start + row_count;
401 row_start = MAX (0, row_start);
402 row_count = CLAMP (i - row_start, 0, terminal->pvt->row_count);
403
404 i = column_start + column_count;
405 column_start = MAX (0, column_start);
406 column_count = CLAMP (i - column_start, 0 , terminal->pvt->column_count);
407
408 if (!column_count || !row_count) {
409 return;
410 }
411 if (column_count == terminal->pvt->column_count &&
412 row_count == terminal->pvt->row_count) {
413 _vte_invalidate_all (terminal);
414 return;
415 }
416
417 /* Convert the column and row start and end to pixel values
418 * by multiplying by the size of a character cell.
419 * Always include the extra pixel border and overlap pixel.
420 */
421 rect.x = column_start * terminal->pvt->char_width - 1;
422 if (column_start != 0) {
423 rect.x += terminal->pvt->padding.left;
424 }
425 rect.width = (column_start + column_count) * terminal->pvt->char_width + 3 + terminal->pvt->padding.left;
426 if (column_start + column_count == terminal->pvt->column_count) {
427 rect.width += terminal->pvt->padding.right;
428 }
429 rect.width -= rect.x;
430
431 rect.y = row_start * terminal->pvt->char_height - 1;
432 if (row_start != 0) {
433 rect.y += terminal->pvt->padding.top;
434 }
435 rect.height = (row_start + row_count) * terminal->pvt->char_height + 2 + terminal->pvt->padding.top;
436 if (row_start + row_count == terminal->pvt->row_count) {
437 rect.height += terminal->pvt->padding.bottom;
438 }
439 rect.height -= rect.y;
440
441 _vte_debug_print (VTE_DEBUG_UPDATES,
442 "Invalidating pixels at (%d,%d)x(%d,%d).\n",
443 rect.x, rect.y, rect.width, rect.height);
444
445 if (terminal->pvt->active != NULL) {
446 terminal->pvt->update_regions = g_slist_prepend (
447 terminal->pvt->update_regions,
448 cairo_region_create_rectangle (&rect));
449 /* Wait a bit before doing any invalidation, just in
450 * case updates are coming in really soon. */
451 add_update_timeout (terminal);
452 } else {
453 gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
454 }
455
456 _vte_debug_print (VTE_DEBUG_WORK, "!");
457 }
458
459 static void
_vte_invalidate_region(VteTerminal * terminal,glong scolumn,glong ecolumn,glong srow,glong erow,gboolean block)460 _vte_invalidate_region (VteTerminal *terminal,
461 glong scolumn, glong ecolumn,
462 glong srow, glong erow,
463 gboolean block)
464 {
465 if (block || srow == erow) {
466 _vte_invalidate_cells(terminal,
467 scolumn, ecolumn - scolumn + 1,
468 srow, erow - srow + 1);
469 } else {
470 _vte_invalidate_cells(terminal,
471 scolumn,
472 terminal->pvt->column_count - scolumn,
473 srow, 1);
474 _vte_invalidate_cells(terminal,
475 0, terminal->pvt->column_count,
476 srow + 1, erow - srow - 1);
477 _vte_invalidate_cells(terminal,
478 0, ecolumn + 1,
479 erow, 1);
480 }
481 }
482
483
484 /* Redraw the entire visible portion of the window. */
485 void
_vte_invalidate_all(VteTerminal * terminal)486 _vte_invalidate_all(VteTerminal *terminal)
487 {
488 cairo_rectangle_int_t rect;
489 GtkAllocation allocation;
490
491 g_assert(VTE_IS_TERMINAL(terminal));
492
493 if (G_UNLIKELY (!gtk_widget_get_realized(&terminal->widget)))
494 return;
495
496 if (terminal->pvt->invalidated_all) {
497 return;
498 }
499
500 _vte_debug_print (VTE_DEBUG_WORK, "*");
501 _vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating all.\n");
502
503 gtk_widget_get_allocation (&terminal->widget, &allocation);
504
505 /* replace invalid regions with one covering the whole terminal */
506 reset_update_regions (terminal);
507 rect.x = rect.y = 0;
508 rect.width = allocation.width;
509 rect.height = allocation.height;
510 terminal->pvt->invalidated_all = TRUE;
511
512 if (terminal->pvt->active != NULL) {
513 terminal->pvt->update_regions = g_slist_prepend (NULL,
514 cairo_region_create_rectangle (&rect));
515 /* Wait a bit before doing any invalidation, just in
516 * case updates are coming in really soon. */
517 add_update_timeout (terminal);
518 } else {
519 gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
520 }
521 }
522
523 /* Scroll a rectangular region up or down by a fixed number of lines,
524 * negative = up, positive = down. */
525 void
_vte_terminal_scroll_region(VteTerminal * terminal,long row,glong count,glong delta)526 _vte_terminal_scroll_region (VteTerminal *terminal,
527 long row, glong count, glong delta)
528 {
529 if ((delta == 0) || (count == 0)) {
530 /* Shenanigans! */
531 return;
532 }
533
534 if (count >= terminal->pvt->row_count) {
535 /* We have to repaint the entire window. */
536 _vte_invalidate_all(terminal);
537 } else {
538 /* We have to repaint the area which is to be
539 * scrolled. */
540 _vte_invalidate_cells(terminal,
541 0, terminal->pvt->column_count,
542 row, count);
543 }
544 }
545
546 /* Find the row in the given position in the backscroll buffer. */
547 static inline const VteRowData *
_vte_terminal_find_row_data(VteTerminal * terminal,glong row)548 _vte_terminal_find_row_data (VteTerminal *terminal, glong row)
549 {
550 const VteRowData *rowdata = NULL;
551 VteScreen *screen = terminal->pvt->screen;
552 if (G_LIKELY (_vte_ring_contains (screen->row_data, row))) {
553 rowdata = _vte_ring_index (screen->row_data, row);
554 }
555 return rowdata;
556 }
557
558 /* Find the row in the given position in the backscroll buffer. */
559 static inline VteRowData *
_vte_terminal_find_row_data_writable(VteTerminal * terminal,glong row)560 _vte_terminal_find_row_data_writable (VteTerminal *terminal, glong row)
561 {
562 VteRowData *rowdata = NULL;
563 VteScreen *screen = terminal->pvt->screen;
564 if (G_LIKELY (_vte_ring_contains (screen->row_data, row))) {
565 rowdata = _vte_ring_index_writable (screen->row_data, row);
566 }
567 return rowdata;
568 }
569
570 /* Find the character an the given position in the backscroll buffer. */
571 static const VteCell *
vte_terminal_find_charcell(VteTerminal * terminal,gulong col,glong row)572 vte_terminal_find_charcell(VteTerminal *terminal, gulong col, glong row)
573 {
574 const VteRowData *rowdata;
575 const VteCell *ret = NULL;
576 VteScreen *screen;
577 screen = terminal->pvt->screen;
578 if (_vte_ring_contains (screen->row_data, row)) {
579 rowdata = _vte_ring_index (screen->row_data, row);
580 ret = _vte_row_data_get (rowdata, col);
581 }
582 return ret;
583 }
584
585 static glong
find_start_column(VteTerminal * terminal,glong col,glong row)586 find_start_column (VteTerminal *terminal, glong col, glong row)
587 {
588 const VteRowData *row_data = _vte_terminal_find_row_data (terminal, row);
589 if (G_UNLIKELY (col < 0))
590 return col;
591 if (row_data != NULL) {
592 const VteCell *cell = _vte_row_data_get (row_data, col);
593 while (col > 0 && cell != NULL && cell->attr.fragment) {
594 cell = _vte_row_data_get (row_data, --col);
595 }
596 }
597 return MAX(col, 0);
598 }
599 static glong
find_end_column(VteTerminal * terminal,glong col,glong row)600 find_end_column (VteTerminal *terminal, glong col, glong row)
601 {
602 const VteRowData *row_data = _vte_terminal_find_row_data (terminal, row);
603 gint columns = 0;
604 if (G_UNLIKELY (col < 0))
605 return col;
606 if (row_data != NULL) {
607 const VteCell *cell = _vte_row_data_get (row_data, col);
608 while (col > 0 && cell != NULL && cell->attr.fragment) {
609 cell = _vte_row_data_get (row_data, --col);
610 }
611 if (cell) {
612 columns = cell->attr.columns - 1;
613 }
614 }
615 return MIN(col + columns, terminal->pvt->column_count);
616 }
617
618
619 /* Determine the width of the portion of the preedit string which lies
620 * to the left of the cursor, or the entire string, in columns. */
621 static gssize
vte_terminal_preedit_width(VteTerminal * terminal,gboolean left_only)622 vte_terminal_preedit_width(VteTerminal *terminal, gboolean left_only)
623 {
624 gunichar c;
625 int i;
626 gssize ret = 0;
627 const char *preedit = NULL;
628
629 if (terminal->pvt->im_preedit != NULL) {
630 preedit = terminal->pvt->im_preedit;
631 for (i = 0;
632 (preedit != NULL) &&
633 (preedit[0] != '\0') &&
634 (!left_only || (i < terminal->pvt->im_preedit_cursor));
635 i++) {
636 c = g_utf8_get_char(preedit);
637 ret += _vte_unichar_width(c, terminal->pvt->utf8_ambiguous_width);
638 preedit = g_utf8_next_char(preedit);
639 }
640 }
641
642 return ret;
643 }
644
645 /* Determine the length of the portion of the preedit string which lies
646 * to the left of the cursor, or the entire string, in gunichars. */
647 static gssize
vte_terminal_preedit_length(VteTerminal * terminal,gboolean left_only)648 vte_terminal_preedit_length(VteTerminal *terminal, gboolean left_only)
649 {
650 int i = 0;
651 const char *preedit = NULL;
652
653 if (terminal->pvt->im_preedit != NULL) {
654 preedit = terminal->pvt->im_preedit;
655 for (i = 0;
656 (preedit != NULL) &&
657 (preedit[0] != '\0') &&
658 (!left_only || (i < terminal->pvt->im_preedit_cursor));
659 i++) {
660 preedit = g_utf8_next_char(preedit);
661 }
662 }
663
664 return i;
665 }
666
667 /* Cause the cell to be redrawn. */
668 void
_vte_invalidate_cell(VteTerminal * terminal,glong col,glong row)669 _vte_invalidate_cell(VteTerminal *terminal, glong col, glong row)
670 {
671 const VteRowData *row_data;
672 int columns;
673 guint style;
674
675 if (G_UNLIKELY (!gtk_widget_get_realized(&terminal->widget)))
676 return;
677
678 if (terminal->pvt->invalidated_all) {
679 return;
680 }
681
682 columns = 1;
683 row_data = _vte_terminal_find_row_data(terminal, row);
684 if (row_data != NULL) {
685 const VteCell *cell;
686 cell = _vte_row_data_get (row_data, col);
687 if (cell != NULL) {
688 while (cell->attr.fragment && col> 0) {
689 cell = _vte_row_data_get (row_data, --col);
690 }
691 columns = cell->attr.columns;
692 style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
693 if (cell->c != 0 &&
694 _vte_draw_get_char_width (
695 terminal->pvt->draw,
696 cell->c, columns, style) >
697 terminal->pvt->char_width * columns) {
698 columns++;
699 }
700 }
701 }
702
703 _vte_debug_print(VTE_DEBUG_UPDATES,
704 "Invalidating cell at (%ld,%ld-%ld).\n",
705 row, col, col + columns);
706 _vte_invalidate_cells(terminal,
707 col, columns,
708 row, 1);
709 }
710
711 /* Cause the cursor to be redrawn. */
712 void
_vte_invalidate_cursor_once(VteTerminal * terminal,gboolean periodic)713 _vte_invalidate_cursor_once(VteTerminal *terminal, gboolean periodic)
714 {
715 const VteCell *cell;
716 gssize preedit_width;
717 glong column, row;
718 gint columns;
719 guint style;
720
721 if (G_UNLIKELY(!gtk_widget_get_realized(&terminal->widget)))
722 return;
723
724 if (terminal->pvt->invalidated_all) {
725 return;
726 }
727
728 if (periodic) {
729 if (!terminal->pvt->cursor_blinks) {
730 return;
731 }
732 }
733
734 if (terminal->pvt->cursor_visible) {
735 preedit_width = vte_terminal_preedit_width(terminal, FALSE);
736
737 row = terminal->pvt->cursor.row;
738 column = terminal->pvt->cursor.col;
739 columns = 1;
740 column = find_start_column (terminal, column, row);
741 cell = vte_terminal_find_charcell(terminal, column, row);
742 if (cell != NULL) {
743 columns = cell->attr.columns;
744 style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
745 if (cell->c != 0 &&
746 _vte_draw_get_char_width (
747 terminal->pvt->draw,
748 cell->c,
749 columns, style) >
750 terminal->pvt->char_width * columns) {
751 columns++;
752 }
753 }
754 if (preedit_width > 0) {
755 columns += preedit_width;
756 columns++; /* one more for the preedit cursor */
757 }
758
759 _vte_debug_print(VTE_DEBUG_UPDATES,
760 "Invalidating cursor at (%ld,%ld-%ld).\n",
761 row, column, column + columns);
762 _vte_invalidate_cells(terminal,
763 column, columns,
764 row, 1);
765 }
766 }
767
768 /* Invalidate the cursor repeatedly. */
769 static gboolean
vte_invalidate_cursor_periodic(VteTerminal * terminal)770 vte_invalidate_cursor_periodic (VteTerminal *terminal)
771 {
772 VteTerminalPrivate *pvt = terminal->pvt;
773
774 pvt->cursor_blink_state = !pvt->cursor_blink_state;
775 pvt->cursor_blink_time += pvt->cursor_blink_cycle;
776
777 _vte_invalidate_cursor_once(terminal, TRUE);
778
779 /* only disable the blink if the cursor is currently shown.
780 * else, wait until next time.
781 */
782 if (pvt->cursor_blink_time / 1000 >= pvt->cursor_blink_timeout &&
783 pvt->cursor_blink_state) {
784 pvt->cursor_blink_tag = 0;
785 return FALSE;
786 }
787
788 pvt->cursor_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
789 terminal->pvt->cursor_blink_cycle,
790 (GSourceFunc)vte_invalidate_cursor_periodic,
791 terminal,
792 NULL);
793 return FALSE;
794 }
795
796 /* Emit a "selection_changed" signal. */
797 static void
vte_terminal_emit_selection_changed(VteTerminal * terminal)798 vte_terminal_emit_selection_changed(VteTerminal *terminal)
799 {
800 _vte_debug_print(VTE_DEBUG_SIGNALS,
801 "Emitting `selection-changed'.\n");
802 g_signal_emit_by_name(terminal, "selection-changed");
803 }
804
805 /* Emit a "commit" signal. */
806 static void
vte_terminal_emit_commit(VteTerminal * terminal,const gchar * text,gssize length)807 vte_terminal_emit_commit(VteTerminal *terminal, const gchar *text, gssize length)
808 {
809 const char *result = NULL;
810 char *wrapped = NULL;
811
812 _vte_debug_print(VTE_DEBUG_SIGNALS,
813 "Emitting `commit' of %" G_GSSIZE_FORMAT" bytes.\n", length);
814
815 if (length == -1) {
816 length = strlen(text);
817 result = text;
818 } else {
819 result = wrapped = g_slice_alloc(length + 1);
820 memcpy(wrapped, text, length);
821 wrapped[length] = '\0';
822 }
823
824 g_signal_emit_by_name(terminal, "commit", result, length);
825
826 if(wrapped)
827 g_slice_free1(length+1, wrapped);
828 }
829
830 /* Emit an "encoding-changed" signal. */
831 static void
vte_terminal_emit_encoding_changed(VteTerminal * terminal)832 vte_terminal_emit_encoding_changed(VteTerminal *terminal)
833 {
834 _vte_debug_print(VTE_DEBUG_SIGNALS,
835 "Emitting `encoding-changed'.\n");
836 g_signal_emit_by_name(terminal, "encoding-changed");
837 g_object_notify(G_OBJECT(terminal), "encoding");
838 }
839
840 /* Emit a "child-exited" signal. */
841 static void
vte_terminal_emit_child_exited(VteTerminal * terminal,int status)842 vte_terminal_emit_child_exited(VteTerminal *terminal,
843 int status)
844 {
845 _vte_debug_print(VTE_DEBUG_SIGNALS,
846 "Emitting `child-exited'.\n");
847 g_signal_emit_by_name(terminal, "child-exited", status);
848 }
849
850 /* Emit a "contents_changed" signal. */
851 static void
vte_terminal_emit_contents_changed(VteTerminal * terminal)852 vte_terminal_emit_contents_changed(VteTerminal *terminal)
853 {
854 if (terminal->pvt->contents_changed_pending) {
855 /* Update dingus match set. */
856 vte_terminal_match_contents_clear(terminal);
857 if (terminal->pvt->mouse_cursor_visible) {
858 vte_terminal_match_hilite_update(terminal,
859 terminal->pvt->mouse_last_x,
860 terminal->pvt->mouse_last_y);
861 }
862
863 _vte_debug_print(VTE_DEBUG_SIGNALS,
864 "Emitting `contents-changed'.\n");
865 g_signal_emit_by_name(terminal, "contents-changed");
866 terminal->pvt->contents_changed_pending = FALSE;
867 }
868 }
869 void
_vte_terminal_queue_contents_changed(VteTerminal * terminal)870 _vte_terminal_queue_contents_changed(VteTerminal *terminal)
871 {
872 _vte_debug_print(VTE_DEBUG_SIGNALS,
873 "Queueing `contents-changed'.\n");
874 terminal->pvt->contents_changed_pending = TRUE;
875 }
876
877 /* Emit a "cursor_moved" signal. */
878 static void
vte_terminal_emit_cursor_moved(VteTerminal * terminal)879 vte_terminal_emit_cursor_moved(VteTerminal *terminal)
880 {
881 if (terminal->pvt->cursor_moved_pending) {
882 _vte_debug_print(VTE_DEBUG_SIGNALS,
883 "Emitting `cursor-moved'.\n");
884 g_signal_emit_by_name(terminal, "cursor-moved");
885 terminal->pvt->cursor_moved_pending = FALSE;
886 }
887 }
888 static void
vte_terminal_queue_cursor_moved(VteTerminal * terminal)889 vte_terminal_queue_cursor_moved(VteTerminal *terminal)
890 {
891 _vte_debug_print(VTE_DEBUG_SIGNALS,
892 "Queueing `cursor-moved'.\n");
893 terminal->pvt->cursor_moved_pending = TRUE;
894 }
895
896 static gboolean
vte_terminal_emit_eof(VteTerminal * terminal)897 vte_terminal_emit_eof(VteTerminal *terminal)
898 {
899 _vte_debug_print(VTE_DEBUG_SIGNALS,
900 "Emitting `eof'.\n");
901 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
902 gdk_threads_enter ();
903 G_GNUC_END_IGNORE_DEPRECATIONS;
904
905 g_signal_emit_by_name(terminal, "eof");
906
907 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
908 gdk_threads_leave ();
909 G_GNUC_END_IGNORE_DEPRECATIONS;
910
911 return FALSE;
912 }
913 /* Emit a "eof" signal. */
914 static void
vte_terminal_queue_eof(VteTerminal * terminal)915 vte_terminal_queue_eof(VteTerminal *terminal)
916 {
917 _vte_debug_print(VTE_DEBUG_SIGNALS,
918 "Queueing `eof'.\n");
919 g_idle_add_full (G_PRIORITY_HIGH,
920 (GSourceFunc) vte_terminal_emit_eof,
921 g_object_ref (terminal),
922 g_object_unref);
923 }
924
925 /* Emit a "char-size-changed" signal. */
926 static void
vte_terminal_emit_char_size_changed(VteTerminal * terminal,guint width,guint height)927 vte_terminal_emit_char_size_changed(VteTerminal *terminal,
928 guint width, guint height)
929 {
930 _vte_debug_print(VTE_DEBUG_SIGNALS,
931 "Emitting `char-size-changed'.\n");
932 g_signal_emit_by_name(terminal, "char-size-changed",
933 width, height);
934 /* g_object_notify(G_OBJECT(terminal), "char-size"); */
935 }
936
937 /* Emit an "increase-font-size" signal. */
938 static void
vte_terminal_emit_increase_font_size(VteTerminal * terminal)939 vte_terminal_emit_increase_font_size(VteTerminal *terminal)
940 {
941 _vte_debug_print(VTE_DEBUG_SIGNALS,
942 "Emitting `increase-font-size'.\n");
943 g_signal_emit_by_name(terminal, "increase-font-size");
944 }
945
946 /* Emit a "decrease-font-size" signal. */
947 static void
vte_terminal_emit_decrease_font_size(VteTerminal * terminal)948 vte_terminal_emit_decrease_font_size(VteTerminal *terminal)
949 {
950 _vte_debug_print(VTE_DEBUG_SIGNALS,
951 "Emitting `decrease-font-size'.\n");
952 g_signal_emit_by_name(terminal, "decrease-font-size");
953 }
954
955 /* Emit a "text-inserted" signal. */
956 void
_vte_terminal_emit_text_inserted(VteTerminal * terminal)957 _vte_terminal_emit_text_inserted(VteTerminal *terminal)
958 {
959 if (!terminal->pvt->accessible_emit) {
960 return;
961 }
962 _vte_debug_print(VTE_DEBUG_SIGNALS,
963 "Emitting `text-inserted'.\n");
964 g_signal_emit_by_name(terminal, "text-inserted");
965 }
966
967 /* Emit a "text-deleted" signal. */
968 void
_vte_terminal_emit_text_deleted(VteTerminal * terminal)969 _vte_terminal_emit_text_deleted(VteTerminal *terminal)
970 {
971 if (!terminal->pvt->accessible_emit) {
972 return;
973 }
974 _vte_debug_print(VTE_DEBUG_SIGNALS,
975 "Emitting `text-deleted'.\n");
976 g_signal_emit_by_name(terminal, "text-deleted");
977 }
978
979 /* Emit a "text-modified" signal. */
980 static void
vte_terminal_emit_text_modified(VteTerminal * terminal)981 vte_terminal_emit_text_modified(VteTerminal *terminal)
982 {
983 if (!terminal->pvt->accessible_emit) {
984 return;
985 }
986 _vte_debug_print(VTE_DEBUG_SIGNALS,
987 "Emitting `text-modified'.\n");
988 g_signal_emit_by_name(terminal, "text-modified");
989 }
990
991 /* Emit a "text-scrolled" signal. */
992 static void
vte_terminal_emit_text_scrolled(VteTerminal * terminal,gint delta)993 vte_terminal_emit_text_scrolled(VteTerminal *terminal, gint delta)
994 {
995 if (!terminal->pvt->accessible_emit) {
996 return;
997 }
998 _vte_debug_print(VTE_DEBUG_SIGNALS,
999 "Emitting `text-scrolled'(%d).\n", delta);
1000 g_signal_emit_by_name(terminal, "text-scrolled", delta);
1001 }
1002
1003 /* Deselect anything which is selected and refresh the screen if needed. */
1004 static void
vte_terminal_deselect_all(VteTerminal * terminal)1005 vte_terminal_deselect_all(VteTerminal *terminal)
1006 {
1007 if (terminal->pvt->has_selection) {
1008 gint sx, sy, ex, ey, extra;
1009
1010 _vte_debug_print(VTE_DEBUG_SELECTION,
1011 "Deselecting all text.\n");
1012
1013 terminal->pvt->has_selection = FALSE;
1014 /* Don't free the current selection, as we need to keep
1015 * hold of it for async copying from the clipboard. */
1016
1017 vte_terminal_emit_selection_changed(terminal);
1018
1019 sx = terminal->pvt->selection_start.col;
1020 sy = terminal->pvt->selection_start.row;
1021 ex = terminal->pvt->selection_end.col;
1022 ey = terminal->pvt->selection_end.row;
1023 extra = terminal->pvt->selection_block_mode ? (VTE_TAB_WIDTH_MAX - 1) : 0;
1024 _vte_invalidate_region(terminal,
1025 MIN (sx, ex), MAX (sx, ex) + extra,
1026 MIN (sy, ey), MAX (sy, ey),
1027 FALSE);
1028 }
1029 }
1030
1031 /* Remove a tabstop. */
1032 void
_vte_terminal_clear_tabstop(VteTerminal * terminal,int column)1033 _vte_terminal_clear_tabstop(VteTerminal *terminal, int column)
1034 {
1035 g_assert(VTE_IS_TERMINAL(terminal));
1036 if (terminal->pvt->tabstops != NULL) {
1037 /* Remove a tab stop from the hash table. */
1038 g_hash_table_remove(terminal->pvt->tabstops,
1039 GINT_TO_POINTER(2 * column + 1));
1040 }
1041 }
1042
1043 /* Check if we have a tabstop at a given position. */
1044 gboolean
_vte_terminal_get_tabstop(VteTerminal * terminal,int column)1045 _vte_terminal_get_tabstop(VteTerminal *terminal, int column)
1046 {
1047 gpointer hash;
1048 g_assert(VTE_IS_TERMINAL(terminal));
1049 if (terminal->pvt->tabstops != NULL) {
1050 hash = g_hash_table_lookup(terminal->pvt->tabstops,
1051 GINT_TO_POINTER(2 * column + 1));
1052 return (hash != NULL);
1053 } else {
1054 return FALSE;
1055 }
1056 }
1057
1058 /* Reset the set of tab stops to the default. */
1059 void
_vte_terminal_set_tabstop(VteTerminal * terminal,int column)1060 _vte_terminal_set_tabstop(VteTerminal *terminal, int column)
1061 {
1062 g_assert(VTE_IS_TERMINAL(terminal));
1063 if (terminal->pvt->tabstops != NULL) {
1064 /* Just set a non-NULL pointer for this column number. */
1065 g_hash_table_insert(terminal->pvt->tabstops,
1066 GINT_TO_POINTER(2 * column + 1),
1067 terminal);
1068 }
1069 }
1070
1071 /* Reset the set of tab stops to the default. */
1072 static void
vte_terminal_set_default_tabstops(VteTerminal * terminal)1073 vte_terminal_set_default_tabstops(VteTerminal *terminal)
1074 {
1075 int i;
1076 if (terminal->pvt->tabstops != NULL) {
1077 g_hash_table_destroy(terminal->pvt->tabstops);
1078 }
1079 terminal->pvt->tabstops = g_hash_table_new(NULL, NULL);
1080 for (i = 0; i <= VTE_TAB_MAX; i += VTE_TAB_WIDTH) {
1081 _vte_terminal_set_tabstop(terminal, i);
1082 }
1083 }
1084
1085 /* Clear the cache of the screen contents we keep. */
1086 static void
vte_terminal_match_contents_clear(VteTerminal * terminal)1087 vte_terminal_match_contents_clear(VteTerminal *terminal)
1088 {
1089 g_assert(VTE_IS_TERMINAL(terminal));
1090 if (terminal->pvt->match_contents != NULL) {
1091 g_free(terminal->pvt->match_contents);
1092 terminal->pvt->match_contents = NULL;
1093 }
1094 if (terminal->pvt->match_attributes != NULL) {
1095 g_array_free(terminal->pvt->match_attributes, TRUE);
1096 terminal->pvt->match_attributes = NULL;
1097 }
1098 vte_terminal_match_hilite_clear(terminal);
1099 }
1100
1101 /* Refresh the cache of the screen contents we keep. */
1102 static gboolean
always_selected(VteTerminal * terminal,glong column,glong row,gpointer data)1103 always_selected(VteTerminal *terminal, glong column, glong row, gpointer data)
1104 {
1105 return TRUE;
1106 }
1107
1108 static void
vte_terminal_match_contents_refresh(VteTerminal * terminal)1109 vte_terminal_match_contents_refresh(VteTerminal *terminal)
1110 {
1111 GArray *array;
1112 vte_terminal_match_contents_clear(terminal);
1113 array = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
1114 terminal->pvt->match_contents = vte_terminal_get_text(terminal,
1115 always_selected,
1116 NULL,
1117 array);
1118 terminal->pvt->match_attributes = array;
1119 }
1120
1121 static void
regex_match_clear_cursor(struct vte_match_regex * regex)1122 regex_match_clear_cursor (struct vte_match_regex *regex)
1123 {
1124 switch (regex->cursor_mode) {
1125 case VTE_REGEX_CURSOR_GDKCURSOR:
1126 if (regex->cursor.cursor != NULL) {
1127 g_object_unref(regex->cursor.cursor);
1128 regex->cursor.cursor = NULL;
1129 }
1130 break;
1131 case VTE_REGEX_CURSOR_GDKCURSORTYPE:
1132 break;
1133 case VTE_REGEX_CURSOR_NAME:
1134 g_free (regex->cursor.cursor_name);
1135 regex->cursor.cursor_name = NULL;
1136 break;
1137 default:
1138 g_assert_not_reached ();
1139 return;
1140 }
1141 }
1142
1143 static void
regex_match_clear(struct vte_match_regex * regex)1144 regex_match_clear (struct vte_match_regex *regex)
1145 {
1146 regex_match_clear_cursor(regex);
1147
1148 g_regex_unref(regex->regex);
1149 regex->regex = NULL;
1150
1151 regex->tag = -1;
1152 }
1153
1154 static void
vte_terminal_set_cursor_from_regex_match(VteTerminal * terminal,struct vte_match_regex * regex)1155 vte_terminal_set_cursor_from_regex_match(VteTerminal *terminal, struct vte_match_regex *regex)
1156 {
1157 GdkCursor *cursor = NULL;
1158
1159 if (! gtk_widget_get_realized (&terminal->widget))
1160 return;
1161
1162 switch (regex->cursor_mode) {
1163 case VTE_REGEX_CURSOR_GDKCURSOR:
1164 if (regex->cursor.cursor != NULL &&
1165 gdk_cursor_get_display(regex->cursor.cursor) == gtk_widget_get_display(&terminal->widget)) {
1166 cursor = g_object_ref(regex->cursor.cursor);
1167 }
1168 break;
1169 case VTE_REGEX_CURSOR_GDKCURSORTYPE:
1170 cursor = gdk_cursor_new_for_display(gtk_widget_get_display(GTK_WIDGET(terminal)), regex->cursor.cursor_type);
1171 break;
1172 case VTE_REGEX_CURSOR_NAME:
1173 cursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(terminal)), regex->cursor.cursor_name);
1174 break;
1175 default:
1176 g_assert_not_reached ();
1177 return;
1178 }
1179
1180 gdk_window_set_cursor (gtk_widget_get_window (&terminal->widget), cursor);
1181
1182 if (cursor)
1183 g_object_unref(cursor);
1184 }
1185
1186 /**
1187 * vte_terminal_match_remove_all:
1188 * @terminal: a #VteTerminal
1189 *
1190 * Clears the list of regular expressions the terminal uses to highlight text
1191 * when the user moves the mouse cursor.
1192 */
1193 void
vte_terminal_match_remove_all(VteTerminal * terminal)1194 vte_terminal_match_remove_all(VteTerminal *terminal)
1195 {
1196 struct vte_match_regex *regex;
1197 guint i;
1198 g_return_if_fail(VTE_IS_TERMINAL(terminal));
1199 for (i = 0; i < terminal->pvt->match_regexes->len; i++) {
1200 regex = &g_array_index(terminal->pvt->match_regexes,
1201 struct vte_match_regex,
1202 i);
1203 /* Unless this is a hole, clean it up. */
1204 if (regex->tag >= 0) {
1205 regex_match_clear (regex);
1206 }
1207 }
1208 g_array_set_size(terminal->pvt->match_regexes, 0);
1209 vte_terminal_match_hilite_clear(terminal);
1210 }
1211
1212 /**
1213 * vte_terminal_match_remove:
1214 * @terminal: a #VteTerminal
1215 * @tag: the tag of the regex to remove
1216 *
1217 * Removes the regular expression which is associated with the given @tag from
1218 * the list of expressions which the terminal will highlight when the user
1219 * moves the mouse cursor over matching text.
1220 */
1221 void
vte_terminal_match_remove(VteTerminal * terminal,int tag)1222 vte_terminal_match_remove(VteTerminal *terminal, int tag)
1223 {
1224 struct vte_match_regex *regex;
1225 g_return_if_fail(VTE_IS_TERMINAL(terminal));
1226 if (terminal->pvt->match_regexes->len > (guint)tag) {
1227 /* The tag is an index, so find the corresponding struct. */
1228 regex = &g_array_index(terminal->pvt->match_regexes,
1229 struct vte_match_regex,
1230 tag);
1231 /* If it's already been removed, return. */
1232 if (regex->tag < 0) {
1233 return;
1234 }
1235 /* Remove this item and leave a hole in its place. */
1236 regex_match_clear (regex);
1237 }
1238 vte_terminal_match_hilite_clear(terminal);
1239 }
1240
1241 static GdkCursor *
vte_terminal_cursor_new(VteTerminal * terminal,GdkCursorType cursor_type)1242 vte_terminal_cursor_new(VteTerminal *terminal, GdkCursorType cursor_type)
1243 {
1244 GdkDisplay *display;
1245 GdkCursor *cursor;
1246
1247 display = gtk_widget_get_display(&terminal->widget);
1248 cursor = gdk_cursor_new_for_display(display, cursor_type);
1249 return cursor;
1250 }
1251
1252 /**
1253 * vte_terminal_match_add_gregex:
1254 * @terminal: a #VteTerminal
1255 * @regex: a #GRegex
1256 * @flags: the #GRegexMatchFlags to use when matching the regex
1257 *
1258 * Adds the regular expression @regex to the list of matching expressions. When the
1259 * user moves the mouse cursor over a section of displayed text which matches
1260 * this expression, the text will be highlighted.
1261 *
1262 * Returns: an integer associated with this expression
1263 */
1264 int
vte_terminal_match_add_gregex(VteTerminal * terminal,GRegex * regex,GRegexMatchFlags flags)1265 vte_terminal_match_add_gregex(VteTerminal *terminal, GRegex *regex, GRegexMatchFlags flags)
1266 {
1267 VteTerminalPrivate *pvt;
1268 struct vte_match_regex new_regex_match, *regex_match;
1269 guint ret, len;
1270
1271 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
1272 g_return_val_if_fail(regex != NULL, -1);
1273
1274 pvt = terminal->pvt;
1275
1276 /* Search for a hole. */
1277 len = pvt->match_regexes->len;
1278 for (ret = 0; ret < len; ret++) {
1279 regex_match = &g_array_index(pvt->match_regexes,
1280 struct vte_match_regex,
1281 ret);
1282 if (regex_match->tag == -1) {
1283 break;
1284 }
1285 }
1286
1287 /* Set the tag to the insertion point. */
1288 new_regex_match.regex = g_regex_ref(regex);
1289 new_regex_match.match_flags = flags;
1290 new_regex_match.tag = ret;
1291 new_regex_match.cursor_mode = VTE_REGEX_CURSOR_GDKCURSORTYPE;
1292 new_regex_match.cursor.cursor_type = VTE_DEFAULT_CURSOR;
1293 if (ret < pvt->match_regexes->len) {
1294 /* Overwrite. */
1295 g_array_index(pvt->match_regexes,
1296 struct vte_match_regex,
1297 ret) = new_regex_match;
1298 } else {
1299 /* Append. */
1300 g_array_append_val(pvt->match_regexes, new_regex_match);
1301 }
1302
1303 return new_regex_match.tag;
1304 }
1305
1306 /**
1307 * vte_terminal_match_set_cursor:
1308 * @terminal: a #VteTerminal
1309 * @tag: the tag of the regex which should use the specified cursor
1310 * @cursor: (allow-none): the #GdkCursor which the terminal should use when the pattern is
1311 * highlighted, or %NULL to use the standard cursor
1312 *
1313 * Sets which cursor the terminal will use if the pointer is over the pattern
1314 * specified by @tag. The terminal keeps a reference to @cursor.
1315 *
1316 * Deprecated: 0.40: Use vte_terminal_match_set_cursor_type() or vte_terminal_match_set_cursor_named() instead.
1317 */
1318 void
vte_terminal_match_set_cursor(VteTerminal * terminal,int tag,GdkCursor * cursor)1319 vte_terminal_match_set_cursor(VteTerminal *terminal, int tag, GdkCursor *cursor)
1320 {
1321 struct vte_match_regex *regex;
1322 g_return_if_fail(VTE_IS_TERMINAL(terminal));
1323 g_return_if_fail((guint) tag < terminal->pvt->match_regexes->len);
1324 regex = &g_array_index(terminal->pvt->match_regexes,
1325 struct vte_match_regex,
1326 tag);
1327 regex_match_clear_cursor(regex);
1328 regex->cursor_mode = VTE_REGEX_CURSOR_GDKCURSOR;
1329 regex->cursor.cursor = cursor ? g_object_ref(cursor) : NULL;
1330 vte_terminal_match_hilite_clear(terminal);
1331 }
1332
1333 /**
1334 * vte_terminal_match_set_cursor_type:
1335 * @terminal: a #VteTerminal
1336 * @tag: the tag of the regex which should use the specified cursor
1337 * @cursor_type: a #GdkCursorType
1338 *
1339 * Sets which cursor the terminal will use if the pointer is over the pattern
1340 * specified by @tag.
1341 */
1342 void
vte_terminal_match_set_cursor_type(VteTerminal * terminal,int tag,GdkCursorType cursor_type)1343 vte_terminal_match_set_cursor_type(VteTerminal *terminal,
1344 int tag, GdkCursorType cursor_type)
1345 {
1346 struct vte_match_regex *regex;
1347 g_return_if_fail(VTE_IS_TERMINAL(terminal));
1348 g_return_if_fail((guint) tag < terminal->pvt->match_regexes->len);
1349 regex = &g_array_index(terminal->pvt->match_regexes,
1350 struct vte_match_regex,
1351 tag);
1352 regex_match_clear_cursor(regex);
1353 regex->cursor_mode = VTE_REGEX_CURSOR_GDKCURSORTYPE;
1354 regex->cursor.cursor_type = cursor_type;
1355 vte_terminal_match_hilite_clear(terminal);
1356 }
1357
1358 /**
1359 * vte_terminal_match_set_cursor_name:
1360 * @terminal: a #VteTerminal
1361 * @tag: the tag of the regex which should use the specified cursor
1362 * @cursor_name: the name of the cursor
1363 *
1364 * Sets which cursor the terminal will use if the pointer is over the pattern
1365 * specified by @tag.
1366 */
1367 void
vte_terminal_match_set_cursor_name(VteTerminal * terminal,int tag,const char * cursor_name)1368 vte_terminal_match_set_cursor_name(VteTerminal *terminal,
1369 int tag, const char *cursor_name)
1370 {
1371 struct vte_match_regex *regex;
1372 g_return_if_fail(VTE_IS_TERMINAL(terminal));
1373 g_return_if_fail(cursor_name != NULL);
1374 g_return_if_fail((guint) tag < terminal->pvt->match_regexes->len);
1375 regex = &g_array_index(terminal->pvt->match_regexes,
1376 struct vte_match_regex,
1377 tag);
1378 regex_match_clear_cursor(regex);
1379 regex->cursor_mode = VTE_REGEX_CURSOR_NAME;
1380 regex->cursor.cursor_name = g_strdup (cursor_name);
1381 vte_terminal_match_hilite_clear(terminal);
1382 }
1383
1384 /* Check if a given cell on the screen contains part of a matched string. If
1385 * it does, return the string, and store the match tag in the optional tag
1386 * argument. */
1387 static char *
vte_terminal_match_check_internal_gregex(VteTerminal * terminal,long column,glong row,int * tag,int * start,int * end)1388 vte_terminal_match_check_internal_gregex(VteTerminal *terminal,
1389 long column, glong row,
1390 int *tag, int *start, int *end)
1391 {
1392 gint start_blank, end_blank;
1393 guint i;
1394 int offset;
1395 struct vte_match_regex *regex = NULL;
1396 struct _VteCharAttributes *attr = NULL;
1397 gssize sattr, eattr;
1398 gchar *line, eol;
1399 GMatchInfo *match_info;
1400
1401 _vte_debug_print(VTE_DEBUG_EVENTS,
1402 "Checking for gregex match at (%ld,%ld).\n", row, column);
1403 if (tag != NULL) {
1404 *tag = -1;
1405 }
1406 if (start != NULL) {
1407 *start = 0;
1408 }
1409 if (end != NULL) {
1410 *end = 0;
1411 }
1412 /* Map the pointer position to a portion of the string. */
1413 eattr = terminal->pvt->match_attributes->len;
1414 for (offset = eattr; offset--; ) {
1415 attr = &g_array_index(terminal->pvt->match_attributes,
1416 struct _VteCharAttributes,
1417 offset);
1418 if (row < attr->row) {
1419 eattr = offset;
1420 }
1421 if (row == attr->row &&
1422 column == attr->column &&
1423 terminal->pvt->match_contents[offset] != ' ') {
1424 break;
1425 }
1426 }
1427
1428 _VTE_DEBUG_IF(VTE_DEBUG_EVENTS) {
1429 if (offset < 0)
1430 g_printerr("Cursor is not on a character.\n");
1431 else
1432 g_printerr("Cursor is on character '%c' at %d.\n",
1433 g_utf8_get_char (terminal->pvt->match_contents + offset),
1434 offset);
1435 }
1436
1437 /* If the pointer isn't on a matchable character, bug out. */
1438 if (offset < 0) {
1439 return NULL;
1440 }
1441
1442 /* If the pointer is on a newline, bug out. */
1443 if ((g_ascii_isspace(terminal->pvt->match_contents[offset])) ||
1444 (terminal->pvt->match_contents[offset] == '\0')) {
1445 _vte_debug_print(VTE_DEBUG_EVENTS,
1446 "Cursor is on whitespace.\n");
1447 return NULL;
1448 }
1449
1450 /* Snip off any final newlines. */
1451 while (terminal->pvt->match_contents[eattr] == '\n' ||
1452 terminal->pvt->match_contents[eattr] == '\0') {
1453 eattr--;
1454 }
1455 /* and scan forwards to find the end of this line */
1456 while (!(terminal->pvt->match_contents[eattr] == '\n' ||
1457 terminal->pvt->match_contents[eattr] == '\0')) {
1458 eattr++;
1459 }
1460
1461 /* find the start of row */
1462 if (row == 0) {
1463 sattr = 0;
1464 } else {
1465 for (sattr = offset; sattr > 0; sattr--) {
1466 attr = &g_array_index(terminal->pvt->match_attributes,
1467 struct _VteCharAttributes,
1468 sattr);
1469 if (row > attr->row) {
1470 break;
1471 }
1472 }
1473 }
1474 /* Scan backwards to find the start of this line */
1475 while (sattr > 0 &&
1476 ! (terminal->pvt->match_contents[sattr] == '\n' ||
1477 terminal->pvt->match_contents[sattr] == '\0')) {
1478 sattr--;
1479 }
1480 /* and skip any initial newlines. */
1481 while (terminal->pvt->match_contents[sattr] == '\n' ||
1482 terminal->pvt->match_contents[sattr] == '\0') {
1483 sattr++;
1484 }
1485 if (eattr <= sattr) { /* blank line */
1486 return NULL;
1487 }
1488 if (eattr <= offset || sattr > offset) {
1489 /* nothing to match on this line */
1490 return NULL;
1491 }
1492 offset -= sattr;
1493 eattr -= sattr;
1494
1495 /* temporarily shorten the contents to this row */
1496 line = terminal->pvt->match_contents + sattr;
1497 eol = line[eattr];
1498 line[eattr] = '\0';
1499
1500 start_blank = 0;
1501 end_blank = eattr;
1502
1503 /* Now iterate over each regex we need to match against. */
1504 for (i = 0; i < terminal->pvt->match_regexes->len; i++) {
1505 regex = &g_array_index(terminal->pvt->match_regexes,
1506 struct vte_match_regex,
1507 i);
1508 /* Skip holes. */
1509 if (regex->tag < 0) {
1510 continue;
1511 }
1512 /* We'll only match the first item in the buffer which
1513 * matches, so we'll have to skip each match until we
1514 * stop getting matches. */
1515 if (!g_regex_match_full(regex->regex,
1516 line, -1, 0,
1517 regex->match_flags,
1518 &match_info,
1519 NULL)) {
1520 g_match_info_free(match_info);
1521 continue;
1522 }
1523
1524 while (g_match_info_matches(match_info)) {
1525 gint ko = offset;
1526 gint sblank=G_MININT, eblank=G_MAXINT;
1527 gint rm_so, rm_eo;
1528
1529 if (g_match_info_fetch_pos (match_info, 0, &rm_so, &rm_eo)) {
1530 /* The offsets should be "sane". */
1531 g_assert(rm_so < eattr);
1532 g_assert(rm_eo <= eattr);
1533 _VTE_DEBUG_IF(VTE_DEBUG_MISC) {
1534 gchar *match;
1535 struct _VteCharAttributes *_sattr, *_eattr;
1536 match = g_strndup(line + rm_so, rm_eo - rm_so);
1537 _sattr = &g_array_index(terminal->pvt->match_attributes,
1538 struct _VteCharAttributes,
1539 rm_so);
1540 _eattr = &g_array_index(terminal->pvt->match_attributes,
1541 struct _VteCharAttributes,
1542 rm_eo - 1);
1543 g_printerr("Match `%s' from %d(%ld,%ld) to %d(%ld,%ld) (%d).\n",
1544 match,
1545 rm_so,
1546 _sattr->column,
1547 _sattr->row,
1548 rm_eo - 1,
1549 _eattr->column,
1550 _eattr->row,
1551 offset);
1552 g_free(match);
1553
1554 }
1555 /* If the pointer is in this substring,
1556 * then we're done. */
1557 if (ko >= rm_so &&
1558 ko < rm_eo) {
1559 gchar *result;
1560 if (tag != NULL) {
1561 *tag = regex->tag;
1562 }
1563 if (start != NULL) {
1564 *start = sattr + rm_so;
1565 }
1566 if (end != NULL) {
1567 *end = sattr + rm_eo - 1;
1568 }
1569 vte_terminal_set_cursor_from_regex_match(terminal, regex);
1570 result = g_match_info_fetch(match_info, 0);
1571 line[eattr] = eol;
1572
1573 g_match_info_free(match_info);
1574 return result;
1575 }
1576 if (ko > rm_eo &&
1577 rm_eo > sblank) {
1578 sblank = rm_eo;
1579 }
1580 if (ko < rm_so &&
1581 rm_so < eblank) {
1582 eblank = rm_so;
1583 }
1584 }
1585 if (sblank > start_blank) {
1586 start_blank = sblank;
1587 }
1588 if (eblank < end_blank) {
1589 end_blank = eblank;
1590 }
1591
1592 g_match_info_next(match_info, NULL);
1593 }
1594
1595 g_match_info_free(match_info);
1596 }
1597 line[eattr] = eol;
1598 if (start != NULL) {
1599 *start = sattr + start_blank;
1600 }
1601 if (end != NULL) {
1602 *end = sattr + end_blank;
1603 }
1604 return NULL;
1605 }
1606
1607 static char *
vte_terminal_match_check_internal(VteTerminal * terminal,long column,glong row,int * tag,int * start,int * end)1608 vte_terminal_match_check_internal(VteTerminal *terminal,
1609 long column, glong row,
1610 int *tag, int *start, int *end)
1611 {
1612 if (terminal->pvt->match_contents == NULL) {
1613 vte_terminal_match_contents_refresh(terminal);
1614 }
1615
1616 return vte_terminal_match_check_internal_gregex(terminal, column, row, tag, start, end);
1617 }
1618
1619 static gboolean
rowcol_inside_match(VteTerminal * terminal,glong row,glong col)1620 rowcol_inside_match (VteTerminal *terminal, glong row, glong col)
1621 {
1622 if (terminal->pvt->match_start.row == terminal->pvt->match_end.row) {
1623 return row == terminal->pvt->match_start.row &&
1624 col >= terminal->pvt->match_start.col &&
1625 col <= terminal->pvt->match_end.col;
1626 } else {
1627 if (row < terminal->pvt->match_start.row ||
1628 row > terminal->pvt->match_end.row) {
1629 return FALSE;
1630 }
1631 if (row == terminal->pvt->match_start.row) {
1632 return col >= terminal->pvt->match_start.col;
1633 }
1634 if (row == terminal->pvt->match_end.row) {
1635 return col <= terminal->pvt->match_end.col;
1636 }
1637 return TRUE;
1638 }
1639 }
1640
1641 /**
1642 * vte_terminal_match_check:
1643 * @terminal: a #VteTerminal
1644 * @column: the text column
1645 * @row: the text row
1646 * @tag: (out) (allow-none): a location to store the tag, or %NULL
1647 *
1648 * Checks if the text in and around the specified position matches any of the
1649 * regular expressions previously set using vte_terminal_match_add(). If a
1650 * match exists, the text string is returned and if @tag is not %NULL, the number
1651 * associated with the matched regular expression will be stored in @tag.
1652 *
1653 * If more than one regular expression has been set with
1654 * vte_terminal_match_add(), then expressions are checked in the order in
1655 * which they were added.
1656 *
1657 * Returns: (transfer full): a newly allocated string which matches one of the previously
1658 * set regular expressions
1659 */
1660 char *
vte_terminal_match_check(VteTerminal * terminal,glong column,glong row,int * tag)1661 vte_terminal_match_check(VteTerminal *terminal, glong column, glong row,
1662 int *tag)
1663 {
1664 long delta;
1665 char *ret;
1666 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
1667 delta = terminal->pvt->screen->scroll_delta;
1668 _vte_debug_print(VTE_DEBUG_EVENTS,
1669 "Checking for match at (%ld,%ld).\n",
1670 row, column);
1671 if (rowcol_inside_match (terminal, row + delta, column)) {
1672 if (tag) {
1673 *tag = terminal->pvt->match_tag;
1674 }
1675 ret = terminal->pvt->match != NULL ?
1676 g_strdup (terminal->pvt->match) :
1677 NULL;
1678 } else {
1679 ret = vte_terminal_match_check_internal(terminal,
1680 column, row + delta,
1681 tag, NULL, NULL);
1682 }
1683 _VTE_DEBUG_IF(VTE_DEBUG_EVENTS) {
1684 if (ret != NULL) g_printerr("Matched `%s'.\n", ret);
1685 }
1686 return ret;
1687 }
1688
1689 /**
1690 * vte_terminal_match_check_event:
1691 * @terminal: a #VteTerminal
1692 * @event: a #GdkEvent
1693 * @tag: (out) (allow-none): a location to store the tag, or %NULL
1694 *
1695 * Checks if the text in and around the position of the event matches any of the
1696 * regular expressions previously set using vte_terminal_match_add(). If a
1697 * match exists, the text string is returned and if @tag is not %NULL, the number
1698 * associated with the matched regular expression will be stored in @tag.
1699 *
1700 * If more than one regular expression has been set with
1701 * vte_terminal_match_add(), then expressions are checked in the order in
1702 * which they were added.
1703 *
1704 * Returns: (transfer full): a newly allocated string which matches one of the previously
1705 * set regular expressions
1706 */
1707 char *
vte_terminal_match_check_event(VteTerminal * terminal,GdkEvent * event,int * tag)1708 vte_terminal_match_check_event(VteTerminal *terminal,
1709 GdkEvent *event,
1710 int *tag)
1711 {
1712 double x, y;
1713 long col, row;
1714
1715 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
1716
1717 if (event == NULL)
1718 return FALSE;
1719 if (((GdkEventAny*)event)->window != gtk_widget_get_window(&terminal->widget))
1720 return FALSE;
1721 if (!gdk_event_get_coords(event, &x, &y))
1722 return FALSE;
1723 if (!_vte_terminal_xy_to_grid(terminal, x, y, &col, &row))
1724 return FALSE;
1725
1726 return vte_terminal_match_check(terminal, col, row, tag);
1727 }
1728
1729 /* Emit an adjustment changed signal on our adjustment object. */
1730 static void
vte_terminal_emit_adjustment_changed(VteTerminal * terminal)1731 vte_terminal_emit_adjustment_changed(VteTerminal *terminal)
1732 {
1733 if (terminal->pvt->adjustment_changed_pending) {
1734 VteScreen *screen = terminal->pvt->screen;
1735 gboolean changed = FALSE;
1736 glong v;
1737 gdouble current;
1738
1739 g_object_freeze_notify (G_OBJECT (terminal->pvt->vadjustment));
1740
1741 v = _vte_ring_delta (screen->row_data);
1742 current = gtk_adjustment_get_lower(terminal->pvt->vadjustment);
1743 if (current != v) {
1744 _vte_debug_print(VTE_DEBUG_ADJ,
1745 "Changing lower bound from %.0f to %ld\n",
1746 current, v);
1747 gtk_adjustment_set_lower(terminal->pvt->vadjustment, v);
1748 changed = TRUE;
1749 }
1750
1751 /* The upper value is the number of rows which might be visible. (Add
1752 * one to the cursor offset because it's zero-based.) */
1753 v = MAX(_vte_ring_next(screen->row_data),
1754 terminal->pvt->cursor.row + 1);
1755 current = gtk_adjustment_get_upper(terminal->pvt->vadjustment);
1756 if (current != v) {
1757 _vte_debug_print(VTE_DEBUG_ADJ,
1758 "Changing upper bound from %.0f to %ld\n",
1759 current, v);
1760 gtk_adjustment_set_upper(terminal->pvt->vadjustment, v);
1761 changed = TRUE;
1762 }
1763
1764 g_object_thaw_notify (G_OBJECT (terminal->pvt->vadjustment));
1765
1766 if (changed)
1767 _vte_debug_print(VTE_DEBUG_SIGNALS,
1768 "Emitting adjustment_changed.\n");
1769 terminal->pvt->adjustment_changed_pending = FALSE;
1770 }
1771 if (terminal->pvt->adjustment_value_changed_pending) {
1772 glong v, delta;
1773 _vte_debug_print(VTE_DEBUG_SIGNALS,
1774 "Emitting adjustment_value_changed.\n");
1775 terminal->pvt->adjustment_value_changed_pending = FALSE;
1776 v = round (gtk_adjustment_get_value(terminal->pvt->vadjustment));
1777 if (v != terminal->pvt->screen->scroll_delta) {
1778 /* this little dance is so that the scroll_delta is
1779 * updated immediately, but we still handled scrolling
1780 * via the adjustment - e.g. user interaction with the
1781 * scrollbar
1782 */
1783 delta = terminal->pvt->screen->scroll_delta;
1784 terminal->pvt->screen->scroll_delta = v;
1785 gtk_adjustment_set_value(terminal->pvt->vadjustment, delta);
1786 }
1787 }
1788 }
1789
1790 /* Queue an adjustment-changed signal to be delivered when convenient. */
1791 static inline void
vte_terminal_queue_adjustment_changed(VteTerminal * terminal)1792 vte_terminal_queue_adjustment_changed(VteTerminal *terminal)
1793 {
1794 terminal->pvt->adjustment_changed_pending = TRUE;
1795 add_update_timeout (terminal);
1796 }
1797
1798 static void
vte_terminal_queue_adjustment_value_changed(VteTerminal * terminal,glong v)1799 vte_terminal_queue_adjustment_value_changed(VteTerminal *terminal, glong v)
1800 {
1801 if (v != terminal->pvt->screen->scroll_delta) {
1802 terminal->pvt->screen->scroll_delta = v;
1803 terminal->pvt->adjustment_value_changed_pending = TRUE;
1804 add_update_timeout (terminal);
1805 }
1806 }
1807
1808 static void
vte_terminal_queue_adjustment_value_changed_clamped(VteTerminal * terminal,glong v)1809 vte_terminal_queue_adjustment_value_changed_clamped(VteTerminal *terminal, glong v)
1810 {
1811 gdouble lower, upper;
1812
1813 lower = gtk_adjustment_get_lower(terminal->pvt->vadjustment);
1814 upper = gtk_adjustment_get_upper(terminal->pvt->vadjustment);
1815
1816 v = CLAMP(v, lower, MAX (lower, upper - terminal->pvt->row_count));
1817
1818 vte_terminal_queue_adjustment_value_changed (terminal, v);
1819 }
1820
1821
1822 void
_vte_terminal_adjust_adjustments(VteTerminal * terminal)1823 _vte_terminal_adjust_adjustments(VteTerminal *terminal)
1824 {
1825 VteScreen *screen;
1826 long delta;
1827
1828 g_assert(terminal->pvt->screen != NULL);
1829 g_assert(terminal->pvt->screen->row_data != NULL);
1830
1831 vte_terminal_queue_adjustment_changed(terminal);
1832
1833 /* The lower value should be the first row in the buffer. */
1834 screen = terminal->pvt->screen;
1835 delta = _vte_ring_delta(screen->row_data);
1836 /* Snap the insert delta and the cursor position to be in the visible
1837 * area. Leave the scrolling delta alone because it will be updated
1838 * when the adjustment changes. */
1839 screen->insert_delta = MAX(screen->insert_delta, delta);
1840 terminal->pvt->cursor.row = MAX(terminal->pvt->cursor.row,
1841 screen->insert_delta);
1842
1843 if (screen->scroll_delta > screen->insert_delta) {
1844 vte_terminal_queue_adjustment_value_changed(terminal,
1845 screen->insert_delta);
1846 }
1847 }
1848
1849 /* Update the adjustment field of the widget. This function should be called
1850 * whenever we add rows to or remove rows from the history or switch screens. */
1851 static void
_vte_terminal_adjust_adjustments_full(VteTerminal * terminal)1852 _vte_terminal_adjust_adjustments_full (VteTerminal *terminal)
1853 {
1854 gboolean changed = FALSE;
1855 gdouble v;
1856
1857 g_assert(terminal->pvt->screen != NULL);
1858 g_assert(terminal->pvt->screen->row_data != NULL);
1859
1860 _vte_terminal_adjust_adjustments(terminal);
1861
1862 g_object_freeze_notify(G_OBJECT(terminal->pvt->vadjustment));
1863
1864 /* The step increment should always be one. */
1865 v = gtk_adjustment_get_step_increment(terminal->pvt->vadjustment);
1866 if (v != 1) {
1867 _vte_debug_print(VTE_DEBUG_ADJ,
1868 "Changing step increment from %.0lf to %ld\n",
1869 v, terminal->pvt->row_count);
1870 gtk_adjustment_set_step_increment(terminal->pvt->vadjustment, 1);
1871 changed = TRUE;
1872 }
1873
1874 /* Set the number of rows the user sees to the number of rows the
1875 * user sees. */
1876 v = gtk_adjustment_get_page_size(terminal->pvt->vadjustment);
1877 if (v != terminal->pvt->row_count) {
1878 _vte_debug_print(VTE_DEBUG_ADJ,
1879 "Changing page size from %.0f to %ld\n",
1880 v, terminal->pvt->row_count);
1881 gtk_adjustment_set_page_size(terminal->pvt->vadjustment,
1882 terminal->pvt->row_count);
1883 changed = TRUE;
1884 }
1885
1886 /* Clicking in the empty area should scroll one screen, so set the
1887 * page size to the number of visible rows. */
1888 v = gtk_adjustment_get_page_increment(terminal->pvt->vadjustment);
1889 if (v != terminal->pvt->row_count) {
1890 _vte_debug_print(VTE_DEBUG_ADJ,
1891 "Changing page increment from "
1892 "%.0f to %ld\n",
1893 v, terminal->pvt->row_count);
1894 gtk_adjustment_set_page_increment(terminal->pvt->vadjustment,
1895 terminal->pvt->row_count);
1896 changed = TRUE;
1897 }
1898
1899 g_object_thaw_notify(G_OBJECT(terminal->pvt->vadjustment));
1900
1901 if (changed)
1902 _vte_debug_print(VTE_DEBUG_SIGNALS,
1903 "Emitting adjustment_changed.\n");
1904 }
1905
1906 /* Scroll a fixed number of lines up or down in the current screen. */
1907 static void
vte_terminal_scroll_lines(VteTerminal * terminal,gint lines)1908 vte_terminal_scroll_lines(VteTerminal *terminal, gint lines)
1909 {
1910 glong destination;
1911 _vte_debug_print(VTE_DEBUG_ADJ, "Scrolling %d lines.\n", lines);
1912 /* Calculate the ideal position where we want to be before clamping. */
1913 destination = terminal->pvt->screen->scroll_delta;
1914 destination += lines;
1915 /* Tell the scrollbar to adjust itself. */
1916 vte_terminal_queue_adjustment_value_changed_clamped (terminal, destination);
1917 }
1918
1919 /* Scroll a fixed number of pages up or down, in the current screen. */
1920 static void
vte_terminal_scroll_pages(VteTerminal * terminal,gint pages)1921 vte_terminal_scroll_pages(VteTerminal *terminal, gint pages)
1922 {
1923 vte_terminal_scroll_lines(terminal, pages * terminal->pvt->row_count);
1924 }
1925
1926 /* Scroll so that the scroll delta is the minimum value. */
1927 static void
vte_terminal_maybe_scroll_to_top(VteTerminal * terminal)1928 vte_terminal_maybe_scroll_to_top(VteTerminal *terminal)
1929 {
1930 vte_terminal_queue_adjustment_value_changed (terminal,
1931 _vte_ring_delta(terminal->pvt->screen->row_data));
1932 }
1933
1934 static void
vte_terminal_maybe_scroll_to_bottom(VteTerminal * terminal)1935 vte_terminal_maybe_scroll_to_bottom(VteTerminal *terminal)
1936 {
1937 glong delta;
1938 delta = terminal->pvt->screen->insert_delta;
1939 vte_terminal_queue_adjustment_value_changed (terminal, delta);
1940 _vte_debug_print(VTE_DEBUG_ADJ,
1941 "Snapping to bottom of screen\n");
1942 }
1943
1944 static void
_vte_terminal_setup_utf8(VteTerminal * terminal)1945 _vte_terminal_setup_utf8 (VteTerminal *terminal)
1946 {
1947 VteTerminalPrivate *pvt = terminal->pvt;
1948 GError *error = NULL;
1949
1950 if (!vte_pty_set_utf8(pvt->pty,
1951 strcmp(terminal->pvt->encoding, "UTF-8") == 0,
1952 &error)) {
1953 g_warning ("Failed to set UTF8 mode: %s\n", error->message);
1954 g_error_free (error);
1955 }
1956 }
1957
1958 /**
1959 * vte_terminal_set_encoding:
1960 * @terminal: a #VteTerminal
1961 * @codeset: (allow-none): a valid #GIConv target, or %NULL to use UTF-8
1962 * @error: (allow-none): return location for a #GError, or %NULL
1963 *
1964 * Changes the encoding the terminal will expect data from the child to
1965 * be encoded with. For certain terminal types, applications executing in the
1966 * terminal can change the encoding. If @codeset is %NULL, it uses "UTF-8".
1967 *
1968 * Returns: %TRUE if the encoding could be changed to the specified one,
1969 * or %FALSE with @error set to %G_CONVERT_ERROR_NO_CONVERSION.
1970 */
1971 gboolean
vte_terminal_set_encoding(VteTerminal * terminal,const char * codeset,GError ** error)1972 vte_terminal_set_encoding(VteTerminal *terminal,
1973 const char *codeset,
1974 GError **error)
1975 {
1976 VteTerminalPrivate *pvt;
1977 GObject *object;
1978 const char *old_codeset;
1979 VteConv conv;
1980 char *obuf1, *obuf2;
1981 gsize bytes_written;
1982
1983 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
1984 g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1985
1986 object = G_OBJECT(terminal);
1987 pvt = terminal->pvt;
1988
1989 old_codeset = pvt->encoding;
1990 if (codeset == NULL) {
1991 codeset = "UTF-8";
1992 }
1993 if ((old_codeset != NULL) && g_str_equal(codeset, old_codeset)) {
1994 /* Nothing to do! */
1995 return TRUE;
1996 }
1997
1998 /* Open new conversions. */
1999 conv = _vte_conv_open(codeset, "UTF-8");
2000 if (conv == VTE_INVALID_CONV) {
2001 g_set_error(error, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION,
2002 _("Unable to convert characters from %s to %s."),
2003 "UTF-8", codeset);
2004 return FALSE;
2005 }
2006
2007 g_object_freeze_notify(object);
2008
2009 if (terminal->pvt->outgoing_conv != VTE_INVALID_CONV) {
2010 _vte_conv_close(terminal->pvt->outgoing_conv);
2011 }
2012 terminal->pvt->outgoing_conv = conv;
2013
2014 /* Set the terminal's encoding to the new value. */
2015 terminal->pvt->encoding = g_intern_string(codeset);
2016
2017 /* Convert any buffered output bytes. */
2018 if ((_vte_byte_array_length(terminal->pvt->outgoing) > 0) &&
2019 (old_codeset != NULL)) {
2020 /* Convert back to UTF-8. */
2021 obuf1 = g_convert((gchar *)terminal->pvt->outgoing->data,
2022 _vte_byte_array_length(terminal->pvt->outgoing),
2023 "UTF-8",
2024 old_codeset,
2025 NULL,
2026 &bytes_written,
2027 NULL);
2028 if (obuf1 != NULL) {
2029 /* Convert to the new encoding. */
2030 obuf2 = g_convert(obuf1,
2031 bytes_written,
2032 codeset,
2033 "UTF-8",
2034 NULL,
2035 &bytes_written,
2036 NULL);
2037 if (obuf2 != NULL) {
2038 _vte_byte_array_clear(terminal->pvt->outgoing);
2039 _vte_byte_array_append(terminal->pvt->outgoing,
2040 obuf2, bytes_written);
2041 g_free(obuf2);
2042 }
2043 g_free(obuf1);
2044 }
2045 }
2046
2047 /* Set the encoding for incoming text. */
2048 _vte_iso2022_state_set_codeset(terminal->pvt->iso2022,
2049 terminal->pvt->encoding);
2050
2051 _vte_debug_print(VTE_DEBUG_IO,
2052 "Set terminal encoding to `%s'.\n",
2053 terminal->pvt->encoding);
2054 vte_terminal_emit_encoding_changed(terminal);
2055
2056 g_object_thaw_notify(object);
2057
2058 return TRUE;
2059 }
2060
2061 /**
2062 * vte_terminal_get_encoding:
2063 * @terminal: a #VteTerminal
2064 *
2065 * Determines the name of the encoding in which the terminal expects data to be
2066 * encoded.
2067 *
2068 * Returns: (transfer none): the current encoding for the terminal
2069 */
2070 const char *
vte_terminal_get_encoding(VteTerminal * terminal)2071 vte_terminal_get_encoding(VteTerminal *terminal)
2072 {
2073 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
2074 return terminal->pvt->encoding;
2075 }
2076
2077 /**
2078 * vte_terminal_set_cjk_ambiguous_width:
2079 * @terminal: a #VteTerminal
2080 * @width: either 1 (narrow) or 2 (wide)
2081 *
2082 * This setting controls whether ambiguous-width characters are narrow or wide
2083 * when using the UTF-8 encoding (vte_terminal_set_encoding()). In all other encodings,
2084 * the width of ambiguous-width characters is fixed.
2085 */
2086 void
vte_terminal_set_cjk_ambiguous_width(VteTerminal * terminal,int width)2087 vte_terminal_set_cjk_ambiguous_width(VteTerminal *terminal, int width)
2088 {
2089 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2090 g_return_if_fail(width == 1 || width == 2);
2091
2092 terminal->pvt->utf8_ambiguous_width = width;
2093 }
2094
2095 /**
2096 * vte_terminal_get_cjk_ambiguous_width:
2097 * @terminal: a #VteTerminal
2098 *
2099 * Returns whether ambiguous-width characters are narrow or wide when using
2100 * the UTF-8 encoding (vte_terminal_set_encoding()).
2101 *
2102 * Returns: 1 if ambiguous-width characters are narrow, or 2 if they are wide
2103 */
2104 int
vte_terminal_get_cjk_ambiguous_width(VteTerminal * terminal)2105 vte_terminal_get_cjk_ambiguous_width(VteTerminal *terminal)
2106 {
2107 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), 1);
2108 return terminal->pvt->utf8_ambiguous_width;
2109 }
2110
2111 static inline VteRowData *
vte_terminal_insert_rows(VteTerminal * terminal,guint cnt)2112 vte_terminal_insert_rows (VteTerminal *terminal, guint cnt)
2113 {
2114 VteRowData *row;
2115 do {
2116 row = _vte_terminal_ring_append (terminal, FALSE);
2117 } while(--cnt);
2118 return row;
2119 }
2120
2121
2122 /* Make sure we have enough rows and columns to hold data at the current
2123 * cursor position. */
2124 VteRowData *
_vte_terminal_ensure_row(VteTerminal * terminal)2125 _vte_terminal_ensure_row (VteTerminal *terminal)
2126 {
2127 VteRowData *row;
2128 VteScreen *screen;
2129 gint delta;
2130 glong v;
2131
2132 /* Must make sure we're in a sane area. */
2133 screen = terminal->pvt->screen;
2134 v = terminal->pvt->cursor.row;
2135
2136 /* Figure out how many rows we need to add. */
2137 delta = v - _vte_ring_next(screen->row_data) + 1;
2138 if (delta > 0) {
2139 row = vte_terminal_insert_rows (terminal, delta);
2140 _vte_terminal_adjust_adjustments(terminal);
2141 } else {
2142 /* Find the row the cursor is in. */
2143 row = _vte_ring_index_writable (screen->row_data, v);
2144 }
2145 g_assert(row != NULL);
2146
2147 return row;
2148 }
2149
2150 static VteRowData *
vte_terminal_ensure_cursor(VteTerminal * terminal)2151 vte_terminal_ensure_cursor(VteTerminal *terminal)
2152 {
2153 VteRowData *row;
2154
2155 row = _vte_terminal_ensure_row (terminal);
2156 _vte_row_data_fill (row, &basic_cell.cell, terminal->pvt->cursor.col);
2157
2158 return row;
2159 }
2160
2161 /* Update the insert delta so that the screen which includes it also
2162 * includes the end of the buffer. */
2163 void
_vte_terminal_update_insert_delta(VteTerminal * terminal)2164 _vte_terminal_update_insert_delta(VteTerminal *terminal)
2165 {
2166 long delta, rows;
2167 VteScreen *screen;
2168
2169 screen = terminal->pvt->screen;
2170
2171 /* The total number of lines. Add one to the cursor offset
2172 * because it's zero-based. */
2173 rows = _vte_ring_next (screen->row_data);
2174 delta = terminal->pvt->cursor.row - rows + 1;
2175 if (G_UNLIKELY (delta > 0)) {
2176 vte_terminal_insert_rows (terminal, delta);
2177 rows = _vte_ring_next (screen->row_data);
2178 }
2179
2180 /* Make sure that the bottom row is visible, and that it's in
2181 * the buffer (even if it's empty). This usually causes the
2182 * top row to become a history-only row. */
2183 delta = screen->insert_delta;
2184 delta = MIN(delta, rows - terminal->pvt->row_count);
2185 delta = MAX(delta,
2186 terminal->pvt->cursor.row - (terminal->pvt->row_count - 1));
2187 delta = MAX(delta, _vte_ring_delta(screen->row_data));
2188
2189 /* Adjust the insert delta and scroll if needed. */
2190 if (delta != screen->insert_delta) {
2191 screen->insert_delta = delta;
2192 _vte_terminal_adjust_adjustments(terminal);
2193 }
2194 }
2195
2196 /* Show or hide the pointer. */
2197 void
_vte_terminal_set_pointer_visible(VteTerminal * terminal,gboolean visible)2198 _vte_terminal_set_pointer_visible(VteTerminal *terminal, gboolean visible)
2199 {
2200 GdkWindow *window;
2201 struct vte_match_regex *regex = NULL;
2202
2203 terminal->pvt->mouse_cursor_visible = visible;
2204
2205 if (! gtk_widget_get_realized (&terminal->widget))
2206 return;
2207
2208 window = gtk_widget_get_window (&terminal->widget);
2209
2210 if (visible || !terminal->pvt->mouse_autohide) {
2211 if (terminal->pvt->mouse_tracking_mode) {
2212 _vte_debug_print(VTE_DEBUG_CURSOR,
2213 "Setting mousing cursor.\n");
2214 gdk_window_set_cursor (window, terminal->pvt->mouse_mousing_cursor);
2215 } else
2216 if ( (guint)terminal->pvt->match_tag < terminal->pvt->match_regexes->len) {
2217 regex = &g_array_index(terminal->pvt->match_regexes,
2218 struct vte_match_regex,
2219 terminal->pvt->match_tag);
2220 vte_terminal_set_cursor_from_regex_match(terminal, regex);
2221 } else {
2222 _vte_debug_print(VTE_DEBUG_CURSOR,
2223 "Setting default mouse cursor.\n");
2224 gdk_window_set_cursor (window, terminal->pvt->mouse_default_cursor);
2225 }
2226 } else {
2227 _vte_debug_print(VTE_DEBUG_CURSOR,
2228 "Setting to invisible cursor.\n");
2229 gdk_window_set_cursor (window, terminal->pvt->mouse_inviso_cursor);
2230 }
2231 }
2232
2233 /**
2234 * vte_terminal_new:
2235 *
2236 * Creates a new terminal widget.
2237 *
2238 * Returns: (transfer none) (type Vte.Terminal): a new #VteTerminal object
2239 */
2240 GtkWidget *
vte_terminal_new(void)2241 vte_terminal_new(void)
2242 {
2243 return g_object_new(VTE_TYPE_TERMINAL, NULL);
2244 }
2245
2246 /*
2247 * Get the actually used color from the palette.
2248 * The return value can be NULL only if entry is one of VTE_CURSOR_BG,
2249 * VTE_HIGHLIGHT_BG or VTE_HIGHLIGHT_FG.
2250 */
2251 PangoColor *
_vte_terminal_get_color(const VteTerminal * terminal,int entry)2252 _vte_terminal_get_color(const VteTerminal *terminal, int entry)
2253 {
2254 VtePaletteColor *palette_color = &terminal->pvt->palette[entry];
2255 guint source;
2256 for (source = 0; source < G_N_ELEMENTS(palette_color->sources); source++)
2257 if (palette_color->sources[source].is_set)
2258 return &palette_color->sources[source].color;
2259 return NULL;
2260 }
2261
2262 /* Set up a palette entry with a more-or-less match for the requested color. */
2263 void
_vte_terminal_set_color_internal(VteTerminal * terminal,int entry,int source,const PangoColor * proposed)2264 _vte_terminal_set_color_internal(VteTerminal *terminal,
2265 int entry,
2266 int source,
2267 const PangoColor *proposed)
2268 {
2269 VtePaletteColor *palette_color = &terminal->pvt->palette[entry];
2270
2271 /* Save the requested color. */
2272 if (proposed != NULL) {
2273 _vte_debug_print(VTE_DEBUG_MISC,
2274 "Set %s color[%d] to (%04x,%04x,%04x).\n",
2275 source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
2276 entry, proposed->red, proposed->green, proposed->blue);
2277
2278 if (palette_color->sources[source].is_set &&
2279 palette_color->sources[source].color.red == proposed->red &&
2280 palette_color->sources[source].color.green == proposed->green &&
2281 palette_color->sources[source].color.blue == proposed->blue) {
2282 return;
2283 }
2284 palette_color->sources[source].is_set = TRUE;
2285 palette_color->sources[source].color.red = proposed->red;
2286 palette_color->sources[source].color.green = proposed->green;
2287 palette_color->sources[source].color.blue = proposed->blue;
2288 } else {
2289 _vte_debug_print(VTE_DEBUG_MISC,
2290 "Reset %s color[%d].\n",
2291 source == VTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",
2292 entry);
2293
2294 if (!palette_color->sources[source].is_set) {
2295 return;
2296 }
2297 palette_color->sources[source].is_set = FALSE;
2298 }
2299
2300 /* If we're not realized yet, there's nothing else to do. */
2301 if (! gtk_widget_get_realized (&terminal->widget)) {
2302 return;
2303 }
2304
2305 /* If we're setting the background color, set the background color
2306 * on the widget as well. */
2307 if (entry == VTE_DEFAULT_BG) {
2308 vte_terminal_background_update(terminal);
2309 }
2310
2311 /* and redraw */
2312 if (entry == VTE_CURSOR_BG)
2313 _vte_invalidate_cursor_once(terminal, FALSE);
2314 else
2315 _vte_invalidate_all (terminal);
2316 }
2317
2318 static void
vte_terminal_generate_bold(const PangoColor * foreground,const PangoColor * background,double factor,PangoColor * bold)2319 vte_terminal_generate_bold(const PangoColor *foreground,
2320 const PangoColor *background,
2321 double factor,
2322 PangoColor *bold /* (out) (callee allocates) */)
2323 {
2324 double fy, fcb, fcr, by, bcb, bcr, r, g, b;
2325 g_assert(foreground != NULL);
2326 g_assert(background != NULL);
2327 g_assert(bold != NULL);
2328 fy = 0.2990 * foreground->red +
2329 0.5870 * foreground->green +
2330 0.1140 * foreground->blue;
2331 fcb = -0.1687 * foreground->red +
2332 -0.3313 * foreground->green +
2333 0.5000 * foreground->blue;
2334 fcr = 0.5000 * foreground->red +
2335 -0.4187 * foreground->green +
2336 -0.0813 * foreground->blue;
2337 by = 0.2990 * background->red +
2338 0.5870 * background->green +
2339 0.1140 * background->blue;
2340 bcb = -0.1687 * background->red +
2341 -0.3313 * background->green +
2342 0.5000 * background->blue;
2343 bcr = 0.5000 * background->red +
2344 -0.4187 * background->green +
2345 -0.0813 * background->blue;
2346 fy = (factor * fy) + ((1.0 - factor) * by);
2347 fcb = (factor * fcb) + ((1.0 - factor) * bcb);
2348 fcr = (factor * fcr) + ((1.0 - factor) * bcr);
2349 r = fy + 1.402 * fcr;
2350 g = fy + 0.34414 * fcb - 0.71414 * fcr;
2351 b = fy + 1.722 * fcb;
2352 _vte_debug_print(VTE_DEBUG_MISC,
2353 "Calculated bold (%d, %d, %d) = (%lf,%lf,%lf)",
2354 foreground->red, foreground->green, foreground->blue,
2355 r, g, b);
2356 bold->red = CLAMP(r, 0, 0xffff);
2357 bold->green = CLAMP(g, 0, 0xffff);
2358 bold->blue = CLAMP(b, 0, 0xffff);
2359 _vte_debug_print(VTE_DEBUG_MISC,
2360 "= (%04x,%04x,%04x).\n",
2361 bold->red, bold->green, bold->blue);
2362 }
2363
2364 /*
2365 * _vte_terminal_set_color_bold:
2366 * @terminal: a #VteTerminal
2367 * @bold: the new bold color
2368 *
2369 * Sets the color used to draw bold text in the default foreground color.
2370 */
2371 static void
_vte_terminal_set_color_bold(VteTerminal * terminal,const PangoColor * bold)2372 _vte_terminal_set_color_bold(VteTerminal *terminal,
2373 const PangoColor *bold)
2374 {
2375 _vte_debug_print(VTE_DEBUG_MISC,
2376 "Set bold color to (%04x,%04x,%04x).\n",
2377 bold->red, bold->green, bold->blue);
2378 _vte_terminal_set_color_internal(terminal, VTE_BOLD_FG, VTE_COLOR_SOURCE_API, bold);
2379 }
2380
2381 /*
2382 * _vte_terminal_set_color_foreground:
2383 * @terminal: a #VteTerminal
2384 * @foreground: the new foreground color
2385 *
2386 * Sets the foreground color used to draw normal text
2387 */
2388 static void
_vte_terminal_set_color_foreground(VteTerminal * terminal,const PangoColor * foreground)2389 _vte_terminal_set_color_foreground(VteTerminal *terminal,
2390 const PangoColor *foreground)
2391 {
2392 _vte_debug_print(VTE_DEBUG_MISC,
2393 "Set foreground color to (%04x,%04x,%04x).\n",
2394 foreground->red, foreground->green, foreground->blue);
2395 _vte_terminal_set_color_internal(terminal, VTE_DEFAULT_FG, VTE_COLOR_SOURCE_API, foreground);
2396 }
2397
2398 /*
2399 * _vte_terminal_set_color_background:
2400 * @terminal: a #VteTerminal
2401 * @background: the new background color
2402 *
2403 * Sets the background color for text which does not have a specific background
2404 * color assigned. Only has effect when no background image is set.
2405 */
2406 static void
_vte_terminal_set_color_background(VteTerminal * terminal,const PangoColor * background)2407 _vte_terminal_set_color_background(VteTerminal *terminal,
2408 const PangoColor *background)
2409 {
2410 _vte_debug_print(VTE_DEBUG_MISC,
2411 "Set background color to (%04x,%04x,%04x).\n",
2412 background->red, background->green, background->blue);
2413 _vte_terminal_set_color_internal(terminal, VTE_DEFAULT_BG, VTE_COLOR_SOURCE_API, background);
2414 }
2415
2416 /*
2417 * _vte_terminal_set_background_alpha:
2418 * @terminal: a #VteTerminal
2419 * @alpha: an alpha value from 0.0 to 1.0
2420 */
2421 static void
_vte_terminal_set_background_alpha(VteTerminal * terminal,gdouble alpha)2422 _vte_terminal_set_background_alpha(VteTerminal *terminal,
2423 gdouble alpha)
2424 {
2425 VteTerminalPrivate *pvt = terminal->pvt;
2426
2427 if (alpha == pvt->background_alpha)
2428 return;
2429
2430 _vte_debug_print(VTE_DEBUG_MISC,
2431 "Setting background alpha to %.3f\n", alpha);
2432
2433 pvt->background_alpha = alpha;
2434
2435 vte_terminal_background_update(terminal);
2436 }
2437
2438 /*
2439 * _vte_terminal_set_color_cursor:
2440 * @terminal: a #VteTerminal
2441 * @cursor_background: (allow-none): the new color to use for the text cursor, or %NULL
2442 *
2443 * Sets the background color for text which is under the cursor. If %NULL, text
2444 * under the cursor will be drawn with foreground and background colors
2445 * reversed.
2446 */
2447 static void
_vte_terminal_set_color_cursor(VteTerminal * terminal,const PangoColor * cursor_background)2448 _vte_terminal_set_color_cursor(VteTerminal *terminal,
2449 const PangoColor *cursor_background)
2450 {
2451 if (cursor_background != NULL) {
2452 _vte_debug_print(VTE_DEBUG_MISC,
2453 "Set cursor color to (%04x,%04x,%04x).\n",
2454 cursor_background->red,
2455 cursor_background->green,
2456 cursor_background->blue);
2457 } else {
2458 _vte_debug_print(VTE_DEBUG_MISC,
2459 "Reset cursor color.\n");
2460 }
2461 _vte_terminal_set_color_internal(terminal, VTE_CURSOR_BG, VTE_COLOR_SOURCE_API, cursor_background);
2462 }
2463
2464 /*
2465 * _vte_terminal_set_color_highlight:
2466 * @terminal: a #VteTerminal
2467 * @highlight_background: (allow-none): the new color to use for highlighted text, or %NULL
2468 *
2469 * Sets the background color for text which is highlighted. If %NULL,
2470 * it is unset. If neither highlight background nor highlight foreground are set,
2471 * highlighted text (which is usually highlighted because it is selected) will
2472 * be drawn with foreground and background colors reversed.
2473 */
2474 static void
_vte_terminal_set_color_highlight(VteTerminal * terminal,const PangoColor * highlight_background)2475 _vte_terminal_set_color_highlight(VteTerminal *terminal,
2476 const PangoColor *highlight_background)
2477 {
2478 if (highlight_background != NULL) {
2479 _vte_debug_print(VTE_DEBUG_MISC,
2480 "Set highlight background color to (%04x,%04x,%04x).\n",
2481 highlight_background->red,
2482 highlight_background->green,
2483 highlight_background->blue);
2484 } else {
2485 _vte_debug_print(VTE_DEBUG_MISC,
2486 "Reset highlight background color.\n");
2487 }
2488 _vte_terminal_set_color_internal(terminal, VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_API, highlight_background);
2489 }
2490
2491 /*
2492 * _vte_terminal_set_color_highlight_foreground:
2493 * @terminal: a #VteTerminal
2494 * @highlight_foreground: (allow-none): the new color to use for highlighted text, or %NULL
2495 *
2496 * Sets the foreground color for text which is highlighted. If %NULL,
2497 * it is unset. If neither highlight background nor highlight foreground are set,
2498 * highlighted text (which is usually highlighted because it is selected) will
2499 * be drawn with foreground and background colors reversed.
2500 */
2501 static void
_vte_terminal_set_color_highlight_foreground(VteTerminal * terminal,const PangoColor * highlight_foreground)2502 _vte_terminal_set_color_highlight_foreground(VteTerminal *terminal,
2503 const PangoColor *highlight_foreground)
2504 {
2505 if (highlight_foreground != NULL) {
2506 _vte_debug_print(VTE_DEBUG_MISC,
2507 "Set highlight foreground color to (%04x,%04x,%04x).\n",
2508 highlight_foreground->red,
2509 highlight_foreground->green,
2510 highlight_foreground->blue);
2511 } else {
2512 _vte_debug_print(VTE_DEBUG_MISC,
2513 "Reset highlight foreground color.\n");
2514 }
2515 _vte_terminal_set_color_internal(terminal, VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_API, highlight_foreground);
2516 }
2517
2518 /*
2519 * _vte_terminal_set_colors:
2520 * @terminal: a #VteTerminal
2521 * @foreground: (allow-none): the new foreground color, or %NULL
2522 * @background: (allow-none): the new background color, or %NULL
2523 * @palette: (array length=palette_size zero-terminated=0) (element-type Gdk.Color): the color palette
2524 * @palette_size: the number of entries in @palette
2525 *
2526 * @palette specifies the new values for the 256 palette colors: 8 standard colors,
2527 * their 8 bright counterparts, 6x6x6 color cube, and 24 grayscale colors.
2528 * Omitted entries will default to a hardcoded value.
2529 *
2530 * @palette_size must be 0, 8, 16, 232 or 256.
2531 *
2532 * If @foreground is %NULL and @palette_size is greater than 0, the new foreground
2533 * color is taken from @palette[7]. If @background is %NULL and @palette_size is
2534 * greater than 0, the new background color is taken from @palette[0].
2535 */
2536 static void
_vte_terminal_set_colors(VteTerminal * terminal,const PangoColor * foreground,const PangoColor * background,const PangoColor * palette,gsize palette_size)2537 _vte_terminal_set_colors(VteTerminal *terminal,
2538 const PangoColor *foreground,
2539 const PangoColor *background,
2540 const PangoColor *palette,
2541 gsize palette_size)
2542 {
2543 gsize i;
2544 PangoColor color;
2545 gboolean unset = FALSE;
2546
2547 _vte_debug_print(VTE_DEBUG_MISC,
2548 "Set color palette [%" G_GSIZE_FORMAT " elements].\n",
2549 palette_size);
2550
2551 /* Accept NULL as the default foreground and background colors if we
2552 * got a palette. */
2553 if ((foreground == NULL) && (palette_size >= 8)) {
2554 foreground = &palette[7];
2555 }
2556 if ((background == NULL) && (palette_size >= 8)) {
2557 background = &palette[0];
2558 }
2559
2560 memset(&color, 0, sizeof(color));
2561
2562 /* Initialize each item in the palette if we got any entries to work
2563 * with. */
2564 for (i=0; i < G_N_ELEMENTS(terminal->pvt->palette); i++) {
2565 unset = FALSE;
2566 if (i < 16) {
2567 color.blue = (i & 4) ? 0xc000 : 0;
2568 color.green = (i & 2) ? 0xc000 : 0;
2569 color.red = (i & 1) ? 0xc000 : 0;
2570 if (i > 7) {
2571 color.blue += 0x3fff;
2572 color.green += 0x3fff;
2573 color.red += 0x3fff;
2574 }
2575 }
2576 else if (i < 232) {
2577 int j = i - 16;
2578 int r = j / 36, g = (j / 6) % 6, b = j % 6;
2579 int red = (r == 0) ? 0 : r * 40 + 55;
2580 int green = (g == 0) ? 0 : g * 40 + 55;
2581 int blue = (b == 0) ? 0 : b * 40 + 55;
2582 color.red = red | red << 8 ;
2583 color.green = green | green << 8;
2584 color.blue = blue | blue << 8;
2585 } else if (i < 256) {
2586 int shade = 8 + (i - 232) * 10;
2587 color.red = color.green = color.blue = shade | shade << 8;
2588 }
2589 else switch (i) {
2590 case VTE_DEFAULT_BG:
2591 if (background != NULL) {
2592 color = *background;
2593 } else {
2594 color.red = 0;
2595 color.blue = 0;
2596 color.green = 0;
2597 }
2598 break;
2599 case VTE_DEFAULT_FG:
2600 if (foreground != NULL) {
2601 color = *foreground;
2602 } else {
2603 color.red = 0xc000;
2604 color.blue = 0xc000;
2605 color.green = 0xc000;
2606 }
2607 break;
2608 case VTE_BOLD_FG:
2609 vte_terminal_generate_bold(_vte_terminal_get_color(terminal, VTE_DEFAULT_FG),
2610 _vte_terminal_get_color(terminal, VTE_DEFAULT_BG),
2611 1.8,
2612 &color);
2613 break;
2614 case VTE_HIGHLIGHT_BG:
2615 unset = TRUE;
2616 break;
2617 case VTE_HIGHLIGHT_FG:
2618 unset = TRUE;
2619 break;
2620 case VTE_CURSOR_BG:
2621 unset = TRUE;
2622 break;
2623 }
2624
2625 /* Override from the supplied palette if there is one. */
2626 if (i < palette_size) {
2627 color = palette[i];
2628 }
2629
2630 /* Set up the color entry. */
2631 _vte_terminal_set_color_internal(terminal, i, VTE_COLOR_SOURCE_API, unset ? NULL : &color);
2632 }
2633 }
2634
2635 static PangoColor *
_pango_color_from_rgba(PangoColor * color,const GdkRGBA * rgba)2636 _pango_color_from_rgba(PangoColor *color,
2637 const GdkRGBA *rgba)
2638 {
2639 if (rgba == NULL)
2640 return NULL;
2641
2642 color->red = rgba->red * 65535.;
2643 color->green = rgba->green * 65535.;
2644 color->blue = rgba->blue * 65535.;
2645
2646 return color;
2647 }
2648
2649 /**
2650 * vte_terminal_set_color_bold:
2651 * @terminal: a #VteTerminal
2652 * @bold: (allow-none): the new bold color or %NULL
2653 *
2654 * Sets the color used to draw bold text in the default foreground color.
2655 * If @bold is %NULL then the default color is used.
2656 */
2657 void
vte_terminal_set_color_bold(VteTerminal * terminal,const GdkRGBA * bold)2658 vte_terminal_set_color_bold(VteTerminal *terminal,
2659 const GdkRGBA *bold)
2660 {
2661 PangoColor color;
2662
2663 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2664
2665 if (bold == NULL)
2666 {
2667 vte_terminal_generate_bold(_vte_terminal_get_color(terminal, VTE_DEFAULT_FG),
2668 _vte_terminal_get_color(terminal, VTE_DEFAULT_BG),
2669 1.8,
2670 &color);
2671 }
2672 else
2673 {
2674 _pango_color_from_rgba(&color, bold);
2675 }
2676
2677 _vte_terminal_set_color_bold(terminal, &color);
2678 }
2679
2680 /**
2681 * vte_terminal_set_color_foreground:
2682 * @terminal: a #VteTerminal
2683 * @foreground: the new foreground color
2684 *
2685 * Sets the foreground color used to draw normal text.
2686 */
2687 void
vte_terminal_set_color_foreground(VteTerminal * terminal,const GdkRGBA * foreground)2688 vte_terminal_set_color_foreground(VteTerminal *terminal,
2689 const GdkRGBA *foreground)
2690 {
2691 PangoColor color;
2692
2693 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2694 g_return_if_fail(foreground != NULL);
2695
2696 _vte_terminal_set_color_foreground(terminal,
2697 _pango_color_from_rgba(&color, foreground));
2698 }
2699
2700 /**
2701 * vte_terminal_set_color_background:
2702 * @terminal: a #VteTerminal
2703 * @background: the new background color
2704 *
2705 * Sets the background color for text which does not have a specific background
2706 * color assigned. Only has effect when no background image is set and when
2707 * the terminal is not transparent.
2708 */
2709 void
vte_terminal_set_color_background(VteTerminal * terminal,const GdkRGBA * background)2710 vte_terminal_set_color_background(VteTerminal *terminal,
2711 const GdkRGBA *background)
2712 {
2713 PangoColor color;
2714
2715 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2716 g_return_if_fail(background != NULL);
2717
2718 _vte_terminal_set_color_background(terminal,
2719 _pango_color_from_rgba(&color, background));
2720 _vte_terminal_set_background_alpha(terminal, background->alpha);
2721 }
2722
2723 /**
2724 * vte_terminal_set_color_cursor:
2725 * @terminal: a #VteTerminal
2726 * @cursor_background: (allow-none): the new color to use for the text cursor, or %NULL
2727 *
2728 * Sets the background color for text which is under the cursor. If %NULL, text
2729 * under the cursor will be drawn with foreground and background colors
2730 * reversed.
2731 */
2732 void
vte_terminal_set_color_cursor(VteTerminal * terminal,const GdkRGBA * cursor_background)2733 vte_terminal_set_color_cursor(VteTerminal *terminal,
2734 const GdkRGBA *cursor_background)
2735 {
2736 PangoColor color;
2737
2738 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2739
2740 _vte_terminal_set_color_cursor(terminal,
2741 _pango_color_from_rgba(&color, cursor_background));
2742 }
2743
2744 /**
2745 * vte_terminal_set_color_highlight:
2746 * @terminal: a #VteTerminal
2747 * @highlight_background: (allow-none): the new color to use for highlighted text, or %NULL
2748 *
2749 * Sets the background color for text which is highlighted. If %NULL,
2750 * it is unset. If neither highlight background nor highlight foreground are set,
2751 * highlighted text (which is usually highlighted because it is selected) will
2752 * be drawn with foreground and background colors reversed.
2753 */
2754 void
vte_terminal_set_color_highlight(VteTerminal * terminal,const GdkRGBA * highlight_background)2755 vte_terminal_set_color_highlight(VteTerminal *terminal,
2756 const GdkRGBA *highlight_background)
2757 {
2758 PangoColor color;
2759
2760 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2761
2762 _vte_terminal_set_color_highlight(terminal,
2763 _pango_color_from_rgba(&color, highlight_background));
2764 }
2765
2766 /**
2767 * vte_terminal_set_color_highlight_foreground:
2768 * @terminal: a #VteTerminal
2769 * @highlight_foreground: (allow-none): the new color to use for highlighted text, or %NULL
2770 *
2771 * Sets the foreground color for text which is highlighted. If %NULL,
2772 * it is unset. If neither highlight background nor highlight foreground are set,
2773 * highlighted text (which is usually highlighted because it is selected) will
2774 * be drawn with foreground and background colors reversed.
2775 */
2776 void
vte_terminal_set_color_highlight_foreground(VteTerminal * terminal,const GdkRGBA * highlight_foreground)2777 vte_terminal_set_color_highlight_foreground(VteTerminal *terminal,
2778 const GdkRGBA *highlight_foreground)
2779 {
2780 PangoColor color;
2781
2782 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2783
2784 _vte_terminal_set_color_highlight_foreground(terminal,
2785 _pango_color_from_rgba(&color, highlight_foreground));
2786 }
2787
2788 /**
2789 * vte_terminal_set_colors:
2790 * @terminal: a #VteTerminal
2791 * @foreground: (allow-none): the new foreground color, or %NULL
2792 * @background: (allow-none): the new background color, or %NULL
2793 * @palette: (array length=palette_size zero-terminated=0) (element-type Gdk.RGBA) (allow-none): the color palette
2794 * @palette_size: the number of entries in @palette
2795 *
2796 * @palette specifies the new values for the 256 palette colors: 8 standard colors,
2797 * their 8 bright counterparts, 6x6x6 color cube, and 24 grayscale colors.
2798 * Omitted entries will default to a hardcoded value.
2799 *
2800 * @palette_size must be 0, 8, 16, 232 or 256.
2801 *
2802 * If @foreground is %NULL and @palette_size is greater than 0, the new foreground
2803 * color is taken from @palette[7]. If @background is %NULL and @palette_size is
2804 * greater than 0, the new background color is taken from @palette[0].
2805 */
2806 void
vte_terminal_set_colors(VteTerminal * terminal,const GdkRGBA * foreground,const GdkRGBA * background,const GdkRGBA * palette,gsize palette_size)2807 vte_terminal_set_colors(VteTerminal *terminal,
2808 const GdkRGBA *foreground,
2809 const GdkRGBA *background,
2810 const GdkRGBA *palette,
2811 gsize palette_size)
2812 {
2813 PangoColor fg, bg, *pal;
2814 gsize i;
2815
2816 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2817 g_return_if_fail((palette_size == 0) ||
2818 (palette_size == 8) ||
2819 (palette_size == 16) ||
2820 (palette_size == 232) ||
2821 (palette_size == 256));
2822
2823 pal = g_new (PangoColor, palette_size);
2824 for (i = 0; i < palette_size; ++i)
2825 _pango_color_from_rgba(&pal[i], &palette[i]);
2826
2827 _vte_terminal_set_colors(terminal,
2828 _pango_color_from_rgba(&fg, foreground),
2829 _pango_color_from_rgba(&bg, background),
2830 pal, palette_size);
2831
2832 _vte_terminal_set_background_alpha(terminal, background ? background->alpha : 1.0);
2833
2834 g_free (pal);
2835 }
2836
2837 /**
2838 * vte_terminal_set_default_colors:
2839 * @terminal: a #VteTerminal
2840 *
2841 * Reset the terminal palette to reasonable compiled-in default color.
2842 */
2843 void
vte_terminal_set_default_colors(VteTerminal * terminal)2844 vte_terminal_set_default_colors(VteTerminal *terminal)
2845 {
2846 g_return_if_fail(VTE_IS_TERMINAL(terminal));
2847 _vte_terminal_set_colors(terminal, NULL, NULL, NULL, 0);
2848 }
2849
2850 /*
2851 * _vte_terminal_cleanup_fragments:
2852 * @terminal: a #VteTerminal
2853 * @start: the starting column, inclusive
2854 * @end: the end column, exclusive
2855 *
2856 * Needs to be called before modifying the contents in the cursor's row,
2857 * between the two given columns. Cleans up TAB and CJK fragments to the
2858 * left of @start and to the right of @end. If a CJK is split in half,
2859 * the remaining half is replaced by a space. If a TAB at @start is split,
2860 * it is replaced by spaces. If a TAB at @end is split, it is replaced by
2861 * a shorter TAB. @start and @end can be equal if characters will be
2862 * inserted at the location rather than overwritten.
2863 *
2864 * The area between @start and @end is not cleaned up, hence the whole row
2865 * can be left in an inconsistent state. It is expected that the caller
2866 * will fill up that range afterwards, resulting in a consistent row again.
2867 *
2868 * Invalidates the cells that visually change outside of the range,
2869 * because the caller can't reasonably be expected to take care of this.
2870 */
2871 void
_vte_terminal_cleanup_fragments(VteTerminal * terminal,long start,long end)2872 _vte_terminal_cleanup_fragments(VteTerminal *terminal,
2873 long start, long end)
2874 {
2875 VteRowData *row = _vte_terminal_ensure_row (terminal);
2876 const VteCell *cell_start;
2877 VteCell *cell_end, *cell_col;
2878 gboolean cell_start_is_fragment;
2879 long col;
2880
2881 g_assert(end >= start);
2882
2883 /* Remember whether the cell at start is a fragment. We'll need to know it when
2884 * handling the left hand side, but handling the right hand side first might
2885 * overwrite it if start == end (inserting to the middle of a character). */
2886 cell_start = _vte_row_data_get (row, start);
2887 cell_start_is_fragment = cell_start != NULL && cell_start->attr.fragment;
2888
2889 /* On the right hand side, try to replace a TAB by a shorter TAB if we can.
2890 * This requires that the TAB on the left (which might be the same TAB) is
2891 * not yet converted to spaces, so start on the right hand side. */
2892 cell_end = _vte_row_data_get_writable (row, end);
2893 if (G_UNLIKELY (cell_end != NULL && cell_end->attr.fragment)) {
2894 col = end;
2895 do {
2896 col--;
2897 g_assert(col >= 0); /* The first cell can't be a fragment. */
2898 cell_col = _vte_row_data_get_writable (row, col);
2899 } while (cell_col->attr.fragment);
2900 if (cell_col->c == '\t') {
2901 _vte_debug_print(VTE_DEBUG_MISC,
2902 "Replacing right part of TAB with a shorter one at %ld (%d cells) => %ld (%ld cells)\n",
2903 col, cell_col->attr.columns, end, cell_col->attr.columns - (end - col));
2904 cell_end->c = '\t';
2905 cell_end->attr.fragment = 0;
2906 g_assert(cell_col->attr.columns > end - col);
2907 cell_end->attr.columns = cell_col->attr.columns - (end - col);
2908 } else {
2909 _vte_debug_print(VTE_DEBUG_MISC,
2910 "Cleaning CJK right half at %ld\n",
2911 end);
2912 g_assert(end - col == 1 && cell_col->attr.columns == 2);
2913 cell_end->c = ' ';
2914 cell_end->attr.fragment = 0;
2915 cell_end->attr.columns = 1;
2916 _vte_invalidate_cells(terminal,
2917 end, 1,
2918 terminal->pvt->cursor.row, 1);
2919 }
2920 }
2921
2922 /* Handle the left hand side. Converting longer TABs to shorter ones probably
2923 * wouldn't make that much sense here, so instead convert to spaces. */
2924 if (G_UNLIKELY (cell_start_is_fragment)) {
2925 gboolean keep_going = TRUE;
2926 col = start;
2927 do {
2928 col--;
2929 g_assert(col >= 0); /* The first cell can't be a fragment. */
2930 cell_col = _vte_row_data_get_writable (row, col);
2931 if (!cell_col->attr.fragment) {
2932 if (cell_col->c == '\t') {
2933 _vte_debug_print(VTE_DEBUG_MISC,
2934 "Replacing left part of TAB with spaces at %ld (%d => %ld cells)\n",
2935 col, cell_col->attr.columns, start - col);
2936 /* nothing to do here */
2937 } else {
2938 _vte_debug_print(VTE_DEBUG_MISC,
2939 "Cleaning CJK left half at %ld\n",
2940 col);
2941 g_assert(start - col == 1);
2942 _vte_invalidate_cells(terminal,
2943 col, 1,
2944 terminal->pvt->cursor.row, 1);
2945 }
2946 keep_going = FALSE;
2947 }
2948 cell_col->c = ' ';
2949 cell_col->attr.fragment = 0;
2950 cell_col->attr.columns = 1;
2951 } while (keep_going);
2952 }
2953 }
2954
2955 /* Cursor down, with scrolling. */
2956 void
_vte_terminal_cursor_down(VteTerminal * terminal)2957 _vte_terminal_cursor_down (VteTerminal *terminal)
2958 {
2959 long start, end;
2960 VteScreen *screen;
2961
2962 screen = terminal->pvt->screen;
2963
2964 if (terminal->pvt->scrolling_restricted) {
2965 start = screen->insert_delta + terminal->pvt->scrolling_region.start;
2966 end = screen->insert_delta + terminal->pvt->scrolling_region.end;
2967 } else {
2968 start = screen->insert_delta;
2969 end = start + terminal->pvt->row_count - 1;
2970 }
2971 if (terminal->pvt->cursor.row == end) {
2972 if (terminal->pvt->scrolling_restricted) {
2973 if (start == screen->insert_delta) {
2974 /* Scroll this line into the scrollback
2975 * buffer by inserting a line at the next
2976 * line and scrolling the area up. */
2977 screen->insert_delta++;
2978 terminal->pvt->cursor.row++;
2979 /* update start and end, as they are relative
2980 * to insert_delta. */
2981 start++;
2982 end++;
2983 _vte_terminal_ring_insert (terminal, terminal->pvt->cursor.row, FALSE);
2984 /* Force the areas below the region to be
2985 * redrawn -- they've moved. */
2986 _vte_terminal_scroll_region(terminal, start,
2987 end - start + 1, 1);
2988 /* Force scroll. */
2989 _vte_terminal_adjust_adjustments(terminal);
2990 } else {
2991 /* If we're at the bottom of the scrolling
2992 * region, add a line at the top to scroll the
2993 * bottom off. */
2994 _vte_terminal_ring_remove (terminal, start);
2995 _vte_terminal_ring_insert (terminal, end, TRUE);
2996 /* Update the display. */
2997 _vte_terminal_scroll_region(terminal, start,
2998 end - start + 1, -1);
2999 _vte_invalidate_cells(terminal,
3000 0, terminal->pvt->column_count,
3001 end - 2, 2);
3002 }
3003 } else {
3004 /* Scroll up with history. */
3005 terminal->pvt->cursor.row++;
3006 _vte_terminal_update_insert_delta(terminal);
3007 }
3008
3009 /* Match xterm and fill the new row when scrolling. */
3010 if (terminal->pvt->fill_defaults.attr.back != VTE_DEFAULT_BG) {
3011 VteRowData *rowdata;
3012 rowdata = _vte_terminal_ensure_row (terminal);
3013 _vte_row_data_fill (rowdata, &terminal->pvt->fill_defaults, terminal->pvt->column_count);
3014 }
3015 } else {
3016 /* Otherwise, just move the cursor down. */
3017 terminal->pvt->cursor.row++;
3018 }
3019 }
3020
3021 /* Drop the scrollback. */
3022 void
_vte_terminal_drop_scrollback(VteTerminal * terminal)3023 _vte_terminal_drop_scrollback (VteTerminal *terminal)
3024 {
3025 /* Only for normal screen; alternate screen doesn't have a scrollback. */
3026 _vte_ring_drop_scrollback (terminal->pvt->normal_screen.row_data,
3027 terminal->pvt->normal_screen.insert_delta);
3028
3029 if (terminal->pvt->screen == &terminal->pvt->normal_screen) {
3030 vte_terminal_queue_adjustment_value_changed (terminal, terminal->pvt->normal_screen.insert_delta);
3031 _vte_terminal_adjust_adjustments_full (terminal);
3032 }
3033 }
3034
3035 /* Restore cursor on a screen. */
3036 void
_vte_terminal_restore_cursor(VteTerminal * terminal,VteScreen * screen)3037 _vte_terminal_restore_cursor (VteTerminal *terminal, VteScreen *screen)
3038 {
3039 terminal->pvt->cursor.col = screen->saved.cursor.col;
3040 terminal->pvt->cursor.row = screen->insert_delta + CLAMP(screen->saved.cursor.row,
3041 0, terminal->pvt->row_count - 1);
3042
3043 terminal->pvt->reverse_mode = screen->saved.reverse_mode;
3044 terminal->pvt->origin_mode = screen->saved.origin_mode;
3045 terminal->pvt->sendrecv_mode = screen->saved.sendrecv_mode;
3046 terminal->pvt->insert_mode = screen->saved.insert_mode;
3047 terminal->pvt->linefeed_mode = screen->saved.linefeed_mode;
3048 terminal->pvt->defaults = screen->saved.defaults;
3049 terminal->pvt->color_defaults = screen->saved.color_defaults;
3050 terminal->pvt->fill_defaults = screen->saved.fill_defaults;
3051 terminal->pvt->character_replacements[0] = screen->saved.character_replacements[0];
3052 terminal->pvt->character_replacements[1] = screen->saved.character_replacements[1];
3053 terminal->pvt->character_replacement = screen->saved.character_replacement;
3054 }
3055
3056 /* Save cursor on a screen. */
3057 void
_vte_terminal_save_cursor(VteTerminal * terminal,VteScreen * screen)3058 _vte_terminal_save_cursor (VteTerminal *terminal, VteScreen *screen)
3059 {
3060 screen->saved.cursor.col = terminal->pvt->cursor.col;
3061 screen->saved.cursor.row = terminal->pvt->cursor.row - screen->insert_delta;
3062
3063 screen->saved.reverse_mode = terminal->pvt->reverse_mode;
3064 screen->saved.origin_mode = terminal->pvt->origin_mode;
3065 screen->saved.sendrecv_mode = terminal->pvt->sendrecv_mode;
3066 screen->saved.insert_mode = terminal->pvt->insert_mode;
3067 screen->saved.linefeed_mode = terminal->pvt->linefeed_mode;
3068 screen->saved.defaults = terminal->pvt->defaults;
3069 screen->saved.color_defaults = terminal->pvt->color_defaults;
3070 screen->saved.fill_defaults = terminal->pvt->fill_defaults;
3071 screen->saved.character_replacements[0] = terminal->pvt->character_replacements[0];
3072 screen->saved.character_replacements[1] = terminal->pvt->character_replacements[1];
3073 screen->saved.character_replacement = terminal->pvt->character_replacement;
3074 }
3075
3076 /* Insert a single character into the stored data array. */
3077 gboolean
_vte_terminal_insert_char(VteTerminal * terminal,gunichar c,gboolean insert,gboolean invalidate_now)3078 _vte_terminal_insert_char(VteTerminal *terminal, gunichar c,
3079 gboolean insert, gboolean invalidate_now)
3080 {
3081 VteCellAttr attr;
3082 VteRowData *row;
3083 long col;
3084 int columns, i;
3085 VteScreen *screen;
3086 gboolean line_wrapped = FALSE; /* cursor moved before char inserted */
3087
3088 /* DEC Special Character and Line Drawing Set. VT100 and higher (per XTerm docs). */
3089 static gunichar line_drawing_map[31] = {
3090 0x25c6, /* ` => diamond */
3091 0x2592, /* a => checkerboard */
3092 0x2409, /* b => HT symbol */
3093 0x240c, /* c => FF symbol */
3094 0x240d, /* d => CR symbol */
3095 0x240a, /* e => LF symbol */
3096 0x00b0, /* f => degree */
3097 0x00b1, /* g => plus/minus */
3098 0x2424, /* h => NL symbol */
3099 0x240b, /* i => VT symbol */
3100 0x2518, /* j => downright corner */
3101 0x2510, /* k => upright corner */
3102 0x250c, /* l => upleft corner */
3103 0x2514, /* m => downleft corner */
3104 0x253c, /* n => cross */
3105 0x23ba, /* o => scan line 1/9 */
3106 0x23bb, /* p => scan line 3/9 */
3107 0x2500, /* q => horizontal line (also scan line 5/9) */
3108 0x23bc, /* r => scan line 7/9 */
3109 0x23bd, /* s => scan line 9/9 */
3110 0x251c, /* t => left t */
3111 0x2524, /* u => right t */
3112 0x2534, /* v => bottom t */
3113 0x252c, /* w => top t */
3114 0x2502, /* x => vertical line */
3115 0x2264, /* y => <= */
3116 0x2265, /* z => >= */
3117 0x03c0, /* { => pi */
3118 0x2260, /* | => not equal */
3119 0x00a3, /* } => pound currency sign */
3120 0x00b7, /* ~ => bullet */
3121 };
3122
3123 screen = terminal->pvt->screen;
3124 insert |= terminal->pvt->insert_mode;
3125 invalidate_now |= insert;
3126
3127 /* If we've enabled the special drawing set, map the characters to
3128 * Unicode. */
3129 if (G_UNLIKELY (*terminal->pvt->character_replacement == VTE_CHARACTER_REPLACEMENT_LINE_DRAWING)) {
3130 if (c >= 96 && c <= 126)
3131 c = line_drawing_map[c - 96];
3132 } else if (G_UNLIKELY (*terminal->pvt->character_replacement == VTE_CHARACTER_REPLACEMENT_BRITISH)) {
3133 if (G_UNLIKELY (c == '#'))
3134 c = 0x00a3; /* pound sign */
3135 }
3136
3137 /* Figure out how many columns this character should occupy. */
3138 columns = _vte_unichar_width(c, terminal->pvt->utf8_ambiguous_width);
3139
3140 /* If we're autowrapping here, do it. */
3141 col = terminal->pvt->cursor.col;
3142 if (G_UNLIKELY (columns && col + columns > terminal->pvt->column_count)) {
3143 if (terminal->pvt->autowrap) {
3144 _vte_debug_print(VTE_DEBUG_ADJ,
3145 "Autowrapping before character\n");
3146 /* Wrap. */
3147 /* XXX clear to the end of line */
3148 col = terminal->pvt->cursor.col = 0;
3149 /* Mark this line as soft-wrapped. */
3150 row = _vte_terminal_ensure_row (terminal);
3151 row->attr.soft_wrapped = 1;
3152 _vte_terminal_cursor_down (terminal);
3153 } else {
3154 /* Don't wrap, stay at the rightmost column. */
3155 col = terminal->pvt->cursor.col =
3156 terminal->pvt->column_count - columns;
3157 }
3158 line_wrapped = TRUE;
3159 }
3160
3161 _vte_debug_print(VTE_DEBUG_PARSE,
3162 "Inserting %ld '%c' (%d/%d) (%ld+%d, %ld), delta = %ld; ",
3163 (long)c, c < 256 ? c : ' ',
3164 terminal->pvt->color_defaults.attr.fore,
3165 terminal->pvt->color_defaults.attr.back,
3166 col, columns, (long)terminal->pvt->cursor.row,
3167 (long)screen->insert_delta);
3168
3169
3170 if (G_UNLIKELY (columns == 0)) {
3171
3172 /* It's a combining mark */
3173
3174 long row_num;
3175 VteCell *cell;
3176
3177 _vte_debug_print(VTE_DEBUG_PARSE, "combining U+%04X", c);
3178
3179 row_num = terminal->pvt->cursor.row;
3180 row = NULL;
3181 if (G_UNLIKELY (col == 0)) {
3182 /* We are at first column. See if the previous line softwrapped.
3183 * If it did, move there. Otherwise skip inserting. */
3184
3185 if (G_LIKELY (row_num > 0)) {
3186 row_num--;
3187 row = _vte_terminal_find_row_data_writable (terminal, row_num);
3188
3189 if (row) {
3190 if (!row->attr.soft_wrapped)
3191 row = NULL;
3192 else
3193 col = _vte_row_data_length (row);
3194 }
3195 }
3196 } else {
3197 row = _vte_terminal_find_row_data_writable (terminal, row_num);
3198 }
3199
3200 if (G_UNLIKELY (!row || !col))
3201 goto not_inserted;
3202
3203 /* Combine it on the previous cell */
3204
3205 col--;
3206 cell = _vte_row_data_get_writable (row, col);
3207
3208 if (G_UNLIKELY (!cell))
3209 goto not_inserted;
3210
3211 /* Find the previous cell */
3212 while (cell && cell->attr.fragment && col > 0)
3213 cell = _vte_row_data_get_writable (row, --col);
3214 if (G_UNLIKELY (!cell || cell->c == '\t'))
3215 goto not_inserted;
3216
3217 /* Combine the new character on top of the cell string */
3218 c = _vte_unistr_append_unichar (cell->c, c);
3219
3220 /* And set it */
3221 columns = cell->attr.columns;
3222 for (i = 0; i < columns; i++) {
3223 cell = _vte_row_data_get_writable (row, col++);
3224 cell->c = c;
3225 }
3226
3227 /* Always invalidate since we put the mark on the *previous* cell
3228 * and the higher level code doesn't know this. */
3229 _vte_invalidate_cells(terminal,
3230 col - columns,
3231 columns,
3232 row_num, 1);
3233
3234 goto done;
3235 }
3236
3237 /* Make sure we have enough rows to hold this data. */
3238 row = vte_terminal_ensure_cursor (terminal);
3239 g_assert(row != NULL);
3240
3241 if (insert) {
3242 _vte_terminal_cleanup_fragments (terminal, col, col);
3243 for (i = 0; i < columns; i++)
3244 _vte_row_data_insert (row, col + i, &terminal->pvt->color_defaults);
3245 } else {
3246 _vte_terminal_cleanup_fragments (terminal, col, col + columns);
3247 _vte_row_data_fill (row, &basic_cell.cell, col + columns);
3248 }
3249
3250 attr = terminal->pvt->defaults.attr;
3251 attr.fore = terminal->pvt->color_defaults.attr.fore;
3252 attr.back = terminal->pvt->color_defaults.attr.back;
3253 attr.columns = columns;
3254
3255 {
3256 VteCell *pcell = _vte_row_data_get_writable (row, col);
3257 pcell->c = c;
3258 pcell->attr = attr;
3259 col++;
3260 }
3261
3262 /* insert wide-char fragments */
3263 attr.fragment = 1;
3264 for (i = 1; i < columns; i++) {
3265 VteCell *pcell = _vte_row_data_get_writable (row, col);
3266 pcell->c = c;
3267 pcell->attr = attr;
3268 col++;
3269 }
3270 if (_vte_row_data_length (row) > terminal->pvt->column_count)
3271 _vte_terminal_cleanup_fragments (terminal, terminal->pvt->column_count, _vte_row_data_length (row));
3272 _vte_row_data_shrink (row, terminal->pvt->column_count);
3273
3274 /* Signal that this part of the window needs drawing. */
3275 if (G_UNLIKELY (invalidate_now)) {
3276 _vte_invalidate_cells(terminal,
3277 col - columns,
3278 insert ? terminal->pvt->column_count : columns,
3279 terminal->pvt->cursor.row, 1);
3280 }
3281
3282 terminal->pvt->cursor.col = col;
3283
3284 done:
3285 /* We added text, so make a note of it. */
3286 terminal->pvt->text_inserted_flag = TRUE;
3287
3288 not_inserted:
3289 _vte_debug_print(VTE_DEBUG_ADJ|VTE_DEBUG_PARSE,
3290 "insertion delta => %ld.\n",
3291 (long)screen->insert_delta);
3292 return line_wrapped;
3293 }
3294
3295 static void
vte_terminal_child_watch_cb(GPid pid,int status,VteTerminal * terminal)3296 vte_terminal_child_watch_cb(GPid pid,
3297 int status,
3298 VteTerminal *terminal)
3299 {
3300 if (terminal == NULL) {
3301 /* The child outlived VteTerminal. Do nothing, we're happy that Glib
3302 * read its exit data and hence it's no longer there as zombie. */
3303 return;
3304 }
3305
3306 if (pid == terminal->pvt->pty_pid) {
3307 GObject *object = G_OBJECT(terminal);
3308
3309 g_object_ref(object);
3310 g_object_freeze_notify(object);
3311
3312 _VTE_DEBUG_IF (VTE_DEBUG_LIFECYCLE) {
3313 g_printerr ("Child[%d] exited with status %d\n",
3314 pid, status);
3315 #ifdef HAVE_SYS_WAIT_H
3316 if (WIFEXITED (status)) {
3317 g_printerr ("Child[%d] exit code %d.\n",
3318 pid, WEXITSTATUS (status));
3319 }else if (WIFSIGNALED (status)) {
3320 g_printerr ("Child[%d] dies with signal %d.\n",
3321 pid, WTERMSIG (status));
3322 }
3323 #endif
3324 }
3325
3326 terminal->pvt->child_watch_source = 0;
3327 terminal->pvt->pty_pid = -1;
3328
3329 /* Close out the PTY. */
3330 vte_terminal_set_pty(terminal, NULL);
3331
3332 /* Tell observers what's happened. */
3333 vte_terminal_emit_child_exited(terminal, status);
3334
3335 g_object_thaw_notify(object);
3336 g_object_unref(object);
3337
3338 /* Note: terminal may be destroyed at this point */
3339 }
3340 }
3341
mark_input_source_invalid(VteTerminal * terminal)3342 static void mark_input_source_invalid(VteTerminal *terminal)
3343 {
3344 _vte_debug_print (VTE_DEBUG_IO, "removed poll of vte_terminal_io_read\n");
3345 terminal->pvt->pty_input_source = 0;
3346 }
3347 static void
_vte_terminal_connect_pty_read(VteTerminal * terminal)3348 _vte_terminal_connect_pty_read(VteTerminal *terminal)
3349 {
3350 if (terminal->pvt->pty_channel == NULL) {
3351 return;
3352 }
3353
3354 if (terminal->pvt->pty_input_source == 0) {
3355 _vte_debug_print (VTE_DEBUG_IO, "polling vte_terminal_io_read\n");
3356 terminal->pvt->pty_input_source =
3357 g_io_add_watch_full(terminal->pvt->pty_channel,
3358 VTE_CHILD_INPUT_PRIORITY,
3359 G_IO_IN | G_IO_HUP,
3360 (GIOFunc) vte_terminal_io_read,
3361 terminal,
3362 (GDestroyNotify) mark_input_source_invalid);
3363 }
3364 }
3365
mark_output_source_invalid(VteTerminal * terminal)3366 static void mark_output_source_invalid(VteTerminal *terminal)
3367 {
3368 _vte_debug_print (VTE_DEBUG_IO, "removed poll of vte_terminal_io_write\n");
3369 terminal->pvt->pty_output_source = 0;
3370 }
3371 static void
_vte_terminal_connect_pty_write(VteTerminal * terminal)3372 _vte_terminal_connect_pty_write(VteTerminal *terminal)
3373 {
3374 VteTerminalPrivate *pvt = terminal->pvt;
3375
3376 g_assert(pvt->pty != NULL);
3377 g_warn_if_fail(pvt->input_enabled);
3378
3379 if (terminal->pvt->pty_channel == NULL) {
3380 pvt->pty_channel =
3381 g_io_channel_unix_new(vte_pty_get_fd(pvt->pty));
3382 }
3383
3384 if (terminal->pvt->pty_output_source == 0) {
3385 if (vte_terminal_io_write (terminal->pvt->pty_channel,
3386 G_IO_OUT,
3387 terminal))
3388 {
3389 _vte_debug_print (VTE_DEBUG_IO, "polling vte_terminal_io_write\n");
3390 terminal->pvt->pty_output_source =
3391 g_io_add_watch_full(terminal->pvt->pty_channel,
3392 VTE_CHILD_OUTPUT_PRIORITY,
3393 G_IO_OUT,
3394 (GIOFunc) vte_terminal_io_write,
3395 terminal,
3396 (GDestroyNotify) mark_output_source_invalid);
3397 }
3398 }
3399 }
3400
3401 static void
_vte_terminal_disconnect_pty_read(VteTerminal * terminal)3402 _vte_terminal_disconnect_pty_read(VteTerminal *terminal)
3403 {
3404 if (terminal->pvt->pty_input_source != 0) {
3405 _vte_debug_print (VTE_DEBUG_IO, "disconnecting poll of vte_terminal_io_read\n");
3406 g_source_remove(terminal->pvt->pty_input_source);
3407 terminal->pvt->pty_input_source = 0;
3408 }
3409 }
3410
3411 static void
_vte_terminal_disconnect_pty_write(VteTerminal * terminal)3412 _vte_terminal_disconnect_pty_write(VteTerminal *terminal)
3413 {
3414 if (terminal->pvt->pty_output_source != 0) {
3415 _vte_debug_print (VTE_DEBUG_IO, "disconnecting poll of vte_terminal_io_write\n");
3416
3417 g_source_remove(terminal->pvt->pty_output_source);
3418 terminal->pvt->pty_output_source = 0;
3419 }
3420 }
3421
3422 /**
3423 * vte_terminal_pty_new_sync:
3424 * @terminal: a #VteTerminal
3425 * @flags: flags from #VtePtyFlags
3426 * @cancellable: (allow-none): a #GCancellable, or %NULL
3427 * @error: (allow-none): return location for a #GError, or %NULL
3428 *
3429 * Creates a new #VtePty, and sets the emulation property
3430 * from #VteTerminal:emulation.
3431 *
3432 * See vte_pty_new() for more information.
3433 *
3434 * Returns: (transfer full): a new #VtePty
3435 */
3436 VtePty *
vte_terminal_pty_new_sync(VteTerminal * terminal,VtePtyFlags flags,GCancellable * cancellable,GError ** error)3437 vte_terminal_pty_new_sync(VteTerminal *terminal,
3438 VtePtyFlags flags,
3439 GCancellable *cancellable,
3440 GError **error)
3441 {
3442 VtePty *pty;
3443
3444 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
3445
3446 pty = vte_pty_new_sync(flags, cancellable, error);
3447 if (pty == NULL)
3448 return NULL;
3449
3450 return pty;
3451 }
3452
3453 /**
3454 * vte_terminal_watch_child:
3455 * @terminal: a #VteTerminal
3456 * @child_pid: a #GPid
3457 *
3458 * Watches @child_pid. When the process exists, the #VteTerminal::child-exited
3459 * signal will be called with the child's exit status.
3460 *
3461 * Prior to calling this function, a #VtePty must have been set in @terminal
3462 * using vte_terminal_set_pty().
3463 * When the child exits, the terminal's #VtePty will be set to %NULL.
3464 *
3465 * Note: g_child_watch_add() or g_child_watch_add_full() must not have
3466 * been called for @child_pid, nor a #GSource for it been created with
3467 * g_child_watch_source_new().
3468 *
3469 * Note: when using the g_spawn_async() family of functions,
3470 * the %G_SPAWN_DO_NOT_REAP_CHILD flag MUST have been passed.
3471 */
3472 void
vte_terminal_watch_child(VteTerminal * terminal,GPid child_pid)3473 vte_terminal_watch_child (VteTerminal *terminal,
3474 GPid child_pid)
3475 {
3476 VteTerminalPrivate *pvt;
3477 GObject *object;
3478
3479 g_return_if_fail(VTE_IS_TERMINAL(terminal));
3480 g_return_if_fail(child_pid != -1);
3481
3482 pvt = terminal->pvt;
3483 g_return_if_fail(pvt->pty != NULL);
3484
3485 // FIXMEchpe: support passing child_pid = -1 to remove the wathch
3486
3487 object = G_OBJECT(terminal);
3488
3489 g_object_freeze_notify(object);
3490
3491 /* Set this as the child's pid. */
3492 pvt->pty_pid = child_pid;
3493
3494 /* Catch a child-exited signal from the child pid. */
3495 if (terminal->pvt->child_watch_source != 0) {
3496 g_source_remove (terminal->pvt->child_watch_source);
3497 }
3498 terminal->pvt->child_watch_source =
3499 g_child_watch_add_full(G_PRIORITY_HIGH,
3500 child_pid,
3501 (GChildWatchFunc)vte_terminal_child_watch_cb,
3502 terminal, NULL);
3503
3504 /* FIXMEchpe: call vte_terminal_set_size here? */
3505
3506 g_object_thaw_notify(object);
3507 }
3508
3509 /**
3510 * vte_get_user_shell:
3511 *
3512 * Gets the user's shell, or %NULL. In the latter case, the
3513 * system default (usually "/bin/sh") should be used.
3514 *
3515 * Returns: (transfer full) (type filename): a newly allocated string with the
3516 * user's shell, or %NULL
3517 */
3518 char *
vte_get_user_shell(void)3519 vte_get_user_shell (void)
3520 {
3521 struct passwd *pwd;
3522
3523 pwd = getpwuid(getuid());
3524 if (pwd && pwd->pw_shell)
3525 return g_strdup (pwd->pw_shell);
3526
3527 return NULL;
3528 }
3529
3530 /**
3531 * vte_get_features:
3532 *
3533 * Gets a list of features vte was compiled with.
3534 *
3535 * Returns: (transfer none): a string with features
3536 *
3537 * Since: 0.40
3538 */
3539 const char *
vte_get_features(void)3540 vte_get_features (void)
3541 {
3542 return
3543 #ifdef WITH_GNUTLS
3544 "+GNUTLS"
3545 #else
3546 "-GNUTLS"
3547 #endif
3548 ;
3549 }
3550
3551 /**
3552 * vte_terminal_spawn_sync:
3553 * @terminal: a #VteTerminal
3554 * @pty_flags: flags from #VtePtyFlags
3555 * @working_directory: (allow-none): the name of a directory the command should start
3556 * in, or %NULL to use the current working directory
3557 * @argv: (array zero-terminated=1) (element-type filename): child's argument vector
3558 * @envv: (allow-none) (array zero-terminated=1) (element-type filename): a list of environment
3559 * variables to be added to the environment before starting the process, or %NULL
3560 * @spawn_flags: flags from #GSpawnFlags
3561 * @child_setup: (allow-none) (scope call): an extra child setup function to run in the child just before exec(), or %NULL
3562 * @child_setup_data: user data for @child_setup
3563 * @child_pid: (out) (allow-none) (transfer full): a location to store the child PID, or %NULL
3564 * @cancellable: (allow-none): a #GCancellable, or %NULL
3565 * @error: (allow-none): return location for a #GError, or %NULL
3566 *
3567 * Starts the specified command under a newly-allocated controlling
3568 * pseudo-terminal. The @argv and @envv lists should be %NULL-terminated.
3569 * The "TERM" environment variable is automatically set to a default value,
3570 * but can be overridden from @envv.
3571 * @pty_flags controls logging the session to the specified system log files.
3572 *
3573 * Note that %G_SPAWN_DO_NOT_REAP_CHILD will always be added to @spawn_flags.
3574 *
3575 * Note that unless @spawn_flags contains %G_SPAWN_LEAVE_DESCRIPTORS_OPEN, all file
3576 * descriptors except stdin/stdout/stderr will be closed before calling exec()
3577 * in the child.
3578 *
3579 * See vte_pty_new(), g_spawn_async() and vte_terminal_watch_child() for more information.
3580 *
3581 * Returns: %TRUE on success, or %FALSE on error with @error filled in
3582 */
3583 gboolean
vte_terminal_spawn_sync(VteTerminal * terminal,VtePtyFlags pty_flags,const char * working_directory,char ** argv,char ** envv,GSpawnFlags spawn_flags,GSpawnChildSetupFunc child_setup,gpointer child_setup_data,GPid * child_pid,GCancellable * cancellable,GError ** error)3584 vte_terminal_spawn_sync(VteTerminal *terminal,
3585 VtePtyFlags pty_flags,
3586 const char *working_directory,
3587 char **argv,
3588 char **envv,
3589 GSpawnFlags spawn_flags,
3590 GSpawnChildSetupFunc child_setup,
3591 gpointer child_setup_data,
3592 GPid *child_pid /* out */,
3593 GCancellable *cancellable,
3594 GError **error)
3595 {
3596 VtePty *pty;
3597 GPid pid;
3598
3599 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
3600 g_return_val_if_fail(argv != NULL, FALSE);
3601 g_return_val_if_fail(child_setup_data == NULL || child_setup, FALSE);
3602 g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
3603
3604 pty = vte_terminal_pty_new_sync(terminal, pty_flags, cancellable, error);
3605 if (pty == NULL)
3606 return FALSE;
3607
3608 /* FIXMEchpe: is this flag needed */
3609 spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
3610
3611 if (!__vte_pty_spawn(pty,
3612 working_directory,
3613 argv,
3614 envv,
3615 spawn_flags,
3616 child_setup, child_setup_data,
3617 &pid,
3618 error)) {
3619 g_object_unref(pty);
3620 return FALSE;
3621 }
3622
3623 vte_terminal_set_pty(terminal, pty);
3624 vte_terminal_watch_child(terminal, pid);
3625 g_object_unref (pty);
3626
3627 if (child_pid)
3628 *child_pid = pid;
3629
3630 return TRUE;
3631 }
3632
3633 /* Handle an EOF from the client. */
3634 static void
vte_terminal_eof(GIOChannel * channel,VteTerminal * terminal)3635 vte_terminal_eof(GIOChannel *channel, VteTerminal *terminal)
3636 {
3637 GObject *object = G_OBJECT(terminal);
3638
3639 g_object_freeze_notify(object);
3640
3641 vte_terminal_set_pty(terminal, NULL);
3642
3643 /* Emit a signal that we read an EOF. */
3644 vte_terminal_queue_eof(terminal);
3645
3646 g_object_thaw_notify(object);
3647 }
3648
3649 /* Reset the input method context. */
3650 static void
vte_terminal_im_reset(VteTerminal * terminal)3651 vte_terminal_im_reset(VteTerminal *terminal)
3652 {
3653 if (gtk_widget_get_realized (&terminal->widget)) {
3654 gtk_im_context_reset(terminal->pvt->im_context);
3655 if (terminal->pvt->im_preedit != NULL) {
3656 g_free(terminal->pvt->im_preedit);
3657 terminal->pvt->im_preedit = NULL;
3658 }
3659 if (terminal->pvt->im_preedit_attrs != NULL) {
3660 pango_attr_list_unref(terminal->pvt->im_preedit_attrs);
3661 terminal->pvt->im_preedit_attrs = NULL;
3662 }
3663 }
3664 }
3665
3666 /* Emit whichever signals are called for here. */
3667 static void
vte_terminal_emit_pending_text_signals(VteTerminal * terminal)3668 vte_terminal_emit_pending_text_signals(VteTerminal *terminal)
3669 {
3670 if (terminal->pvt->text_modified_flag) {
3671 _vte_debug_print(VTE_DEBUG_SIGNALS,
3672 "Emitting buffered `text-modified'.\n");
3673 vte_terminal_emit_text_modified(terminal);
3674 terminal->pvt->text_modified_flag = FALSE;
3675 }
3676 if (terminal->pvt->text_inserted_flag) {
3677 _vte_debug_print(VTE_DEBUG_SIGNALS,
3678 "Emitting buffered `text-inserted'\n");
3679 _vte_terminal_emit_text_inserted(terminal);
3680 terminal->pvt->text_inserted_flag = FALSE;
3681 }
3682 if (terminal->pvt->text_deleted_flag) {
3683 _vte_debug_print(VTE_DEBUG_SIGNALS,
3684 "Emitting buffered `text-deleted'\n");
3685 _vte_terminal_emit_text_deleted(terminal);
3686 terminal->pvt->text_deleted_flag = FALSE;
3687 }
3688 }
3689
3690 /* Process incoming data, first converting it to unicode characters, and then
3691 * processing control sequences. */
3692 static void
vte_terminal_process_incoming(VteTerminal * terminal)3693 vte_terminal_process_incoming(VteTerminal *terminal)
3694 {
3695 VteScreen *screen;
3696 VteVisualPosition cursor;
3697 gboolean cursor_visible;
3698 GdkPoint bbox_topleft, bbox_bottomright;
3699 gunichar *wbuf, c;
3700 long wcount, start, delta;
3701 gboolean leftovers, modified, bottom, again;
3702 gboolean invalidated_text;
3703 gboolean in_scroll_region;
3704 GArray *unichars;
3705 struct _vte_incoming_chunk *chunk, *next_chunk, *achunk = NULL;
3706
3707 _vte_debug_print(VTE_DEBUG_IO,
3708 "Handler processing %"G_GSIZE_FORMAT" bytes over %"G_GSIZE_FORMAT" chunks + %d bytes pending.\n",
3709 _vte_incoming_chunks_length(terminal->pvt->incoming),
3710 _vte_incoming_chunks_count(terminal->pvt->incoming),
3711 terminal->pvt->pending->len);
3712 _vte_debug_print (VTE_DEBUG_WORK, "(");
3713
3714 screen = terminal->pvt->screen;
3715
3716 delta = screen->scroll_delta;
3717 bottom = screen->insert_delta == delta;
3718
3719 /* Save the current cursor position. */
3720 cursor = terminal->pvt->cursor;
3721 cursor_visible = terminal->pvt->cursor_visible;
3722
3723 in_scroll_region = terminal->pvt->scrolling_restricted
3724 && (terminal->pvt->cursor.row >= (screen->insert_delta + terminal->pvt->scrolling_region.start))
3725 && (terminal->pvt->cursor.row <= (screen->insert_delta + terminal->pvt->scrolling_region.end));
3726
3727 /* We should only be called when there's data to process. */
3728 g_assert(terminal->pvt->incoming ||
3729 (terminal->pvt->pending->len > 0));
3730
3731 /* Convert the data into unicode characters. */
3732 unichars = terminal->pvt->pending;
3733 for (chunk = _vte_incoming_chunks_reverse (terminal->pvt->incoming);
3734 chunk != NULL;
3735 chunk = next_chunk) {
3736 gsize processed;
3737 next_chunk = chunk->next;
3738 if (chunk->len == 0) {
3739 goto skip_chunk;
3740 }
3741 processed = _vte_iso2022_process(terminal->pvt->iso2022,
3742 chunk->data, chunk->len,
3743 unichars);
3744 if (G_UNLIKELY (processed != chunk->len)) {
3745 /* shuffle the data about */
3746 g_memmove (chunk->data, chunk->data + processed,
3747 chunk->len - processed);
3748 chunk->len = chunk->len - processed;
3749 processed = sizeof (chunk->data) - chunk->len;
3750 if (processed != 0 && next_chunk != NULL) {
3751 if (next_chunk->len <= processed) {
3752 /* consume it entirely */
3753 memcpy (chunk->data + chunk->len,
3754 next_chunk->data,
3755 next_chunk->len);
3756 chunk->len += next_chunk->len;
3757 chunk->next = next_chunk->next;
3758 release_chunk (next_chunk);
3759 } else {
3760 /* next few bytes */
3761 memcpy (chunk->data + chunk->len,
3762 next_chunk->data,
3763 processed);
3764 chunk->len += processed;
3765 g_memmove (next_chunk->data,
3766 next_chunk->data + processed,
3767 next_chunk->len - processed);
3768 next_chunk->len -= processed;
3769 }
3770 next_chunk = chunk; /* repeat */
3771 } else {
3772 break;
3773 }
3774 } else {
3775 skip_chunk:
3776 /* cache the last chunk */
3777 if (achunk) {
3778 release_chunk (achunk);
3779 }
3780 achunk = chunk;
3781 }
3782 }
3783 if (achunk) {
3784 if (chunk != NULL) {
3785 release_chunk (achunk);
3786 } else {
3787 chunk = achunk;
3788 chunk->next = NULL;
3789 chunk->len = 0;
3790 }
3791 }
3792 terminal->pvt->incoming = chunk;
3793
3794 /* Compute the number of unicode characters we got. */
3795 wbuf = &g_array_index(unichars, gunichar, 0);
3796 wcount = unichars->len;
3797
3798 /* Try initial substrings. */
3799 start = 0;
3800 modified = leftovers = again = FALSE;
3801 invalidated_text = FALSE;
3802
3803 bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
3804 bbox_topleft.x = bbox_topleft.y = G_MAXINT;
3805
3806 while (start < wcount && !leftovers) {
3807 const char *match;
3808 const gunichar *next;
3809 GValueArray *params = NULL;
3810
3811 /* Try to match any control sequences. */
3812 _vte_matcher_match(terminal->pvt->matcher,
3813 &wbuf[start],
3814 wcount - start,
3815 &match,
3816 &next,
3817 ¶ms);
3818 /* We're in one of three possible situations now.
3819 * First, the match string is a non-empty string and next
3820 * points to the first character which isn't part of this
3821 * sequence. */
3822 if ((match != NULL) && (match[0] != '\0')) {
3823 gboolean new_in_scroll_region;
3824
3825 /* Call the right sequence handler for the requested
3826 * behavior. */
3827 _vte_terminal_handle_sequence(terminal,
3828 match,
3829 params);
3830 /* Skip over the proper number of unicode chars. */
3831 start = (next - wbuf);
3832 modified = TRUE;
3833
3834 new_in_scroll_region = terminal->pvt->scrolling_restricted
3835 && (terminal->pvt->cursor.row >= (screen->insert_delta + terminal->pvt->scrolling_region.start))
3836 && (terminal->pvt->cursor.row <= (screen->insert_delta + terminal->pvt->scrolling_region.end));
3837
3838 delta = screen->scroll_delta; /* delta may have changed from sequence. */
3839
3840 /* if we have moved greatly during the sequence handler, or moved
3841 * into a scroll_region from outside it, restart the bbox.
3842 */
3843 if (invalidated_text &&
3844 ((new_in_scroll_region && !in_scroll_region) ||
3845 (terminal->pvt->cursor.col > bbox_bottomright.x + VTE_CELL_BBOX_SLACK ||
3846 terminal->pvt->cursor.col < bbox_topleft.x - VTE_CELL_BBOX_SLACK ||
3847 terminal->pvt->cursor.row > bbox_bottomright.y + VTE_CELL_BBOX_SLACK ||
3848 terminal->pvt->cursor.row < bbox_topleft.y - VTE_CELL_BBOX_SLACK))) {
3849 /* Clip off any part of the box which isn't already on-screen. */
3850 bbox_topleft.x = MAX(bbox_topleft.x, 0);
3851 bbox_topleft.y = MAX(bbox_topleft.y, delta);
3852 bbox_bottomright.x = MIN(bbox_bottomright.x,
3853 terminal->pvt->column_count);
3854 /* lazily apply the +1 to the cursor_row */
3855 bbox_bottomright.y = MIN(bbox_bottomright.y + 1,
3856 delta + terminal->pvt->row_count);
3857
3858 _vte_invalidate_cells(terminal,
3859 bbox_topleft.x,
3860 bbox_bottomright.x - bbox_topleft.x,
3861 bbox_topleft.y,
3862 bbox_bottomright.y - bbox_topleft.y);
3863
3864 invalidated_text = FALSE;
3865 bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
3866 bbox_topleft.x = bbox_topleft.y = G_MAXINT;
3867 }
3868
3869 in_scroll_region = new_in_scroll_region;
3870 } else
3871 /* Second, we have a NULL match, and next points to the very
3872 * next character in the buffer. Insert the character which
3873 * we're currently examining into the screen. */
3874 if (match == NULL) {
3875 c = wbuf[start];
3876 /* If it's a control character, permute the order, per
3877 * vttest. */
3878 if ((c != *next) &&
3879 ((*next & 0x1f) == *next) &&
3880 (start + 1 < next - wbuf)) {
3881 const gunichar *tnext = NULL;
3882 const char *tmatch = NULL;
3883 gunichar ctrl;
3884 int i;
3885 /* We don't want to permute it if it's another
3886 * control sequence, so check if it is. */
3887 _vte_matcher_match(terminal->pvt->matcher,
3888 next,
3889 wcount - (next - wbuf),
3890 &tmatch,
3891 &tnext,
3892 NULL);
3893 /* We only do this for non-control-sequence
3894 * characters and random garbage. */
3895 if (tnext == next + 1) {
3896 /* Save the control character. */
3897 ctrl = *next;
3898 /* Move everything before it up a
3899 * slot. */
3900 for (i = next - wbuf; i > start; i--) {
3901 wbuf[i] = wbuf[i - 1];
3902 }
3903 /* Move the control character to the
3904 * front. */
3905 wbuf[i] = ctrl;
3906 goto next_match;
3907 }
3908 }
3909 _VTE_DEBUG_IF(VTE_DEBUG_PARSE) {
3910 if (c > 255) {
3911 g_printerr("U+%04lx\n", (long) c);
3912 } else {
3913 if (c > 127) {
3914 g_printerr("%ld = ",
3915 (long) c);
3916 }
3917 if (c < 32) {
3918 g_printerr("^%lc\n",
3919 (wint_t)c + 64);
3920 } else {
3921 g_printerr("`%lc'\n",
3922 (wint_t)c);
3923 }
3924 }
3925 }
3926
3927 bbox_topleft.x = MIN(bbox_topleft.x,
3928 terminal->pvt->cursor.col);
3929 bbox_topleft.y = MIN(bbox_topleft.y,
3930 terminal->pvt->cursor.row);
3931
3932 /* Insert the character. */
3933 if (G_UNLIKELY (_vte_terminal_insert_char(terminal, c,
3934 FALSE, FALSE))) {
3935 /* line wrapped, correct bbox */
3936 if (invalidated_text &&
3937 (terminal->pvt->cursor.col > bbox_bottomright.x + VTE_CELL_BBOX_SLACK ||
3938 terminal->pvt->cursor.col < bbox_topleft.x - VTE_CELL_BBOX_SLACK ||
3939 terminal->pvt->cursor.row > bbox_bottomright.y + VTE_CELL_BBOX_SLACK ||
3940 terminal->pvt->cursor.row < bbox_topleft.y - VTE_CELL_BBOX_SLACK)) {
3941 /* Clip off any part of the box which isn't already on-screen. */
3942 bbox_topleft.x = MAX(bbox_topleft.x, 0);
3943 bbox_topleft.y = MAX(bbox_topleft.y, delta);
3944 bbox_bottomright.x = MIN(bbox_bottomright.x,
3945 terminal->pvt->column_count);
3946 /* lazily apply the +1 to the cursor_row */
3947 bbox_bottomright.y = MIN(bbox_bottomright.y + 1,
3948 delta + terminal->pvt->row_count);
3949
3950 _vte_invalidate_cells(terminal,
3951 bbox_topleft.x,
3952 bbox_bottomright.x - bbox_topleft.x,
3953 bbox_topleft.y,
3954 bbox_bottomright.y - bbox_topleft.y);
3955 bbox_bottomright.x = bbox_bottomright.y = -G_MAXINT;
3956 bbox_topleft.x = bbox_topleft.y = G_MAXINT;
3957
3958 }
3959 bbox_topleft.x = MIN(bbox_topleft.x, 0);
3960 bbox_topleft.y = MIN(bbox_topleft.y,
3961 terminal->pvt->cursor.row);
3962 }
3963 /* Add the cells over which we have moved to the region
3964 * which we need to refresh for the user. */
3965 bbox_bottomright.x = MAX(bbox_bottomright.x,
3966 terminal->pvt->cursor.col);
3967 /* cursor.row + 1 (defer until inv.) */
3968 bbox_bottomright.y = MAX(bbox_bottomright.y,
3969 terminal->pvt->cursor.row);
3970 invalidated_text = TRUE;
3971
3972 /* We *don't* emit flush pending signals here. */
3973 modified = TRUE;
3974 start++;
3975 } else {
3976 /* Case three: the read broke in the middle of a
3977 * control sequence, so we're undecided with no more
3978 * data to consult. If we have data following the
3979 * middle of the sequence, then it's just garbage data,
3980 * and for compatibility, we should discard it. */
3981 if (wbuf + wcount > next) {
3982 _vte_debug_print(VTE_DEBUG_PARSE,
3983 "Invalid control "
3984 "sequence, discarding %ld "
3985 "characters.\n",
3986 (long)(next - (wbuf + start)));
3987 /* Discard. */
3988 start = next - wbuf + 1;
3989 } else {
3990 /* Pause processing here and wait for more
3991 * data before continuing. */
3992 leftovers = TRUE;
3993 }
3994 }
3995
3996 #ifdef VTE_DEBUG
3997 /* Some safety checks: ensure the visible parts of the buffer
3998 * are all in the buffer. */
3999 g_assert(screen->insert_delta >=
4000 _vte_ring_delta(screen->row_data));
4001 /* The cursor shouldn't be above or below the addressable
4002 * part of the display buffer. */
4003 g_assert(terminal->pvt->cursor.row >= terminal->pvt->screen->insert_delta);
4004 #endif
4005
4006 next_match:
4007 if (G_LIKELY(params != NULL)) {
4008 /* Free any parameters we don't care about any more. */
4009 _vte_matcher_free_params_array(terminal->pvt->matcher,
4010 params);
4011 }
4012 }
4013
4014 /* Remove most of the processed characters. */
4015 if (start < wcount) {
4016 g_array_remove_range(terminal->pvt->pending, 0, start);
4017 } else {
4018 g_array_set_size(terminal->pvt->pending, 0);
4019 /* If we're out of data, we needn't pause to let the
4020 * controlling application respond to incoming data, because
4021 * the main loop is already going to do that. */
4022 }
4023
4024 if (modified) {
4025 /* Keep the cursor on-screen if we scroll on output, or if
4026 * we're currently at the bottom of the buffer. */
4027 _vte_terminal_update_insert_delta(terminal);
4028 if (terminal->pvt->scroll_on_output || bottom) {
4029 vte_terminal_maybe_scroll_to_bottom(terminal);
4030 }
4031 /* Deselect the current selection if its contents are changed
4032 * by this insertion. */
4033 if (terminal->pvt->has_selection) {
4034 char *selection;
4035 selection =
4036 vte_terminal_get_text_range(terminal,
4037 terminal->pvt->selection_start.row,
4038 0,
4039 terminal->pvt->selection_end.row,
4040 terminal->pvt->column_count,
4041 vte_cell_is_selected,
4042 NULL,
4043 NULL);
4044 if ((selection == NULL) || (terminal->pvt->selection == NULL) ||
4045 (strcmp(selection, terminal->pvt->selection) != 0)) {
4046 vte_terminal_deselect_all(terminal);
4047 }
4048 g_free(selection);
4049 }
4050 }
4051
4052 if (modified || (screen != terminal->pvt->screen)) {
4053 /* Signal that the visible contents changed. */
4054 _vte_terminal_queue_contents_changed(terminal);
4055 }
4056
4057 vte_terminal_emit_pending_signals (terminal);
4058
4059 if (invalidated_text) {
4060 /* Clip off any part of the box which isn't already on-screen. */
4061 bbox_topleft.x = MAX(bbox_topleft.x, 0);
4062 bbox_topleft.y = MAX(bbox_topleft.y, delta);
4063 bbox_bottomright.x = MIN(bbox_bottomright.x,
4064 terminal->pvt->column_count);
4065 /* lazily apply the +1 to the cursor_row */
4066 bbox_bottomright.y = MIN(bbox_bottomright.y + 1,
4067 delta + terminal->pvt->row_count);
4068
4069 _vte_invalidate_cells(terminal,
4070 bbox_topleft.x,
4071 bbox_bottomright.x - bbox_topleft.x,
4072 bbox_topleft.y,
4073 bbox_bottomright.y - bbox_topleft.y);
4074 }
4075
4076
4077 if ((cursor.col != terminal->pvt->cursor.col) ||
4078 (cursor.row != terminal->pvt->cursor.row)) {
4079 /* invalidate the old and new cursor positions */
4080 if (cursor_visible)
4081 _vte_invalidate_cell(terminal, cursor.col, cursor.row);
4082 _vte_invalidate_cursor_once(terminal, FALSE);
4083 _vte_check_cursor_blink(terminal);
4084 /* Signal that the cursor moved. */
4085 vte_terminal_queue_cursor_moved(terminal);
4086 } else if (cursor_visible != terminal->pvt->cursor_visible) {
4087 _vte_invalidate_cell(terminal, cursor.col, cursor.row);
4088 _vte_check_cursor_blink(terminal);
4089 }
4090
4091 /* Tell the input method where the cursor is. */
4092 if (gtk_widget_get_realized (&terminal->widget)) {
4093 GdkRectangle rect;
4094 rect.x = terminal->pvt->cursor.col *
4095 terminal->pvt->char_width + terminal->pvt->padding.left;
4096 rect.width = terminal->pvt->char_width;
4097 rect.y = (terminal->pvt->cursor.row - delta) *
4098 terminal->pvt->char_height + terminal->pvt->padding.top;
4099 rect.height = terminal->pvt->char_height;
4100 gtk_im_context_set_cursor_location(terminal->pvt->im_context,
4101 &rect);
4102 }
4103
4104 _vte_debug_print (VTE_DEBUG_WORK, ")");
4105 _vte_debug_print (VTE_DEBUG_IO,
4106 "%ld chars and %ld bytes in %"G_GSIZE_FORMAT" chunks left to process.\n",
4107 (long) unichars->len,
4108 (long) _vte_incoming_chunks_length(terminal->pvt->incoming),
4109 _vte_incoming_chunks_count(terminal->pvt->incoming));
4110 }
4111
4112 static inline void
_vte_terminal_enable_input_source(VteTerminal * terminal)4113 _vte_terminal_enable_input_source (VteTerminal *terminal)
4114 {
4115 if (terminal->pvt->pty_channel == NULL) {
4116 return;
4117 }
4118
4119 if (terminal->pvt->pty_input_source == 0) {
4120 _vte_debug_print (VTE_DEBUG_IO, "polling vte_terminal_io_read\n");
4121 terminal->pvt->pty_input_source =
4122 g_io_add_watch_full(terminal->pvt->pty_channel,
4123 VTE_CHILD_INPUT_PRIORITY,
4124 G_IO_IN | G_IO_HUP,
4125 (GIOFunc) vte_terminal_io_read,
4126 terminal,
4127 (GDestroyNotify) mark_input_source_invalid);
4128 }
4129 }
4130 static void
_vte_terminal_feed_chunks(VteTerminal * terminal,struct _vte_incoming_chunk * chunks)4131 _vte_terminal_feed_chunks (VteTerminal *terminal, struct _vte_incoming_chunk *chunks)
4132 {
4133 struct _vte_incoming_chunk *last;
4134
4135 _vte_debug_print(VTE_DEBUG_IO, "Feed %"G_GSIZE_FORMAT" bytes, in %"G_GSIZE_FORMAT" chunks.\n",
4136 _vte_incoming_chunks_length(chunks),
4137 _vte_incoming_chunks_count(chunks));
4138
4139 for (last = chunks; last->next != NULL; last = last->next) ;
4140 last->next = terminal->pvt->incoming;
4141 terminal->pvt->incoming = chunks;
4142 }
4143 /* Read and handle data from the child. */
4144 static gboolean
vte_terminal_io_read(GIOChannel * channel,GIOCondition condition,VteTerminal * terminal)4145 vte_terminal_io_read(GIOChannel *channel,
4146 GIOCondition condition,
4147 VteTerminal *terminal)
4148 {
4149 int err = 0;
4150 gboolean eof, again = TRUE;
4151
4152 _vte_debug_print (VTE_DEBUG_WORK, ".");
4153
4154 /* Check for end-of-file. */
4155 eof = condition & G_IO_HUP;
4156
4157 /* Read some data in from this channel. */
4158 if (condition & G_IO_IN) {
4159 struct _vte_incoming_chunk *chunk, *chunks = NULL;
4160 const int fd = g_io_channel_unix_get_fd (channel);
4161 guchar *bp;
4162 int rem, len;
4163 guint bytes, max_bytes;
4164
4165 /* Limit the amount read between updates, so as to
4166 * 1. maintain fairness between multiple terminals;
4167 * 2. prevent reading the entire output of a command in one
4168 * pass, i.e. we always try to refresh the terminal ~40Hz.
4169 * See time_process_incoming() where we estimate the
4170 * maximum number of bytes we can read/process in between
4171 * updates.
4172 */
4173 max_bytes = terminal->pvt->active ?
4174 g_list_length (active_terminals) - 1 : 0;
4175 if (max_bytes) {
4176 max_bytes = terminal->pvt->max_input_bytes / max_bytes;
4177 } else {
4178 max_bytes = terminal->pvt->max_input_bytes;
4179 }
4180 bytes = terminal->pvt->input_bytes;
4181
4182 chunk = terminal->pvt->incoming;
4183 do {
4184 if (!chunk || chunk->len >= 3*sizeof (chunk->data)/4) {
4185 chunk = get_chunk ();
4186 chunk->next = chunks;
4187 chunks = chunk;
4188 }
4189 rem = sizeof (chunk->data) - chunk->len;
4190 bp = chunk->data + chunk->len;
4191 len = 0;
4192 do {
4193 int ret = read (fd, bp, rem);
4194 switch (ret){
4195 case -1:
4196 err = errno;
4197 goto out;
4198 case 0:
4199 eof = TRUE;
4200 goto out;
4201 default:
4202 bp += ret;
4203 rem -= ret;
4204 len += ret;
4205 break;
4206 }
4207 } while (rem);
4208 out:
4209 chunk->len += len;
4210 bytes += len;
4211 } while (bytes < max_bytes &&
4212 chunk->len == sizeof (chunk->data));
4213 if (chunk->len == 0 && chunk == chunks) {
4214 chunks = chunks->next;
4215 release_chunk (chunk);
4216 }
4217
4218 if (chunks != NULL) {
4219 _vte_terminal_feed_chunks (terminal, chunks);
4220 }
4221 if (!vte_terminal_is_processing (terminal)) {
4222 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
4223 gdk_threads_enter ();
4224 G_GNUC_END_IGNORE_DEPRECATIONS;
4225
4226 vte_terminal_add_process_timeout (terminal);
4227 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
4228 gdk_threads_leave ();
4229 G_GNUC_END_IGNORE_DEPRECATIONS;
4230 }
4231 terminal->pvt->pty_input_active = len != 0;
4232 terminal->pvt->input_bytes = bytes;
4233 again = bytes < max_bytes;
4234
4235 _vte_debug_print (VTE_DEBUG_IO, "read %d/%d bytes, again? %s, active? %s\n",
4236 bytes, max_bytes,
4237 again ? "yes" : "no",
4238 terminal->pvt->pty_input_active ? "yes" : "no");
4239 }
4240
4241 /* Error? */
4242 switch (err) {
4243 case 0: /* no error */
4244 break;
4245 case EIO: /* Fake an EOF. */
4246 eof = TRUE;
4247 break;
4248 case EAGAIN:
4249 case EBUSY: /* do nothing */
4250 break;
4251 default:
4252 /* Translators: %s is replaced with error message returned by strerror(). */
4253 g_warning (_("Error reading from child: " "%s."),
4254 g_strerror (err));
4255 break;
4256 }
4257
4258 /* If we detected an eof condition, signal one. */
4259 if (eof) {
4260 /* potential deadlock ... */
4261 if (!vte_terminal_is_processing (terminal)) {
4262 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
4263 gdk_threads_enter ();
4264 G_GNUC_END_IGNORE_DEPRECATIONS;
4265
4266 vte_terminal_eof (channel, terminal);
4267
4268 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
4269 gdk_threads_leave ();
4270 G_GNUC_END_IGNORE_DEPRECATIONS;
4271 } else {
4272 vte_terminal_eof (channel, terminal);
4273 }
4274
4275 again = FALSE;
4276 }
4277
4278 return again;
4279 }
4280
4281 /**
4282 * vte_terminal_feed:
4283 * @terminal: a #VteTerminal
4284 * @data: (array length=length) (element-type guint8): a string in the terminal's current encoding
4285 * @length: the length of the string, or -1 to use the full length or a nul-terminated string
4286 *
4287 * Interprets @data as if it were data received from a child process. This
4288 * can either be used to drive the terminal without a child process, or just
4289 * to mess with your users.
4290 */
4291 void
vte_terminal_feed(VteTerminal * terminal,const char * data,gssize length)4292 vte_terminal_feed(VteTerminal *terminal, const char *data, gssize length)
4293 {
4294 /* If length == -1, use the length of the data string. */
4295 if (length == -1) {
4296 length = strlen(data);
4297 }
4298
4299 /* If we have data, modify the incoming buffer. */
4300 if (length > 0) {
4301 struct _vte_incoming_chunk *chunk;
4302 if (terminal->pvt->incoming &&
4303 (gsize)length < sizeof (terminal->pvt->incoming->data) - terminal->pvt->incoming->len) {
4304 chunk = terminal->pvt->incoming;
4305 } else {
4306 chunk = get_chunk ();
4307 _vte_terminal_feed_chunks (terminal, chunk);
4308 }
4309 do { /* break the incoming data into chunks */
4310 gsize rem = sizeof (chunk->data) - chunk->len;
4311 gsize len = (gsize) length < rem ? (gsize) length : rem;
4312 memcpy (chunk->data + chunk->len, data, len);
4313 chunk->len += len;
4314 length -= len;
4315 if (length == 0) {
4316 break;
4317 }
4318 data += len;
4319
4320 chunk = get_chunk ();
4321 _vte_terminal_feed_chunks (terminal, chunk);
4322 } while (1);
4323 vte_terminal_start_processing (terminal);
4324 }
4325 }
4326
4327 /* Send locally-encoded characters to the child. */
4328 static gboolean
vte_terminal_io_write(GIOChannel * channel,GIOCondition condition,VteTerminal * terminal)4329 vte_terminal_io_write(GIOChannel *channel,
4330 GIOCondition condition,
4331 VteTerminal *terminal)
4332 {
4333 gssize count;
4334 int fd;
4335 gboolean leave_open;
4336
4337 fd = g_io_channel_unix_get_fd(channel);
4338
4339 count = write(fd, terminal->pvt->outgoing->data,
4340 _vte_byte_array_length(terminal->pvt->outgoing));
4341 if (count != -1) {
4342 _VTE_DEBUG_IF (VTE_DEBUG_IO) {
4343 gssize i;
4344 for (i = 0; i < count; i++) {
4345 g_printerr("Wrote %c%c\n",
4346 ((guint8)terminal->pvt->outgoing->data[i]) >= 32 ?
4347 ' ' : '^',
4348 ((guint8)terminal->pvt->outgoing->data[i]) >= 32 ?
4349 terminal->pvt->outgoing->data[i] :
4350 ((guint8)terminal->pvt->outgoing->data[i]) + 64);
4351 }
4352 }
4353 _vte_byte_array_consume(terminal->pvt->outgoing, count);
4354 }
4355
4356 if (_vte_byte_array_length(terminal->pvt->outgoing) == 0) {
4357 leave_open = FALSE;
4358 } else {
4359 leave_open = TRUE;
4360 }
4361
4362 return leave_open;
4363 }
4364
4365 /* Convert some arbitrarily-encoded data to send to the child. */
4366 static void
vte_terminal_send(VteTerminal * terminal,const char * encoding,const void * data,gssize length,gboolean local_echo,gboolean newline_stuff)4367 vte_terminal_send(VteTerminal *terminal, const char *encoding,
4368 const void *data, gssize length,
4369 gboolean local_echo, gboolean newline_stuff)
4370 {
4371 gsize icount, ocount;
4372 const guchar *ibuf;
4373 guchar *obuf, *obufptr;
4374 gchar *cooked;
4375 VteConv conv;
4376 long crcount, cooked_length, i;
4377
4378 g_assert(VTE_IS_TERMINAL(terminal));
4379 g_assert(encoding && strcmp(encoding, "UTF-8") == 0);
4380
4381 if (!terminal->pvt->input_enabled)
4382 return;
4383
4384 conv = VTE_INVALID_CONV;
4385 if (strcmp(encoding, "UTF-8") == 0) {
4386 conv = terminal->pvt->outgoing_conv;
4387 }
4388 if (conv == VTE_INVALID_CONV) {
4389 g_warning (_("Unable to send data to child, invalid charset convertor"));
4390 return;
4391 }
4392
4393 icount = length;
4394 ibuf = data;
4395 ocount = ((length + 1) * VTE_UTF8_BPC) + 1;
4396 _vte_byte_array_set_minimum_size(terminal->pvt->conv_buffer, ocount);
4397 obuf = obufptr = terminal->pvt->conv_buffer->data;
4398
4399 if (_vte_conv(conv, &ibuf, &icount, &obuf, &ocount) == (gsize)-1) {
4400 g_warning(_("Error (%s) converting data for child, dropping."),
4401 g_strerror(errno));
4402 } else {
4403 crcount = 0;
4404 if (newline_stuff) {
4405 for (i = 0; i < obuf - obufptr; i++) {
4406 switch (obufptr[i]) {
4407 case '\015':
4408 crcount++;
4409 break;
4410 default:
4411 break;
4412 }
4413 }
4414 }
4415 if (crcount > 0) {
4416 cooked = g_malloc(obuf - obufptr + crcount);
4417 cooked_length = 0;
4418 for (i = 0; i < obuf - obufptr; i++) {
4419 switch (obufptr[i]) {
4420 case '\015':
4421 cooked[cooked_length++] = '\015';
4422 cooked[cooked_length++] = '\012';
4423 break;
4424 default:
4425 cooked[cooked_length++] = obufptr[i];
4426 break;
4427 }
4428 }
4429 } else {
4430 cooked = (gchar *)obufptr;
4431 cooked_length = obuf - obufptr;
4432 }
4433 /* Tell observers that we're sending this to the child. */
4434 if (cooked_length > 0) {
4435 vte_terminal_emit_commit(terminal,
4436 cooked, cooked_length);
4437 }
4438 /* Echo the text if we've been asked to do so. */
4439 if ((cooked_length > 0) && local_echo) {
4440 gunichar *ucs4;
4441 ucs4 = g_utf8_to_ucs4(cooked, cooked_length,
4442 NULL, NULL, NULL);
4443 if (ucs4 != NULL) {
4444 int len;
4445 len = g_utf8_strlen(cooked, cooked_length);
4446 for (i = 0; i < len; i++) {
4447 _vte_terminal_insert_char(terminal,
4448 ucs4[i],
4449 FALSE,
4450 TRUE);
4451 }
4452 g_free(ucs4);
4453 }
4454 }
4455 /* If there's a place for it to go, add the data to the
4456 * outgoing buffer. */
4457 if ((cooked_length > 0) && (terminal->pvt->pty != NULL)) {
4458 _vte_byte_array_append(terminal->pvt->outgoing,
4459 cooked, cooked_length);
4460 _VTE_DEBUG_IF(VTE_DEBUG_KEYBOARD) {
4461 for (i = 0; i < cooked_length; i++) {
4462 if ((((guint8) cooked[i]) < 32) ||
4463 (((guint8) cooked[i]) > 127)) {
4464 g_printerr(
4465 "Sending <%02x> "
4466 "to child.\n",
4467 cooked[i]);
4468 } else {
4469 g_printerr(
4470 "Sending '%c' "
4471 "to child.\n",
4472 cooked[i]);
4473 }
4474 }
4475 }
4476 /* If we need to start waiting for the child pty to
4477 * become available for writing, set that up here. */
4478 _vte_terminal_connect_pty_write(terminal);
4479 }
4480 if (crcount > 0) {
4481 g_free(cooked);
4482 }
4483 }
4484 return;
4485 }
4486
4487 /**
4488 * vte_terminal_feed_child:
4489 * @terminal: a #VteTerminal
4490 * @text: data to send to the child
4491 * @length: length of @text in bytes, or -1 if @text is NUL-terminated
4492 *
4493 * Sends a block of UTF-8 text to the child as if it were entered by the user
4494 * at the keyboard.
4495 */
4496 void
vte_terminal_feed_child(VteTerminal * terminal,const char * text,gssize length)4497 vte_terminal_feed_child(VteTerminal *terminal, const char *text, gssize length)
4498 {
4499 g_return_if_fail(VTE_IS_TERMINAL(terminal));
4500
4501 if (!terminal->pvt->input_enabled)
4502 return;
4503
4504 if (length == -1) {
4505 length = strlen(text);
4506 }
4507 if (length > 0) {
4508 vte_terminal_send(terminal, "UTF-8", text, length,
4509 FALSE, FALSE);
4510 }
4511 }
4512
4513 /**
4514 * vte_terminal_feed_child_binary:
4515 * @terminal: a #VteTerminal
4516 * @data: data to send to the child
4517 * @length: length of @data
4518 *
4519 * Sends a block of binary data to the child.
4520 */
4521 void
vte_terminal_feed_child_binary(VteTerminal * terminal,const guint8 * data,gsize length)4522 vte_terminal_feed_child_binary(VteTerminal *terminal, const guint8 *data, gsize length)
4523 {
4524 g_return_if_fail(VTE_IS_TERMINAL(terminal));
4525
4526 if (!terminal->pvt->input_enabled)
4527 return;
4528
4529 /* Tell observers that we're sending this to the child. */
4530 if (length > 0) {
4531 vte_terminal_emit_commit(terminal,
4532 (char*)data, length);
4533
4534 /* If there's a place for it to go, add the data to the
4535 * outgoing buffer. */
4536 if (terminal->pvt->pty != NULL) {
4537 _vte_byte_array_append(terminal->pvt->outgoing,
4538 data, length);
4539 /* If we need to start waiting for the child pty to
4540 * become available for writing, set that up here. */
4541 _vte_terminal_connect_pty_write(terminal);
4542 }
4543 }
4544 }
4545
4546 static void
vte_terminal_feed_child_using_modes(VteTerminal * terminal,const char * data,glong length)4547 vte_terminal_feed_child_using_modes(VteTerminal *terminal,
4548 const char *data, glong length)
4549 {
4550 if (length == ((gssize)-1)) {
4551 length = strlen(data);
4552 }
4553 if (length > 0) {
4554 vte_terminal_send(terminal, "UTF-8", data, length,
4555 !terminal->pvt->sendrecv_mode,
4556 terminal->pvt->linefeed_mode);
4557 }
4558 }
4559
4560 /* Send text from the input method to the child. */
4561 static void
vte_terminal_im_commit(GtkIMContext * im_context,gchar * text,VteTerminal * terminal)4562 vte_terminal_im_commit(GtkIMContext *im_context, gchar *text, VteTerminal *terminal)
4563 {
4564 _vte_debug_print(VTE_DEBUG_EVENTS,
4565 "Input method committed `%s'.\n", text);
4566 vte_terminal_feed_child_using_modes(terminal, text, -1);
4567 /* Committed text was committed because the user pressed a key, so
4568 * we need to obey the scroll-on-keystroke setting. */
4569 if (terminal->pvt->scroll_on_keystroke) {
4570 vte_terminal_maybe_scroll_to_bottom(terminal);
4571 }
4572 }
4573
4574 /* We've started pre-editing. */
4575 static void
vte_terminal_im_preedit_start(GtkIMContext * im_context,VteTerminal * terminal)4576 vte_terminal_im_preedit_start(GtkIMContext *im_context, VteTerminal *terminal)
4577 {
4578 _vte_debug_print(VTE_DEBUG_EVENTS,
4579 "Input method pre-edit started.\n");
4580 terminal->pvt->im_preedit_active = TRUE;
4581 }
4582
4583 /* We've stopped pre-editing. */
4584 static void
vte_terminal_im_preedit_end(GtkIMContext * im_context,VteTerminal * terminal)4585 vte_terminal_im_preedit_end(GtkIMContext *im_context, VteTerminal *terminal)
4586 {
4587 _vte_debug_print(VTE_DEBUG_EVENTS,
4588 "Input method pre-edit ended.\n");
4589 terminal->pvt->im_preedit_active = FALSE;
4590 }
4591
4592 /* The pre-edit string changed. */
4593 static void
vte_terminal_im_preedit_changed(GtkIMContext * im_context,VteTerminal * terminal)4594 vte_terminal_im_preedit_changed(GtkIMContext *im_context, VteTerminal *terminal)
4595 {
4596 gchar *str;
4597 PangoAttrList *attrs;
4598 gint cursor;
4599
4600 gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor);
4601 _vte_debug_print(VTE_DEBUG_EVENTS,
4602 "Input method pre-edit changed (%s,%d).\n",
4603 str, cursor);
4604
4605 /* Queue the area where the current preedit string is being displayed
4606 * for repainting. */
4607 _vte_invalidate_cursor_once(terminal, FALSE);
4608
4609 g_free(terminal->pvt->im_preedit);
4610 terminal->pvt->im_preedit = str;
4611
4612 if (terminal->pvt->im_preedit_attrs != NULL) {
4613 pango_attr_list_unref(terminal->pvt->im_preedit_attrs);
4614 }
4615 terminal->pvt->im_preedit_attrs = attrs;
4616
4617 terminal->pvt->im_preedit_cursor = cursor;
4618
4619 _vte_invalidate_cursor_once(terminal, FALSE);
4620 }
4621
4622 static void
vte_terminal_set_padding(VteTerminal * terminal)4623 vte_terminal_set_padding(VteTerminal *terminal)
4624 {
4625 VteTerminalPrivate *pvt = terminal->pvt;
4626 GtkWidget *widget = GTK_WIDGET(terminal);
4627 GtkBorder padding;
4628
4629 gtk_style_context_get_padding(gtk_widget_get_style_context(widget),
4630 gtk_widget_get_state_flags(widget),
4631 &padding);
4632
4633 _vte_debug_print(VTE_DEBUG_MISC,
4634 "Setting padding to (%d,%d,%d,%d)\n",
4635 padding.left, padding.right,
4636 padding.top, padding.bottom);
4637
4638 if (memcmp(&padding, &pvt->padding, sizeof(GtkBorder)) == 0)
4639 return;
4640
4641 pvt->padding = padding;
4642
4643 gtk_widget_queue_resize(widget);
4644 }
4645
4646 static void
vte_terminal_style_updated(GtkWidget * widget)4647 vte_terminal_style_updated (GtkWidget *widget)
4648 {
4649 VteTerminal *terminal = VTE_TERMINAL(widget);
4650 float aspect;
4651
4652 GTK_WIDGET_CLASS (vte_terminal_parent_class)->style_updated (widget);
4653
4654 vte_terminal_set_font(terminal, terminal->pvt->unscaled_font_desc);
4655 vte_terminal_set_padding(terminal);
4656
4657 gtk_widget_style_get(widget, "cursor-aspect-ratio", &aspect, NULL);
4658 if (aspect != terminal->pvt->cursor_aspect_ratio) {
4659 terminal->pvt->cursor_aspect_ratio = aspect;
4660 _vte_invalidate_cursor_once(terminal, FALSE);
4661 }
4662 }
4663
4664 static void
add_cursor_timeout(VteTerminal * terminal)4665 add_cursor_timeout (VteTerminal *terminal)
4666 {
4667 if (terminal->pvt->cursor_blink_tag)
4668 return; /* already added */
4669
4670 terminal->pvt->cursor_blink_time = 0;
4671 terminal->pvt->cursor_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
4672 terminal->pvt->cursor_blink_cycle,
4673 (GSourceFunc)vte_invalidate_cursor_periodic,
4674 terminal,
4675 NULL);
4676 }
4677
4678 static void
remove_cursor_timeout(VteTerminal * terminal)4679 remove_cursor_timeout (VteTerminal *terminal)
4680 {
4681 if (terminal->pvt->cursor_blink_tag == 0)
4682 return; /* already removed */
4683
4684 g_source_remove (terminal->pvt->cursor_blink_tag);
4685 terminal->pvt->cursor_blink_tag = 0;
4686 }
4687
4688 /* Activates / disactivates the cursor blink timer to reduce wakeups */
4689 static void
_vte_check_cursor_blink(VteTerminal * terminal)4690 _vte_check_cursor_blink(VteTerminal *terminal)
4691 {
4692 if (terminal->pvt->has_focus &&
4693 terminal->pvt->cursor_blinks &&
4694 terminal->pvt->cursor_visible)
4695 add_cursor_timeout(terminal);
4696 else
4697 remove_cursor_timeout(terminal);
4698 }
4699
4700 void
_vte_terminal_audible_beep(VteTerminal * terminal)4701 _vte_terminal_audible_beep(VteTerminal *terminal)
4702 {
4703 GdkDisplay *display;
4704
4705 g_assert(VTE_IS_TERMINAL(terminal));
4706 display = gtk_widget_get_display(&terminal->widget);
4707 gdk_display_beep(display);
4708 }
4709
4710 void
_vte_terminal_beep(VteTerminal * terminal)4711 _vte_terminal_beep(VteTerminal *terminal)
4712 {
4713 if (terminal->pvt->audible_bell) {
4714 _vte_terminal_audible_beep (terminal);
4715 }
4716 }
4717
4718
4719 static guint
vte_translate_ctrlkey(GdkEventKey * event)4720 vte_translate_ctrlkey (GdkEventKey *event)
4721 {
4722 guint keyval;
4723 GdkKeymap *keymap;
4724 unsigned int i;
4725
4726 if (event->keyval < 128)
4727 return event->keyval;
4728
4729 keymap = gdk_keymap_get_for_display(gdk_window_get_display (event->window));
4730
4731 /* Try groups in order to find one mapping the key to ASCII */
4732 for (i = 0; i < 4; i++) {
4733 GdkModifierType consumed_modifiers;
4734
4735 gdk_keymap_translate_keyboard_state (keymap,
4736 event->hardware_keycode, event->state,
4737 i,
4738 &keyval, NULL, NULL, &consumed_modifiers);
4739 if (keyval < 128) {
4740 _vte_debug_print (VTE_DEBUG_EVENTS,
4741 "ctrl+Key, group=%d de-grouped into keyval=0x%x\n",
4742 event->group, keyval);
4743 return keyval;
4744 }
4745 }
4746
4747 return event->keyval;
4748 }
4749
4750 static void
vte_terminal_read_modifiers(VteTerminal * terminal,GdkEvent * event)4751 vte_terminal_read_modifiers (VteTerminal *terminal,
4752 GdkEvent *event)
4753 {
4754 GdkKeymap *keymap;
4755 GdkModifierType modifiers;
4756
4757 /* Read the modifiers. */
4758 if (!gdk_event_get_state((GdkEvent*)event, &modifiers))
4759 return;
4760
4761 keymap = gdk_keymap_get_for_display(gdk_window_get_display(((GdkEventAny*)event)->window));
4762
4763 gdk_keymap_add_virtual_modifiers (keymap, &modifiers);
4764
4765 #if 1
4766 /* HACK! Treat ALT as META; see bug #663779. */
4767 if (modifiers & GDK_MOD1_MASK)
4768 modifiers |= VTE_META_MASK;
4769 #endif
4770
4771 terminal->pvt->modifiers = modifiers;
4772 }
4773
4774 /* Read and handle a keypress event. */
4775 static gint
vte_terminal_key_press(GtkWidget * widget,GdkEventKey * event)4776 vte_terminal_key_press(GtkWidget *widget, GdkEventKey *event)
4777 {
4778 VteTerminal *terminal;
4779 GdkModifierType modifiers;
4780 char *normal = NULL, *output;
4781 gssize normal_length = 0;
4782 int i;
4783 struct termios tio;
4784 gboolean scrolled = FALSE, steal = FALSE, modifier = FALSE, handled,
4785 suppress_meta_esc = FALSE, add_modifiers = FALSE;
4786 guint keyval = 0;
4787 gunichar keychar = 0;
4788 char keybuf[VTE_UTF8_BPC];
4789
4790 terminal = VTE_TERMINAL(widget);
4791
4792 /* We do NOT want chain up to GtkWidget::key-press-event, since that would
4793 * cause GtkWidget's keybindings to be handled and consumed. However we'll
4794 * have to handle the one sane binding (Shift-F10 or MenuKey, to pop up the
4795 * context menu) ourself, so for now we simply skip the offending keybinding
4796 * in class_init.
4797 */
4798
4799 /* First, check if GtkWidget's behavior already does something with
4800 * this key. */
4801 if (GTK_WIDGET_CLASS(vte_terminal_parent_class)->key_press_event) {
4802 if ((GTK_WIDGET_CLASS(vte_terminal_parent_class))->key_press_event(widget,
4803 event)) {
4804 return TRUE;
4805 }
4806 }
4807
4808 /* If it's a keypress, record that we got the event, in case the
4809 * input method takes the event from us. */
4810 if (event->type == GDK_KEY_PRESS) {
4811 /* Store a copy of the key. */
4812 keyval = event->keyval;
4813 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
4814
4815 /* If we're in margin bell mode and on the border of the
4816 * margin, bell. */
4817 if (terminal->pvt->margin_bell) {
4818 if ((terminal->pvt->cursor.col +
4819 (glong) terminal->pvt->bell_margin) ==
4820 terminal->pvt->column_count) {
4821 _vte_terminal_beep (terminal);
4822 }
4823 }
4824
4825 if (terminal->pvt->cursor_blink_tag != 0)
4826 {
4827 remove_cursor_timeout (terminal);
4828 if (terminal->pvt->cursor_blink_state == FALSE) {
4829 _vte_invalidate_cursor_once(terminal, FALSE);
4830 terminal->pvt->cursor_blink_state = TRUE;
4831 }
4832 add_cursor_timeout (terminal);
4833 }
4834
4835 /* Determine if this is just a modifier key. */
4836 modifier = _vte_keymap_key_is_modifier(keyval);
4837
4838 /* Unless it's a modifier key, hide the pointer. */
4839 if (!modifier) {
4840 _vte_terminal_set_pointer_visible(terminal, FALSE);
4841 }
4842
4843 _vte_debug_print(VTE_DEBUG_EVENTS,
4844 "Keypress, modifiers=0x%x, "
4845 "keyval=0x%x, raw string=`%s'.\n",
4846 terminal->pvt->modifiers,
4847 keyval, event->string);
4848
4849 /* We steal many keypad keys here. */
4850 if (!terminal->pvt->im_preedit_active) {
4851 switch (keyval) {
4852 case GDK_KEY_KP_Add:
4853 case GDK_KEY_KP_Subtract:
4854 case GDK_KEY_KP_Multiply:
4855 case GDK_KEY_KP_Divide:
4856 case GDK_KEY_KP_Enter:
4857 steal = TRUE;
4858 break;
4859 default:
4860 break;
4861 }
4862 if (terminal->pvt->modifiers & VTE_META_MASK) {
4863 steal = TRUE;
4864 }
4865 switch (keyval) {
4866 case GDK_KEY_ISO_Lock:
4867 case GDK_KEY_ISO_Level2_Latch:
4868 case GDK_KEY_ISO_Level3_Shift:
4869 case GDK_KEY_ISO_Level3_Latch:
4870 case GDK_KEY_ISO_Level3_Lock:
4871 case GDK_KEY_ISO_Level5_Shift:
4872 case GDK_KEY_ISO_Level5_Latch:
4873 case GDK_KEY_ISO_Level5_Lock:
4874 case GDK_KEY_ISO_Group_Shift:
4875 case GDK_KEY_ISO_Group_Latch:
4876 case GDK_KEY_ISO_Group_Lock:
4877 case GDK_KEY_ISO_Next_Group:
4878 case GDK_KEY_ISO_Next_Group_Lock:
4879 case GDK_KEY_ISO_Prev_Group:
4880 case GDK_KEY_ISO_Prev_Group_Lock:
4881 case GDK_KEY_ISO_First_Group:
4882 case GDK_KEY_ISO_First_Group_Lock:
4883 case GDK_KEY_ISO_Last_Group:
4884 case GDK_KEY_ISO_Last_Group_Lock:
4885 case GDK_KEY_Multi_key:
4886 case GDK_KEY_Codeinput:
4887 case GDK_KEY_SingleCandidate:
4888 case GDK_KEY_MultipleCandidate:
4889 case GDK_KEY_PreviousCandidate:
4890 case GDK_KEY_Kanji:
4891 case GDK_KEY_Muhenkan:
4892 case GDK_KEY_Henkan_Mode:
4893 /* case GDK_KEY_Henkan: is GDK_KEY_Henkan_Mode */
4894 case GDK_KEY_Romaji:
4895 case GDK_KEY_Hiragana:
4896 case GDK_KEY_Katakana:
4897 case GDK_KEY_Hiragana_Katakana:
4898 case GDK_KEY_Zenkaku:
4899 case GDK_KEY_Hankaku:
4900 case GDK_KEY_Zenkaku_Hankaku:
4901 case GDK_KEY_Touroku:
4902 case GDK_KEY_Massyo:
4903 case GDK_KEY_Kana_Lock:
4904 case GDK_KEY_Kana_Shift:
4905 case GDK_KEY_Eisu_Shift:
4906 case GDK_KEY_Eisu_toggle:
4907 /* case GDK_KEY_Kanji_Bangou: is GDK_KEY_Codeinput */
4908 /* case GDK_KEY_Zen_Koho: is GDK_KEY_MultipleCandidate */
4909 /* case GDK_KEY_Mae_Koho: is GDK_KEY_PreviousCandidate */
4910 /* case GDK_KEY_kana_switch: is GDK_KEY_ISO_Group_Shift */
4911 case GDK_KEY_Hangul:
4912 case GDK_KEY_Hangul_Start:
4913 case GDK_KEY_Hangul_End:
4914 case GDK_KEY_Hangul_Hanja:
4915 case GDK_KEY_Hangul_Jamo:
4916 case GDK_KEY_Hangul_Romaja:
4917 /* case GDK_KEY_Hangul_Codeinput: is GDK_KEY_Codeinput */
4918 case GDK_KEY_Hangul_Jeonja:
4919 case GDK_KEY_Hangul_Banja:
4920 case GDK_KEY_Hangul_PreHanja:
4921 case GDK_KEY_Hangul_PostHanja:
4922 /* case GDK_KEY_Hangul_SingleCandidate: is GDK_KEY_SingleCandidate */
4923 /* case GDK_KEY_Hangul_MultipleCandidate: is GDK_KEY_MultipleCandidate */
4924 /* case GDK_KEY_Hangul_PreviousCandidate: is GDK_KEY_PreviousCandidate */
4925 case GDK_KEY_Hangul_Special:
4926 /* case GDK_KEY_Hangul_switch: is GDK_KEY_ISO_Group_Shift */
4927
4928 steal = FALSE;
4929 break;
4930 default:
4931 break;
4932 }
4933 }
4934 }
4935
4936 modifiers = terminal->pvt->modifiers;
4937
4938 /* Let the input method at this one first. */
4939 if (!steal && terminal->pvt->input_enabled) {
4940 if (gtk_widget_get_realized (&terminal->widget)
4941 && gtk_im_context_filter_keypress (terminal->pvt->im_context, event)) {
4942 _vte_debug_print(VTE_DEBUG_EVENTS,
4943 "Keypress taken by IM.\n");
4944 return TRUE;
4945 }
4946 }
4947
4948 /* Now figure out what to send to the child. */
4949 if ((event->type == GDK_KEY_PRESS) && !modifier) {
4950 handled = FALSE;
4951 /* Map the key to a sequence name if we can. */
4952 switch (keyval) {
4953 case GDK_KEY_BackSpace:
4954 switch (terminal->pvt->backspace_binding) {
4955 case VTE_ERASE_ASCII_BACKSPACE:
4956 normal = g_strdup("");
4957 normal_length = 1;
4958 suppress_meta_esc = FALSE;
4959 break;
4960 case VTE_ERASE_ASCII_DELETE:
4961 normal = g_strdup("");
4962 normal_length = 1;
4963 suppress_meta_esc = FALSE;
4964 break;
4965 case VTE_ERASE_DELETE_SEQUENCE:
4966 normal = g_strdup("\e[3~");
4967 normal_length = 4;
4968 add_modifiers = TRUE;
4969 suppress_meta_esc = TRUE;
4970 break;
4971 case VTE_ERASE_TTY:
4972 if (terminal->pvt->pty != NULL &&
4973 tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1)
4974 {
4975 normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
4976 normal_length = 1;
4977 }
4978 suppress_meta_esc = FALSE;
4979 break;
4980 case VTE_ERASE_AUTO:
4981 default:
4982 #ifndef _POSIX_VDISABLE
4983 #define _POSIX_VDISABLE '\0'
4984 #endif
4985 if (terminal->pvt->pty != NULL &&
4986 tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1 &&
4987 tio.c_cc[VERASE] != _POSIX_VDISABLE)
4988 {
4989 normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
4990 normal_length = 1;
4991 }
4992 else
4993 {
4994 normal = g_strdup("");
4995 normal_length = 1;
4996 suppress_meta_esc = FALSE;
4997 }
4998 suppress_meta_esc = FALSE;
4999 break;
5000 }
5001 handled = TRUE;
5002 break;
5003 case GDK_KEY_KP_Delete:
5004 case GDK_KEY_Delete:
5005 switch (terminal->pvt->delete_binding) {
5006 case VTE_ERASE_ASCII_BACKSPACE:
5007 normal = g_strdup("\010");
5008 normal_length = 1;
5009 break;
5010 case VTE_ERASE_ASCII_DELETE:
5011 normal = g_strdup("\177");
5012 normal_length = 1;
5013 break;
5014 case VTE_ERASE_TTY:
5015 if (terminal->pvt->pty != NULL &&
5016 tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1)
5017 {
5018 normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
5019 normal_length = 1;
5020 }
5021 suppress_meta_esc = FALSE;
5022 break;
5023 case VTE_ERASE_DELETE_SEQUENCE:
5024 case VTE_ERASE_AUTO:
5025 default:
5026 normal = g_strdup("\e[3~");
5027 normal_length = 4;
5028 add_modifiers = TRUE;
5029 break;
5030 }
5031 handled = TRUE;
5032 /* FIXMEchpe: why? this overrides the FALSE set above? */
5033 suppress_meta_esc = TRUE;
5034 break;
5035 case GDK_KEY_KP_Insert:
5036 case GDK_KEY_Insert:
5037 if (modifiers & GDK_SHIFT_MASK) {
5038 if (modifiers & GDK_CONTROL_MASK) {
5039 vte_terminal_paste_clipboard(terminal);
5040 handled = TRUE;
5041 suppress_meta_esc = TRUE;
5042 } else {
5043 vte_terminal_paste_primary(terminal);
5044 handled = TRUE;
5045 suppress_meta_esc = TRUE;
5046 }
5047 } else if (modifiers & GDK_CONTROL_MASK) {
5048 vte_terminal_copy_clipboard(terminal);
5049 handled = TRUE;
5050 suppress_meta_esc = TRUE;
5051 }
5052 break;
5053 /* Keypad/motion keys. */
5054 case GDK_KEY_KP_Up:
5055 case GDK_KEY_Up:
5056 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5057 && modifiers & GDK_CONTROL_MASK
5058 && modifiers & GDK_SHIFT_MASK) {
5059 vte_terminal_scroll_lines(terminal, -1);
5060 scrolled = TRUE;
5061 handled = TRUE;
5062 suppress_meta_esc = TRUE;
5063 }
5064 break;
5065 case GDK_KEY_KP_Down:
5066 case GDK_KEY_Down:
5067 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5068 && modifiers & GDK_CONTROL_MASK
5069 && modifiers & GDK_SHIFT_MASK) {
5070 vte_terminal_scroll_lines(terminal, 1);
5071 scrolled = TRUE;
5072 handled = TRUE;
5073 suppress_meta_esc = TRUE;
5074 }
5075 break;
5076 case GDK_KEY_KP_Page_Up:
5077 case GDK_KEY_Page_Up:
5078 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5079 && modifiers & GDK_SHIFT_MASK) {
5080 vte_terminal_scroll_pages(terminal, -1);
5081 scrolled = TRUE;
5082 handled = TRUE;
5083 suppress_meta_esc = TRUE;
5084 }
5085 break;
5086 case GDK_KEY_KP_Page_Down:
5087 case GDK_KEY_Page_Down:
5088 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5089 && modifiers & GDK_SHIFT_MASK) {
5090 vte_terminal_scroll_pages(terminal, 1);
5091 scrolled = TRUE;
5092 handled = TRUE;
5093 suppress_meta_esc = TRUE;
5094 }
5095 break;
5096 case GDK_KEY_KP_Home:
5097 case GDK_KEY_Home:
5098 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5099 && modifiers & GDK_SHIFT_MASK) {
5100 vte_terminal_maybe_scroll_to_top(terminal);
5101 scrolled = TRUE;
5102 handled = TRUE;
5103 }
5104 break;
5105 case GDK_KEY_KP_End:
5106 case GDK_KEY_End:
5107 if (terminal->pvt->screen == &terminal->pvt->normal_screen
5108 && modifiers & GDK_SHIFT_MASK) {
5109 vte_terminal_maybe_scroll_to_bottom(terminal);
5110 scrolled = TRUE;
5111 handled = TRUE;
5112 }
5113 break;
5114 /* Let Shift +/- tweak the font, like XTerm does. */
5115 case GDK_KEY_KP_Add:
5116 case GDK_KEY_KP_Subtract:
5117 if (modifiers &
5118 (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
5119 switch (keyval) {
5120 case GDK_KEY_KP_Add:
5121 vte_terminal_emit_increase_font_size(terminal);
5122 handled = TRUE;
5123 suppress_meta_esc = TRUE;
5124 break;
5125 case GDK_KEY_KP_Subtract:
5126 vte_terminal_emit_decrease_font_size(terminal);
5127 handled = TRUE;
5128 suppress_meta_esc = TRUE;
5129 break;
5130 }
5131 }
5132 break;
5133 default:
5134 break;
5135 }
5136 /* If the above switch statement didn't do the job, try mapping
5137 * it to a literal or capability name. */
5138 if (handled == FALSE) {
5139 _vte_keymap_map(keyval, modifiers,
5140 terminal->pvt->cursor_mode == VTE_KEYMODE_APPLICATION,
5141 terminal->pvt->keypad_mode == VTE_KEYMODE_APPLICATION,
5142 &normal,
5143 &normal_length);
5144 /* If we found something this way, suppress
5145 * escape-on-meta. */
5146 if (normal != NULL && normal_length > 0) {
5147 suppress_meta_esc = TRUE;
5148 }
5149 }
5150
5151 /* Shall we do this here or earlier? See bug 375112 and bug 589557 */
5152 if (modifiers & GDK_CONTROL_MASK)
5153 keyval = vte_translate_ctrlkey(event);
5154
5155 /* If we didn't manage to do anything, try to salvage a
5156 * printable string. */
5157 if (handled == FALSE && normal == NULL) {
5158
5159 /* Convert the keyval to a gunichar. */
5160 keychar = gdk_keyval_to_unicode(keyval);
5161 normal_length = 0;
5162 if (keychar != 0) {
5163 /* Convert the gunichar to a string. */
5164 normal_length = g_unichar_to_utf8(keychar,
5165 keybuf);
5166 if (normal_length != 0) {
5167 normal = g_malloc(normal_length + 1);
5168 memcpy(normal, keybuf, normal_length);
5169 normal[normal_length] = '\0';
5170 } else {
5171 normal = NULL;
5172 }
5173 }
5174 if ((normal != NULL) &&
5175 (modifiers & GDK_CONTROL_MASK)) {
5176 /* Replace characters which have "control"
5177 * counterparts with those counterparts. */
5178 for (i = 0; i < normal_length; i++) {
5179 if ((((guint8)normal[i]) >= 0x40) &&
5180 (((guint8)normal[i]) < 0x80)) {
5181 normal[i] &= (~(0x60));
5182 }
5183 }
5184 }
5185 _VTE_DEBUG_IF (VTE_DEBUG_EVENTS) {
5186 if (normal) g_printerr(
5187 "Keypress, modifiers=0x%x, "
5188 "keyval=0x%x, cooked string=`%s'.\n",
5189 modifiers,
5190 keyval, normal);
5191 }
5192 }
5193 /* If we got normal characters, send them to the child. */
5194 if (normal != NULL) {
5195 if (add_modifiers) {
5196 _vte_keymap_key_add_key_modifiers(keyval,
5197 modifiers,
5198 terminal->pvt->cursor_mode == VTE_KEYMODE_APPLICATION,
5199 &normal,
5200 &normal_length);
5201 }
5202 if (terminal->pvt->meta_sends_escape &&
5203 !suppress_meta_esc &&
5204 (normal_length > 0) &&
5205 (modifiers & VTE_META_MASK)) {
5206 vte_terminal_feed_child(terminal,
5207 _VTE_CAP_ESC,
5208 1);
5209 }
5210 if (normal_length > 0) {
5211 vte_terminal_feed_child_using_modes(terminal,
5212 normal,
5213 normal_length);
5214 }
5215 g_free(normal);
5216 }
5217 /* Keep the cursor on-screen. */
5218 if (!scrolled && !modifier &&
5219 terminal->pvt->scroll_on_keystroke) {
5220 vte_terminal_maybe_scroll_to_bottom(terminal);
5221 }
5222 return TRUE;
5223 }
5224 return FALSE;
5225 }
5226
5227 static gboolean
vte_terminal_key_release(GtkWidget * widget,GdkEventKey * event)5228 vte_terminal_key_release(GtkWidget *widget, GdkEventKey *event)
5229 {
5230 VteTerminal *terminal;
5231
5232 terminal = VTE_TERMINAL(widget);
5233
5234 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
5235
5236 if (gtk_widget_get_realized (&terminal->widget) &&
5237 terminal->pvt->input_enabled &&
5238 gtk_im_context_filter_keypress (terminal->pvt->im_context, event))
5239 return TRUE;
5240
5241 return FALSE;
5242 }
5243
5244 static int
compare_unichar_p(const void * u1p,const void * u2p)5245 compare_unichar_p(const void *u1p,
5246 const void *u2p)
5247 {
5248 const gunichar u1 = *(gunichar*)u1p;
5249 const gunichar u2 = *(gunichar*)u2p;
5250 return u1 < u2 ? -1 : u1 > u2 ? 1 : 0;
5251 }
5252
5253 static const guint8 word_char_by_category[] = {
5254 [G_UNICODE_CONTROL] = 2,
5255 [G_UNICODE_FORMAT] = 2,
5256 [G_UNICODE_UNASSIGNED] = 2,
5257 [G_UNICODE_PRIVATE_USE] = 0,
5258 [G_UNICODE_SURROGATE] = 2,
5259 [G_UNICODE_LOWERCASE_LETTER] = 1,
5260 [G_UNICODE_MODIFIER_LETTER] = 1,
5261 [G_UNICODE_OTHER_LETTER] = 1,
5262 [G_UNICODE_TITLECASE_LETTER] = 1,
5263 [G_UNICODE_UPPERCASE_LETTER] = 1,
5264 [G_UNICODE_SPACING_MARK] = 0,
5265 [G_UNICODE_ENCLOSING_MARK] = 0,
5266 [G_UNICODE_NON_SPACING_MARK] = 0,
5267 [G_UNICODE_DECIMAL_NUMBER] = 1,
5268 [G_UNICODE_LETTER_NUMBER] = 1,
5269 [G_UNICODE_OTHER_NUMBER] = 1,
5270 [G_UNICODE_CONNECT_PUNCTUATION] = 0,
5271 [G_UNICODE_DASH_PUNCTUATION] = 0,
5272 [G_UNICODE_CLOSE_PUNCTUATION] = 0,
5273 [G_UNICODE_FINAL_PUNCTUATION] = 0,
5274 [G_UNICODE_INITIAL_PUNCTUATION] = 0,
5275 [G_UNICODE_OTHER_PUNCTUATION] = 0,
5276 [G_UNICODE_OPEN_PUNCTUATION] = 0,
5277 [G_UNICODE_CURRENCY_SYMBOL] = 0,
5278 [G_UNICODE_MODIFIER_SYMBOL] = 0,
5279 [G_UNICODE_MATH_SYMBOL] = 0,
5280 [G_UNICODE_OTHER_SYMBOL] = 0,
5281 [G_UNICODE_LINE_SEPARATOR] = 2,
5282 [G_UNICODE_PARAGRAPH_SEPARATOR] = 2,
5283 [G_UNICODE_SPACE_SEPARATOR] = 2,
5284 };
5285
5286 /*
5287 * _vte_terminal_is_word_char:
5288 * @terminal: a #VteTerminal
5289 * @c: a candidate Unicode code point
5290 *
5291 * Checks if a particular character is considered to be part of a word or not.
5292 *
5293 * Returns: %TRUE if the character is considered to be part of a word
5294 */
5295 gboolean
_vte_terminal_is_word_char(VteTerminal * terminal,gunichar c)5296 _vte_terminal_is_word_char(VteTerminal *terminal,
5297 gunichar c)
5298 {
5299 const guint8 v = word_char_by_category[g_unichar_type(c)];
5300
5301 if (v)
5302 return v == 1;
5303
5304 /* Do we have an exception? */
5305 return bsearch(&c,
5306 terminal->pvt->word_char_exceptions,
5307 terminal->pvt->word_char_exceptions_len,
5308 sizeof(gunichar),
5309 compare_unichar_p) != NULL;
5310 }
5311
5312 /* Check if the characters in the two given locations are in the same class
5313 * (word vs. non-word characters). */
5314 static gboolean
vte_same_class(VteTerminal * terminal,glong acol,glong arow,glong bcol,glong brow)5315 vte_same_class(VteTerminal *terminal, glong acol, glong arow,
5316 glong bcol, glong brow)
5317 {
5318 const VteCell *pcell = NULL;
5319 gboolean word_char;
5320 if ((pcell = vte_terminal_find_charcell(terminal, acol, arow)) != NULL && pcell->c != 0) {
5321 word_char = _vte_terminal_is_word_char(terminal, _vte_unistr_get_base (pcell->c));
5322
5323 /* Lets not group non-wordchars together (bug #25290) */
5324 if (!word_char)
5325 return FALSE;
5326
5327 pcell = vte_terminal_find_charcell(terminal, bcol, brow);
5328 if (pcell == NULL || pcell->c == 0) {
5329 return FALSE;
5330 }
5331 if (word_char != _vte_terminal_is_word_char(terminal, _vte_unistr_get_base (pcell->c))) {
5332 return FALSE;
5333 }
5334 return TRUE;
5335 }
5336 return FALSE;
5337 }
5338
5339 /* Check if we soft-wrapped on the given line. */
5340 static gboolean
vte_line_is_wrappable(VteTerminal * terminal,glong row)5341 vte_line_is_wrappable(VteTerminal *terminal, glong row)
5342 {
5343 const VteRowData *rowdata;
5344 rowdata = _vte_terminal_find_row_data(terminal, row);
5345 return rowdata && rowdata->attr.soft_wrapped;
5346 }
5347
5348 /* Check if the given point is in the region between the two points,
5349 * optionally treating the second point as included in the region or not. */
5350 static gboolean
vte_cell_is_between(glong col,glong row,glong acol,glong arow,glong bcol,glong brow,gboolean inclusive)5351 vte_cell_is_between(glong col, glong row,
5352 glong acol, glong arow, glong bcol, glong brow,
5353 gboolean inclusive)
5354 {
5355 /* Negative between never allowed. */
5356 if ((arow > brow) || ((arow == brow) && (acol > bcol))) {
5357 return FALSE;
5358 }
5359 /* Zero-length between only allowed if we're being inclusive. */
5360 if ((row == arow) && (row == brow) && (col == acol) && (col == bcol)) {
5361 return inclusive;
5362 }
5363 /* A cell is between two points if it's on a line after the
5364 * specified area starts, or before the line where it ends,
5365 * or any of the lines in between. */
5366 if ((row > arow) && (row < brow)) {
5367 return TRUE;
5368 }
5369 /* It's also between the two points if they're on the same row
5370 * the cell lies between the start and end columns. */
5371 if ((row == arow) && (row == brow)) {
5372 if (col >= acol) {
5373 if (col < bcol) {
5374 return TRUE;
5375 } else {
5376 if ((col == bcol) && inclusive) {
5377 return TRUE;
5378 } else {
5379 return FALSE;
5380 }
5381 }
5382 } else {
5383 return FALSE;
5384 }
5385 }
5386 /* It's also "between" if it's on the line where the area starts and
5387 * at or after the start column, or on the line where the area ends and
5388 * before the end column. */
5389 if ((row == arow) && (col >= acol)) {
5390 return TRUE;
5391 } else {
5392 if (row == brow) {
5393 if (col < bcol) {
5394 return TRUE;
5395 } else {
5396 if ((col == bcol) && inclusive) {
5397 return TRUE;
5398 } else {
5399 return FALSE;
5400 }
5401 }
5402 } else {
5403 return FALSE;
5404 }
5405 }
5406 return FALSE;
5407 }
5408
5409 /* Check if a cell is selected or not. */
5410 static gboolean
vte_cell_is_selected(VteTerminal * terminal,glong col,glong row,gpointer data)5411 vte_cell_is_selected(VteTerminal *terminal, glong col, glong row, gpointer data)
5412 {
5413 VteVisualPosition ss, se;
5414
5415 /* If there's nothing selected, it's an easy question to answer. */
5416 if (!terminal->pvt->has_selection) {
5417 return FALSE;
5418 }
5419
5420 /* If the selection is obviously bogus, then it's also very easy. */
5421 ss = terminal->pvt->selection_start;
5422 se = terminal->pvt->selection_end;
5423 if ((ss.row < 0) || (se.row < 0)) {
5424 return FALSE;
5425 }
5426
5427 /* Limit selection in block mode. */
5428 if (terminal->pvt->selection_block_mode) {
5429 if (col < ss.col || col > se.col) {
5430 return FALSE;
5431 }
5432 }
5433
5434 /* Now it boils down to whether or not the point is between the
5435 * begin and endpoint of the selection. */
5436 return vte_cell_is_between(col, row, ss.col, ss.row, se.col, se.row, TRUE);
5437 }
5438
5439 /* Once we get text data, actually paste it in. */
5440 static void
vte_terminal_paste_cb(GtkClipboard * clipboard,const gchar * text,gpointer data)5441 vte_terminal_paste_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
5442 {
5443 VteTerminal *terminal;
5444 gchar *paste, *p;
5445 long length;
5446 terminal = data;
5447 if (text != NULL) {
5448 _vte_debug_print(VTE_DEBUG_SELECTION,
5449 "Pasting %"G_GSIZE_FORMAT" UTF-8 bytes.\n",
5450 strlen(text));
5451 if (!g_utf8_validate(text, -1, NULL)) {
5452 g_warning(_("Error (%s) converting data for child, dropping."), g_strerror(EINVAL));
5453 return;
5454 }
5455
5456 /* Convert newlines to carriage returns, which more software
5457 * is able to cope with (cough, pico, cough). */
5458 paste = g_strdup(text);
5459 length = strlen(paste);
5460 p = paste;
5461 while ((p != NULL) && (p - paste < length)) {
5462 p = memchr(p, '\n', length - (p - paste));
5463 if (p != NULL) {
5464 *p = '\r';
5465 p++;
5466 }
5467 }
5468 if (terminal->pvt->bracketed_paste_mode)
5469 vte_terminal_feed_child(terminal, "\e[200~", -1);
5470 vte_terminal_feed_child(terminal, paste, length);
5471 if (terminal->pvt->bracketed_paste_mode)
5472 vte_terminal_feed_child(terminal, "\e[201~", -1);
5473 g_free(paste);
5474 }
5475 }
5476
5477 /**
5478 * _vte_terminal_xy_to_grid:
5479 * @x: the X coordinate
5480 * @y: the Y coordinate
5481 * @col: return location to store the column
5482 * @row: return location to store the row
5483 *
5484 * Translates from widget coordinates to grid coordinates.
5485 *
5486 * If the coordinates are outside the grid, returns %FALSE.
5487 */
5488 gboolean
_vte_terminal_xy_to_grid(VteTerminal * terminal,long x,long y,long * col,long * row)5489 _vte_terminal_xy_to_grid(VteTerminal *terminal,
5490 long x,
5491 long y,
5492 long *col,
5493 long *row)
5494 {
5495 VteTerminalPrivate *pvt = terminal->pvt;
5496 long c, r;
5497
5498 /* FIXMEchpe: is this correct for RTL? */
5499 c = (x - pvt->padding.left) / pvt->char_width;
5500 r = (y - pvt->padding.top) / pvt->char_height;
5501
5502 if ((c < 0 || c >= pvt->column_count) ||
5503 (r < 0 || r >= pvt->row_count))
5504 return FALSE;
5505
5506 *col = c;
5507 *row = r;
5508 return TRUE;
5509 }
5510
5511 /*
5512 * _vte_terminal_size_to_grid_size:
5513 * @w: the width in px
5514 * @h: the height in px
5515 * @col: return location to store the column count
5516 * @row: return location to store the row count
5517 *
5518 * Translates from widget size to grid size.
5519 *
5520 * If the given width or height are insufficient to show even
5521 * one column or row (i.e due to padding), returns %FALSE.
5522 */
5523 gboolean
_vte_terminal_size_to_grid_size(VteTerminal * terminal,long w,long h,long * cols,long * rows)5524 _vte_terminal_size_to_grid_size(VteTerminal *terminal,
5525 long w,
5526 long h,
5527 long *cols,
5528 long *rows)
5529 {
5530 VteTerminalPrivate *pvt = terminal->pvt;
5531 long n_cols, n_rows;
5532
5533 n_cols = (w - pvt->padding.left - pvt->padding.right) / pvt->char_width;
5534 n_rows = (h - pvt->padding.top -pvt->padding.bottom) / pvt->char_height;
5535
5536 if (n_cols <= 0 || n_rows <= 0)
5537 return FALSE;
5538
5539 *cols = n_cols;
5540 *rows = n_rows;
5541 return TRUE;
5542 }
5543
5544 static void
vte_terminal_feed_mouse_event(VteTerminal * terminal,int button,gboolean is_drag,gboolean is_release,long col,long row)5545 vte_terminal_feed_mouse_event(VteTerminal *terminal,
5546 int button,
5547 gboolean is_drag,
5548 gboolean is_release,
5549 long col,
5550 long row)
5551 {
5552 unsigned char cb = 0;
5553 long cx, cy;
5554 char buf[LINE_MAX];
5555 gint len = 0;
5556
5557 /* Encode the button information in cb. */
5558 switch (button) {
5559 case 0: /* No button, just dragging. */
5560 cb = 3;
5561 break;
5562 case 1: /* Left. */
5563 cb = 0;
5564 break;
5565 case 2: /* Middle. */
5566 cb = 1;
5567 break;
5568 case 3: /* Right. */
5569 cb = 2;
5570 break;
5571 case 4:
5572 cb = 64; /* Scroll up. */
5573 break;
5574 case 5:
5575 cb = 65; /* Scroll down. */
5576 break;
5577 }
5578
5579 /* With the exception of the 1006 mode, button release is also encoded here. */
5580 /* Note that if multiple extensions are enabled, the 1006 is used, so it's okay to check for only that. */
5581 if (is_release && !terminal->pvt->mouse_xterm_extension) {
5582 cb = 3;
5583 }
5584
5585 /* Encode the modifiers. */
5586 if (terminal->pvt->modifiers & GDK_SHIFT_MASK) {
5587 cb |= 4;
5588 }
5589 if (terminal->pvt->modifiers & VTE_META_MASK) {
5590 cb |= 8;
5591 }
5592 if (terminal->pvt->modifiers & GDK_CONTROL_MASK) {
5593 cb |= 16;
5594 }
5595
5596 /* Encode a drag event. */
5597 if (is_drag) {
5598 cb |= 32;
5599 }
5600
5601 /* Clamp the cursor coordinates. Make them 1-based. */
5602 cx = CLAMP(1 + col,
5603 1, terminal->pvt->column_count);
5604 cy = CLAMP(1 + row,
5605 1, terminal->pvt->row_count);
5606
5607 /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
5608 if (terminal->pvt->mouse_xterm_extension) {
5609 /* xterm's extended mode (1006) */
5610 len = g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "<%d;%ld;%ld%c", cb, cx, cy, is_release ? 'm' : 'M');
5611 } else if (terminal->pvt->mouse_urxvt_extension) {
5612 /* urxvt's extended mode (1015) */
5613 len = g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "%d;%ld;%ldM", 32 + cb, cx, cy);
5614 } else if (cx <= 231 && cy <= 231) {
5615 /* legacy mode */
5616 len = g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "M%c%c%c", 32 + cb, 32 + (guchar)cx, 32 + (guchar)cy);
5617 }
5618
5619 /* Send event direct to the child, this is binary not text data */
5620 vte_terminal_feed_child_binary(terminal, (guint8*) buf, len);
5621 }
5622
5623 static void
vte_terminal_send_mouse_button_internal(VteTerminal * terminal,int button,gboolean is_release,long x,long y)5624 vte_terminal_send_mouse_button_internal(VteTerminal *terminal,
5625 int button,
5626 gboolean is_release,
5627 long x,
5628 long y)
5629 {
5630 int width = terminal->pvt->char_width;
5631 int height = terminal->pvt->char_height;
5632 long col = (x - terminal->pvt->padding.left) / width;
5633 long row = (y - terminal->pvt->padding.top) / height;
5634
5635 vte_terminal_feed_mouse_event(terminal, button, FALSE /* not drag */, is_release, col, row);
5636 }
5637
5638 /*
5639 * vte_terminal_maybe_send_mouse_button:
5640 * @terminal:
5641 * @event:
5642 *
5643 * Sends a mouse button click or release notification to the application,
5644 * if the terminal is in mouse tracking mode.
5645 *
5646 * Returns: %TRUE iff the event was consumed
5647 */
5648 static gboolean
vte_terminal_maybe_send_mouse_button(VteTerminal * terminal,GdkEventButton * event)5649 vte_terminal_maybe_send_mouse_button(VteTerminal *terminal,
5650 GdkEventButton *event)
5651 {
5652 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
5653
5654 switch (event->type) {
5655 case GDK_BUTTON_PRESS:
5656 if (terminal->pvt->mouse_tracking_mode < MOUSE_TRACKING_SEND_XY_ON_CLICK) {
5657 return FALSE;
5658 }
5659 break;
5660 case GDK_BUTTON_RELEASE: {
5661 if (terminal->pvt->mouse_tracking_mode < MOUSE_TRACKING_SEND_XY_ON_BUTTON) {
5662 return FALSE;
5663 }
5664 break;
5665 }
5666 default:
5667 return FALSE;
5668 break;
5669 }
5670
5671 vte_terminal_send_mouse_button_internal(terminal,
5672 event->button,
5673 event->type == GDK_BUTTON_RELEASE,
5674 event->x, event->y);
5675 return TRUE;
5676 }
5677
5678 /*
5679 * vte_terminal_maybe_send_mouse_drag:
5680 * @terminal:
5681 * @event:
5682 *
5683 * Sends a mouse motion notification to the application,
5684 * if the terminal is in mouse tracking mode.
5685 *
5686 * Returns: %TRUE iff the event was consumed
5687 */
5688 static gboolean
vte_terminal_maybe_send_mouse_drag(VteTerminal * terminal,GdkEventMotion * event)5689 vte_terminal_maybe_send_mouse_drag(VteTerminal *terminal, GdkEventMotion *event)
5690 {
5691 int width = terminal->pvt->char_width;
5692 int height = terminal->pvt->char_height;
5693 long col = ((long) event->x - terminal->pvt->padding.left) / width;
5694 long row = ((long) event->y - terminal->pvt->padding.top) / height;
5695 int button;
5696
5697 /* First determine if we even want to send notification. */
5698 switch (event->type) {
5699 case GDK_MOTION_NOTIFY:
5700 if (terminal->pvt->mouse_tracking_mode < MOUSE_TRACKING_CELL_MOTION_TRACKING)
5701 return FALSE;
5702
5703 if (terminal->pvt->mouse_tracking_mode < MOUSE_TRACKING_ALL_MOTION_TRACKING) {
5704
5705 if (terminal->pvt->mouse_pressed_buttons == 0) {
5706 return FALSE;
5707 }
5708 /* the xterm doc is not clear as to whether
5709 * all-tracking also sends degenerate same-cell events */
5710 if (col == terminal->pvt->mouse_last_x / width &&
5711 row == terminal->pvt->mouse_last_y / height)
5712 return FALSE;
5713 }
5714 break;
5715 default:
5716 return FALSE;
5717 break;
5718 }
5719
5720 /* As per xterm, report the leftmost pressed button - if any. */
5721 if (terminal->pvt->mouse_pressed_buttons & 1)
5722 button = 1;
5723 else if (terminal->pvt->mouse_pressed_buttons & 2)
5724 button = 2;
5725 else if (terminal->pvt->mouse_pressed_buttons & 4)
5726 button = 3;
5727 else
5728 button = 0;
5729 vte_terminal_feed_mouse_event(terminal, button,
5730 TRUE /* drag */, FALSE /* not release */,
5731 col, row);
5732 return TRUE;
5733 }
5734
5735 /* Clear all match hilites. */
5736 static void
vte_terminal_match_hilite_clear(VteTerminal * terminal)5737 vte_terminal_match_hilite_clear(VteTerminal *terminal)
5738 {
5739 long srow, scolumn, erow, ecolumn;
5740 srow = terminal->pvt->match_start.row;
5741 scolumn = terminal->pvt->match_start.col;
5742 erow = terminal->pvt->match_end.row;
5743 ecolumn = terminal->pvt->match_end.col;
5744 terminal->pvt->match_start.row = -1;
5745 terminal->pvt->match_start.col = -1;
5746 terminal->pvt->match_end.row = -2;
5747 terminal->pvt->match_end.col = -2;
5748 if (terminal->pvt->match_tag != -1) {
5749 _vte_debug_print(VTE_DEBUG_EVENTS,
5750 "Clearing hilite (%ld,%ld) to (%ld,%ld).\n",
5751 srow, scolumn, erow, ecolumn);
5752 _vte_invalidate_region (terminal,
5753 scolumn, ecolumn, srow, erow, FALSE);
5754 terminal->pvt->match_tag = -1;
5755 }
5756 terminal->pvt->show_match = FALSE;
5757 if (terminal->pvt->match) {
5758 g_free (terminal->pvt->match);
5759 terminal->pvt->match = NULL;
5760 }
5761 }
5762
5763 static gboolean
cursor_inside_match(VteTerminal * terminal,long x,long y)5764 cursor_inside_match (VteTerminal *terminal, long x, long y)
5765 {
5766 gint width = terminal->pvt->char_width;
5767 gint height = terminal->pvt->char_height;
5768 glong col = x / width;
5769 glong row = y / height + terminal->pvt->screen->scroll_delta;
5770 if (terminal->pvt->match_start.row == terminal->pvt->match_end.row) {
5771 return row == terminal->pvt->match_start.row &&
5772 col >= terminal->pvt->match_start.col &&
5773 col <= terminal->pvt->match_end.col;
5774 } else {
5775 if (row < terminal->pvt->match_start.row ||
5776 row > terminal->pvt->match_end.row) {
5777 return FALSE;
5778 }
5779 if (row == terminal->pvt->match_start.row) {
5780 return col >= terminal->pvt->match_start.col;
5781 }
5782 if (row == terminal->pvt->match_end.row) {
5783 return col <= terminal->pvt->match_end.col;
5784 }
5785 return TRUE;
5786 }
5787 }
5788
5789 static void
vte_terminal_match_hilite_show(VteTerminal * terminal,long x,long y)5790 vte_terminal_match_hilite_show(VteTerminal *terminal, long x, long y)
5791 {
5792 if(terminal->pvt->match != NULL && !terminal->pvt->show_match){
5793 if (cursor_inside_match (terminal, x, y)) {
5794 _vte_invalidate_region (terminal,
5795 terminal->pvt->match_start.col,
5796 terminal->pvt->match_end.col,
5797 terminal->pvt->match_start.row,
5798 terminal->pvt->match_end.row,
5799 FALSE);
5800 terminal->pvt->show_match = TRUE;
5801 }
5802 }
5803 }
5804 static void
vte_terminal_match_hilite_hide(VteTerminal * terminal)5805 vte_terminal_match_hilite_hide(VteTerminal *terminal)
5806 {
5807 if(terminal->pvt->match != NULL && terminal->pvt->show_match){
5808 _vte_invalidate_region (terminal,
5809 terminal->pvt->match_start.col,
5810 terminal->pvt->match_end.col,
5811 terminal->pvt->match_start.row,
5812 terminal->pvt->match_end.row,
5813 FALSE);
5814 terminal->pvt->show_match = FALSE;
5815 }
5816 }
5817
5818
5819 static void
vte_terminal_match_hilite_update(VteTerminal * terminal,long x,long y)5820 vte_terminal_match_hilite_update(VteTerminal *terminal, long x, long y)
5821 {
5822 int start, end, width, height;
5823 char *match;
5824 struct _VteCharAttributes *attr;
5825 VteScreen *screen;
5826 long delta;
5827
5828 width = terminal->pvt->char_width;
5829 height = terminal->pvt->char_height;
5830
5831 /* Check for matches. */
5832 screen = terminal->pvt->screen;
5833 delta = screen->scroll_delta;
5834
5835 _vte_debug_print(VTE_DEBUG_EVENTS,
5836 "Match hilite update (%ld, %ld) -> %ld, %ld\n",
5837 x, y,
5838 x / width,
5839 y / height + delta);
5840
5841 match = vte_terminal_match_check_internal(terminal,
5842 x / width,
5843 y / height + delta,
5844 &terminal->pvt->match_tag,
5845 &start,
5846 &end);
5847 if (terminal->pvt->show_match) {
5848 /* Repaint what used to be hilited, if anything. */
5849 _vte_invalidate_region(terminal,
5850 terminal->pvt->match_start.col,
5851 terminal->pvt->match_end.col,
5852 terminal->pvt->match_start.row,
5853 terminal->pvt->match_end.row,
5854 FALSE);
5855 }
5856
5857 /* Read the new locations. */
5858 attr = NULL;
5859 if ((guint) start < terminal->pvt->match_attributes->len) {
5860 attr = &g_array_index(terminal->pvt->match_attributes,
5861 struct _VteCharAttributes,
5862 start);
5863 terminal->pvt->match_start.row = attr->row;
5864 terminal->pvt->match_start.col = attr->column;
5865
5866 attr = NULL;
5867 if ((guint) end < terminal->pvt->match_attributes->len) {
5868 attr = &g_array_index(terminal->pvt->match_attributes,
5869 struct _VteCharAttributes,
5870 end);
5871 terminal->pvt->match_end.row = attr->row;
5872 terminal->pvt->match_end.col = attr->column;
5873 }
5874 }
5875 if (attr == NULL) { /* i.e. if either endpoint is not found */
5876 terminal->pvt->match_start.row = -1;
5877 terminal->pvt->match_start.col = -1;
5878 terminal->pvt->match_end.row = -2;
5879 terminal->pvt->match_end.col = -2;
5880 g_assert (match == NULL);
5881 }
5882
5883 g_free (terminal->pvt->match);
5884 terminal->pvt->match = match;
5885
5886 /* If there are no matches, repaint what we had matched before. */
5887 if (match == NULL) {
5888 _vte_debug_print(VTE_DEBUG_EVENTS,
5889 "No matches. [(%ld,%ld) to (%ld,%ld)]\n",
5890 terminal->pvt->match_start.col,
5891 terminal->pvt->match_start.row,
5892 terminal->pvt->match_end.col,
5893 terminal->pvt->match_end.row);
5894 terminal->pvt->show_match = FALSE;
5895 } else {
5896 terminal->pvt->show_match = TRUE;
5897 /* Repaint the newly-hilited area. */
5898 _vte_invalidate_region(terminal,
5899 terminal->pvt->match_start.col,
5900 terminal->pvt->match_end.col,
5901 terminal->pvt->match_start.row,
5902 terminal->pvt->match_end.row,
5903 FALSE);
5904 _vte_debug_print(VTE_DEBUG_EVENTS,
5905 "Matched (%ld,%ld) to (%ld,%ld).\n",
5906 terminal->pvt->match_start.col,
5907 terminal->pvt->match_start.row,
5908 terminal->pvt->match_end.col,
5909 terminal->pvt->match_end.row);
5910 }
5911 }
5912 /* Update the hilited text if the pointer has moved to a new character cell. */
5913 static void
vte_terminal_match_hilite(VteTerminal * terminal,long x,long y)5914 vte_terminal_match_hilite(VteTerminal *terminal, long x, long y)
5915 {
5916 int width, height;
5917 GtkAllocation allocation;
5918
5919 width = terminal->pvt->char_width;
5920 height = terminal->pvt->char_height;
5921
5922 gtk_widget_get_allocation (&terminal->widget, &allocation);
5923
5924 /* if the cursor is not above a cell, skip */
5925 if (x < 0 || x > allocation.width
5926 || y < 0 || y > allocation.height) {
5927 return;
5928 }
5929
5930 /* If the pointer hasn't moved to another character cell, then we
5931 * need do nothing. */
5932 if (x / width == terminal->pvt->mouse_last_x / width &&
5933 y / height == terminal->pvt->mouse_last_y / height) {
5934 terminal->pvt->show_match = terminal->pvt->match != NULL;
5935 return;
5936 }
5937
5938 if (cursor_inside_match (terminal, x, y)) {
5939 terminal->pvt->show_match = terminal->pvt->match != NULL;
5940 return;
5941 }
5942
5943 vte_terminal_match_hilite_update(terminal, x, y);
5944 }
5945
5946
5947 /* Note that the clipboard has cleared. */
5948 static void
vte_terminal_clear_cb(GtkClipboard * clipboard,gpointer owner)5949 vte_terminal_clear_cb(GtkClipboard *clipboard, gpointer owner)
5950 {
5951 VteTerminal *terminal;
5952 terminal = owner;
5953 if (terminal->pvt->has_selection) {
5954 _vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n");
5955 vte_terminal_deselect_all(terminal);
5956 }
5957 }
5958
5959 /* Supply the selected text to the clipboard. */
5960 static void
vte_terminal_copy_cb(GtkClipboard * clipboard,GtkSelectionData * data,guint info,gpointer owner)5961 vte_terminal_copy_cb(GtkClipboard *clipboard, GtkSelectionData *data,
5962 guint info, gpointer owner)
5963 {
5964 VteTerminal *terminal;
5965 terminal = owner;
5966 if (terminal->pvt->selection != NULL) {
5967 _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) {
5968 int i;
5969 g_printerr("Setting selection (%"G_GSIZE_FORMAT" UTF-8 bytes.)\n",
5970 strlen(terminal->pvt->selection));
5971 for (i = 0; terminal->pvt->selection[i] != '\0'; i++) {
5972 g_printerr("0x%04x\n",
5973 terminal->pvt->selection[i]);
5974 }
5975 }
5976 gtk_selection_data_set_text(data, terminal->pvt->selection, -1);
5977 }
5978 }
5979
5980 /* Convert the internal color code (either index or RGB, see vte-private.h) into RGB. */
5981 static void
vte_terminal_get_rgb_from_index(const VteTerminal * terminal,guint index,PangoColor * color)5982 vte_terminal_get_rgb_from_index(const VteTerminal *terminal, guint index, PangoColor *color)
5983 {
5984 gboolean dim = FALSE;
5985 if (!(index & VTE_RGB_COLOR) && (index & VTE_DIM_COLOR)) {
5986 index &= ~VTE_DIM_COLOR;
5987 dim = TRUE;
5988 }
5989
5990 if (index >= VTE_LEGACY_COLORS_OFFSET && index < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_FULL_COLOR_SET_SIZE)
5991 index -= VTE_LEGACY_COLORS_OFFSET;
5992 if (index < VTE_PALETTE_SIZE) {
5993 memcpy(color, _vte_terminal_get_color(terminal, index), sizeof(PangoColor));
5994 if (dim) {
5995 /* magic formula taken from xterm */
5996 color->red = color->red * 2 / 3;
5997 color->green = color->green * 2 / 3;
5998 color->blue = color->blue * 2 / 3;
5999 }
6000 } else if (index & VTE_RGB_COLOR) {
6001 color->red = ((index >> 16) & 0xFF) * 257;
6002 color->green = ((index >> 8) & 0xFF) * 257;
6003 color->blue = (index & 0xFF) * 257;
6004 } else {
6005 g_assert_not_reached();
6006 }
6007 }
6008
6009 /**
6010 * VteSelectionFunc:
6011 * @terminal: terminal in which the cell is.
6012 * @column: column in which the cell is.
6013 * @row: row in which the cell is.
6014 * @data: (closure): user data.
6015 *
6016 * Specifies the type of a selection function used to check whether
6017 * a cell has to be selected or not.
6018 *
6019 * Returns: %TRUE if cell has to be selected; %FALSE if otherwise.
6020 */
6021
6022 /**
6023 * vte_terminal_get_text_range:
6024 * @terminal: a #VteTerminal
6025 * @start_row: first row to search for data
6026 * @start_col: first column to search for data
6027 * @end_row: last row to search for data
6028 * @end_col: last column to search for data
6029 * @is_selected: (scope call) (allow-none): a #VteSelectionFunc callback
6030 * @user_data: (closure): user data to be passed to the callback
6031 * @attributes: (out caller-allocates) (transfer full) (array) (element-type Vte.CharAttributes): location for storing text attributes
6032 *
6033 * Extracts a view of the visible part of the terminal. If @is_selected is not
6034 * %NULL, characters will only be read if @is_selected returns %TRUE after being
6035 * passed the column and row, respectively. A #VteCharAttributes structure
6036 * is added to @attributes for each byte added to the returned string detailing
6037 * the character's position, colors, and other characteristics. The
6038 * entire scrollback buffer is scanned, so it is possible to read the entire
6039 * contents of the buffer using this function.
6040 *
6041 * Returns: (transfer full): a newly allocated text string, or %NULL.
6042 */
6043 char *
vte_terminal_get_text_range(VteTerminal * terminal,glong start_row,glong start_col,glong end_row,glong end_col,VteSelectionFunc is_selected,gpointer user_data,GArray * attributes)6044 vte_terminal_get_text_range(VteTerminal *terminal,
6045 glong start_row, glong start_col,
6046 glong end_row, glong end_col,
6047 VteSelectionFunc is_selected,
6048 gpointer user_data,
6049 GArray *attributes)
6050 {
6051 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
6052 return vte_terminal_get_text_range_maybe_wrapped(terminal,
6053 start_row, start_col,
6054 end_row, end_col,
6055 TRUE,
6056 is_selected,
6057 user_data,
6058 attributes,
6059 FALSE);
6060 }
6061
6062 static char *
vte_terminal_get_text_range_maybe_wrapped(VteTerminal * terminal,glong start_row,glong start_col,glong end_row,glong end_col,gboolean wrap,VteSelectionFunc is_selected,gpointer data,GArray * attributes,gboolean include_trailing_spaces)6063 vte_terminal_get_text_range_maybe_wrapped(VteTerminal *terminal,
6064 glong start_row, glong start_col,
6065 glong end_row, glong end_col,
6066 gboolean wrap,
6067 VteSelectionFunc is_selected,
6068 gpointer data,
6069 GArray *attributes,
6070 gboolean include_trailing_spaces)
6071 {
6072 glong col, row, last_empty, last_emptycol, last_nonempty, last_nonemptycol;
6073 const VteCell *pcell = NULL;
6074 GString *string;
6075 struct _VteCharAttributes attr;
6076 PangoColor fore, back;
6077
6078 if (!is_selected)
6079 is_selected = always_selected;
6080
6081 if (attributes)
6082 g_array_set_size (attributes, 0);
6083
6084 string = g_string_new(NULL);
6085 memset(&attr, 0, sizeof(attr));
6086
6087 col = start_col;
6088 for (row = start_row; row < end_row + 1; row++, col = 0) {
6089 const VteRowData *row_data = _vte_terminal_find_row_data (terminal, row);
6090 last_empty = last_nonempty = string->len;
6091 last_emptycol = last_nonemptycol = -1;
6092
6093 attr.row = row;
6094 attr.column = col;
6095 pcell = NULL;
6096 if (row_data != NULL) {
6097 while ((pcell = _vte_row_data_get (row_data, col))) {
6098
6099 attr.column = col;
6100
6101 /* If it's not part of a multi-column character,
6102 * and passes the selection criterion, add it to
6103 * the selection. */
6104 if (!pcell->attr.fragment && is_selected(terminal, col, row, data)) {
6105 /* Store the attributes of this character. */
6106 vte_terminal_get_rgb_from_index(terminal, pcell->attr.fore, &fore);
6107 vte_terminal_get_rgb_from_index(terminal, pcell->attr.back, &back);
6108 attr.fore.red = fore.red;
6109 attr.fore.green = fore.green;
6110 attr.fore.blue = fore.blue;
6111 attr.back.red = back.red;
6112 attr.back.green = back.green;
6113 attr.back.blue = back.blue;
6114 attr.underline = pcell->attr.underline;
6115 attr.strikethrough = pcell->attr.strikethrough;
6116
6117 /* Store the cell string */
6118 if (pcell->c == 0) {
6119 g_string_append_c (string, ' ');
6120 last_empty = string->len;
6121 last_emptycol = col;
6122 } else {
6123 _vte_unistr_append_to_string (pcell->c, string);
6124 last_nonempty = string->len;
6125 last_nonemptycol = col;
6126 }
6127
6128 /* If we added text to the string, record its
6129 * attributes, one per byte. */
6130 if (attributes) {
6131 vte_g_array_fill(attributes,
6132 &attr, string->len);
6133 }
6134 }
6135 /* If we're on the last line, and have just looked in
6136 * the last column, stop. */
6137 if ((row == end_row) && (col >= end_col)) {
6138 break;
6139 }
6140
6141 col++;
6142 }
6143 }
6144
6145 /* If the last thing we saw was a empty, and we stopped at the
6146 * right edge of the selected area, trim the trailing spaces
6147 * off of the line. */
6148 if (!include_trailing_spaces && last_empty > last_nonempty) {
6149
6150 col = last_emptycol + 1;
6151
6152 if (row_data != NULL) {
6153 while ((pcell = _vte_row_data_get (row_data, col))) {
6154 col++;
6155
6156 if (pcell->attr.fragment)
6157 continue;
6158
6159 if (pcell->c != 0)
6160 break;
6161 }
6162 }
6163 if (pcell == NULL) {
6164 g_string_truncate(string, last_nonempty);
6165 if (attributes)
6166 g_array_set_size(attributes, string->len);
6167 attr.column = last_nonemptycol;
6168 }
6169 }
6170
6171 /* Adjust column, in case we want to append a newline */
6172 attr.column = MAX(terminal->pvt->column_count, attr.column + 1);
6173
6174 /* Add a newline in block mode. */
6175 if (terminal->pvt->selection_block_mode) {
6176 string = g_string_append_c(string, '\n');
6177 }
6178 /* Else, if the last visible column on this line was selected and
6179 * not soft-wrapped, append a newline. */
6180 else if (is_selected(terminal, terminal->pvt->column_count, row, data)) {
6181 /* If we didn't softwrap, add a newline. */
6182 /* XXX need to clear row->soft_wrap on deletion! */
6183 if (!vte_line_is_wrappable(terminal, row)) {
6184 string = g_string_append_c(string, '\n');
6185 }
6186 }
6187
6188 /* Make sure that the attributes array is as long as the string. */
6189 if (attributes) {
6190 vte_g_array_fill (attributes, &attr, string->len);
6191 }
6192 }
6193 /* Sanity check. */
6194 g_assert(attributes == NULL || string->len == attributes->len);
6195 return g_string_free(string, FALSE);
6196 }
6197
6198 static char *
vte_terminal_get_text_maybe_wrapped(VteTerminal * terminal,gboolean wrap,VteSelectionFunc is_selected,gpointer data,GArray * attributes,gboolean include_trailing_spaces)6199 vte_terminal_get_text_maybe_wrapped(VteTerminal *terminal,
6200 gboolean wrap,
6201 VteSelectionFunc is_selected,
6202 gpointer data,
6203 GArray *attributes,
6204 gboolean include_trailing_spaces)
6205 {
6206 long start_row, start_col, end_row, end_col;
6207 start_row = terminal->pvt->screen->scroll_delta;
6208 start_col = 0;
6209 end_row = start_row + terminal->pvt->row_count - 1;
6210 end_col = terminal->pvt->column_count - 1;
6211 return vte_terminal_get_text_range_maybe_wrapped(terminal,
6212 start_row, start_col,
6213 end_row, end_col,
6214 wrap,
6215 is_selected,
6216 data,
6217 attributes,
6218 include_trailing_spaces);
6219 }
6220
6221 /**
6222 * vte_terminal_get_text:
6223 * @terminal: a #VteTerminal
6224 * @is_selected: (scope call) (allow-none): a #VteSelectionFunc callback
6225 * @user_data: (closure): user data to be passed to the callback
6226 * @attributes: (out caller-allocates) (transfer full) (array) (element-type Vte.CharAttributes): location for storing text attributes
6227 *
6228 * Extracts a view of the visible part of the terminal. If @is_selected is not
6229 * %NULL, characters will only be read if @is_selected returns %TRUE after being
6230 * passed the column and row, respectively. A #VteCharAttributes structure
6231 * is added to @attributes for each byte added to the returned string detailing
6232 * the character's position, colors, and other characteristics.
6233 *
6234 * Returns: (transfer full): a newly allocated text string, or %NULL.
6235 */
6236 char *
vte_terminal_get_text(VteTerminal * terminal,VteSelectionFunc is_selected,gpointer user_data,GArray * attributes)6237 vte_terminal_get_text(VteTerminal *terminal,
6238 VteSelectionFunc is_selected,
6239 gpointer user_data,
6240 GArray *attributes)
6241 {
6242 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
6243 return vte_terminal_get_text_maybe_wrapped(terminal,
6244 TRUE,
6245 is_selected,
6246 user_data,
6247 attributes,
6248 FALSE);
6249 }
6250
6251 /**
6252 * vte_terminal_get_text_include_trailing_spaces:
6253 * @terminal: a #VteTerminal
6254 * @is_selected: (scope call) (allow-none): a #VteSelectionFunc callback
6255 * @user_data: (closure): user data to be passed to the callback
6256 * @attributes: (out caller-allocates) (transfer full) (array) (element-type Vte.CharAttributes): location for storing text attributes
6257 *
6258 * Extracts a view of the visible part of the terminal. If @is_selected is not
6259 * %NULL, characters will only be read if @is_selected returns %TRUE after being
6260 * passed the column and row, respectively. A #VteCharAttributes structure
6261 * is added to @attributes for each byte added to the returned string detailing
6262 * the character's position, colors, and other characteristics. This function
6263 * differs from vte_terminal_get_text() in that trailing spaces at the end of
6264 * lines are included.
6265 *
6266 * Returns: (transfer full): a newly allocated text string, or %NULL.
6267 */
6268 char *
vte_terminal_get_text_include_trailing_spaces(VteTerminal * terminal,VteSelectionFunc is_selected,gpointer user_data,GArray * attributes)6269 vte_terminal_get_text_include_trailing_spaces(VteTerminal *terminal,
6270 VteSelectionFunc is_selected,
6271 gpointer user_data,
6272 GArray *attributes)
6273 {
6274 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
6275 return vte_terminal_get_text_maybe_wrapped(terminal,
6276 TRUE,
6277 is_selected,
6278 user_data,
6279 attributes,
6280 TRUE);
6281 }
6282
6283 /**
6284 * vte_terminal_get_cursor_position:
6285 * @terminal: a #VteTerminal
6286 * @column: (out) (allow-none): a location to store the column, or %NULL
6287 * @row: (out) (allow-none): a location to store the row, or %NULL
6288 *
6289 * Reads the location of the insertion cursor and returns it. The row
6290 * coordinate is absolute.
6291 */
6292 void
vte_terminal_get_cursor_position(VteTerminal * terminal,glong * column,glong * row)6293 vte_terminal_get_cursor_position(VteTerminal *terminal,
6294 glong *column, glong *row)
6295 {
6296 g_return_if_fail(VTE_IS_TERMINAL(terminal));
6297 if (column) {
6298 *column = terminal->pvt->cursor.col;
6299 }
6300 if (row) {
6301 *row = terminal->pvt->cursor.row;
6302 }
6303 }
6304
6305 static GtkClipboard *
vte_terminal_clipboard_get(VteTerminal * terminal,GdkAtom board)6306 vte_terminal_clipboard_get(VteTerminal *terminal, GdkAtom board)
6307 {
6308 GdkDisplay *display;
6309 display = gtk_widget_get_display(&terminal->widget);
6310 return gtk_clipboard_get_for_display(display, board);
6311 }
6312
6313 /* Place the selected text onto the clipboard. Do this asynchronously so that
6314 * we get notified when the selection we placed on the clipboard is replaced. */
6315 static void
vte_terminal_copy(VteTerminal * terminal,GdkAtom board)6316 vte_terminal_copy(VteTerminal *terminal, GdkAtom board)
6317 {
6318 GtkClipboard *clipboard;
6319 static GtkTargetEntry *targets = NULL;
6320 static gint n_targets = 0;
6321
6322 clipboard = vte_terminal_clipboard_get(terminal, board);
6323
6324 /* Chuck old selected text and retrieve the newly-selected text. */
6325 g_free(terminal->pvt->selection);
6326 terminal->pvt->selection =
6327 vte_terminal_get_text_range(terminal,
6328 terminal->pvt->selection_start.row,
6329 0,
6330 terminal->pvt->selection_end.row,
6331 terminal->pvt->column_count,
6332 vte_cell_is_selected,
6333 NULL,
6334 NULL);
6335 terminal->pvt->has_selection = TRUE;
6336
6337 /* Place the text on the clipboard. */
6338 if (terminal->pvt->selection != NULL) {
6339 _vte_debug_print(VTE_DEBUG_SELECTION,
6340 "Assuming ownership of selection.\n");
6341 if (!targets) {
6342 GtkTargetList *list;
6343
6344 list = gtk_target_list_new (NULL, 0);
6345 gtk_target_list_add_text_targets (list, 0);
6346 targets = gtk_target_table_new_from_list (list, &n_targets);
6347 gtk_target_list_unref (list);
6348 }
6349
6350 gtk_clipboard_set_with_owner(clipboard,
6351 targets,
6352 n_targets,
6353 vte_terminal_copy_cb,
6354 vte_terminal_clear_cb,
6355 G_OBJECT(terminal));
6356 gtk_clipboard_set_can_store(clipboard, NULL, 0);
6357 }
6358 }
6359
6360 /* Paste from the given clipboard. */
6361 static void
vte_terminal_paste(VteTerminal * terminal,GdkAtom board)6362 vte_terminal_paste(VteTerminal *terminal, GdkAtom board)
6363 {
6364 GtkClipboard *clipboard;
6365
6366 if (!terminal->pvt->input_enabled)
6367 return;
6368
6369 clipboard = vte_terminal_clipboard_get(terminal, board);
6370 if (clipboard != NULL) {
6371 _vte_debug_print(VTE_DEBUG_SELECTION,
6372 "Requesting clipboard contents.\n");
6373 gtk_clipboard_request_text(clipboard,
6374 vte_terminal_paste_cb,
6375 terminal);
6376 }
6377 }
6378
6379 static void
vte_terminal_invalidate_selection(VteTerminal * terminal)6380 vte_terminal_invalidate_selection (VteTerminal *terminal)
6381 {
6382 _vte_invalidate_region (terminal,
6383 terminal->pvt->selection_start.col,
6384 terminal->pvt->selection_end.col,
6385 terminal->pvt->selection_start.row,
6386 terminal->pvt->selection_end.row,
6387 terminal->pvt->selection_block_mode);
6388 }
6389
6390 /* Confine coordinates into the visible area. Padding is alreday subtracted. */
6391 static void
vte_terminal_confine_coordinates(VteTerminal * terminal,long * xp,long * yp)6392 vte_terminal_confine_coordinates (VteTerminal *terminal, long *xp, long *yp)
6393 {
6394 long x = *xp;
6395 long y = *yp;
6396
6397 if (y < 0) {
6398 y = 0;
6399 if (!terminal->pvt->selection_block_mode)
6400 x = 0;
6401 } else if (y >= terminal->pvt->row_count * terminal->pvt->char_height) {
6402 y = terminal->pvt->row_count * terminal->pvt->char_height - 1;
6403 if (!terminal->pvt->selection_block_mode)
6404 x = terminal->pvt->column_count * terminal->pvt->char_width - 1;
6405 }
6406 if (x < 0) {
6407 x = 0;
6408 } else if (x >= terminal->pvt->column_count * terminal->pvt->char_width) {
6409 x = terminal->pvt->column_count * terminal->pvt->char_width - 1;
6410 }
6411
6412 *xp = x;
6413 *yp = y;
6414 }
6415
6416 /* Start selection at the location of the event. */
6417 static void
vte_terminal_start_selection(VteTerminal * terminal,long x,long y,enum vte_selection_type selection_type)6418 vte_terminal_start_selection(VteTerminal *terminal, long x, long y,
6419 enum vte_selection_type selection_type)
6420 {
6421 long delta;
6422
6423 if (terminal->pvt->selection_block_mode)
6424 selection_type = selection_type_char;
6425
6426 /* Confine coordinates into the visible area. (#563024, #722635c7) */
6427 vte_terminal_confine_coordinates(terminal, &x, &y);
6428
6429 /* Record that we have the selection, and where it started. */
6430 delta = terminal->pvt->screen->scroll_delta;
6431 terminal->pvt->has_selection = TRUE;
6432 terminal->pvt->selection_last.x = x;
6433 terminal->pvt->selection_last.y = y + (terminal->pvt->char_height * delta);
6434
6435 /* Decide whether or not to restart on the next drag. */
6436 switch (selection_type) {
6437 case selection_type_char:
6438 /* Restart selection once we register a drag. */
6439 terminal->pvt->selecting_restart = TRUE;
6440 terminal->pvt->has_selection = FALSE;
6441 terminal->pvt->selecting_had_delta = FALSE;
6442
6443 terminal->pvt->selection_origin = terminal->pvt->selection_last;
6444 break;
6445 case selection_type_word:
6446 case selection_type_line:
6447 /* Mark the newly-selected areas now. */
6448 terminal->pvt->selecting_restart = FALSE;
6449 terminal->pvt->has_selection = FALSE;
6450 terminal->pvt->selecting_had_delta = FALSE;
6451 break;
6452 }
6453
6454 /* Record the selection type. */
6455 terminal->pvt->selection_type = selection_type;
6456 terminal->pvt->selecting = TRUE;
6457 terminal->pvt->selecting_after_threshold = FALSE;
6458
6459 _vte_debug_print(VTE_DEBUG_SELECTION,
6460 "Selection started at (%ld,%ld).\n",
6461 terminal->pvt->selection_start.col,
6462 terminal->pvt->selection_start.row);
6463
6464 /* Take care of updating the display. */
6465 vte_terminal_extend_selection(terminal, x, y, FALSE, TRUE);
6466
6467 /* Temporarily stop caring about input from the child. */
6468 _vte_terminal_disconnect_pty_read(terminal);
6469 }
6470
6471 static gboolean
_vte_terminal_maybe_end_selection(VteTerminal * terminal)6472 _vte_terminal_maybe_end_selection (VteTerminal *terminal)
6473 {
6474 if (terminal->pvt->selecting) {
6475 /* Copy only if something was selected. */
6476 if (terminal->pvt->has_selection &&
6477 !terminal->pvt->selecting_restart &&
6478 terminal->pvt->selecting_had_delta) {
6479 vte_terminal_copy_primary(terminal);
6480 vte_terminal_emit_selection_changed(terminal);
6481 }
6482 terminal->pvt->selecting = FALSE;
6483
6484 /* Reconnect to input from the child if we paused it. */
6485 _vte_terminal_connect_pty_read(terminal);
6486
6487 return TRUE;
6488 }
6489
6490 if (terminal->pvt->selecting_after_threshold)
6491 return TRUE;
6492
6493 return FALSE;
6494 }
6495
6496 static long
math_div(long a,long b)6497 math_div (long a, long b)
6498 {
6499 if (G_LIKELY (a >= 0))
6500 return a / b;
6501 else
6502 return (a / b) - 1;
6503 }
6504
6505 /* Helper */
6506 static void
vte_terminal_extend_selection_expand(VteTerminal * terminal)6507 vte_terminal_extend_selection_expand (VteTerminal *terminal)
6508 {
6509 long i, j;
6510 VteScreen *screen;
6511 const VteRowData *rowdata;
6512 const VteCell *cell;
6513 VteVisualPosition *sc, *ec;
6514
6515 if (terminal->pvt->selection_block_mode)
6516 return;
6517
6518 screen = terminal->pvt->screen;
6519 sc = &terminal->pvt->selection_start;
6520 ec = &terminal->pvt->selection_end;
6521
6522 /* Extend the selection to handle end-of-line cases, word, and line
6523 * selection. We do this here because calculating it once is cheaper
6524 * than recalculating for each cell as we render it. */
6525
6526 /* Handle end-of-line at the start-cell. */
6527 rowdata = _vte_terminal_find_row_data(terminal, sc->row);
6528 if (rowdata != NULL) {
6529 /* Find the last non-empty character on the first line. */
6530 for (i = _vte_row_data_length (rowdata); i > 0; i--) {
6531 cell = _vte_row_data_get (rowdata, i - 1);
6532 if (cell->attr.fragment || cell->c != 0)
6533 break;
6534 }
6535 } else {
6536 i = 0;
6537 }
6538 if (sc->col > i) {
6539 if (terminal->pvt->selection_type == selection_type_char) {
6540 /* If the start point is neither over the used cells, nor over the first
6541 * unused one, then move it to the next line. This way you can still start
6542 * selecting at the newline character by clicking over the first unused cell.
6543 * See bug 725909. */
6544 sc->col = -1;
6545 sc->row++;
6546 } else if (terminal->pvt->selection_type == selection_type_word) {
6547 sc->col = i;
6548 }
6549 }
6550 sc->col = find_start_column (terminal, sc->col, sc->row);
6551
6552 /* Handle end-of-line at the end-cell. */
6553 rowdata = _vte_terminal_find_row_data(terminal, ec->row);
6554 if (rowdata != NULL) {
6555 /* Find the last non-empty character on the last line. */
6556 for (i = _vte_row_data_length (rowdata); i > 0; i--) {
6557 cell = _vte_row_data_get (rowdata, i - 1);
6558 if (cell->attr.fragment || cell->c != 0)
6559 break;
6560 }
6561 /* If the end point is to its right, then extend the
6562 * endpoint to the beginning of the next row. */
6563 if (ec->col >= i) {
6564 ec->col = -1;
6565 ec->row++;
6566 }
6567 } else {
6568 /* Snap to the beginning of the next line, only if
6569 * selecting anything of this row. */
6570 if (ec->col >= 0) {
6571 ec->col = -1;
6572 ec->row++;
6573 }
6574 }
6575 ec->col = find_end_column (terminal, ec->col, ec->row);
6576
6577
6578 /* Now extend again based on selection type. */
6579 switch (terminal->pvt->selection_type) {
6580 case selection_type_char:
6581 /* Nothing more to do. */
6582 break;
6583 case selection_type_word:
6584 /* Keep selecting to the left as long as the next character we
6585 * look at is of the same class as the current start point. */
6586 i = sc->col;
6587 j = sc->row;
6588 while (_vte_ring_contains (screen->row_data, j)) {
6589 /* Get the data for the row we're looking at. */
6590 rowdata = _vte_ring_index(screen->row_data, j);
6591 if (rowdata == NULL) {
6592 break;
6593 }
6594 /* Back up. */
6595 for (i = (j == sc->row) ?
6596 sc->col :
6597 terminal->pvt->column_count;
6598 i > 0;
6599 i--) {
6600 if (vte_same_class(terminal,
6601 i - 1,
6602 j,
6603 i,
6604 j)) {
6605 sc->col = i - 1;
6606 sc->row = j;
6607 } else {
6608 break;
6609 }
6610 }
6611 if (i > 0) {
6612 /* We hit a stopping point, so stop. */
6613 break;
6614 } else {
6615 if (vte_line_is_wrappable(terminal, j - 1) &&
6616 vte_same_class(terminal,
6617 terminal->pvt->column_count - 1,
6618 j - 1,
6619 0,
6620 j)) {
6621 /* Move on to the previous line. */
6622 j--;
6623 sc->col = terminal->pvt->column_count - 1;
6624 sc->row = j;
6625 } else {
6626 break;
6627 }
6628 }
6629 }
6630 /* Keep selecting to the right as long as the next character we
6631 * look at is of the same class as the current end point. */
6632 i = ec->col;
6633 j = ec->row;
6634 while (_vte_ring_contains (screen->row_data, j)) {
6635 /* Get the data for the row we're looking at. */
6636 rowdata = _vte_ring_index(screen->row_data, j);
6637 if (rowdata == NULL) {
6638 break;
6639 }
6640 /* Move forward. */
6641 for (i = (j == ec->row) ?
6642 ec->col :
6643 0;
6644 i < terminal->pvt->column_count - 1;
6645 i++) {
6646 if (vte_same_class(terminal,
6647 i,
6648 j,
6649 i + 1,
6650 j)) {
6651 ec->col = i + 1;
6652 ec->row = j;
6653 } else {
6654 break;
6655 }
6656 }
6657 if (i < terminal->pvt->column_count - 1) {
6658 /* We hit a stopping point, so stop. */
6659 break;
6660 } else {
6661 if (vte_line_is_wrappable(terminal, j) &&
6662 vte_same_class(terminal,
6663 terminal->pvt->column_count - 1,
6664 j,
6665 0,
6666 j + 1)) {
6667 /* Move on to the next line. */
6668 j++;
6669 ec->col = 0;
6670 ec->row = j;
6671 } else {
6672 break;
6673 }
6674 }
6675 }
6676 break;
6677 case selection_type_line:
6678 /* Extend the selection to the beginning of the start line. */
6679 sc->col = 0;
6680 /* Now back up as far as we can go. */
6681 j = sc->row;
6682 while (_vte_ring_contains (screen->row_data, j - 1) &&
6683 vte_line_is_wrappable(terminal, j - 1)) {
6684 j--;
6685 sc->row = j;
6686 }
6687 /* And move forward as far as we can go. */
6688 if (ec->col < 0) {
6689 /* If triple clicking on an unused area, ec already points
6690 * to the beginning of the next line after the second click.
6691 * Go back to the actual row we're at. See bug 725909. */
6692 ec->row--;
6693 }
6694 j = ec->row;
6695 while (_vte_ring_contains (screen->row_data, j) &&
6696 vte_line_is_wrappable(terminal, j)) {
6697 j++;
6698 ec->row = j;
6699 }
6700 /* Make sure we include all of the last line by extending
6701 * to the beginning of the next line. */
6702 ec->row++;
6703 ec->col = -1;
6704 break;
6705 }
6706 }
6707
6708 /* Extend selection to include the given event coordinates. */
6709 static void
vte_terminal_extend_selection(VteTerminal * terminal,long x,long y,gboolean always_grow,gboolean force)6710 vte_terminal_extend_selection(VteTerminal *terminal, long x, long y,
6711 gboolean always_grow, gboolean force)
6712 {
6713 VteScreen *screen;
6714 int width, height;
6715 long delta, residual;
6716 struct selection_event_coords *origin, *last, *start, *end;
6717 VteVisualPosition old_start, old_end, *sc, *ec, *so, *eo;
6718 gboolean invalidate_selected = FALSE;
6719 gboolean had_selection;
6720
6721 height = terminal->pvt->char_height;
6722 width = terminal->pvt->char_width;
6723
6724 /* Confine coordinates into the visible area. (#563024, #722635c7) */
6725 vte_terminal_confine_coordinates(terminal, &x, &y);
6726
6727 screen = terminal->pvt->screen;
6728 old_start = terminal->pvt->selection_start;
6729 old_end = terminal->pvt->selection_end;
6730 so = &old_start;
6731 eo = &old_end;
6732
6733 /* Convert the event coordinates to cell coordinates. */
6734 delta = screen->scroll_delta;
6735
6736 /* If we're restarting on a drag, then mark this as the start of
6737 * the selected block. */
6738 if (terminal->pvt->selecting_restart) {
6739 vte_terminal_deselect_all(terminal);
6740 invalidate_selected = TRUE;
6741 _vte_debug_print(VTE_DEBUG_SELECTION,
6742 "Selection delayed start at (%ld,%ld).\n",
6743 terminal->pvt->selection_origin.x / width,
6744 terminal->pvt->selection_origin.y / height);
6745 }
6746
6747 /* Recognize that we've got a selected block. */
6748 had_selection = terminal->pvt->has_selection;
6749 terminal->pvt->has_selection = TRUE;
6750 terminal->pvt->selecting_had_delta = TRUE;
6751 terminal->pvt->selecting_restart = FALSE;
6752
6753 /* If we're not in always-grow mode, update the last location of
6754 * the selection. */
6755 last = &terminal->pvt->selection_last;
6756
6757 /* Map the origin and last selected points to a start and end. */
6758 origin = &terminal->pvt->selection_origin;
6759 if (terminal->pvt->selection_block_mode) {
6760 last->x = x;
6761 last->y = y + height * delta;
6762
6763 /* We don't support always_grow in block mode */
6764 if (always_grow)
6765 vte_terminal_invalidate_selection (terminal);
6766
6767 if (origin->y <= last->y) {
6768 /* The origin point is "before" the last point. */
6769 start = origin;
6770 end = last;
6771 } else {
6772 /* The last point is "before" the origin point. */
6773 start = last;
6774 end = origin;
6775 }
6776 } else {
6777 if (!always_grow) {
6778 last->x = x;
6779 last->y = y + height * delta;
6780 }
6781
6782 if ((origin->y / height < last->y / height) ||
6783 ((origin->y / height == last->y / height) &&
6784 (origin->x / width < last->x / width ))) {
6785 /* The origin point is "before" the last point. */
6786 start = origin;
6787 end = last;
6788 } else {
6789 /* The last point is "before" the origin point. */
6790 start = last;
6791 end = origin;
6792 }
6793
6794 /* Extend the selection by moving whichever end of the selection is
6795 * closer to the new point. */
6796 if (always_grow) {
6797 /* New endpoint is before existing selection. */
6798 if ((y / height < ((start->y / height) - delta)) ||
6799 ((y / height == ((start->y / height) - delta)) &&
6800 (x / width < start->x / width))) {
6801 start->x = x;
6802 start->y = y + height * delta;
6803 } else {
6804 /* New endpoint is after existing selection. */
6805 end->x = x;
6806 end->y = y + height * delta;
6807 }
6808 }
6809 }
6810
6811 #if 0
6812 _vte_debug_print(VTE_DEBUG_SELECTION,
6813 "Selection is (%ld,%ld) to (%ld,%ld).\n",
6814 start->x, start->y, end->x, end->y);
6815 #endif
6816
6817 /* Recalculate the selection area in terms of cell positions. */
6818
6819 sc = &terminal->pvt->selection_start;
6820 ec = &terminal->pvt->selection_end;
6821
6822 sc->row = MAX (0, start->y / height);
6823 ec->row = MAX (0, end->y / height);
6824
6825 /* Sort x using row cell coordinates */
6826 if ((terminal->pvt->selection_block_mode || sc->row == ec->row) && (start->x > end->x)) {
6827 struct selection_event_coords *tmp;
6828 tmp = start;
6829 start = end;
6830 end = tmp;
6831 }
6832
6833 /* We want to be more lenient on the user with their column selection.
6834 * We round to the closest logical position (positions are located between
6835 * cells). But we don't want to fully round. So we divide the cell
6836 * width into three parts. The side parts round to their nearest
6837 * position. The middle part is always inclusive in the selection.
6838 *
6839 * math_div and no MAX, to allow selecting no cells in the line,
6840 * ie. ec->col = -1, which is essentially equal to copying the
6841 * newline from previous line but no chars from current line. */
6842 residual = (width + 1) / 3;
6843 sc->col = math_div (start->x + residual, width);
6844 ec->col = math_div (end->x - residual, width);
6845
6846
6847 vte_terminal_extend_selection_expand (terminal);
6848
6849 if (!invalidate_selected && !force &&
6850 0 == memcmp (sc, so, sizeof (*sc)) &&
6851 0 == memcmp (ec, eo, sizeof (*ec)))
6852 /* No change */
6853 return;
6854
6855 /* Invalidate */
6856
6857 if (had_selection) {
6858
6859 if (terminal->pvt->selection_block_mode) {
6860 /* Update the selection area diff in block mode. */
6861
6862 /* The top band */
6863 _vte_invalidate_region (terminal,
6864 MIN(sc->col, so->col),
6865 MAX(ec->col, eo->col),
6866 MIN(sc->row, so->row),
6867 MAX(sc->row, so->row) - 1,
6868 TRUE);
6869 /* The bottom band */
6870 _vte_invalidate_region (terminal,
6871 MIN(sc->col, so->col),
6872 MAX(ec->col, eo->col),
6873 MIN(ec->row, eo->row) + 1,
6874 MAX(ec->row, eo->row),
6875 TRUE);
6876 /* The left band */
6877 _vte_invalidate_region (terminal,
6878 MIN(sc->col, so->col),
6879 MAX(sc->col, so->col) - 1 + (VTE_TAB_WIDTH_MAX - 1),
6880 MIN(sc->row, so->row),
6881 MAX(ec->row, eo->row),
6882 TRUE);
6883 /* The right band */
6884 _vte_invalidate_region (terminal,
6885 MIN(ec->col, eo->col) + 1,
6886 MAX(ec->col, eo->col) + (VTE_TAB_WIDTH_MAX - 1),
6887 MIN(sc->row, so->row),
6888 MAX(ec->row, eo->row),
6889 TRUE);
6890 } else {
6891 /* Update the selection area diff in non-block mode. */
6892
6893 /* The before band */
6894 if (sc->row < so->row)
6895 _vte_invalidate_region (terminal,
6896 sc->col, so->col - 1,
6897 sc->row, so->row,
6898 FALSE);
6899 else if (sc->row > so->row)
6900 _vte_invalidate_region (terminal,
6901 so->col, sc->col - 1,
6902 so->row, sc->row,
6903 FALSE);
6904 else
6905 _vte_invalidate_region (terminal,
6906 MIN(sc->col, so->col), MAX(sc->col, so->col) - 1,
6907 sc->row, sc->row,
6908 TRUE);
6909
6910 /* The after band */
6911 if (ec->row < eo->row)
6912 _vte_invalidate_region (terminal,
6913 ec->col + 1, eo->col,
6914 ec->row, eo->row,
6915 FALSE);
6916 else if (ec->row > eo->row)
6917 _vte_invalidate_region (terminal,
6918 eo->col + 1, ec->col,
6919 eo->row, ec->row,
6920 FALSE);
6921 else
6922 _vte_invalidate_region (terminal,
6923 MIN(ec->col, eo->col) + 1, MAX(ec->col, eo->col),
6924 ec->row, ec->row,
6925 TRUE);
6926 }
6927 }
6928
6929 if (invalidate_selected || !had_selection) {
6930 _vte_debug_print(VTE_DEBUG_SELECTION, "Invalidating selection.");
6931 vte_terminal_invalidate_selection (terminal);
6932 }
6933
6934 _vte_debug_print(VTE_DEBUG_SELECTION,
6935 "Selection changed to "
6936 "(%ld,%ld) to (%ld,%ld).\n",
6937 sc->col, sc->row, ec->col, ec->row);
6938 }
6939
6940 /**
6941 * vte_terminal_select_all:
6942 * @terminal: a #VteTerminal
6943 *
6944 * Selects all text within the terminal (including the scrollback buffer).
6945 */
6946 void
vte_terminal_select_all(VteTerminal * terminal)6947 vte_terminal_select_all (VteTerminal *terminal)
6948 {
6949 g_return_if_fail (VTE_IS_TERMINAL (terminal));
6950
6951 vte_terminal_deselect_all (terminal);
6952
6953 terminal->pvt->has_selection = TRUE;
6954 terminal->pvt->selecting_had_delta = TRUE;
6955 terminal->pvt->selecting_restart = FALSE;
6956
6957 terminal->pvt->selection_start.row = _vte_ring_delta (terminal->pvt->screen->row_data);
6958 terminal->pvt->selection_start.col = 0;
6959 terminal->pvt->selection_end.row = _vte_ring_next (terminal->pvt->screen->row_data);
6960 terminal->pvt->selection_end.col = -1;
6961
6962 _vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n");
6963
6964 vte_terminal_copy_primary(terminal);
6965 vte_terminal_emit_selection_changed (terminal);
6966 _vte_invalidate_all (terminal);
6967 }
6968
6969 /**
6970 * vte_terminal_unselect_all:
6971 * @terminal: a #VteTerminal
6972 *
6973 * Clears the current selection.
6974 */
6975 void
vte_terminal_unselect_all(VteTerminal * terminal)6976 vte_terminal_unselect_all(VteTerminal *terminal)
6977 {
6978 g_return_if_fail (VTE_IS_TERMINAL (terminal));
6979
6980 _vte_debug_print(VTE_DEBUG_SELECTION, "Clearing selection.\n");
6981
6982 vte_terminal_deselect_all (terminal);
6983 }
6984
6985 /* Autoscroll a bit. */
6986 static gboolean
vte_terminal_autoscroll(VteTerminal * terminal)6987 vte_terminal_autoscroll(VteTerminal *terminal)
6988 {
6989 gboolean extend = FALSE;
6990 long x, y, xmax, ymax;
6991 glong adj;
6992
6993 /* Provide an immediate effect for mouse wigglers. */
6994 if (terminal->pvt->mouse_last_y < 0) {
6995 if (terminal->pvt->vadjustment) {
6996 /* Try to scroll up by one line. */
6997 adj = terminal->pvt->screen->scroll_delta - 1;
6998 vte_terminal_queue_adjustment_value_changed_clamped (terminal, adj);
6999 extend = TRUE;
7000 }
7001 _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling down.\n");
7002 }
7003 if (terminal->pvt->mouse_last_y >=
7004 terminal->pvt->row_count * terminal->pvt->char_height) {
7005 if (terminal->pvt->vadjustment) {
7006 /* Try to scroll up by one line. */
7007 adj = terminal->pvt->screen->scroll_delta + 1;
7008 vte_terminal_queue_adjustment_value_changed_clamped (terminal, adj);
7009 extend = TRUE;
7010 }
7011 _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling up.\n");
7012 }
7013 if (extend) {
7014 /* Don't select off-screen areas. That just confuses people. */
7015 xmax = terminal->pvt->column_count * terminal->pvt->char_width;
7016 ymax = terminal->pvt->row_count * terminal->pvt->char_height;
7017
7018 x = CLAMP(terminal->pvt->mouse_last_x, 0, xmax);
7019 y = CLAMP(terminal->pvt->mouse_last_y, 0, ymax);
7020 /* If we clamped the Y, mess with the X to get the entire
7021 * lines. */
7022 if (terminal->pvt->mouse_last_y < 0 && !terminal->pvt->selection_block_mode) {
7023 x = 0;
7024 }
7025 if (terminal->pvt->mouse_last_y >= ymax && !terminal->pvt->selection_block_mode) {
7026 x = terminal->pvt->column_count * terminal->pvt->char_width;
7027 }
7028 /* Extend selection to cover the newly-scrolled area. */
7029 vte_terminal_extend_selection(terminal, x, y, FALSE, TRUE);
7030 } else {
7031 /* Stop autoscrolling. */
7032 terminal->pvt->mouse_autoscroll_tag = 0;
7033 }
7034 return (terminal->pvt->mouse_autoscroll_tag != 0);
7035 }
7036
7037 /* Start autoscroll. */
7038 static void
vte_terminal_start_autoscroll(VteTerminal * terminal)7039 vte_terminal_start_autoscroll(VteTerminal *terminal)
7040 {
7041 if (terminal->pvt->mouse_autoscroll_tag == 0) {
7042 terminal->pvt->mouse_autoscroll_tag =
7043 g_timeout_add_full(G_PRIORITY_LOW,
7044 666 / terminal->pvt->row_count,
7045 (GSourceFunc)vte_terminal_autoscroll,
7046 terminal,
7047 NULL);
7048 }
7049 }
7050
7051 /* Stop autoscroll. */
7052 static void
vte_terminal_stop_autoscroll(VteTerminal * terminal)7053 vte_terminal_stop_autoscroll(VteTerminal *terminal)
7054 {
7055 if (terminal->pvt->mouse_autoscroll_tag != 0) {
7056 g_source_remove(terminal->pvt->mouse_autoscroll_tag);
7057 terminal->pvt->mouse_autoscroll_tag = 0;
7058 }
7059 }
7060
7061 /* Read and handle a motion event. */
7062 static gboolean
vte_terminal_motion_notify(GtkWidget * widget,GdkEventMotion * event)7063 vte_terminal_motion_notify(GtkWidget *widget, GdkEventMotion *event)
7064 {
7065 VteTerminal *terminal = VTE_TERMINAL(widget);
7066 int width, height;
7067 long x, y;
7068 gboolean handled = FALSE;
7069
7070 /* check to see if it matters */
7071 if (G_UNLIKELY(!gtk_widget_get_realized(&terminal->widget)))
7072 return FALSE;
7073
7074 x = event->x - terminal->pvt->padding.left;
7075 y = event->y - terminal->pvt->padding.top;
7076 width = terminal->pvt->char_width;
7077 height = terminal->pvt->char_height;
7078
7079 _vte_debug_print(VTE_DEBUG_EVENTS,
7080 "Motion notify (%ld,%ld) [%ld, %ld].\n",
7081 x, y,
7082 x / width, y / height + terminal->pvt->screen->scroll_delta);
7083
7084 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
7085
7086 if (terminal->pvt->mouse_pressed_buttons != 0) {
7087 vte_terminal_match_hilite_hide (terminal);
7088 } else {
7089 /* Hilite any matches. */
7090 vte_terminal_match_hilite(terminal, x, y);
7091 /* Show the cursor. */
7092 _vte_terminal_set_pointer_visible(terminal, TRUE);
7093 }
7094
7095 switch (event->type) {
7096 case GDK_MOTION_NOTIFY:
7097 if (terminal->pvt->selecting_after_threshold) {
7098 if (!gtk_drag_check_threshold (widget,
7099 terminal->pvt->mouse_last_x,
7100 terminal->pvt->mouse_last_y,
7101 x, y))
7102 return TRUE;
7103
7104 vte_terminal_start_selection(terminal,
7105 terminal->pvt->mouse_last_x,
7106 terminal->pvt->mouse_last_y,
7107 selection_type_char);
7108 }
7109
7110 if (terminal->pvt->selecting &&
7111 (terminal->pvt->mouse_handled_buttons & 1) != 0) {
7112 _vte_debug_print(VTE_DEBUG_EVENTS, "Mousing drag 1.\n");
7113 vte_terminal_extend_selection(terminal,
7114 x, y, FALSE, FALSE);
7115
7116 /* Start scrolling if we need to. */
7117 if (event->y < terminal->pvt->padding.top ||
7118 event->y >= terminal->pvt->row_count * height +
7119 terminal->pvt->padding.top)
7120 {
7121 /* Give mouse wigglers something. */
7122 vte_terminal_autoscroll(terminal);
7123 /* Start a timed autoscroll if we're not doing it
7124 * already. */
7125 vte_terminal_start_autoscroll(terminal);
7126 }
7127
7128 handled = TRUE;
7129 }
7130
7131 if (!handled && terminal->pvt->input_enabled)
7132 vte_terminal_maybe_send_mouse_drag(terminal, event);
7133 break;
7134 default:
7135 break;
7136 }
7137
7138 /* Save the pointer coordinates for later use. */
7139 terminal->pvt->mouse_last_x = x;
7140 terminal->pvt->mouse_last_y = y;
7141
7142 return handled;
7143 }
7144
7145 /* Read and handle a pointing device buttonpress event. */
7146 static gint
vte_terminal_button_press(GtkWidget * widget,GdkEventButton * event)7147 vte_terminal_button_press(GtkWidget *widget, GdkEventButton *event)
7148 {
7149 VteTerminal *terminal;
7150 long height, width, delta;
7151 gboolean handled = FALSE;
7152 gboolean start_selecting = FALSE, extend_selecting = FALSE;
7153 long cellx, celly;
7154 long x,y;
7155
7156 terminal = VTE_TERMINAL(widget);
7157
7158 x = event->x - terminal->pvt->padding.left;
7159 y = event->y - terminal->pvt->padding.top;
7160
7161 height = terminal->pvt->char_height;
7162 width = terminal->pvt->char_width;
7163 delta = terminal->pvt->screen->scroll_delta;
7164
7165 vte_terminal_match_hilite(terminal, x, y);
7166
7167 _vte_terminal_set_pointer_visible(terminal, TRUE);
7168
7169 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
7170
7171 /* Convert the event coordinates to cell coordinates. */
7172 cellx = x / width;
7173 celly = y / height + delta;
7174
7175 switch (event->type) {
7176 case GDK_BUTTON_PRESS:
7177 _vte_debug_print(VTE_DEBUG_EVENTS,
7178 "Button %d single-click at (%ld,%ld)\n",
7179 event->button,
7180 x, y + terminal->pvt->char_height * delta);
7181 /* Handle this event ourselves. */
7182 switch (event->button) {
7183 case 1:
7184 _vte_debug_print(VTE_DEBUG_EVENTS,
7185 "Handling click ourselves.\n");
7186 /* Grab focus. */
7187 if (! gtk_widget_has_focus (widget)) {
7188 gtk_widget_grab_focus(widget);
7189 }
7190
7191 /* If we're in event mode, and the user held down the
7192 * shift key, we start selecting. */
7193 if (terminal->pvt->mouse_tracking_mode) {
7194 if (terminal->pvt->modifiers & GDK_SHIFT_MASK) {
7195 start_selecting = TRUE;
7196 }
7197 } else {
7198 /* If the user hit shift, then extend the
7199 * selection instead. */
7200 if ((terminal->pvt->modifiers & GDK_SHIFT_MASK) &&
7201 (terminal->pvt->has_selection ||
7202 terminal->pvt->selecting_restart) &&
7203 !vte_cell_is_selected(terminal,
7204 cellx,
7205 celly,
7206 NULL)) {
7207 extend_selecting = TRUE;
7208 } else {
7209 start_selecting = TRUE;
7210 }
7211 }
7212 if (start_selecting) {
7213 vte_terminal_deselect_all(terminal);
7214 terminal->pvt->selecting_after_threshold = TRUE;
7215 terminal->pvt->selection_block_mode = !!(terminal->pvt->modifiers & GDK_CONTROL_MASK);
7216 handled = TRUE;
7217 }
7218 if (extend_selecting) {
7219 vte_terminal_extend_selection(terminal,
7220 x, y,
7221 !terminal->pvt->selecting_restart, TRUE);
7222 /* The whole selection code needs to be
7223 * rewritten. For now, put this here to
7224 * fix bug 614658 */
7225 terminal->pvt->selecting = TRUE;
7226 handled = TRUE;
7227 }
7228 break;
7229 /* Paste if the user pressed shift or we're not sending events
7230 * to the app. */
7231 case 2:
7232 if ((terminal->pvt->modifiers & GDK_SHIFT_MASK) ||
7233 !terminal->pvt->mouse_tracking_mode) {
7234 gboolean do_paste;
7235
7236 g_object_get (gtk_widget_get_settings(widget),
7237 "gtk-enable-primary-paste",
7238 &do_paste, NULL);
7239 if (do_paste)
7240 vte_terminal_paste_primary(terminal);
7241 handled = do_paste;
7242 }
7243 break;
7244 case 3:
7245 default:
7246 break;
7247 }
7248 if (event->button >= 1 && event->button <= 3) {
7249 if (handled)
7250 terminal->pvt->mouse_handled_buttons |= (1 << (event->button - 1));
7251 else
7252 terminal->pvt->mouse_handled_buttons &= ~(1 << (event->button - 1));
7253 }
7254 /* If we haven't done anything yet, try sending the mouse
7255 * event to the app. */
7256 if (handled == FALSE) {
7257 handled = vte_terminal_maybe_send_mouse_button(terminal, event);
7258 }
7259 break;
7260 case GDK_2BUTTON_PRESS:
7261 _vte_debug_print(VTE_DEBUG_EVENTS,
7262 "Button %d double-click at (%ld,%ld)\n",
7263 event->button,
7264 x, y + (terminal->pvt->char_height * delta));
7265 switch (event->button) {
7266 case 1:
7267 if (terminal->pvt->selecting_after_threshold) {
7268 vte_terminal_start_selection(terminal,
7269 x, y,
7270 selection_type_char);
7271 handled = TRUE;
7272 }
7273 if ((terminal->pvt->mouse_handled_buttons & 1) != 0) {
7274 vte_terminal_start_selection(terminal,
7275 x, y,
7276 selection_type_word);
7277 handled = TRUE;
7278 }
7279 break;
7280 case 2:
7281 case 3:
7282 default:
7283 break;
7284 }
7285 break;
7286 case GDK_3BUTTON_PRESS:
7287 _vte_debug_print(VTE_DEBUG_EVENTS,
7288 "Button %d triple-click at (%ld,%ld).\n",
7289 event->button,
7290 x, y + (terminal->pvt->char_height * delta));
7291 switch (event->button) {
7292 case 1:
7293 if ((terminal->pvt->mouse_handled_buttons & 1) != 0) {
7294 vte_terminal_start_selection(terminal,
7295 x, y,
7296 selection_type_line);
7297 handled = TRUE;
7298 }
7299 break;
7300 case 2:
7301 case 3:
7302 default:
7303 break;
7304 }
7305 default:
7306 break;
7307 }
7308
7309 /* Save the pointer state for later use. */
7310 if (event->button >= 1 && event->button <= 3)
7311 terminal->pvt->mouse_pressed_buttons |= (1 << (event->button - 1));
7312 terminal->pvt->mouse_last_x = x;
7313 terminal->pvt->mouse_last_y = y;
7314
7315 return handled;
7316 }
7317
7318 /* Read and handle a pointing device buttonrelease event. */
7319 static gint
vte_terminal_button_release(GtkWidget * widget,GdkEventButton * event)7320 vte_terminal_button_release(GtkWidget *widget, GdkEventButton *event)
7321 {
7322 VteTerminal *terminal;
7323 gboolean handled = FALSE;
7324 int x, y;
7325
7326 terminal = VTE_TERMINAL(widget);
7327
7328 x = event->x - terminal->pvt->padding.left;
7329 y = event->y - terminal->pvt->padding.top;
7330
7331 vte_terminal_match_hilite(terminal, x, y);
7332
7333 _vte_terminal_set_pointer_visible(terminal, TRUE);
7334
7335 vte_terminal_stop_autoscroll(terminal);
7336
7337 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
7338
7339 switch (event->type) {
7340 case GDK_BUTTON_RELEASE:
7341 _vte_debug_print(VTE_DEBUG_EVENTS,
7342 "Button %d released at (%d,%d).\n",
7343 event->button, x, y);
7344 switch (event->button) {
7345 case 1:
7346 if ((terminal->pvt->mouse_handled_buttons & 1) != 0)
7347 handled = _vte_terminal_maybe_end_selection (terminal);
7348 break;
7349 case 2:
7350 handled = (terminal->pvt->mouse_handled_buttons & 2) != 0;
7351 terminal->pvt->mouse_handled_buttons &= ~2;
7352 break;
7353 case 3:
7354 default:
7355 break;
7356 }
7357 if (!handled && terminal->pvt->input_enabled) {
7358 handled = vte_terminal_maybe_send_mouse_button(terminal, event);
7359 }
7360 break;
7361 default:
7362 break;
7363 }
7364
7365 /* Save the pointer state for later use. */
7366 if (event->button >= 1 && event->button <= 3)
7367 terminal->pvt->mouse_pressed_buttons &= ~(1 << (event->button - 1));
7368 terminal->pvt->mouse_last_x = x;
7369 terminal->pvt->mouse_last_y = y;
7370 terminal->pvt->selecting_after_threshold = FALSE;
7371
7372 return handled;
7373 }
7374
7375 /* Handle receiving or losing focus. */
7376 static gboolean
vte_terminal_focus_in(GtkWidget * widget,GdkEventFocus * event)7377 vte_terminal_focus_in(GtkWidget *widget, GdkEventFocus *event)
7378 {
7379 VteTerminal *terminal;
7380
7381 _vte_debug_print(VTE_DEBUG_EVENTS, "Focus in.\n");
7382
7383 terminal = VTE_TERMINAL(widget);
7384 gtk_widget_grab_focus (widget);
7385
7386 /* Read the keyboard modifiers, though they're probably garbage. */
7387 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
7388
7389 /* We only have an IM context when we're realized, and there's not much
7390 * point to painting the cursor if we don't have a window. */
7391 if (gtk_widget_get_realized (widget)) {
7392 terminal->pvt->cursor_blink_state = TRUE;
7393 terminal->pvt->has_focus = TRUE;
7394
7395 _vte_check_cursor_blink (terminal);
7396
7397 gtk_im_context_focus_in(terminal->pvt->im_context);
7398 _vte_invalidate_cursor_once(terminal, FALSE);
7399 _vte_terminal_set_pointer_visible(terminal, TRUE);
7400 }
7401
7402 return FALSE;
7403 }
7404
7405 static gboolean
vte_terminal_focus_out(GtkWidget * widget,GdkEventFocus * event)7406 vte_terminal_focus_out(GtkWidget *widget, GdkEventFocus *event)
7407 {
7408 VteTerminal *terminal;
7409 _vte_debug_print(VTE_DEBUG_EVENTS, "Focus out.\n");
7410 terminal = VTE_TERMINAL(widget);
7411 /* Read the keyboard modifiers, though they're probably garbage. */
7412 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
7413 /* We only have an IM context when we're realized, and there's not much
7414 * point to painting ourselves if we don't have a window. */
7415 if (gtk_widget_get_realized (widget)) {
7416 _vte_terminal_maybe_end_selection (terminal);
7417
7418 gtk_im_context_focus_out(terminal->pvt->im_context);
7419 _vte_invalidate_cursor_once(terminal, FALSE);
7420
7421 /* XXX Do we want to hide the match just because the terminal
7422 * lost keyboard focus, but the pointer *is* still within our
7423 * area top? */
7424 vte_terminal_match_hilite_hide (terminal);
7425 /* Mark the cursor as invisible to disable hilite updating */
7426 terminal->pvt->mouse_cursor_visible = FALSE;
7427 terminal->pvt->mouse_pressed_buttons = 0;
7428 terminal->pvt->mouse_handled_buttons = 0;
7429 }
7430
7431 terminal->pvt->has_focus = FALSE;
7432 _vte_check_cursor_blink (terminal);
7433
7434 return FALSE;
7435 }
7436
7437 static gboolean
vte_terminal_enter(GtkWidget * widget,GdkEventCrossing * event)7438 vte_terminal_enter(GtkWidget *widget, GdkEventCrossing *event)
7439 {
7440 gboolean ret = FALSE;
7441 _vte_debug_print(VTE_DEBUG_EVENTS, "Enter.\n");
7442 if (GTK_WIDGET_CLASS (vte_terminal_parent_class)->enter_notify_event) {
7443 ret = GTK_WIDGET_CLASS (vte_terminal_parent_class)->enter_notify_event (widget, event);
7444 }
7445 if (gtk_widget_get_realized (widget)) {
7446 VteTerminal *terminal = VTE_TERMINAL (widget);
7447 /* Hilite any matches. */
7448 vte_terminal_match_hilite_show(terminal,
7449 event->x - terminal->pvt->padding.left,
7450 event->y - terminal->pvt->padding.top);
7451 }
7452 return ret;
7453 }
7454 static gboolean
vte_terminal_leave(GtkWidget * widget,GdkEventCrossing * event)7455 vte_terminal_leave(GtkWidget *widget, GdkEventCrossing *event)
7456 {
7457 gboolean ret = FALSE;
7458 _vte_debug_print(VTE_DEBUG_EVENTS, "Leave.\n");
7459 if (GTK_WIDGET_CLASS (vte_terminal_parent_class)->leave_notify_event) {
7460 ret = GTK_WIDGET_CLASS (vte_terminal_parent_class)->leave_notify_event (widget, event);
7461 }
7462 if (gtk_widget_get_realized (widget)) {
7463 VteTerminal *terminal = VTE_TERMINAL (widget);
7464 vte_terminal_match_hilite_hide (terminal);
7465 /* Mark the cursor as invisible to disable hilite updating,
7466 * whilst the cursor is absent (otherwise we copy the entire
7467 * buffer after each update for nothing...)
7468 */
7469 terminal->pvt->mouse_cursor_visible = FALSE;
7470 }
7471 return ret;
7472 }
7473
7474 static G_GNUC_UNUSED inline const char *
visibility_state_str(GdkVisibilityState state)7475 visibility_state_str(GdkVisibilityState state)
7476 {
7477 switch(state){
7478 case GDK_VISIBILITY_FULLY_OBSCURED:
7479 return "fully-obscured";
7480 case GDK_VISIBILITY_UNOBSCURED:
7481 return "unobscured";
7482 default:
7483 return "partial";
7484 }
7485 }
7486
7487 static void
vte_terminal_set_visibility(VteTerminal * terminal,GdkVisibilityState state)7488 vte_terminal_set_visibility (VteTerminal *terminal, GdkVisibilityState state)
7489 {
7490 _vte_debug_print(VTE_DEBUG_MISC, "change visibility: %s -> %s.\n",
7491 visibility_state_str(terminal->pvt->visibility_state),
7492 visibility_state_str(state));
7493
7494 if (state == terminal->pvt->visibility_state) {
7495 return;
7496 }
7497
7498 /* fully obscured to visible switch, force the fast path */
7499 if (terminal->pvt->visibility_state == GDK_VISIBILITY_FULLY_OBSCURED) {
7500 /* set invalidated_all false, since we didn't really mean it
7501 * when we set it to TRUE when becoming obscured */
7502 terminal->pvt->invalidated_all = FALSE;
7503
7504 /* if all unobscured now, invalidate all, otherwise, wait
7505 * for the expose event */
7506 if (state == GDK_VISIBILITY_UNOBSCURED) {
7507 _vte_invalidate_all (terminal);
7508 }
7509 }
7510
7511 terminal->pvt->visibility_state = state;
7512
7513 /* no longer visible, stop processing display updates */
7514 if (terminal->pvt->visibility_state == GDK_VISIBILITY_FULLY_OBSCURED) {
7515 remove_update_timeout (terminal);
7516 /* if fully obscured, just act like we have invalidated all,
7517 * so no updates are accumulated. */
7518 terminal->pvt->invalidated_all = TRUE;
7519 }
7520 }
7521
7522 static gboolean
vte_terminal_visibility_notify(GtkWidget * widget,GdkEventVisibility * event)7523 vte_terminal_visibility_notify(GtkWidget *widget, GdkEventVisibility *event)
7524 {
7525 VteTerminal *terminal;
7526 terminal = VTE_TERMINAL(widget);
7527
7528 _vte_debug_print(VTE_DEBUG_EVENTS, "Visibility (%s -> %s).\n",
7529 visibility_state_str(terminal->pvt->visibility_state),
7530 visibility_state_str(event->state));
7531 vte_terminal_set_visibility(terminal, event->state);
7532
7533 return FALSE;
7534 }
7535
7536 /* Apply the changed metrics, and queue a resize if need be. */
7537 static void
vte_terminal_apply_metrics(VteTerminal * terminal,gint width,gint height,gint ascent,gint descent)7538 vte_terminal_apply_metrics(VteTerminal *terminal,
7539 gint width, gint height, gint ascent, gint descent)
7540 {
7541 gboolean resize = FALSE, cresize = FALSE;
7542 gint line_thickness;
7543
7544 /* Sanity check for broken font changes. */
7545 width = MAX(width, 1);
7546 height = MAX(height, 2);
7547 ascent = MAX(ascent, 1);
7548 descent = MAX(descent, 1);
7549
7550 /* Change settings, and keep track of when we've changed anything. */
7551 if (width != terminal->pvt->char_width) {
7552 resize = cresize = TRUE;
7553 terminal->pvt->char_width = width;
7554 }
7555 if (height != terminal->pvt->char_height) {
7556 resize = cresize = TRUE;
7557 terminal->pvt->char_height = height;
7558 }
7559 if (ascent != terminal->pvt->char_ascent) {
7560 resize = TRUE;
7561 terminal->pvt->char_ascent = ascent;
7562 }
7563 if (descent != terminal->pvt->char_descent) {
7564 resize = TRUE;
7565 terminal->pvt->char_descent = descent;
7566 }
7567 terminal->pvt->line_thickness = line_thickness = MAX (MIN ((height - ascent) / 2, height / 14), 1);
7568 terminal->pvt->underline_position = MIN (ascent + line_thickness, height - line_thickness);
7569 terminal->pvt->strikethrough_position = ascent - height / 4;
7570
7571 /* Queue a resize if anything's changed. */
7572 if (resize) {
7573 if (gtk_widget_get_realized (&terminal->widget)) {
7574 gtk_widget_queue_resize_no_redraw(&terminal->widget);
7575 }
7576 }
7577 /* Emit a signal that the font changed. */
7578 if (cresize) {
7579 vte_terminal_emit_char_size_changed(terminal,
7580 terminal->pvt->char_width,
7581 terminal->pvt->char_height);
7582 }
7583 /* Repaint. */
7584 _vte_invalidate_all(terminal);
7585 }
7586
7587
7588 static void
vte_terminal_ensure_font(VteTerminal * terminal)7589 vte_terminal_ensure_font (VteTerminal *terminal)
7590 {
7591 if (terminal->pvt->draw != NULL) {
7592 /* Load default fonts, if no fonts have been loaded. */
7593 if (!terminal->pvt->has_fonts) {
7594 vte_terminal_set_font(terminal,
7595 terminal->pvt->unscaled_font_desc);
7596 }
7597 if (terminal->pvt->fontdirty) {
7598 gint width, height, ascent;
7599 terminal->pvt->fontdirty = FALSE;
7600 _vte_draw_set_text_font (terminal->pvt->draw,
7601 &terminal->widget,
7602 terminal->pvt->fontdesc);
7603 _vte_draw_get_text_metrics (terminal->pvt->draw,
7604 &width, &height, &ascent);
7605 vte_terminal_apply_metrics(terminal,
7606 width, height, ascent, height - ascent);
7607 }
7608 }
7609 }
7610
7611 static void
vte_terminal_update_font(VteTerminal * terminal)7612 vte_terminal_update_font(VteTerminal *terminal)
7613 {
7614 VteTerminalPrivate *pvt = terminal->pvt;
7615 PangoFontDescription *desc;
7616 gdouble size;
7617
7618 /* We'll get called again later */
7619 if (pvt->unscaled_font_desc == NULL)
7620 return;
7621
7622 desc = pango_font_description_copy(pvt->unscaled_font_desc);
7623
7624 size = pango_font_description_get_size(desc);
7625 if (pango_font_description_get_size_is_absolute(desc)) {
7626 pango_font_description_set_absolute_size(desc, pvt->font_scale * size);
7627 } else {
7628 pango_font_description_set_size(desc, pvt->font_scale * size);
7629 }
7630
7631 if (pvt->fontdesc) {
7632 pango_font_description_free(pvt->fontdesc);
7633 }
7634 pvt->fontdesc = desc;
7635
7636 pvt->fontdirty = TRUE;
7637 pvt->has_fonts = TRUE;
7638
7639 /* Set the drawing font. */
7640 if (gtk_widget_get_realized (&terminal->widget)) {
7641 vte_terminal_ensure_font (terminal);
7642 }
7643 }
7644
7645 /**
7646 * vte_terminal_set_font:
7647 * @terminal: a #VteTerminal
7648 * @font_desc: (allow-none): a #PangoFontDescription for the desired font, or %NULL
7649 *
7650 * Sets the font used for rendering all text displayed by the terminal,
7651 * overriding any fonts set using gtk_widget_modify_font(). The terminal
7652 * will immediately attempt to load the desired font, retrieve its
7653 * metrics, and attempt to resize itself to keep the same number of rows
7654 * and columns. The font scale is applied to the specified font.
7655 */
7656 void
vte_terminal_set_font(VteTerminal * terminal,const PangoFontDescription * font_desc)7657 vte_terminal_set_font(VteTerminal *terminal,
7658 const PangoFontDescription *font_desc)
7659 {
7660 GObject *object;
7661 GtkStyleContext *context;
7662 VteTerminalPrivate *pvt;
7663 PangoFontDescription *desc;
7664 gboolean same_desc;
7665
7666 g_return_if_fail(VTE_IS_TERMINAL(terminal));
7667
7668 object = G_OBJECT(terminal);
7669 pvt = terminal->pvt;
7670
7671 /* Create an owned font description. */
7672 context = gtk_widget_get_style_context(&terminal->widget);
7673 gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "font", &desc, NULL);
7674 pango_font_description_set_family_static (desc, "monospace");
7675 if (font_desc != NULL) {
7676 pango_font_description_merge (desc, font_desc, TRUE);
7677 _VTE_DEBUG_IF(VTE_DEBUG_MISC) {
7678 if (desc) {
7679 char *tmp;
7680 tmp = pango_font_description_to_string(desc);
7681 g_printerr("Using pango font \"%s\".\n", tmp);
7682 g_free (tmp);
7683 }
7684 }
7685 } else {
7686 _vte_debug_print(VTE_DEBUG_MISC,
7687 "Using default monospace font.\n");
7688 }
7689
7690 same_desc = pvt->unscaled_font_desc &&
7691 pango_font_description_equal (pvt->unscaled_font_desc, desc);
7692
7693 /* Note that we proceed to recreating the font even if the description
7694 * are the same. This is because maybe screen
7695 * font options were changed, or new fonts installed. Those will be
7696 * detected at font creation time and respected.
7697 */
7698
7699 /* Free the old font description and save the new one. */
7700 if (terminal->pvt->unscaled_font_desc != NULL) {
7701 pango_font_description_free(terminal->pvt->unscaled_font_desc);
7702 }
7703
7704 terminal->pvt->unscaled_font_desc = desc /* adopted */;
7705
7706 vte_terminal_update_font(terminal);
7707
7708 if (!same_desc)
7709 g_object_notify(object, "font-desc");
7710 }
7711
7712 /**
7713 * vte_terminal_get_font:
7714 * @terminal: a #VteTerminal
7715 *
7716 * Queries the terminal for information about the fonts which will be
7717 * used to draw text in the terminal. The actual font takes the font scale
7718 * into account, this is not reflected in the return value, the unscaled
7719 * font is returned.
7720 *
7721 * Returns: (transfer none): a #PangoFontDescription describing the font the
7722 * terminal uses to render text at the default font scale of 1.0.
7723 */
7724 const PangoFontDescription *
vte_terminal_get_font(VteTerminal * terminal)7725 vte_terminal_get_font(VteTerminal *terminal)
7726 {
7727 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
7728
7729 return terminal->pvt->unscaled_font_desc;
7730 }
7731
7732 /**
7733 * vte_terminal_set_font_scale:
7734 * @terminal: a #VteTerminal
7735 * @scale: the font scale
7736 *
7737 * Sets the terminal's font scale to @scale.
7738 */
7739 void
vte_terminal_set_font_scale(VteTerminal * terminal,gdouble scale)7740 vte_terminal_set_font_scale(VteTerminal *terminal,
7741 gdouble scale)
7742 {
7743 g_return_if_fail(VTE_IS_TERMINAL(terminal));
7744
7745 terminal->pvt->font_scale = CLAMP(scale, VTE_FONT_SCALE_MIN, VTE_FONT_SCALE_MAX);
7746 vte_terminal_update_font(terminal);
7747
7748 g_object_notify(G_OBJECT(terminal), "font-scale");
7749 }
7750
7751 /**
7752 * vte_terminal_get_font_scale:
7753 * @terminal: a #VteTerminal
7754 *
7755 * Returns: the terminal's font scale
7756 */
7757 gdouble
vte_terminal_get_font_scale(VteTerminal * terminal)7758 vte_terminal_get_font_scale(VteTerminal *terminal)
7759 {
7760 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), 1.);
7761
7762 return terminal->pvt->font_scale;
7763 }
7764
7765 /* Read and refresh our perception of the size of the PTY. */
7766 static void
vte_terminal_refresh_size(VteTerminal * terminal)7767 vte_terminal_refresh_size(VteTerminal *terminal)
7768 {
7769 VteTerminalPrivate *pvt = terminal->pvt;
7770 int rows, columns;
7771 GError *error = NULL;
7772
7773 if (pvt->pty == NULL)
7774 return;
7775
7776 if (vte_pty_get_size(pvt->pty, &rows, &columns, &error)) {
7777 terminal->pvt->row_count = rows;
7778 terminal->pvt->column_count = columns;
7779 } else {
7780 g_warning(_("Error reading PTY size, using defaults: %s\n"), error->message);
7781 g_error_free(error);
7782 }
7783 }
7784
7785 /* Resize the given screen (normal or alternate) of the terminal. */
7786 static void
vte_terminal_screen_set_size(VteTerminal * terminal,VteScreen * screen,glong old_columns,glong old_rows,gboolean do_rewrap)7787 vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old_columns, glong old_rows, gboolean do_rewrap)
7788 {
7789 VteRing *ring = screen->row_data;
7790 VteVisualPosition cursor_saved_absolute;
7791 VteVisualPosition below_viewport;
7792 VteVisualPosition below_current_paragraph;
7793 VteVisualPosition *markers[7];
7794 gboolean was_scrolled_to_top = (screen->scroll_delta == _vte_ring_delta(ring));
7795 gboolean was_scrolled_to_bottom = (screen->scroll_delta == screen->insert_delta);
7796 glong old_top_lines;
7797 glong new_scroll_delta;
7798
7799 if (terminal->pvt->selection_block_mode && do_rewrap && old_columns != terminal->pvt->column_count)
7800 vte_terminal_deselect_all(terminal);
7801
7802 _vte_debug_print(VTE_DEBUG_RESIZE,
7803 "Resizing %s screen\n"
7804 "Old insert_delta=%ld scroll_delta=%ld\n"
7805 " cursor (absolute) row=%ld (visual line %ld) col=%ld\n"
7806 " cursor_saved (relative to insert_delta) row=%ld col=%ld\n",
7807 screen == &terminal->pvt->normal_screen ? "normal" : "alternate",
7808 screen->insert_delta, screen->scroll_delta,
7809 terminal->pvt->cursor.row, terminal->pvt->cursor.row - screen->scroll_delta + 1, terminal->pvt->cursor.col,
7810 screen->saved.cursor.row, screen->saved.cursor.col);
7811
7812 cursor_saved_absolute.row = screen->saved.cursor.row + screen->insert_delta;
7813 cursor_saved_absolute.col = screen->saved.cursor.col;
7814 below_viewport.row = screen->scroll_delta + old_rows;
7815 below_viewport.col = 0;
7816 below_current_paragraph.row = terminal->pvt->cursor.row + 1;
7817 while (below_current_paragraph.row < _vte_ring_next(ring)
7818 && _vte_ring_index(ring, below_current_paragraph.row - 1)->attr.soft_wrapped) {
7819 below_current_paragraph.row++;
7820 }
7821 below_current_paragraph.col = 0;
7822 memset(&markers, 0, sizeof(markers));
7823 markers[0] = &cursor_saved_absolute;
7824 markers[1] = &below_viewport;
7825 markers[2] = &below_current_paragraph;
7826 if (screen == terminal->pvt->screen)
7827 /* Tracking the current cursor only makes sense on the active screen. */
7828 markers[3] = &terminal->pvt->cursor;
7829 if (terminal->pvt->has_selection) {
7830 /* selection_end is inclusive, make it non-inclusive, see bug 722635. */
7831 terminal->pvt->selection_end.col++;
7832 markers[4] = &terminal->pvt->selection_start;
7833 markers[5] = &terminal->pvt->selection_end;
7834 }
7835
7836 old_top_lines = below_current_paragraph.row - screen->insert_delta;
7837
7838 if (do_rewrap && old_columns != terminal->pvt->column_count)
7839 _vte_ring_rewrap(ring, terminal->pvt->column_count, markers);
7840
7841 if (_vte_ring_length(ring) > terminal->pvt->row_count) {
7842 /* The content won't fit without scrollbars. Before figuring out the position, we might need to
7843 drop some lines from the ring if the cursor is not at the bottom, as XTerm does. See bug 708213.
7844 This code is really tricky, see ../doc/rewrap.txt for details! */
7845 glong new_top_lines, drop1, drop2, drop3, drop;
7846 screen->insert_delta = _vte_ring_next(ring) - terminal->pvt->row_count;
7847 new_top_lines = below_current_paragraph.row - screen->insert_delta;
7848 drop1 = _vte_ring_length(ring) - terminal->pvt->row_count;
7849 drop2 = _vte_ring_next(ring) - below_current_paragraph.row;
7850 drop3 = old_top_lines - new_top_lines;
7851 drop = MIN(MIN(drop1, drop2), drop3);
7852 if (drop > 0) {
7853 int new_ring_next = screen->insert_delta + terminal->pvt->row_count - drop;
7854 _vte_debug_print(VTE_DEBUG_RESIZE,
7855 "Dropping %ld [== MIN(%ld, %ld, %ld)] rows at the bottom\n",
7856 drop, drop1, drop2, drop3);
7857 _vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
7858 }
7859 }
7860
7861 if (screen == terminal->pvt->screen && terminal->pvt->has_selection) {
7862 /* Make selection_end inclusive again, see above. */
7863 terminal->pvt->selection_end.col--;
7864 }
7865
7866 /* Figure out new insert and scroll deltas */
7867 if (_vte_ring_length(ring) <= terminal->pvt->row_count) {
7868 /* Everything fits without scrollbars. Align at top. */
7869 screen->insert_delta = _vte_ring_delta(ring);
7870 new_scroll_delta = screen->insert_delta;
7871 _vte_debug_print(VTE_DEBUG_RESIZE,
7872 "Everything fits without scrollbars\n");
7873 } else {
7874 /* Scrollbar required. Can't afford unused lines at bottom. */
7875 screen->insert_delta = _vte_ring_next(ring) - terminal->pvt->row_count;
7876 if (was_scrolled_to_bottom) {
7877 /* Was scrolled to bottom, keep this way. */
7878 new_scroll_delta = screen->insert_delta;
7879 _vte_debug_print(VTE_DEBUG_RESIZE,
7880 "Scroll to bottom\n");
7881 } else if (was_scrolled_to_top) {
7882 /* Was scrolled to top, keep this way. Not sure if this special case is worth it. */
7883 new_scroll_delta = _vte_ring_delta(ring);
7884 _vte_debug_print(VTE_DEBUG_RESIZE,
7885 "Scroll to top\n");
7886 } else {
7887 /* Try to scroll so that the bottom visible row stays.
7888 More precisely, the character below the bottom left corner stays in that
7889 (invisible) row.
7890 So if the bottom of the screen was at a hard line break then that hard
7891 line break will stay there.
7892 TODO: What would be the best behavior if the bottom of the screen is a
7893 soft line break, i.e. only a partial line is visible at the bottom? */
7894 new_scroll_delta = below_viewport.row - terminal->pvt->row_count;
7895 _vte_debug_print(VTE_DEBUG_RESIZE,
7896 "Scroll so bottom row stays\n");
7897 }
7898 }
7899
7900 /* Don't clamp, they'll be clamped when restored. Until then remember off-screen values
7901 since they might become on-screen again on subsequent resizes. */
7902 screen->saved.cursor.row = cursor_saved_absolute.row - screen->insert_delta;
7903 screen->saved.cursor.col = cursor_saved_absolute.col;
7904
7905 _vte_debug_print(VTE_DEBUG_RESIZE,
7906 "New insert_delta=%ld scroll_delta=%ld\n"
7907 " cursor (absolute) row=%ld (visual line %ld) col=%ld\n"
7908 " cursor_saved (relative to insert_delta) row=%ld col=%ld\n\n",
7909 screen->insert_delta, new_scroll_delta,
7910 terminal->pvt->cursor.row, terminal->pvt->cursor.row - new_scroll_delta + 1, terminal->pvt->cursor.col,
7911 screen->saved.cursor.row, screen->saved.cursor.col);
7912
7913 if (screen == terminal->pvt->screen)
7914 vte_terminal_queue_adjustment_value_changed (
7915 terminal,
7916 new_scroll_delta);
7917 else
7918 screen->scroll_delta = new_scroll_delta;
7919 }
7920
7921 /**
7922 * vte_terminal_set_size:
7923 * @terminal: a #VteTerminal
7924 * @columns: the desired number of columns
7925 * @rows: the desired number of rows
7926 *
7927 * Attempts to change the terminal's size in terms of rows and columns. If
7928 * the attempt succeeds, the widget will resize itself to the proper size.
7929 */
7930 void
vte_terminal_set_size(VteTerminal * terminal,glong columns,glong rows)7931 vte_terminal_set_size(VteTerminal *terminal, glong columns, glong rows)
7932 {
7933 glong old_columns, old_rows;
7934
7935 g_return_if_fail(VTE_IS_TERMINAL(terminal));
7936
7937 _vte_debug_print(VTE_DEBUG_RESIZE,
7938 "Setting PTY size to %ldx%ld.\n",
7939 columns, rows);
7940
7941 old_rows = terminal->pvt->row_count;
7942 old_columns = terminal->pvt->column_count;
7943
7944 if (terminal->pvt->pty != NULL) {
7945 GError *error = NULL;
7946
7947 /* Try to set the terminal size, and read it back,
7948 * in case something went awry.
7949 */
7950 if (!vte_pty_set_size(terminal->pvt->pty, rows, columns, &error)) {
7951 g_warning("%s\n", error->message);
7952 g_error_free(error);
7953 }
7954 vte_terminal_refresh_size(terminal);
7955 } else {
7956 terminal->pvt->row_count = rows;
7957 terminal->pvt->column_count = columns;
7958 }
7959 if (old_rows != terminal->pvt->row_count || old_columns != terminal->pvt->column_count) {
7960 terminal->pvt->scrolling_restricted = FALSE;
7961
7962 _vte_ring_set_visible_rows(terminal->pvt->normal_screen.row_data, terminal->pvt->row_count);
7963 _vte_ring_set_visible_rows(terminal->pvt->alternate_screen.row_data, terminal->pvt->row_count);
7964
7965 /* Resize the normal screen and (if rewrapping is enabled) rewrap it even if the alternate screen is visible: bug 415277 */
7966 vte_terminal_screen_set_size(terminal, &terminal->pvt->normal_screen, old_columns, old_rows, terminal->pvt->rewrap_on_resize);
7967 /* Resize the alternate screen if it's the current one, but never rewrap it: bug 336238 comment 60 */
7968 if (terminal->pvt->screen == &terminal->pvt->alternate_screen)
7969 vte_terminal_screen_set_size(terminal, &terminal->pvt->alternate_screen, old_columns, old_rows, FALSE);
7970
7971 /* Ensure scrollback buffers cover the screen. */
7972 vte_terminal_set_scrollback_lines(terminal,
7973 terminal->pvt->scrollback_lines);
7974 /* Ensure the cursor is valid */
7975 terminal->pvt->cursor.row = CLAMP (terminal->pvt->cursor.row,
7976 _vte_ring_delta (terminal->pvt->screen->row_data),
7977 MAX (_vte_ring_delta (terminal->pvt->screen->row_data),
7978 _vte_ring_next (terminal->pvt->screen->row_data) - 1));
7979
7980 _vte_terminal_adjust_adjustments_full (terminal);
7981 gtk_widget_queue_resize_no_redraw (&terminal->widget);
7982 /* Our visible text changed. */
7983 vte_terminal_emit_text_modified(terminal);
7984 }
7985 }
7986
7987 /* Redraw the widget. */
7988 static void
vte_terminal_handle_scroll(VteTerminal * terminal)7989 vte_terminal_handle_scroll(VteTerminal *terminal)
7990 {
7991 long dy, adj;
7992 VteScreen *screen;
7993
7994 screen = terminal->pvt->screen;
7995
7996 /* Read the new adjustment value and save the difference. */
7997 adj = round (gtk_adjustment_get_value(terminal->pvt->vadjustment));
7998 dy = adj - screen->scroll_delta;
7999 screen->scroll_delta = adj;
8000
8001 /* Sanity checks. */
8002 if (G_UNLIKELY(!gtk_widget_get_realized(&terminal->widget)))
8003 return;
8004 if (terminal->pvt->visibility_state == GDK_VISIBILITY_FULLY_OBSCURED) {
8005 return;
8006 }
8007
8008 if (dy != 0) {
8009 _vte_debug_print(VTE_DEBUG_ADJ,
8010 "Scrolling by %ld\n", dy);
8011 _vte_terminal_scroll_region(terminal, screen->scroll_delta,
8012 terminal->pvt->row_count, -dy);
8013 vte_terminal_emit_text_scrolled(terminal, dy);
8014 _vte_terminal_queue_contents_changed(terminal);
8015 } else {
8016 _vte_debug_print(VTE_DEBUG_ADJ, "Not scrolling\n");
8017 }
8018 }
8019
8020 static void
vte_terminal_set_hadjustment(VteTerminal * terminal,GtkAdjustment * adjustment)8021 vte_terminal_set_hadjustment(VteTerminal *terminal,
8022 GtkAdjustment *adjustment)
8023 {
8024 VteTerminalPrivate *pvt = terminal->pvt;
8025
8026 if (adjustment == pvt->hadjustment)
8027 return;
8028
8029 if (pvt->hadjustment)
8030 g_object_unref (pvt->hadjustment);
8031
8032 pvt->hadjustment = adjustment ? g_object_ref_sink (adjustment) : NULL;
8033 }
8034
8035 static void
vte_terminal_set_vadjustment(VteTerminal * terminal,GtkAdjustment * adjustment)8036 vte_terminal_set_vadjustment(VteTerminal *terminal,
8037 GtkAdjustment *adjustment)
8038 {
8039 if (adjustment != NULL && adjustment == terminal->pvt->vadjustment)
8040 return;
8041 if (adjustment == NULL && terminal->pvt->vadjustment != NULL)
8042 return;
8043
8044 if (adjustment == NULL)
8045 adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
8046 else
8047 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
8048
8049 /* Add a reference to the new adjustment object. */
8050 g_object_ref_sink(adjustment);
8051 /* Get rid of the old adjustment object. */
8052 if (terminal->pvt->vadjustment != NULL) {
8053 /* Disconnect our signal handlers from this object. */
8054 g_signal_handlers_disconnect_by_func(terminal->pvt->vadjustment,
8055 vte_terminal_handle_scroll,
8056 terminal);
8057 g_object_unref(terminal->pvt->vadjustment);
8058 }
8059
8060 /* Set the new adjustment object. */
8061 terminal->pvt->vadjustment = adjustment;
8062
8063 /* We care about the offset, not the top or bottom. */
8064 g_signal_connect_swapped(terminal->pvt->vadjustment,
8065 "value-changed",
8066 G_CALLBACK(vte_terminal_handle_scroll),
8067 terminal);
8068 }
8069
8070 void
_vte_terminal_inline_error_message(VteTerminal * terminal,const char * format,...)8071 _vte_terminal_inline_error_message(VteTerminal *terminal, const char *format, ...)
8072 {
8073 va_list ap;
8074 char *str;
8075
8076 va_start (ap, format);
8077 str = g_strdup_vprintf (format, ap);
8078 va_end (ap);
8079
8080 vte_terminal_feed (terminal, "*** VTE ***: ", 13);
8081 vte_terminal_feed (terminal, str, -1);
8082 vte_terminal_feed (terminal, "\r\n", 2);
8083 g_free (str);
8084 }
8085
8086 /* Initialize the terminal widget after the base widget stuff is initialized.
8087 * We need to create a new psuedo-terminal pair, and set ourselves up to do
8088 * the interpretation of sequences. */
8089 static void
vte_terminal_init(VteTerminal * terminal)8090 vte_terminal_init(VteTerminal *terminal)
8091 {
8092 VteTerminalPrivate *pvt;
8093 GtkStyleContext *context;
8094 int i;
8095
8096 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_init()\n");
8097
8098 /* Initialize private data. */
8099 pvt = terminal->pvt = G_TYPE_INSTANCE_GET_PRIVATE (terminal, VTE_TYPE_TERMINAL, VteTerminalPrivate);
8100
8101 gtk_widget_set_can_focus(&terminal->widget, TRUE);
8102
8103 gtk_widget_set_app_paintable (&terminal->widget, TRUE);
8104
8105 /* We do our own redrawing. */
8106 gtk_widget_set_redraw_on_allocate (&terminal->widget, FALSE);
8107
8108 /* Set an adjustment for the application to use to control scrolling. */
8109 terminal->pvt->vadjustment = NULL;
8110 pvt->hadjustment = NULL;
8111 /* GtkScrollable */
8112 pvt->hscroll_policy = GTK_SCROLL_NATURAL;
8113 pvt->vscroll_policy = GTK_SCROLL_NATURAL;
8114
8115 vte_terminal_set_hadjustment(terminal, NULL);
8116 vte_terminal_set_vadjustment(terminal, NULL);
8117
8118 /* Set up dummy metrics, value != 0 to avoid division by 0 */
8119 terminal->pvt->char_width = 1;
8120 terminal->pvt->char_height = 1;
8121 terminal->pvt->char_ascent = 1;
8122 terminal->pvt->char_descent = 1;
8123 terminal->pvt->line_thickness = 1;
8124 terminal->pvt->underline_position = 1;
8125 terminal->pvt->strikethrough_position = 1;
8126
8127 /* We allocated zeroed memory, just fill in non-zero stuff. */
8128
8129 /* Initialize the screens and histories. */
8130 _vte_ring_init (pvt->alternate_screen.row_data, terminal->pvt->row_count, FALSE);
8131 pvt->screen = &terminal->pvt->alternate_screen;
8132 _vte_ring_init (pvt->normal_screen.row_data, VTE_SCROLLBACK_INIT, TRUE);
8133 pvt->screen = &terminal->pvt->normal_screen;
8134
8135 _vte_terminal_set_default_attributes(terminal);
8136
8137 /* Initialize charset modes. */
8138 pvt->character_replacements[0] = VTE_CHARACTER_REPLACEMENT_NONE;
8139 pvt->character_replacements[1] = VTE_CHARACTER_REPLACEMENT_NONE;
8140 pvt->character_replacement = &pvt->character_replacements[0];
8141
8142 /* Set up the desired palette. */
8143 vte_terminal_set_default_colors(terminal);
8144 for (i = 0; i < VTE_PALETTE_SIZE; i++)
8145 terminal->pvt->palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
8146
8147 /* Set up I/O encodings. */
8148 pvt->utf8_ambiguous_width = VTE_DEFAULT_UTF8_AMBIGUOUS_WIDTH;
8149 pvt->iso2022 = _vte_iso2022_state_new(pvt->encoding);
8150 pvt->incoming = NULL;
8151 pvt->pending = g_array_new(FALSE, TRUE, sizeof(gunichar));
8152 pvt->max_input_bytes = VTE_MAX_INPUT_READ;
8153 pvt->cursor_blink_tag = 0;
8154 pvt->outgoing = _vte_byte_array_new();
8155 pvt->outgoing_conv = VTE_INVALID_CONV;
8156 pvt->conv_buffer = _vte_byte_array_new();
8157 vte_terminal_set_encoding(terminal, NULL /* UTF-8 */, NULL);
8158 g_assert_cmpstr(terminal->pvt->encoding, ==, "UTF-8");
8159
8160 /* Set up the emulation. */
8161 pvt->keypad_mode = VTE_KEYMODE_NORMAL;
8162 pvt->cursor_mode = VTE_KEYMODE_NORMAL;
8163 pvt->autowrap = TRUE;
8164 pvt->sendrecv_mode = TRUE;
8165 pvt->dec_saved = g_hash_table_new(NULL, NULL);
8166 pvt->matcher = _vte_matcher_new();
8167 pvt->alternate_screen_scroll = TRUE;
8168
8169 /* Setting the terminal type and size requires the PTY master to
8170 * be set up properly first. */
8171 pvt->pty = NULL;
8172 vte_terminal_set_size(terminal, VTE_COLUMNS, VTE_ROWS);
8173 pvt->pty_input_source = 0;
8174 pvt->pty_output_source = 0;
8175 pvt->pty_pid = -1;
8176
8177 /* Scrolling options. */
8178 pvt->scroll_on_keystroke = TRUE;
8179 pvt->alternate_screen_scroll = TRUE;
8180 pvt->scrollback_lines = -1; /* force update in vte_terminal_set_scrollback_lines */
8181 vte_terminal_set_scrollback_lines(terminal, VTE_SCROLLBACK_INIT);
8182
8183 /* Miscellaneous options. */
8184 vte_terminal_set_backspace_binding(terminal, VTE_ERASE_AUTO);
8185 vte_terminal_set_delete_binding(terminal, VTE_ERASE_AUTO);
8186 pvt->meta_sends_escape = TRUE;
8187 pvt->audible_bell = TRUE;
8188 pvt->bell_margin = 10;
8189 pvt->allow_bold = TRUE;
8190 pvt->deccolm_mode = FALSE;
8191 pvt->rewrap_on_resize = TRUE;
8192 vte_terminal_set_default_tabstops(terminal);
8193
8194 pvt->input_enabled = TRUE;
8195
8196 /* Cursor shape. */
8197 pvt->cursor_shape = VTE_CURSOR_SHAPE_BLOCK;
8198 pvt->cursor_aspect_ratio = 0.04;
8199
8200 /* Cursor blinking. */
8201 pvt->cursor_visible = TRUE;
8202 pvt->cursor_blink_timeout = 500;
8203 pvt->cursor_blinks = FALSE;
8204 pvt->cursor_blink_mode = VTE_CURSOR_BLINK_SYSTEM;
8205
8206 /* DECSCUSR cursor style (shape and blinking possibly overridden
8207 * via escape sequence) */
8208 pvt->cursor_style = VTE_CURSOR_STYLE_TERMINAL_DEFAULT;
8209
8210 /* Initialize the saved cursor. */
8211 _vte_terminal_save_cursor(terminal, &terminal->pvt->normal_screen);
8212 _vte_terminal_save_cursor(terminal, &terminal->pvt->alternate_screen);
8213
8214 /* Matching data. */
8215 pvt->match_regexes = g_array_new(FALSE, TRUE,
8216 sizeof(struct vte_match_regex));
8217 pvt->match_tag = -1;
8218 vte_terminal_match_hilite_clear(terminal);
8219
8220 /* Rendering data */
8221 pvt->draw = _vte_draw_new();
8222
8223 /* Set up background information. */
8224 pvt->background_alpha = 1.;
8225
8226 /* Word chars */
8227 vte_terminal_set_word_char_exceptions(terminal, WORD_CHAR_EXCEPTIONS_DEFAULT);
8228
8229 /* Selection */
8230 pvt->selection_block_mode = FALSE;
8231 pvt->unscaled_font_desc = pvt->fontdesc = NULL;
8232 pvt->font_scale = 1.;
8233 pvt->has_fonts = FALSE;
8234
8235 /* Not all backends generate GdkVisibilityNotify, so mark the
8236 * window as unobscured initially. */
8237 pvt->visibility_state = GDK_VISIBILITY_UNOBSCURED;
8238
8239 pvt->padding = default_padding;
8240
8241 context = gtk_widget_get_style_context (&terminal->widget);
8242 gtk_style_context_add_provider (context,
8243 VTE_TERMINAL_GET_CLASS (terminal)->priv->style_provider,
8244 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8245 }
8246
8247 /* Tell GTK+ how much space we need. */
8248 static void
vte_terminal_get_preferred_width(GtkWidget * widget,int * minimum_width,int * natural_width)8249 vte_terminal_get_preferred_width(GtkWidget *widget,
8250 int *minimum_width,
8251 int *natural_width)
8252 {
8253 VteTerminal *terminal;
8254
8255 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_width()\n");
8256
8257 terminal = VTE_TERMINAL(widget);
8258
8259 vte_terminal_ensure_font (terminal);
8260
8261 vte_terminal_refresh_size(terminal);
8262 *minimum_width = terminal->pvt->char_width * 1;
8263 *natural_width = terminal->pvt->char_width * terminal->pvt->column_count;
8264
8265 *minimum_width += terminal->pvt->padding.left +
8266 terminal->pvt->padding.right;
8267 *natural_width += terminal->pvt->padding.left +
8268 terminal->pvt->padding.right;
8269
8270 _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
8271 "[Terminal %p] minimum_width=%d, natural_width=%d for %ldx%ld cells.\n",
8272 terminal,
8273 *minimum_width, *natural_width,
8274 terminal->pvt->column_count,
8275 terminal->pvt->row_count);
8276 }
8277
8278 static void
vte_terminal_get_preferred_height(GtkWidget * widget,int * minimum_height,int * natural_height)8279 vte_terminal_get_preferred_height(GtkWidget *widget,
8280 int *minimum_height,
8281 int *natural_height)
8282 {
8283 VteTerminal *terminal;
8284
8285 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_get_preferred_height()\n");
8286
8287 terminal = VTE_TERMINAL(widget);
8288
8289 vte_terminal_ensure_font (terminal);
8290
8291 vte_terminal_refresh_size(terminal);
8292 *minimum_height = terminal->pvt->char_height * 1;
8293 *natural_height = terminal->pvt->char_height * terminal->pvt->row_count;
8294
8295 *minimum_height += terminal->pvt->padding.left +
8296 terminal->pvt->padding.right;
8297 *natural_height += terminal->pvt->padding.left +
8298 terminal->pvt->padding.right;
8299
8300 _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
8301 "[Terminal %p] minimum_height=%d, natural_height=%d for %ldx%ld cells.\n",
8302 terminal,
8303 *minimum_height, *natural_height,
8304 terminal->pvt->column_count,
8305 terminal->pvt->row_count);
8306 }
8307
8308 /* Accept a given size from GTK+. */
8309 static void
vte_terminal_size_allocate(GtkWidget * widget,GtkAllocation * allocation)8310 vte_terminal_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
8311 {
8312 VteTerminal *terminal;
8313 glong width, height;
8314 GtkAllocation current_allocation;
8315 gboolean repaint, update_scrollback;
8316
8317 _vte_debug_print(VTE_DEBUG_LIFECYCLE,
8318 "vte_terminal_size_allocate()\n");
8319
8320 terminal = VTE_TERMINAL(widget);
8321
8322 width = (allocation->width - (terminal->pvt->padding.left + terminal->pvt->padding.right)) /
8323 terminal->pvt->char_width;
8324 height = (allocation->height - (terminal->pvt->padding.top + terminal->pvt->padding.bottom)) /
8325 terminal->pvt->char_height;
8326 width = MAX(width, 1);
8327 height = MAX(height, 1);
8328
8329 _vte_debug_print(VTE_DEBUG_WIDGET_SIZE,
8330 "[Terminal %p] Sizing window to %dx%d (%ldx%ld).\n",
8331 terminal,
8332 allocation->width, allocation->height,
8333 width, height);
8334
8335 gtk_widget_get_allocation (widget, ¤t_allocation);
8336
8337 repaint = current_allocation.width != allocation->width
8338 || current_allocation.height != allocation->height;
8339 update_scrollback = current_allocation.height != allocation->height;
8340
8341 /* Set our allocation to match the structure. */
8342 gtk_widget_set_allocation (widget, allocation);
8343
8344 if (width != terminal->pvt->column_count
8345 || height != terminal->pvt->row_count
8346 || update_scrollback)
8347 {
8348 /* Set the size of the pseudo-terminal. */
8349 vte_terminal_set_size(terminal, width, height);
8350
8351 /* Notify viewers that the contents have changed. */
8352 _vte_terminal_queue_contents_changed(terminal);
8353 }
8354
8355 /* Resize the GDK window. */
8356 if (gtk_widget_get_realized (widget)) {
8357 gdk_window_move_resize (gtk_widget_get_window (widget),
8358 allocation->x,
8359 allocation->y,
8360 allocation->width,
8361 allocation->height);
8362 /* Force a repaint if we were resized. */
8363 if (repaint) {
8364 reset_update_regions (terminal);
8365 _vte_invalidate_all(terminal);
8366 }
8367 }
8368 }
8369
8370 /* The window is being destroyed. */
8371 static void
vte_terminal_unrealize(GtkWidget * widget)8372 vte_terminal_unrealize(GtkWidget *widget)
8373 {
8374 GdkWindow *window;
8375 VteTerminal *terminal;
8376
8377 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_unrealize()\n");
8378
8379 terminal = VTE_TERMINAL (widget);
8380 window = gtk_widget_get_window (widget);
8381
8382 /* Deallocate the cursors. */
8383 terminal->pvt->mouse_cursor_visible = FALSE;
8384 g_object_unref(terminal->pvt->mouse_default_cursor);
8385 terminal->pvt->mouse_default_cursor = NULL;
8386 g_object_unref(terminal->pvt->mouse_mousing_cursor);
8387 terminal->pvt->mouse_mousing_cursor = NULL;
8388 g_object_unref(terminal->pvt->mouse_inviso_cursor);
8389 terminal->pvt->mouse_inviso_cursor = NULL;
8390
8391 vte_terminal_match_hilite_clear(terminal);
8392
8393 /* Shut down input methods. */
8394 if (terminal->pvt->im_context != NULL) {
8395 g_signal_handlers_disconnect_by_func (terminal->pvt->im_context,
8396 vte_terminal_im_preedit_changed,
8397 terminal);
8398 vte_terminal_im_reset(terminal);
8399 gtk_im_context_set_client_window(terminal->pvt->im_context,
8400 NULL);
8401 g_object_unref(terminal->pvt->im_context);
8402 terminal->pvt->im_context = NULL;
8403 }
8404 terminal->pvt->im_preedit_active = FALSE;
8405 if (terminal->pvt->im_preedit != NULL) {
8406 g_free(terminal->pvt->im_preedit);
8407 terminal->pvt->im_preedit = NULL;
8408 }
8409 if (terminal->pvt->im_preedit_attrs != NULL) {
8410 pango_attr_list_unref(terminal->pvt->im_preedit_attrs);
8411 terminal->pvt->im_preedit_attrs = NULL;
8412 }
8413 terminal->pvt->im_preedit_cursor = 0;
8414
8415 /* Clean up our draw structure. */
8416 if (terminal->pvt->draw != NULL) {
8417 _vte_draw_free(terminal->pvt->draw);
8418 terminal->pvt->draw = NULL;
8419 }
8420 terminal->pvt->fontdirty = TRUE;
8421
8422 /* Unmap the widget if it hasn't been already. */
8423 if (gtk_widget_get_mapped (widget)) {
8424 gtk_widget_unmap (widget);
8425 }
8426
8427 /* Remove the GDK window. */
8428 if (window != NULL) {
8429 gdk_window_set_user_data (window, NULL);
8430 gtk_widget_set_window (widget, NULL);
8431
8432 gdk_window_destroy (window);
8433 }
8434
8435 /* Remove the blink timeout function. */
8436 remove_cursor_timeout(terminal);
8437
8438 /* Cancel any pending redraws. */
8439 remove_update_timeout (terminal);
8440
8441 /* Cancel any pending signals */
8442 terminal->pvt->contents_changed_pending = FALSE;
8443 terminal->pvt->cursor_moved_pending = FALSE;
8444 terminal->pvt->text_modified_flag = FALSE;
8445 terminal->pvt->text_inserted_flag = FALSE;
8446 terminal->pvt->text_deleted_flag = FALSE;
8447
8448 /* Clear modifiers. */
8449 terminal->pvt->modifiers = 0;
8450
8451 /* Mark that we no longer have a GDK window. */
8452 gtk_widget_set_realized (widget, FALSE);
8453 }
8454
8455 static void
vte_terminal_sync_settings(GtkSettings * settings,GParamSpec * pspec,VteTerminal * terminal)8456 vte_terminal_sync_settings (GtkSettings *settings,
8457 GParamSpec *pspec,
8458 VteTerminal *terminal)
8459 {
8460 VteTerminalPrivate *pvt = terminal->pvt;
8461 gboolean blink;
8462 int blink_time = 1000;
8463 int blink_timeout = G_MAXINT;
8464
8465 g_object_get(G_OBJECT (settings),
8466 "gtk-cursor-blink", &blink,
8467 "gtk-cursor-blink-time", &blink_time,
8468 "gtk-cursor-blink-timeout", &blink_timeout,
8469 NULL);
8470
8471 _vte_debug_print(VTE_DEBUG_MISC,
8472 "Cursor blinking settings setting: blink=%d time=%d timeout=%d\n",
8473 blink, blink_time, blink_timeout);
8474
8475 pvt->cursor_blink_cycle = blink_time / 2;
8476 pvt->cursor_blink_timeout = blink_timeout;
8477
8478 vte_terminal_update_cursor_blinks_internal(terminal);
8479 }
8480
8481 static void
vte_terminal_screen_changed(GtkWidget * widget,GdkScreen * previous_screen)8482 vte_terminal_screen_changed (GtkWidget *widget,
8483 GdkScreen *previous_screen)
8484 {
8485 VteTerminal *terminal = VTE_TERMINAL (widget);
8486 GdkScreen *screen;
8487 GtkSettings *settings;
8488
8489 screen = gtk_widget_get_screen (widget);
8490 if (previous_screen != NULL &&
8491 (screen != previous_screen || screen == NULL)) {
8492 settings = gtk_settings_get_for_screen (previous_screen);
8493 g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
8494 0, 0, NULL, NULL,
8495 widget);
8496 }
8497
8498 if (GTK_WIDGET_CLASS (vte_terminal_parent_class)->screen_changed) {
8499 GTK_WIDGET_CLASS (vte_terminal_parent_class)->screen_changed (widget, previous_screen);
8500 }
8501
8502 if (screen == previous_screen || screen == NULL)
8503 return;
8504
8505 settings = gtk_widget_get_settings (widget);
8506 vte_terminal_sync_settings (settings, NULL, terminal);
8507 g_signal_connect (settings, "notify::gtk-cursor-blink",
8508 G_CALLBACK (vte_terminal_sync_settings), widget);
8509 g_signal_connect (settings, "notify::gtk-cursor-blink-time",
8510 G_CALLBACK (vte_terminal_sync_settings), widget);
8511 g_signal_connect (settings, "notify::gtk-cursor-blink-timeout",
8512 G_CALLBACK (vte_terminal_sync_settings), widget);
8513 }
8514
8515 /* Perform final cleanups for the widget before it's freed. */
8516 static void
vte_terminal_finalize(GObject * object)8517 vte_terminal_finalize(GObject *object)
8518 {
8519 GtkWidget *widget = GTK_WIDGET (object);
8520 VteTerminal *terminal = VTE_TERMINAL (object);
8521 VteTerminalPrivate *pvt = terminal->pvt;
8522 GtkClipboard *clipboard;
8523 GtkSettings *settings;
8524 struct vte_match_regex *regex;
8525 guint i;
8526
8527 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_finalize()\n");
8528
8529 /* Free the draw structure. */
8530 if (terminal->pvt->draw != NULL) {
8531 _vte_draw_free(terminal->pvt->draw);
8532 }
8533
8534 /* The NLS maps. */
8535 _vte_iso2022_state_free(terminal->pvt->iso2022);
8536
8537 /* Free the font description. */
8538 if (pvt->unscaled_font_desc != NULL) {
8539 pango_font_description_free(pvt->unscaled_font_desc);
8540 }
8541 if (terminal->pvt->fontdesc != NULL) {
8542 pango_font_description_free(terminal->pvt->fontdesc);
8543 }
8544
8545 /* Free matching data. */
8546 if (terminal->pvt->match_attributes != NULL) {
8547 g_array_free(terminal->pvt->match_attributes, TRUE);
8548 }
8549 g_free(terminal->pvt->match_contents);
8550 if (terminal->pvt->match_regexes != NULL) {
8551 for (i = 0; i < terminal->pvt->match_regexes->len; i++) {
8552 regex = &g_array_index(terminal->pvt->match_regexes,
8553 struct vte_match_regex,
8554 i);
8555 /* Skip holes. */
8556 if (regex->tag < 0) {
8557 continue;
8558 }
8559 regex_match_clear(regex);
8560 }
8561 g_array_free(terminal->pvt->match_regexes, TRUE);
8562 }
8563
8564 if (terminal->pvt->search_regex)
8565 g_regex_unref (terminal->pvt->search_regex);
8566 if (terminal->pvt->search_attrs)
8567 g_array_free (terminal->pvt->search_attrs, TRUE);
8568
8569 /* Disconnect from autoscroll requests. */
8570 vte_terminal_stop_autoscroll(terminal);
8571
8572 /* Cancel pending adjustment change notifications. */
8573 terminal->pvt->adjustment_changed_pending = FALSE;
8574
8575 /* Tabstop information. */
8576 if (terminal->pvt->tabstops != NULL) {
8577 g_hash_table_destroy(terminal->pvt->tabstops);
8578 }
8579
8580 /* Free any selected text, but if we currently own the selection,
8581 * throw the text onto the clipboard without an owner so that it
8582 * doesn't just disappear. */
8583 if (terminal->pvt->selection != NULL) {
8584 clipboard = vte_terminal_clipboard_get(terminal,
8585 GDK_SELECTION_PRIMARY);
8586 if (gtk_clipboard_get_owner(clipboard) == object) {
8587 gtk_clipboard_set_text(clipboard,
8588 terminal->pvt->selection,
8589 -1);
8590 }
8591 g_free(terminal->pvt->selection);
8592 }
8593
8594 /* Clear the output histories. */
8595 _vte_ring_fini(terminal->pvt->normal_screen.row_data);
8596 _vte_ring_fini(terminal->pvt->alternate_screen.row_data);
8597
8598 /* Free conversion descriptors. */
8599 if (terminal->pvt->outgoing_conv != VTE_INVALID_CONV) {
8600 _vte_conv_close(terminal->pvt->outgoing_conv);
8601 terminal->pvt->outgoing_conv = VTE_INVALID_CONV;
8602 }
8603
8604 /* Start listening for child-exited signals and ignore them, so that no zombie child is left behind. */
8605 if (terminal->pvt->child_watch_source != 0) {
8606 g_source_remove (terminal->pvt->child_watch_source);
8607 terminal->pvt->child_watch_source = 0;
8608 g_child_watch_add_full(G_PRIORITY_HIGH,
8609 terminal->pvt->pty_pid,
8610 (GChildWatchFunc)vte_terminal_child_watch_cb,
8611 NULL, NULL);
8612 }
8613
8614 /* Stop processing input. */
8615 vte_terminal_stop_processing (terminal);
8616
8617 /* Discard any pending data. */
8618 _vte_incoming_chunks_release (terminal->pvt->incoming);
8619 _vte_byte_array_free(terminal->pvt->outgoing);
8620 g_array_free(terminal->pvt->pending, TRUE);
8621 _vte_byte_array_free(terminal->pvt->conv_buffer);
8622
8623 /* Stop the child and stop watching for input from the child. */
8624 if (terminal->pvt->pty_pid != -1) {
8625 #ifdef HAVE_GETPGID
8626 pid_t pgrp;
8627 pgrp = getpgid(terminal->pvt->pty_pid);
8628 if (pgrp != -1) {
8629 kill(-pgrp, SIGHUP);
8630 }
8631 #endif
8632 kill(terminal->pvt->pty_pid, SIGHUP);
8633 }
8634 _vte_terminal_disconnect_pty_read(terminal);
8635 _vte_terminal_disconnect_pty_write(terminal);
8636 if (terminal->pvt->pty_channel != NULL) {
8637 g_io_channel_unref (terminal->pvt->pty_channel);
8638 }
8639 if (terminal->pvt->pty != NULL) {
8640 vte_pty_close(terminal->pvt->pty);
8641 g_object_unref(terminal->pvt->pty);
8642 }
8643
8644 /* Remove hash tables. */
8645 if (terminal->pvt->dec_saved != NULL) {
8646 g_hash_table_destroy(terminal->pvt->dec_saved);
8647 }
8648
8649 /* Clean up emulation structures. */
8650 if (terminal->pvt->matcher != NULL) {
8651 _vte_matcher_free(terminal->pvt->matcher);
8652 }
8653
8654 remove_update_timeout (terminal);
8655
8656 /* discard title updates */
8657 g_free(terminal->pvt->window_title);
8658 g_free(terminal->pvt->window_title_changed);
8659 g_free(terminal->pvt->icon_title_changed);
8660 g_free(terminal->pvt->current_directory_uri_changed);
8661 g_free(terminal->pvt->current_directory_uri);
8662 g_free(terminal->pvt->current_file_uri_changed);
8663 g_free(terminal->pvt->current_file_uri);
8664
8665 /* Word char exceptions */
8666 g_free(terminal->pvt->word_char_exceptions_string);
8667 g_free(terminal->pvt->word_char_exceptions);
8668
8669 /* Free public-facing data. */
8670 g_free(terminal->pvt->icon_title);
8671 if (terminal->pvt->vadjustment != NULL) {
8672 g_object_unref(terminal->pvt->vadjustment);
8673 }
8674
8675 settings = gtk_widget_get_settings (widget);
8676 g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
8677 0, 0, NULL, NULL,
8678 terminal);
8679
8680 /* Call the inherited finalize() method. */
8681 G_OBJECT_CLASS(vte_terminal_parent_class)->finalize(object);
8682 }
8683
8684 /* Handle realizing the widget. Most of this is copy-paste from GGAD. */
8685 static void
vte_terminal_realize(GtkWidget * widget)8686 vte_terminal_realize(GtkWidget *widget)
8687 {
8688 GdkWindow *window;
8689 VteTerminal *terminal;
8690 GdkWindowAttr attributes;
8691 GtkAllocation allocation;
8692 guint attributes_mask = 0;
8693
8694 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_realize()\n");
8695
8696 terminal = VTE_TERMINAL(widget);
8697 gtk_widget_get_allocation (widget, &allocation);
8698
8699 /* Create the stock cursors. */
8700 terminal->pvt->mouse_cursor_visible = TRUE;
8701 terminal->pvt->mouse_default_cursor =
8702 vte_terminal_cursor_new(terminal, VTE_DEFAULT_CURSOR);
8703 terminal->pvt->mouse_mousing_cursor =
8704 vte_terminal_cursor_new(terminal, VTE_MOUSING_CURSOR);
8705
8706 /* Create a GDK window for the widget. */
8707 attributes.window_type = GDK_WINDOW_CHILD;
8708 attributes.x = allocation.x;
8709 attributes.y = allocation.y;
8710 attributes.width = allocation.width;
8711 attributes.height = allocation.height;
8712 attributes.wclass = GDK_INPUT_OUTPUT;
8713 attributes.visual = gtk_widget_get_visual (widget);
8714 attributes.event_mask = gtk_widget_get_events(widget) |
8715 GDK_EXPOSURE_MASK |
8716 GDK_VISIBILITY_NOTIFY_MASK |
8717 GDK_FOCUS_CHANGE_MASK |
8718 #if GTK_CHECK_VERSION (3, 4, 0)
8719 GDK_SMOOTH_SCROLL_MASK |
8720 #endif
8721 GDK_SCROLL_MASK |
8722 GDK_BUTTON_PRESS_MASK |
8723 GDK_BUTTON_RELEASE_MASK |
8724 GDK_POINTER_MOTION_MASK |
8725 GDK_BUTTON1_MOTION_MASK |
8726 GDK_ENTER_NOTIFY_MASK |
8727 GDK_LEAVE_NOTIFY_MASK |
8728 GDK_KEY_PRESS_MASK |
8729 GDK_KEY_RELEASE_MASK;
8730 attributes.cursor = terminal->pvt->mouse_default_cursor;
8731 attributes_mask = GDK_WA_X |
8732 GDK_WA_Y |
8733 (attributes.visual ? GDK_WA_VISUAL : 0) |
8734 GDK_WA_CURSOR;
8735
8736 window = gdk_window_new (gtk_widget_get_parent_window (widget),
8737 &attributes, attributes_mask);
8738
8739 gtk_widget_set_window (widget, window);
8740 gdk_window_set_user_data (window, widget);
8741 gtk_style_context_set_background(gtk_widget_get_style_context(widget), window);
8742 _VTE_DEBUG_IF (VTE_DEBUG_UPDATES) gdk_window_set_debug_updates (TRUE);
8743
8744 /* Set the realized flag. */
8745 gtk_widget_set_realized (widget, TRUE);
8746
8747 /* Create rendering data if this is a re-realise */
8748 if (terminal->pvt->draw == NULL) {
8749 terminal->pvt->draw = _vte_draw_new();
8750 }
8751
8752 /* Set up input method support. FIXME: do we need to handle the
8753 * "retrieve-surrounding" and "delete-surrounding" events? */
8754 if (terminal->pvt->im_context != NULL) {
8755 vte_terminal_im_reset(terminal);
8756 g_object_unref(terminal->pvt->im_context);
8757 terminal->pvt->im_context = NULL;
8758 }
8759 terminal->pvt->im_preedit_active = FALSE;
8760 terminal->pvt->im_context = gtk_im_multicontext_new();
8761 gtk_im_context_set_client_window (terminal->pvt->im_context, window);
8762 g_signal_connect(terminal->pvt->im_context, "commit",
8763 G_CALLBACK(vte_terminal_im_commit), terminal);
8764 g_signal_connect(terminal->pvt->im_context, "preedit-start",
8765 G_CALLBACK(vte_terminal_im_preedit_start),
8766 terminal);
8767 g_signal_connect(terminal->pvt->im_context, "preedit-changed",
8768 G_CALLBACK(vte_terminal_im_preedit_changed),
8769 terminal);
8770 g_signal_connect(terminal->pvt->im_context, "preedit-end",
8771 G_CALLBACK(vte_terminal_im_preedit_end),
8772 terminal);
8773 gtk_im_context_set_use_preedit(terminal->pvt->im_context, TRUE);
8774
8775 /* Clear modifiers. */
8776 terminal->pvt->modifiers = 0;
8777
8778 /* Create our invisible cursor. */
8779 terminal->pvt->mouse_inviso_cursor = gdk_cursor_new_for_display(gtk_widget_get_display(widget), GDK_BLANK_CURSOR);
8780
8781 /* Make sure the style is set, bug 727614. */
8782 vte_terminal_style_updated (widget);
8783
8784 vte_terminal_ensure_font (terminal);
8785
8786 /* Set up the background, *now*. */
8787 vte_terminal_background_update(terminal);
8788 }
8789
8790 static inline void
swap(guint * a,guint * b)8791 swap (guint *a, guint *b)
8792 {
8793 guint tmp;
8794 tmp = *a, *a = *b, *b = tmp;
8795 }
8796
8797 static void
vte_terminal_determine_colors_internal(VteTerminal * terminal,const VteCell * cell,gboolean selected,gboolean cursor,guint * pfore,guint * pback)8798 vte_terminal_determine_colors_internal(VteTerminal *terminal,
8799 const VteCell *cell,
8800 gboolean selected,
8801 gboolean cursor,
8802 guint *pfore, guint *pback)
8803 {
8804 guint fore, back;
8805
8806 if (!cell)
8807 cell = &basic_cell.cell;
8808
8809 /* Start with cell colors */
8810 fore = cell->attr.fore;
8811 back = cell->attr.back;
8812
8813 /* Reverse-mode switches default fore and back colors */
8814 if (G_UNLIKELY (terminal->pvt->reverse_mode)) {
8815 if (fore == VTE_DEFAULT_FG)
8816 fore = VTE_DEFAULT_BG;
8817 if (back == VTE_DEFAULT_BG)
8818 back = VTE_DEFAULT_FG;
8819 }
8820
8821 /* Handle bold by using set bold color or brightening */
8822 if (cell->attr.bold) {
8823 if (fore == VTE_DEFAULT_FG)
8824 fore = VTE_BOLD_FG;
8825 else if (fore >= VTE_LEGACY_COLORS_OFFSET && fore < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) {
8826 fore += VTE_COLOR_BRIGHT_OFFSET;
8827 }
8828 }
8829
8830 /* Handle dim colors. Only apply to palette colors, dimming direct RGB wouldn't make sense.
8831 * Apply to the foreground color only, but do this before handling reverse/highlight so that
8832 * those can be used to dim the background instead. */
8833 if (cell->attr.dim && !(fore & VTE_RGB_COLOR)) {
8834 fore |= VTE_DIM_COLOR;
8835 }
8836
8837 /* Reverse cell? */
8838 if (cell->attr.reverse) {
8839 swap (&fore, &back);
8840 }
8841
8842 /* Selection: use hightlight back/fore, or inverse */
8843 if (selected) {
8844 /* XXX what if hightlight back is same color as current back? */
8845 gboolean do_swap = TRUE;
8846 if (_vte_terminal_get_color(terminal, VTE_HIGHLIGHT_BG) != NULL) {
8847 back = VTE_HIGHLIGHT_BG;
8848 do_swap = FALSE;
8849 }
8850 if (_vte_terminal_get_color(terminal, VTE_HIGHLIGHT_FG) != NULL) {
8851 fore = VTE_HIGHLIGHT_FG;
8852 do_swap = FALSE;
8853 }
8854 if (do_swap)
8855 swap (&fore, &back);
8856 }
8857
8858 /* Cursor: use cursor back, or inverse */
8859 if (cursor) {
8860 /* XXX what if cursor back is same color as current back? */
8861 if (_vte_terminal_get_color(terminal, VTE_CURSOR_BG) != NULL)
8862 back = VTE_CURSOR_BG;
8863 else
8864 swap (&fore, &back);
8865 }
8866
8867 /* Invisible? */
8868 if (cell && cell->attr.invisible) {
8869 fore = back;
8870 }
8871
8872 *pfore = fore;
8873 *pback = back;
8874 }
8875
8876 static inline void
vte_terminal_determine_colors(VteTerminal * terminal,const VteCell * cell,gboolean highlight,guint * fore,guint * back)8877 vte_terminal_determine_colors (VteTerminal *terminal,
8878 const VteCell *cell,
8879 gboolean highlight,
8880 guint *fore, guint *back)
8881 {
8882 vte_terminal_determine_colors_internal (terminal, cell,
8883 highlight, FALSE,
8884 fore, back);
8885 }
8886
8887 static inline void
vte_terminal_determine_cursor_colors(VteTerminal * terminal,const VteCell * cell,gboolean highlight,guint * fore,guint * back)8888 vte_terminal_determine_cursor_colors (VteTerminal *terminal,
8889 const VteCell *cell,
8890 gboolean highlight,
8891 guint *fore, guint *back)
8892 {
8893 vte_terminal_determine_colors_internal (terminal, cell,
8894 highlight, TRUE,
8895 fore, back);
8896 }
8897
8898 static void
vte_terminal_fill_rectangle(VteTerminal * terminal,const PangoColor * color,gint x,gint y,gint width,gint height)8899 vte_terminal_fill_rectangle(VteTerminal *terminal,
8900 const PangoColor *color,
8901 gint x,
8902 gint y,
8903 gint width,
8904 gint height)
8905 {
8906 _vte_draw_fill_rectangle(terminal->pvt->draw,
8907 x + terminal->pvt->padding.left,
8908 y + terminal->pvt->padding.top,
8909 width, height,
8910 color, VTE_DRAW_OPAQUE);
8911 }
8912
8913 static void
vte_terminal_draw_line(VteTerminal * terminal,const PangoColor * color,gint x,gint y,gint xp,gint yp)8914 vte_terminal_draw_line(VteTerminal *terminal,
8915 const PangoColor *color,
8916 gint x,
8917 gint y,
8918 gint xp,
8919 gint yp)
8920 {
8921 vte_terminal_fill_rectangle(terminal, color,
8922 x, y,
8923 MAX(VTE_LINE_WIDTH, xp - x + 1), MAX(VTE_LINE_WIDTH, yp - y + 1));
8924 }
8925
8926 static void
vte_terminal_draw_rectangle(VteTerminal * terminal,const PangoColor * color,gint x,gint y,gint width,gint height)8927 vte_terminal_draw_rectangle(VteTerminal *terminal,
8928 const PangoColor *color,
8929 gint x,
8930 gint y,
8931 gint width,
8932 gint height)
8933 {
8934 _vte_draw_draw_rectangle(terminal->pvt->draw,
8935 x + terminal->pvt->padding.left,
8936 y + terminal->pvt->padding.top,
8937 width, height,
8938 color, VTE_DRAW_OPAQUE);
8939 }
8940
8941 /* Draw a string of characters with similar attributes. */
8942 static void
vte_terminal_draw_cells(VteTerminal * terminal,struct _vte_draw_text_request * items,gssize n,guint fore,guint back,gboolean clear,gboolean draw_default_bg,gboolean bold,gboolean italic,gboolean underline,gboolean strikethrough,gboolean hilite,gboolean boxed,gint column_width,gint row_height)8943 vte_terminal_draw_cells(VteTerminal *terminal,
8944 struct _vte_draw_text_request *items, gssize n,
8945 guint fore, guint back, gboolean clear,
8946 gboolean draw_default_bg,
8947 gboolean bold, gboolean italic, gboolean underline,
8948 gboolean strikethrough, gboolean hilite, gboolean boxed,
8949 gint column_width, gint row_height)
8950 {
8951 int i, x, y;
8952 gint columns = 0;
8953 PangoColor fg, bg;
8954
8955 g_assert(n > 0);
8956 _VTE_DEBUG_IF(VTE_DEBUG_CELLS) {
8957 GString *str = g_string_new (NULL);
8958 gchar *tmp;
8959 for (i = 0; i < n; i++) {
8960 g_string_append_unichar (str, items[i].c);
8961 }
8962 tmp = g_string_free (str, FALSE);
8963 g_printerr ("draw_cells('%s', fore=%d, back=%d, bold=%d,"
8964 " ul=%d, strike=%d, hilite=%d, boxed=%d)\n",
8965 tmp, fore, back, bold,
8966 underline, strikethrough, hilite, boxed);
8967 g_free (tmp);
8968 }
8969
8970 bold = bold && terminal->pvt->allow_bold;
8971 vte_terminal_get_rgb_from_index(terminal, fore, &fg);
8972 vte_terminal_get_rgb_from_index(terminal, back, &bg);
8973
8974 i = 0;
8975 do {
8976 columns = 0;
8977 x = items[i].x;
8978 y = items[i].y;
8979 for (; i < n && items[i].y == y; i++) {
8980 /* Adjust for the border. */
8981 items[i].x += terminal->pvt->padding.left;
8982 items[i].y += terminal->pvt->padding.top;
8983 columns += items[i].columns;
8984 }
8985 if (clear && (draw_default_bg || back != VTE_DEFAULT_BG)) {
8986 gint bold_offset = _vte_draw_has_bold(terminal->pvt->draw,
8987 VTE_DRAW_BOLD) ? 0 : bold;
8988 _vte_draw_fill_rectangle(terminal->pvt->draw,
8989 x + terminal->pvt->padding.left,
8990 y + terminal->pvt->padding.top,
8991 columns * column_width + bold_offset, row_height,
8992 &bg, VTE_DRAW_OPAQUE);
8993 }
8994 } while (i < n);
8995
8996 _vte_draw_text(terminal->pvt->draw,
8997 items, n,
8998 &fg, VTE_DRAW_OPAQUE,
8999 _vte_draw_get_style(bold, italic));
9000
9001 for (i = 0; i < n; i++) {
9002 /* Deadjust for the border. */
9003 items[i].x -= terminal->pvt->padding.left;
9004 items[i].y -= terminal->pvt->padding.top;
9005 }
9006
9007 /* Draw whatever SFX are required. */
9008 if (underline | strikethrough | hilite | boxed) {
9009 i = 0;
9010 do {
9011 x = items[i].x;
9012 y = items[i].y;
9013 for (columns = 0; i < n && items[i].y == y; i++) {
9014 columns += items[i].columns;
9015 }
9016 if (underline) {
9017 vte_terminal_draw_line(terminal,
9018 &fg,
9019 x,
9020 y + terminal->pvt->underline_position,
9021 x + (columns * column_width) - 1,
9022 y + terminal->pvt->underline_position + terminal->pvt->line_thickness - 1);
9023 }
9024 if (strikethrough) {
9025 vte_terminal_draw_line(terminal,
9026 &fg,
9027 x,
9028 y + terminal->pvt->strikethrough_position,
9029 x + (columns * column_width) - 1,
9030 y + terminal->pvt->strikethrough_position + terminal->pvt->line_thickness - 1);
9031 }
9032 if (hilite) {
9033 vte_terminal_draw_line(terminal,
9034 &fg,
9035 x,
9036 y + row_height - 1,
9037 x + (columns * column_width) - 1,
9038 y + row_height - 1);
9039 }
9040 if (boxed) {
9041 vte_terminal_draw_rectangle(terminal,
9042 &fg,
9043 x, y,
9044 MAX(0, (columns * column_width)),
9045 MAX(0, row_height));
9046 }
9047 }while (i < n);
9048 }
9049 }
9050
9051 /* FIXME: we don't have a way to tell GTK+ what the default text attributes
9052 * should be, so for now at least it's assuming white-on-black is the norm and
9053 * is using "black-on-white" to signify "inverse". Pick up on that state and
9054 * fix things. Do this here, so that if we suddenly get red-on-black, we'll do
9055 * the right thing. */
9056 static void
_vte_terminal_fudge_pango_colors(VteTerminal * terminal,GSList * attributes,VteCell * cells,gssize n)9057 _vte_terminal_fudge_pango_colors(VteTerminal *terminal, GSList *attributes,
9058 VteCell *cells, gssize n)
9059 {
9060 int i, sumlen = 0;
9061 struct _fudge_cell_props{
9062 gboolean saw_fg, saw_bg;
9063 PangoColor fg, bg;
9064 guint index;
9065 }*props = g_newa (struct _fudge_cell_props, n);
9066
9067 for (i = 0; i < n; i++) {
9068 gchar ubuf[7];
9069 gint len = g_unichar_to_utf8 (cells[i].c, ubuf);
9070 props[i].index = sumlen;
9071 props[i].saw_fg = props[i].saw_bg = FALSE;
9072 sumlen += len;
9073 }
9074
9075 while (attributes != NULL) {
9076 PangoAttribute *attr = attributes->data;
9077 PangoAttrColor *color;
9078 switch (attr->klass->type) {
9079 case PANGO_ATTR_FOREGROUND:
9080 for (i = 0; i < n; i++) {
9081 if (props[i].index < attr->start_index) {
9082 continue;
9083 }
9084 if (props[i].index >= attr->end_index) {
9085 break;
9086 }
9087 props[i].saw_fg = TRUE;
9088 color = (PangoAttrColor*) attr;
9089 props[i].fg = color->color;
9090 }
9091 break;
9092 case PANGO_ATTR_BACKGROUND:
9093 for (i = 0; i < n; i++) {
9094 if (props[i].index < attr->start_index) {
9095 continue;
9096 }
9097 if (props[i].index >= attr->end_index) {
9098 break;
9099 }
9100 props[i].saw_bg = TRUE;
9101 color = (PangoAttrColor*) attr;
9102 props[i].bg = color->color;
9103 }
9104 break;
9105 default:
9106 break;
9107 }
9108 attributes = g_slist_next(attributes);
9109 }
9110
9111 for (i = 0; i < n; i++) {
9112 if (props[i].saw_fg && props[i].saw_bg &&
9113 (props[i].fg.red == 0xffff) &&
9114 (props[i].fg.green == 0xffff) &&
9115 (props[i].fg.blue == 0xffff) &&
9116 (props[i].bg.red == 0) &&
9117 (props[i].bg.green == 0) &&
9118 (props[i].bg.blue == 0)) {
9119 cells[i].attr.fore = terminal->pvt->color_defaults.attr.fore;
9120 cells[i].attr.back = terminal->pvt->color_defaults.attr.back;
9121 cells[i].attr.reverse = TRUE;
9122 }
9123 }
9124 }
9125
9126 /* Apply the attribute given in the PangoAttribute to the list of cells. */
9127 static void
_vte_terminal_apply_pango_attr(VteTerminal * terminal,PangoAttribute * attr,VteCell * cells,guint n_cells)9128 _vte_terminal_apply_pango_attr(VteTerminal *terminal, PangoAttribute *attr,
9129 VteCell *cells, guint n_cells)
9130 {
9131 guint i, ival;
9132 PangoAttrInt *attrint;
9133 PangoAttrColor *attrcolor;
9134
9135 switch (attr->klass->type) {
9136 case PANGO_ATTR_FOREGROUND:
9137 case PANGO_ATTR_BACKGROUND:
9138 attrcolor = (PangoAttrColor*) attr;
9139 ival = VTE_RGB_COLOR |
9140 ((attrcolor->color.red & 0xFF00) << 8) |
9141 ((attrcolor->color.green & 0xFF00)) |
9142 ((attrcolor->color.blue & 0xFF00) >> 8);
9143 for (i = attr->start_index;
9144 i < attr->end_index && i < n_cells;
9145 i++) {
9146 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
9147 cells[i].attr.fore = ival;
9148 }
9149 if (attr->klass->type == PANGO_ATTR_BACKGROUND) {
9150 cells[i].attr.back = ival;
9151 }
9152 }
9153 break;
9154 case PANGO_ATTR_STRIKETHROUGH:
9155 attrint = (PangoAttrInt*) attr;
9156 ival = attrint->value;
9157 for (i = attr->start_index;
9158 (i < attr->end_index) && (i < n_cells);
9159 i++) {
9160 cells[i].attr.strikethrough = (ival != FALSE);
9161 }
9162 break;
9163 case PANGO_ATTR_UNDERLINE:
9164 attrint = (PangoAttrInt*) attr;
9165 ival = attrint->value;
9166 for (i = attr->start_index;
9167 (i < attr->end_index) && (i < n_cells);
9168 i++) {
9169 cells[i].attr.underline = (ival != PANGO_UNDERLINE_NONE);
9170 }
9171 break;
9172 case PANGO_ATTR_WEIGHT:
9173 attrint = (PangoAttrInt*) attr;
9174 ival = attrint->value;
9175 for (i = attr->start_index;
9176 (i < attr->end_index) && (i < n_cells);
9177 i++) {
9178 cells[i].attr.bold = (ival >= PANGO_WEIGHT_BOLD);
9179 }
9180 break;
9181 default:
9182 break;
9183 }
9184 }
9185
9186 /* Convert a PangoAttrList and a location in that list to settings in a
9187 * charcell structure. The cells array is assumed to contain enough items
9188 * so that all ranges in the attribute list can be mapped into the array, which
9189 * typically means that the cell array should have the same length as the
9190 * string (byte-wise) which the attributes describe. */
9191 static void
_vte_terminal_pango_attribute_destroy(gpointer attr,gpointer data)9192 _vte_terminal_pango_attribute_destroy(gpointer attr, gpointer data)
9193 {
9194 pango_attribute_destroy(attr);
9195 }
9196 static void
_vte_terminal_translate_pango_cells(VteTerminal * terminal,PangoAttrList * attrs,VteCell * cells,guint n_cells)9197 _vte_terminal_translate_pango_cells(VteTerminal *terminal, PangoAttrList *attrs,
9198 VteCell *cells, guint n_cells)
9199 {
9200 PangoAttribute *attr;
9201 PangoAttrIterator *attriter;
9202 GSList *list, *listiter;
9203 guint i;
9204
9205 for (i = 0; i < n_cells; i++) {
9206 cells[i] = terminal->pvt->fill_defaults;
9207 }
9208
9209 attriter = pango_attr_list_get_iterator(attrs);
9210 if (attriter != NULL) {
9211 do {
9212 list = pango_attr_iterator_get_attrs(attriter);
9213 if (list != NULL) {
9214 for (listiter = list;
9215 listiter != NULL;
9216 listiter = g_slist_next(listiter)) {
9217 attr = listiter->data;
9218 _vte_terminal_apply_pango_attr(terminal,
9219 attr,
9220 cells,
9221 n_cells);
9222 }
9223 attr = list->data;
9224 _vte_terminal_fudge_pango_colors(terminal,
9225 list,
9226 cells +
9227 attr->start_index,
9228 attr->end_index -
9229 attr->start_index);
9230 g_slist_foreach(list,
9231 _vte_terminal_pango_attribute_destroy,
9232 NULL);
9233 g_slist_free(list);
9234 }
9235 } while (pango_attr_iterator_next(attriter) == TRUE);
9236 pango_attr_iterator_destroy(attriter);
9237 }
9238 }
9239
9240 /* Draw the listed items using the given attributes. Tricky because the
9241 * attribute string is indexed by byte in the UTF-8 representation of the string
9242 * of characters. Because we draw a character at a time, this is slower. */
9243 static void
vte_terminal_draw_cells_with_attributes(VteTerminal * terminal,struct _vte_draw_text_request * items,gssize n,PangoAttrList * attrs,gboolean draw_default_bg,gint column_width,gint height)9244 vte_terminal_draw_cells_with_attributes(VteTerminal *terminal,
9245 struct _vte_draw_text_request *items,
9246 gssize n,
9247 PangoAttrList *attrs,
9248 gboolean draw_default_bg,
9249 gint column_width, gint height)
9250 {
9251 int i, j, cell_count;
9252 VteCell *cells;
9253 char scratch_buf[VTE_UTF8_BPC];
9254 guint fore, back;
9255
9256 /* Note: since this function is only called with the pre-edit text,
9257 * all the items contain gunichar only, not vteunistr. */
9258
9259 for (i = 0, cell_count = 0; i < n; i++) {
9260 cell_count += g_unichar_to_utf8(items[i].c, scratch_buf);
9261 }
9262 cells = g_new(VteCell, cell_count);
9263 _vte_terminal_translate_pango_cells(terminal, attrs, cells, cell_count);
9264 for (i = 0, j = 0; i < n; i++) {
9265 vte_terminal_determine_colors(terminal, &cells[j], FALSE, &fore, &back);
9266 vte_terminal_draw_cells(terminal, items + i, 1,
9267 fore,
9268 back,
9269 TRUE, draw_default_bg,
9270 cells[j].attr.bold,
9271 cells[j].attr.italic,
9272 cells[j].attr.underline,
9273 cells[j].attr.strikethrough,
9274 FALSE, FALSE, column_width, height);
9275 j += g_unichar_to_utf8(items[i].c, scratch_buf);
9276 }
9277 g_free(cells);
9278 }
9279
9280
9281 /* Paint the contents of a given row at the given location. Take advantage
9282 * of multiple-draw APIs by finding runs of characters with identical
9283 * attributes and bundling them together. */
9284 static void
vte_terminal_draw_rows(VteTerminal * terminal,VteScreen * screen,gint start_row,gint row_count,gint start_column,gint column_count,gint start_x,gint start_y,gint column_width,gint row_height)9285 vte_terminal_draw_rows(VteTerminal *terminal,
9286 VteScreen *screen,
9287 gint start_row, gint row_count,
9288 gint start_column, gint column_count,
9289 gint start_x, gint start_y,
9290 gint column_width, gint row_height)
9291 {
9292 struct _vte_draw_text_request items[4*VTE_DRAW_MAX_LENGTH];
9293 gint i, j, row, rows, x, y, end_column;
9294 guint fore, nfore, back, nback;
9295 gboolean underline, nunderline, bold, nbold, italic, nitalic, hilite, nhilite,
9296 selected, nselected, strikethrough, nstrikethrough;
9297 guint item_count;
9298 const VteCell *cell;
9299 const VteRowData *row_data;
9300
9301 /* adjust for the absolute start of row */
9302 start_x -= start_column * column_width;
9303 end_column = start_column + column_count;
9304
9305 /* clear the background */
9306 x = start_x + terminal->pvt->padding.left;
9307 y = start_y + terminal->pvt->padding.top;
9308 row = start_row;
9309 rows = row_count;
9310 do {
9311 row_data = _vte_terminal_find_row_data(terminal, row);
9312 /* Back up in case this is a multicolumn character,
9313 * making the drawing area a little wider. */
9314 i = start_column;
9315 if (row_data != NULL) {
9316 cell = _vte_row_data_get (row_data, i);
9317 if (cell != NULL) {
9318 while (cell->attr.fragment && i > 0) {
9319 cell = _vte_row_data_get (row_data, --i);
9320 }
9321 }
9322 /* Walk the line. */
9323 do {
9324 /* Get the character cell's contents. */
9325 cell = _vte_row_data_get (row_data, i);
9326 /* Find the colors for this cell. */
9327 selected = vte_cell_is_selected(terminal, i, row, NULL);
9328 vte_terminal_determine_colors(terminal, cell, selected, &fore, &back);
9329
9330 bold = cell && cell->attr.bold;
9331 j = i + (cell ? cell->attr.columns : 1);
9332
9333 while (j < end_column){
9334 /* Retrieve the cell. */
9335 cell = _vte_row_data_get (row_data, j);
9336 /* Don't render fragments of multicolumn characters
9337 * which have the same attributes as the initial
9338 * portions. */
9339 if (cell != NULL && cell->attr.fragment) {
9340 j++;
9341 continue;
9342 }
9343 /* Resolve attributes to colors where possible and
9344 * compare visual attributes to the first character
9345 * in this chunk. */
9346 selected = vte_cell_is_selected(terminal, j, row, NULL);
9347 vte_terminal_determine_colors(terminal, cell, selected, &nfore, &nback);
9348 if (nback != back) {
9349 break;
9350 }
9351 bold = cell && cell->attr.bold;
9352 j += cell ? cell->attr.columns : 1;
9353 }
9354 if (back != VTE_DEFAULT_BG) {
9355 PangoColor bg;
9356 gint bold_offset = _vte_draw_has_bold(terminal->pvt->draw,
9357 VTE_DRAW_BOLD) ? 0 : bold;
9358 vte_terminal_get_rgb_from_index(terminal, back, &bg);
9359 _vte_draw_fill_rectangle (
9360 terminal->pvt->draw,
9361 x + i * column_width,
9362 y,
9363 (j - i) * column_width + bold_offset,
9364 row_height,
9365 &bg, VTE_DRAW_OPAQUE);
9366 }
9367 /* We'll need to continue at the first cell which didn't
9368 * match the first one in this set. */
9369 i = j;
9370 } while (i < end_column);
9371 } else {
9372 do {
9373 selected = vte_cell_is_selected(terminal, i, row, NULL);
9374 j = i + 1;
9375 while (j < end_column){
9376 nselected = vte_cell_is_selected(terminal, j, row, NULL);
9377 if (nselected != selected) {
9378 break;
9379 }
9380 j++;
9381 }
9382 vte_terminal_determine_colors(terminal, NULL, selected, &fore, &back);
9383 if (back != VTE_DEFAULT_BG) {
9384 PangoColor bg;
9385 vte_terminal_get_rgb_from_index(terminal, back, &bg);
9386 _vte_draw_fill_rectangle (terminal->pvt->draw,
9387 x + i *column_width,
9388 y,
9389 (j - i) * column_width,
9390 row_height,
9391 &bg, VTE_DRAW_OPAQUE);
9392 }
9393 i = j;
9394 } while (i < end_column);
9395 }
9396 row++;
9397 y += row_height;
9398 } while (--rows);
9399
9400
9401 /* render the text */
9402 y = start_y;
9403 row = start_row;
9404 rows = row_count;
9405 item_count = 1;
9406 do {
9407 row_data = _vte_terminal_find_row_data(terminal, row);
9408 if (row_data == NULL) {
9409 goto fg_skip_row;
9410 }
9411 /* Back up in case this is a multicolumn character,
9412 * making the drawing area a little wider. */
9413 i = start_column;
9414 cell = _vte_row_data_get (row_data, i);
9415 if (cell == NULL) {
9416 goto fg_skip_row;
9417 }
9418 while (cell->attr.fragment && i > 0)
9419 cell = _vte_row_data_get (row_data, --i);
9420
9421 /* Walk the line. */
9422 do {
9423 /* Get the character cell's contents. */
9424 cell = _vte_row_data_get (row_data, i);
9425 if (cell == NULL) {
9426 goto fg_skip_row;
9427 }
9428 while (cell->c == 0 || cell->attr.invisible ||
9429 (cell->c == ' ' &&
9430 !cell->attr.underline &&
9431 !cell->attr.strikethrough) ||
9432 cell->attr.fragment) {
9433 if (++i >= end_column) {
9434 goto fg_skip_row;
9435 }
9436 cell = _vte_row_data_get (row_data, i);
9437 if (cell == NULL) {
9438 goto fg_skip_row;
9439 }
9440 }
9441 /* Find the colors for this cell. */
9442 selected = vte_cell_is_selected(terminal, i, row, NULL);
9443 vte_terminal_determine_colors(terminal, cell, selected, &fore, &back);
9444 underline = cell->attr.underline;
9445 strikethrough = cell->attr.strikethrough;
9446 bold = cell->attr.bold;
9447 italic = cell->attr.italic;
9448 if (terminal->pvt->show_match) {
9449 hilite = vte_cell_is_between(i, row,
9450 terminal->pvt->match_start.col,
9451 terminal->pvt->match_start.row,
9452 terminal->pvt->match_end.col,
9453 terminal->pvt->match_end.row,
9454 TRUE);
9455 } else {
9456 hilite = FALSE;
9457 }
9458
9459 items[0].c = cell->c;
9460 items[0].columns = cell->attr.columns;
9461 items[0].x = start_x + i * column_width;
9462 items[0].y = y;
9463 j = i + items[0].columns;
9464
9465 /* Now find out how many cells have the same attributes. */
9466 do {
9467 while (j < end_column &&
9468 item_count < G_N_ELEMENTS(items)) {
9469 /* Retrieve the cell. */
9470 cell = _vte_row_data_get (row_data, j);
9471 if (cell == NULL) {
9472 goto fg_next_row;
9473 }
9474 /* Don't render blank cells or fragments of multicolumn characters
9475 * which have the same attributes as the initial
9476 * portions. Don't render invisible cells */
9477 if (cell->attr.fragment || cell->attr.invisible) {
9478 j++;
9479 continue;
9480 }
9481 if (cell->c == 0){
9482 /* only break the run if we
9483 * are drawing attributes
9484 */
9485 if (underline || strikethrough || hilite) {
9486 break;
9487 } else {
9488 j++;
9489 continue;
9490 }
9491 }
9492 /* Resolve attributes to colors where possible and
9493 * compare visual attributes to the first character
9494 * in this chunk. */
9495 selected = vte_cell_is_selected(terminal, j, row, NULL);
9496 vte_terminal_determine_colors(terminal, cell, selected, &nfore, &nback);
9497 if (nfore != fore) {
9498 break;
9499 }
9500 nbold = cell->attr.bold;
9501 if (nbold != bold) {
9502 break;
9503 }
9504 nitalic = cell->attr.italic;
9505 if (nitalic != italic) {
9506 break;
9507 }
9508 /* Break up underlined/not-underlined text. */
9509 nunderline = cell->attr.underline;
9510 if (nunderline != underline) {
9511 break;
9512 }
9513 nstrikethrough = cell->attr.strikethrough;
9514 if (nstrikethrough != strikethrough) {
9515 break;
9516 }
9517 /* Break up matched/not-matched text. */
9518 nhilite = FALSE;
9519 if (terminal->pvt->show_match) {
9520 nhilite = vte_cell_is_between(j, row,
9521 terminal->pvt->match_start.col,
9522 terminal->pvt->match_start.row,
9523 terminal->pvt->match_end.col,
9524 terminal->pvt->match_end.row,
9525 TRUE);
9526 }
9527 if (nhilite != hilite) {
9528 break;
9529 }
9530 /* Add this cell to the draw list. */
9531 items[item_count].c = cell->c;
9532 items[item_count].columns = cell->attr.columns;
9533 items[item_count].x = start_x + j * column_width;
9534 items[item_count].y = y;
9535 j += items[item_count].columns;
9536 item_count++;
9537 }
9538 /* have we encountered a state change? */
9539 if (j < end_column) {
9540 break;
9541 }
9542 fg_next_row:
9543 /* is this the last column, on the last row? */
9544 do {
9545 do {
9546 if (!--rows) {
9547 goto fg_draw;
9548 }
9549
9550 /* restart on the next row */
9551 row++;
9552 y += row_height;
9553 row_data = _vte_terminal_find_row_data(terminal, row);
9554 } while (row_data == NULL);
9555
9556 /* Back up in case this is a
9557 * multicolumn character, making the drawing
9558 * area a little wider. */
9559 j = start_column;
9560 cell = _vte_row_data_get (row_data, j);
9561 } while (cell == NULL);
9562 while (cell->attr.fragment && j > 0) {
9563 cell = _vte_row_data_get (row_data, --j);
9564 }
9565 } while (TRUE);
9566 fg_draw:
9567 /* Draw the cells. */
9568 vte_terminal_draw_cells(terminal,
9569 items,
9570 item_count,
9571 fore, back, FALSE, FALSE,
9572 bold, italic, underline,
9573 strikethrough, hilite, FALSE,
9574 column_width, row_height);
9575 item_count = 1;
9576 /* We'll need to continue at the first cell which didn't
9577 * match the first one in this set. */
9578 i = j;
9579 if (!rows) {
9580 goto fg_out;
9581 }
9582 } while (i < end_column);
9583 fg_skip_row:
9584 row++;
9585 y += row_height;
9586 } while (--rows);
9587 fg_out:
9588 return;
9589 }
9590
9591 static void
vte_terminal_expand_region(VteTerminal * terminal,cairo_region_t * region,const GdkRectangle * area)9592 vte_terminal_expand_region (VteTerminal *terminal, cairo_region_t *region, const GdkRectangle *area)
9593 {
9594 int width, height;
9595 int row, col, row_stop, col_stop;
9596 cairo_rectangle_int_t rect;
9597
9598 width = terminal->pvt->char_width;
9599 height = terminal->pvt->char_height;
9600
9601 /* increase the paint by one pixel on all sides to force the
9602 * inclusion of neighbouring cells */
9603 row = MAX(0, (area->y - terminal->pvt->padding.top - 1) / height);
9604 row_stop = MIN(howmany(area->height + area->y - terminal->pvt->padding.top + 1, height),
9605 terminal->pvt->row_count);
9606 if (row_stop <= row) {
9607 return;
9608 }
9609 col = MAX(0, (area->x - terminal->pvt->padding.left - 1) / width);
9610 col_stop = MIN(howmany(area->width + area->x - terminal->pvt->padding.left + 1, width),
9611 terminal->pvt->column_count);
9612 if (col_stop <= col) {
9613 return;
9614 }
9615
9616 rect.x = col*width + terminal->pvt->padding.left;
9617 rect.width = (col_stop - col) * width;
9618
9619 rect.y = row*height + terminal->pvt->padding.top;
9620 rect.height = (row_stop - row)*height;
9621
9622 /* the rect must be cell aligned to avoid overlapping XY bands */
9623 cairo_region_union_rectangle(region, &rect);
9624
9625 _vte_debug_print (VTE_DEBUG_UPDATES,
9626 "vte_terminal_expand_region"
9627 " (%d,%d)x(%d,%d) pixels,"
9628 " (%d,%d)x(%d,%d) cells"
9629 " [(%d,%d)x(%d,%d) pixels]\n",
9630 area->x, area->y, area->width, area->height,
9631 col, row, col_stop - col, row_stop - row,
9632 rect.x, rect.y, rect.width, rect.height);
9633 }
9634
9635 static void
vte_terminal_paint_area(VteTerminal * terminal,const GdkRectangle * area)9636 vte_terminal_paint_area (VteTerminal *terminal, const GdkRectangle *area)
9637 {
9638 VteScreen *screen;
9639 int width, height, delta;
9640 int row, col, row_stop, col_stop;
9641
9642 screen = terminal->pvt->screen;
9643
9644 width = terminal->pvt->char_width;
9645 height = terminal->pvt->char_height;
9646
9647 row = MAX(0, (area->y - terminal->pvt->padding.top) / height);
9648 row_stop = MIN((area->height + area->y - terminal->pvt->padding.top) / height,
9649 terminal->pvt->row_count);
9650 if (row_stop <= row) {
9651 return;
9652 }
9653 col = MAX(0, (area->x - terminal->pvt->padding.left) / width);
9654 col_stop = MIN((area->width + area->x - terminal->pvt->padding.left) / width,
9655 terminal->pvt->column_count);
9656 if (col_stop <= col) {
9657 return;
9658 }
9659 _vte_debug_print (VTE_DEBUG_UPDATES,
9660 "vte_terminal_paint_area"
9661 " (%d,%d)x(%d,%d) pixels,"
9662 " (%d,%d)x(%d,%d) cells"
9663 " [(%d,%d)x(%d,%d) pixels]\n",
9664 area->x, area->y, area->width, area->height,
9665 col, row, col_stop - col, row_stop - row,
9666 col * width + terminal->pvt->padding.left,
9667 row * height + terminal->pvt->padding.top,
9668 (col_stop - col) * width,
9669 (row_stop - row) * height);
9670
9671 /* Now we're ready to draw the text. Iterate over the rows we
9672 * need to draw. */
9673 delta = screen->scroll_delta;
9674 vte_terminal_draw_rows(terminal,
9675 screen,
9676 row + delta, row_stop - row,
9677 col, col_stop - col,
9678 col * width,
9679 row * height,
9680 width,
9681 height);
9682 }
9683
9684 static void
vte_terminal_paint_cursor(VteTerminal * terminal)9685 vte_terminal_paint_cursor(VteTerminal *terminal)
9686 {
9687 VteScreen *screen;
9688 const VteCell *cell;
9689 struct _vte_draw_text_request item;
9690 int row, drow, col;
9691 long width, height, delta, cursor_width;
9692 guint fore, back;
9693 PangoColor bg;
9694 int x, y;
9695 gboolean blink, selected, focus;
9696
9697 if (!terminal->pvt->cursor_visible)
9698 return;
9699
9700 screen = terminal->pvt->screen;
9701 delta = screen->scroll_delta;
9702 col = terminal->pvt->cursor.col;
9703 drow = terminal->pvt->cursor.row;
9704 row = drow - delta;
9705 width = terminal->pvt->char_width;
9706 height = terminal->pvt->char_height;
9707
9708 if ((CLAMP(col, 0, terminal->pvt->column_count - 1) != col) ||
9709 (CLAMP(row, 0, terminal->pvt->row_count - 1) != row))
9710 return;
9711
9712 focus = terminal->pvt->has_focus;
9713 blink = terminal->pvt->cursor_blink_state;
9714
9715 if (focus && !blink)
9716 return;
9717
9718 /* Find the first cell of the character "under" the cursor.
9719 * This is for CJK. For TAB, paint the cursor where it really is. */
9720 cell = vte_terminal_find_charcell(terminal, col, drow);
9721 while (cell != NULL && cell->attr.fragment && cell->c != '\t' && col > 0) {
9722 col--;
9723 cell = vte_terminal_find_charcell(terminal, col, drow);
9724 }
9725
9726 /* Draw the cursor. */
9727 item.c = (cell && cell->c) ? cell->c : ' ';
9728 item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns : 1;
9729 item.x = col * width;
9730 item.y = row * height;
9731 cursor_width = item.columns * width;
9732 if (cell && cell->c != 0) {
9733 guint style;
9734 gint cw;
9735 style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
9736 cw = _vte_draw_get_char_width (terminal->pvt->draw, cell->c,
9737 cell->attr.columns, style);
9738 cursor_width = MAX(cursor_width, cw);
9739 }
9740
9741 selected = vte_cell_is_selected(terminal, col, drow, NULL);
9742
9743 vte_terminal_determine_cursor_colors(terminal, cell, selected, &fore, &back);
9744 vte_terminal_get_rgb_from_index(terminal, back, &bg);
9745
9746 x = item.x;
9747 y = item.y;
9748
9749 switch (_vte_terminal_decscusr_cursor_shape(terminal)) {
9750
9751 case VTE_CURSOR_SHAPE_IBEAM: {
9752 int stem_width;
9753
9754 stem_width = (int) (((float) height) * terminal->pvt->cursor_aspect_ratio + 0.5);
9755 stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, cursor_width);
9756
9757 vte_terminal_fill_rectangle(terminal, &bg,
9758 x, y, stem_width, height);
9759 break;
9760 }
9761
9762 case VTE_CURSOR_SHAPE_UNDERLINE: {
9763 int line_height;
9764
9765 /* use height (not width) so underline and ibeam will
9766 * be equally visible */
9767 line_height = (int) (((float) height) * terminal->pvt->cursor_aspect_ratio + 0.5);
9768 line_height = CLAMP (line_height, VTE_LINE_WIDTH, height);
9769
9770 vte_terminal_fill_rectangle(terminal, &bg,
9771 x, y + height - line_height,
9772 cursor_width, line_height);
9773 break;
9774 }
9775
9776 case VTE_CURSOR_SHAPE_BLOCK:
9777
9778 if (focus) {
9779 gboolean hilite = FALSE;
9780
9781 /* just reverse the character under the cursor */
9782 vte_terminal_fill_rectangle (terminal,
9783 &bg,
9784 x, y,
9785 cursor_width, height);
9786
9787 if (cell && terminal->pvt->show_match) {
9788 hilite = vte_cell_is_between(col, row,
9789 terminal->pvt->match_start.col,
9790 terminal->pvt->match_start.row,
9791 terminal->pvt->match_end.col,
9792 terminal->pvt->match_end.row,
9793 TRUE);
9794 }
9795 if (cell && cell->c != 0 && cell->c != ' ') {
9796 vte_terminal_draw_cells(terminal,
9797 &item, 1,
9798 fore, back, TRUE, FALSE,
9799 cell->attr.bold,
9800 cell->attr.italic,
9801 cell->attr.underline,
9802 cell->attr.strikethrough,
9803 hilite,
9804 FALSE,
9805 width,
9806 height);
9807 }
9808
9809 } else {
9810 /* draw a box around the character */
9811 vte_terminal_draw_rectangle (terminal,
9812 &bg,
9813 x - VTE_LINE_WIDTH,
9814 y - VTE_LINE_WIDTH,
9815 cursor_width + 2*VTE_LINE_WIDTH,
9816 height + 2*VTE_LINE_WIDTH);
9817 }
9818
9819 break;
9820 }
9821 }
9822
9823 static void
vte_terminal_paint_im_preedit_string(VteTerminal * terminal)9824 vte_terminal_paint_im_preedit_string(VteTerminal *terminal)
9825 {
9826 VteScreen *screen;
9827 int row, col, columns;
9828 long width, height, delta;
9829 int i, len;
9830 guint fore, back;
9831
9832 if (!terminal->pvt->im_preedit)
9833 return;
9834
9835 /* Get going. */
9836 screen = terminal->pvt->screen;
9837
9838 /* Keep local copies of rendering information. */
9839 width = terminal->pvt->char_width;
9840 height = terminal->pvt->char_height;
9841 delta = screen->scroll_delta;
9842
9843 row = terminal->pvt->cursor.row - delta;
9844
9845 /* Find out how many columns the pre-edit string takes up. */
9846 columns = vte_terminal_preedit_width(terminal, FALSE);
9847 len = vte_terminal_preedit_length(terminal, FALSE);
9848
9849 /* If the pre-edit string won't fit on the screen if we start
9850 * drawing it at the cursor's position, move it left. */
9851 col = terminal->pvt->cursor.col;
9852 if (col + columns > terminal->pvt->column_count) {
9853 col = MAX(0, terminal->pvt->column_count - columns);
9854 }
9855
9856 /* Draw the preedit string, boxed. */
9857 if (len > 0) {
9858 struct _vte_draw_text_request *items;
9859 const char *preedit = terminal->pvt->im_preedit;
9860 int preedit_cursor;
9861
9862 items = g_new(struct _vte_draw_text_request, len);
9863 for (i = columns = 0; i < len; i++) {
9864 items[i].c = g_utf8_get_char(preedit);
9865 items[i].columns = _vte_unichar_width(items[i].c,
9866 terminal->pvt->utf8_ambiguous_width);
9867 items[i].x = (col + columns) * width;
9868 items[i].y = row * height;
9869 columns += items[i].columns;
9870 preedit = g_utf8_next_char(preedit);
9871 }
9872 _vte_draw_clear(terminal->pvt->draw,
9873 col * width + terminal->pvt->padding.left,
9874 row * height + terminal->pvt->padding.top,
9875 width * columns,
9876 height);
9877 fore = terminal->pvt->color_defaults.attr.fore;
9878 back = terminal->pvt->color_defaults.attr.back;
9879 vte_terminal_draw_cells_with_attributes(terminal,
9880 items, len,
9881 terminal->pvt->im_preedit_attrs,
9882 TRUE,
9883 width, height);
9884 preedit_cursor = terminal->pvt->im_preedit_cursor;
9885 if (preedit_cursor >= 0 && preedit_cursor < len) {
9886 /* Cursored letter in reverse. */
9887 vte_terminal_draw_cells(terminal,
9888 &items[preedit_cursor], 1,
9889 back, fore, TRUE, TRUE,
9890 FALSE,
9891 FALSE,
9892 FALSE,
9893 FALSE,
9894 FALSE,
9895 TRUE,
9896 width, height);
9897 }
9898 g_free(items);
9899 }
9900 }
9901
9902 static gboolean
vte_terminal_draw(GtkWidget * widget,cairo_t * cr)9903 vte_terminal_draw(GtkWidget *widget,
9904 cairo_t *cr)
9905 {
9906 VteTerminal *terminal = VTE_TERMINAL (widget);
9907 cairo_rectangle_int_t clip_rect;
9908 cairo_region_t *region;
9909 int allocated_width, allocated_height;
9910
9911 if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
9912 return FALSE;
9913
9914 _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_draw()\n");
9915 _vte_debug_print (VTE_DEBUG_WORK, "+");
9916 _vte_debug_print (VTE_DEBUG_UPDATES, "Draw (%d,%d)x(%d,%d)\n",
9917 clip_rect.x, clip_rect.y,
9918 clip_rect.width, clip_rect.height);
9919
9920 region = vte_cairo_get_clip_region (cr);
9921 if (region == NULL)
9922 return FALSE;
9923
9924 allocated_width = gtk_widget_get_allocated_width(widget);
9925 allocated_height = gtk_widget_get_allocated_height(widget);
9926
9927 /* Designate the start of the drawing operation and clear the area. */
9928 _vte_draw_set_cairo(terminal->pvt->draw, cr);
9929
9930 _vte_draw_clear (terminal->pvt->draw, 0, 0,
9931 allocated_width, allocated_height);
9932
9933 /* Calculate the bounding rectangle. */
9934 {
9935 cairo_rectangle_int_t *rectangles;
9936 gint n, n_rectangles;
9937 n_rectangles = cairo_region_num_rectangles (region);
9938 rectangles = g_new (cairo_rectangle_int_t, n_rectangles);
9939 for (n = 0; n < n_rectangles; n++) {
9940 cairo_region_get_rectangle (region, n, &rectangles[n]);
9941 }
9942
9943 /* don't bother to enlarge an invalidate all */
9944 if (!(n_rectangles == 1
9945 && rectangles[0].width == allocated_width
9946 && rectangles[0].height == allocated_height)) {
9947 cairo_region_t *rr = cairo_region_create ();
9948 /* convert pixels into whole cells */
9949 for (n = 0; n < n_rectangles; n++) {
9950 vte_terminal_expand_region (terminal, rr, rectangles + n);
9951 }
9952 g_free (rectangles);
9953
9954 n_rectangles = cairo_region_num_rectangles (rr);
9955 rectangles = g_new (cairo_rectangle_int_t, n_rectangles);
9956 for (n = 0; n < n_rectangles; n++) {
9957 cairo_region_get_rectangle (rr, n, &rectangles[n]);
9958 }
9959 cairo_region_destroy (rr);
9960 }
9961
9962 /* and now paint them */
9963 for (n = 0; n < n_rectangles; n++) {
9964 vte_terminal_paint_area (terminal, rectangles + n);
9965 }
9966 g_free (rectangles);
9967 }
9968
9969 vte_terminal_paint_cursor(terminal);
9970
9971 vte_terminal_paint_im_preedit_string(terminal);
9972
9973 /* Done with various structures. */
9974 _vte_draw_set_cairo(terminal->pvt->draw, NULL);
9975
9976 cairo_region_destroy (region);
9977
9978 terminal->pvt->invalidated_all = FALSE;
9979
9980 return FALSE;
9981 }
9982
9983 /* Handle an expose event by painting the exposed area. */
9984 static cairo_region_t *
vte_cairo_get_clip_region(cairo_t * cr)9985 vte_cairo_get_clip_region (cairo_t *cr)
9986 {
9987 cairo_rectangle_list_t *list;
9988 cairo_region_t *region;
9989 int i;
9990
9991 list = cairo_copy_clip_rectangle_list (cr);
9992 if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) {
9993 cairo_rectangle_int_t clip_rect;
9994
9995 cairo_rectangle_list_destroy (list);
9996
9997 if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
9998 return NULL;
9999 return cairo_region_create_rectangle (&clip_rect);
10000 }
10001
10002
10003 region = cairo_region_create ();
10004 for (i = list->num_rectangles - 1; i >= 0; --i) {
10005 cairo_rectangle_t *rect = &list->rectangles[i];
10006 cairo_rectangle_int_t clip_rect;
10007
10008 clip_rect.x = floor (rect->x);
10009 clip_rect.y = floor (rect->y);
10010 clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x;
10011 clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y;
10012
10013 if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS) {
10014 cairo_region_destroy (region);
10015 region = NULL;
10016 break;
10017 }
10018 }
10019
10020 cairo_rectangle_list_destroy (list);
10021 return region;
10022 }
10023
10024 /* Handle a scroll event. */
10025 static gboolean
vte_terminal_scroll(GtkWidget * widget,GdkEventScroll * event)10026 vte_terminal_scroll(GtkWidget *widget, GdkEventScroll *event)
10027 {
10028 GtkAdjustment *adj;
10029 VteTerminal *terminal;
10030 gdouble delta_x, delta_y;
10031 gdouble v;
10032 gint cnt, i;
10033 int button;
10034
10035 terminal = VTE_TERMINAL(widget);
10036
10037 vte_terminal_read_modifiers (terminal, (GdkEvent*) event);
10038
10039 switch (event->direction) {
10040 case GDK_SCROLL_UP:
10041 terminal->pvt->mouse_smooth_scroll_delta -= 1.;
10042 _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll up\n");
10043 break;
10044 case GDK_SCROLL_DOWN:
10045 terminal->pvt->mouse_smooth_scroll_delta += 1.;
10046 _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll down\n");
10047 break;
10048 #if GTK_CHECK_VERSION (3, 4, 0)
10049 case GDK_SCROLL_SMOOTH:
10050 gdk_event_get_scroll_deltas ((GdkEvent*) event, &delta_x, &delta_y);
10051 terminal->pvt->mouse_smooth_scroll_delta += delta_y;
10052 _vte_debug_print(VTE_DEBUG_EVENTS,
10053 "Smooth scroll by %f, delta now at %f\n",
10054 delta_y, terminal->pvt->mouse_smooth_scroll_delta);
10055 break;
10056 #endif
10057 default:
10058 break;
10059 }
10060
10061 /* If we're running a mouse-aware application, map the scroll event
10062 * to a button press on buttons four and five. */
10063 if (terminal->pvt->mouse_tracking_mode) {
10064 cnt = terminal->pvt->mouse_smooth_scroll_delta;
10065 if (cnt == 0)
10066 return TRUE;
10067 terminal->pvt->mouse_smooth_scroll_delta -= cnt;
10068 _vte_debug_print(VTE_DEBUG_EVENTS,
10069 "Scroll application by %d lines, smooth scroll delta set back to %f\n",
10070 cnt, terminal->pvt->mouse_smooth_scroll_delta);
10071
10072 button = cnt > 0 ? 5 : 4;
10073 if (cnt < 0)
10074 cnt = -cnt;
10075 for (i = 0; i < cnt; i++) {
10076 /* Encode the parameters and send them to the app. */
10077 vte_terminal_send_mouse_button_internal(terminal,
10078 button,
10079 FALSE /* not release */,
10080 event->x,
10081 event->y);
10082 }
10083 return TRUE;
10084 }
10085
10086 adj = terminal->pvt->vadjustment;
10087 v = MAX (1., ceil (gtk_adjustment_get_page_increment (adj) / 10.));
10088 _vte_debug_print(VTE_DEBUG_EVENTS,
10089 "Scroll speed is %d lines per non-smooth scroll unit\n",
10090 (int) v);
10091 cnt = v * terminal->pvt->mouse_smooth_scroll_delta;
10092 if (cnt == 0)
10093 return TRUE;
10094 terminal->pvt->mouse_smooth_scroll_delta -= cnt / v;
10095 _vte_debug_print(VTE_DEBUG_EVENTS,
10096 "Scroll by %d lines, smooth scroll delta set back to %f\n",
10097 cnt, terminal->pvt->mouse_smooth_scroll_delta);
10098
10099 if (terminal->pvt->screen == &terminal->pvt->alternate_screen &&
10100 terminal->pvt->alternate_screen_scroll) {
10101 char *normal;
10102 gssize normal_length;
10103
10104 /* In the alternate screen there is no scrolling,
10105 * so fake a few cursor keystrokes. */
10106
10107 _vte_keymap_map (
10108 cnt > 0 ? GDK_KEY_Down : GDK_KEY_Up,
10109 terminal->pvt->modifiers,
10110 terminal->pvt->cursor_mode == VTE_KEYMODE_APPLICATION,
10111 terminal->pvt->keypad_mode == VTE_KEYMODE_APPLICATION,
10112 &normal,
10113 &normal_length);
10114 if (cnt < 0)
10115 cnt = -cnt;
10116 for (i = 0; i < cnt; i++) {
10117 vte_terminal_feed_child_using_modes (terminal,
10118 normal, normal_length);
10119 }
10120 g_free (normal);
10121 } else {
10122 /* Perform a history scroll. */
10123 cnt += terminal->pvt->screen->scroll_delta;
10124 vte_terminal_queue_adjustment_value_changed_clamped (terminal, cnt);
10125 }
10126
10127 return TRUE;
10128 }
10129
10130 static void
vte_terminal_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)10131 vte_terminal_get_property (GObject *object,
10132 guint prop_id,
10133 GValue *value,
10134 GParamSpec *pspec)
10135 {
10136 VteTerminal *terminal = VTE_TERMINAL (object);
10137 VteTerminalPrivate *pvt = terminal->pvt;
10138
10139 switch (prop_id)
10140 {
10141 case PROP_HADJUSTMENT:
10142 g_value_set_object (value, pvt->hadjustment);
10143 break;
10144 case PROP_VADJUSTMENT:
10145 g_value_set_object (value, terminal->pvt->vadjustment);
10146 break;
10147 case PROP_HSCROLL_POLICY:
10148 g_value_set_enum (value, pvt->hscroll_policy);
10149 break;
10150 case PROP_VSCROLL_POLICY:
10151 g_value_set_enum (value, pvt->vscroll_policy);
10152 break;
10153 case PROP_ALLOW_BOLD:
10154 g_value_set_boolean (value, vte_terminal_get_allow_bold (terminal));
10155 break;
10156 case PROP_AUDIBLE_BELL:
10157 g_value_set_boolean (value, vte_terminal_get_audible_bell (terminal));
10158 break;
10159 case PROP_BACKSPACE_BINDING:
10160 g_value_set_enum (value, pvt->backspace_binding);
10161 break;
10162 case PROP_CJK_AMBIGUOUS_WIDTH:
10163 g_value_set_int (value, vte_terminal_get_cjk_ambiguous_width (terminal));
10164 break;
10165 case PROP_CURSOR_BLINK_MODE:
10166 g_value_set_enum (value, vte_terminal_get_cursor_blink_mode (terminal));
10167 break;
10168 case PROP_CURRENT_DIRECTORY_URI:
10169 g_value_set_string (value, vte_terminal_get_current_directory_uri (terminal));
10170 break;
10171 case PROP_CURRENT_FILE_URI:
10172 g_value_set_string (value, vte_terminal_get_current_file_uri (terminal));
10173 break;
10174 case PROP_CURSOR_SHAPE:
10175 g_value_set_enum (value, vte_terminal_get_cursor_shape (terminal));
10176 break;
10177 case PROP_DELETE_BINDING:
10178 g_value_set_enum (value, pvt->delete_binding);
10179 break;
10180 case PROP_ENCODING:
10181 g_value_set_string (value, vte_terminal_get_encoding (terminal));
10182 break;
10183 case PROP_FONT_DESC:
10184 g_value_set_boxed (value, vte_terminal_get_font (terminal));
10185 break;
10186 case PROP_FONT_SCALE:
10187 g_value_set_double (value, vte_terminal_get_font_scale (terminal));
10188 break;
10189 case PROP_ICON_TITLE:
10190 g_value_set_string (value, vte_terminal_get_icon_title (terminal));
10191 break;
10192 case PROP_INPUT_ENABLED:
10193 g_value_set_boolean (value, vte_terminal_get_input_enabled (terminal));
10194 break;
10195 case PROP_MOUSE_POINTER_AUTOHIDE:
10196 g_value_set_boolean (value, vte_terminal_get_mouse_autohide (terminal));
10197 break;
10198 case PROP_PTY:
10199 g_value_set_object (value, vte_terminal_get_pty(terminal));
10200 break;
10201 case PROP_REWRAP_ON_RESIZE:
10202 g_value_set_boolean (value, vte_terminal_get_rewrap_on_resize (terminal));
10203 break;
10204 case PROP_SCROLLBACK_LINES:
10205 g_value_set_uint (value, pvt->scrollback_lines);
10206 break;
10207 case PROP_SCROLL_ON_KEYSTROKE:
10208 g_value_set_boolean (value, pvt->scroll_on_keystroke);
10209 break;
10210 case PROP_SCROLL_ON_OUTPUT:
10211 g_value_set_boolean (value, pvt->scroll_on_output);
10212 break;
10213 case PROP_WINDOW_TITLE:
10214 g_value_set_string (value, vte_terminal_get_window_title (terminal));
10215 break;
10216 case PROP_WORD_CHAR_EXCEPTIONS:
10217 g_value_set_string (value, vte_terminal_get_word_char_exceptions (terminal));
10218 break;
10219
10220 default:
10221 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
10222 return;
10223 }
10224 }
10225
10226 static void
vte_terminal_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)10227 vte_terminal_set_property (GObject *object,
10228 guint prop_id,
10229 const GValue *value,
10230 GParamSpec *pspec)
10231 {
10232 VteTerminal *terminal = VTE_TERMINAL (object);
10233 VteTerminalPrivate *pvt = terminal->pvt;
10234
10235 switch (prop_id)
10236 {
10237 case PROP_HADJUSTMENT:
10238 vte_terminal_set_hadjustment (terminal, g_value_get_object (value));
10239 break;
10240 case PROP_VADJUSTMENT:
10241 vte_terminal_set_vadjustment (terminal, g_value_get_object (value));
10242 break;
10243 case PROP_HSCROLL_POLICY:
10244 pvt->hscroll_policy = g_value_get_enum (value);
10245 gtk_widget_queue_resize_no_redraw (GTK_WIDGET (terminal));
10246 break;
10247 case PROP_VSCROLL_POLICY:
10248 pvt->vscroll_policy = g_value_get_enum (value);
10249 gtk_widget_queue_resize_no_redraw (GTK_WIDGET (terminal));
10250 break;
10251 case PROP_ALLOW_BOLD:
10252 vte_terminal_set_allow_bold (terminal, g_value_get_boolean (value));
10253 break;
10254 case PROP_AUDIBLE_BELL:
10255 vte_terminal_set_audible_bell (terminal, g_value_get_boolean (value));
10256 break;
10257 case PROP_BACKSPACE_BINDING:
10258 vte_terminal_set_backspace_binding (terminal, g_value_get_enum (value));
10259 break;
10260 case PROP_CJK_AMBIGUOUS_WIDTH:
10261 vte_terminal_set_cjk_ambiguous_width (terminal, g_value_get_int (value));
10262 break;
10263 case PROP_CURSOR_BLINK_MODE:
10264 vte_terminal_set_cursor_blink_mode (terminal, g_value_get_enum (value));
10265 break;
10266 case PROP_CURSOR_SHAPE:
10267 vte_terminal_set_cursor_shape (terminal, g_value_get_enum (value));
10268 break;
10269 case PROP_DELETE_BINDING:
10270 vte_terminal_set_delete_binding (terminal, g_value_get_enum (value));
10271 break;
10272 case PROP_ENCODING:
10273 vte_terminal_set_encoding (terminal, g_value_get_string (value), NULL);
10274 break;
10275 case PROP_FONT_DESC:
10276 vte_terminal_set_font (terminal, g_value_get_boxed (value));
10277 break;
10278 case PROP_FONT_SCALE:
10279 vte_terminal_set_font_scale (terminal, g_value_get_double (value));
10280 break;
10281 case PROP_INPUT_ENABLED:
10282 vte_terminal_set_input_enabled (terminal, g_value_get_boolean (value));
10283 break;
10284 case PROP_MOUSE_POINTER_AUTOHIDE:
10285 vte_terminal_set_mouse_autohide (terminal, g_value_get_boolean (value));
10286 break;
10287 case PROP_PTY:
10288 vte_terminal_set_pty (terminal, g_value_get_object (value));
10289 break;
10290 case PROP_REWRAP_ON_RESIZE:
10291 vte_terminal_set_rewrap_on_resize (terminal, g_value_get_boolean (value));
10292 break;
10293 case PROP_SCROLLBACK_LINES:
10294 vte_terminal_set_scrollback_lines (terminal, g_value_get_uint (value));
10295 break;
10296 case PROP_SCROLL_ON_KEYSTROKE:
10297 vte_terminal_set_scroll_on_keystroke(terminal, g_value_get_boolean (value));
10298 break;
10299 case PROP_SCROLL_ON_OUTPUT:
10300 vte_terminal_set_scroll_on_output (terminal, g_value_get_boolean (value));
10301 break;
10302 case PROP_WORD_CHAR_EXCEPTIONS:
10303 vte_terminal_set_word_char_exceptions (terminal, g_value_get_string (value));
10304 break;
10305
10306 /* Not writable */
10307 case PROP_CURRENT_DIRECTORY_URI:
10308 case PROP_CURRENT_FILE_URI:
10309 case PROP_ICON_TITLE:
10310 case PROP_WINDOW_TITLE:
10311 g_assert_not_reached ();
10312 break;
10313
10314 default:
10315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
10316 return;
10317 }
10318 }
10319
10320 /* Initialize methods. */
10321 static void
vte_terminal_class_init(VteTerminalClass * klass)10322 vte_terminal_class_init(VteTerminalClass *klass)
10323 {
10324 GObjectClass *gobject_class;
10325 GtkWidgetClass *widget_class;
10326 GtkBindingSet *binding_set;
10327
10328 #ifdef VTE_DEBUG
10329 {
10330 _vte_debug_init();
10331 _vte_debug_print(VTE_DEBUG_LIFECYCLE,
10332 "vte_terminal_class_init()\n");
10333 /* print out the legend */
10334 _vte_debug_print(VTE_DEBUG_WORK,
10335 "Debugging work flow (top input to bottom output):\n"
10336 " . _vte_terminal_process_incoming\n"
10337 " < start process_timeout\n"
10338 " {[ start update_timeout [ => rate limited\n"
10339 " T start of terminal in update_timeout\n"
10340 " ( start _vte_terminal_process_incoming\n"
10341 " ? _vte_invalidate_cells (call)\n"
10342 " ! _vte_invalidate_cells (dirty)\n"
10343 " * _vte_invalidate_all\n"
10344 " ) end _vte_terminal_process_incoming\n"
10345 " - gdk_window_process_updates\n"
10346 " = vte_terminal_paint\n"
10347 " ]} end update_timeout\n"
10348 " > end process_timeout\n");
10349 }
10350 #endif
10351
10352 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
10353 #ifdef HAVE_DECL_BIND_TEXTDOMAIN_CODESET
10354 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
10355 #endif
10356
10357 g_type_class_add_private(klass, sizeof (VteTerminalPrivate));
10358
10359 gobject_class = G_OBJECT_CLASS(klass);
10360 widget_class = GTK_WIDGET_CLASS(klass);
10361
10362 /* Override some of the default handlers. */
10363 gobject_class->finalize = vte_terminal_finalize;
10364 gobject_class->get_property = vte_terminal_get_property;
10365 gobject_class->set_property = vte_terminal_set_property;
10366 widget_class->realize = vte_terminal_realize;
10367 widget_class->scroll_event = vte_terminal_scroll;
10368 widget_class->draw = vte_terminal_draw;
10369 widget_class->key_press_event = vte_terminal_key_press;
10370 widget_class->key_release_event = vte_terminal_key_release;
10371 widget_class->button_press_event = vte_terminal_button_press;
10372 widget_class->button_release_event = vte_terminal_button_release;
10373 widget_class->motion_notify_event = vte_terminal_motion_notify;
10374 widget_class->enter_notify_event = vte_terminal_enter;
10375 widget_class->leave_notify_event = vte_terminal_leave;
10376 widget_class->focus_in_event = vte_terminal_focus_in;
10377 widget_class->focus_out_event = vte_terminal_focus_out;
10378 widget_class->visibility_notify_event = vte_terminal_visibility_notify;
10379 widget_class->unrealize = vte_terminal_unrealize;
10380 widget_class->style_updated = vte_terminal_style_updated;
10381 widget_class->get_preferred_width = vte_terminal_get_preferred_width;
10382 widget_class->get_preferred_height = vte_terminal_get_preferred_height;
10383 widget_class->size_allocate = vte_terminal_size_allocate;
10384 widget_class->screen_changed = vte_terminal_screen_changed;
10385
10386 /* Initialize default handlers. */
10387 klass->eof = NULL;
10388 klass->child_exited = NULL;
10389 klass->encoding_changed = NULL;
10390 klass->char_size_changed = NULL;
10391 klass->window_title_changed = NULL;
10392 klass->icon_title_changed = NULL;
10393 klass->selection_changed = NULL;
10394 klass->contents_changed = NULL;
10395 klass->cursor_moved = NULL;
10396 klass->commit = NULL;
10397
10398 klass->deiconify_window = NULL;
10399 klass->iconify_window = NULL;
10400 klass->raise_window = NULL;
10401 klass->lower_window = NULL;
10402 klass->refresh_window = NULL;
10403 klass->restore_window = NULL;
10404 klass->maximize_window = NULL;
10405 klass->resize_window = NULL;
10406 klass->move_window = NULL;
10407
10408 klass->increase_font_size = NULL;
10409 klass->decrease_font_size = NULL;
10410
10411 klass->text_modified = NULL;
10412 klass->text_inserted = NULL;
10413 klass->text_deleted = NULL;
10414 klass->text_scrolled = NULL;
10415
10416 klass->copy_clipboard = vte_terminal_real_copy_clipboard;
10417 klass->paste_clipboard = vte_terminal_real_paste_clipboard;
10418
10419 klass->bell = NULL;
10420
10421 /* GtkScrollable interface properties */
10422 g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
10423 g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
10424 g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
10425 g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
10426
10427 /* Register some signals of our own. */
10428
10429 /**
10430 * VteTerminal::eof:
10431 * @vteterminal: the object which received the signal
10432 *
10433 * Emitted when the terminal receives an end-of-file from a child which
10434 * is running in the terminal. This signal is frequently (but not
10435 * always) emitted with a #VteTerminal::child-exited signal.
10436 */
10437 g_signal_new(I_("eof"),
10438 G_OBJECT_CLASS_TYPE(klass),
10439 G_SIGNAL_RUN_LAST,
10440 G_STRUCT_OFFSET(VteTerminalClass, eof),
10441 NULL,
10442 NULL,
10443 g_cclosure_marshal_VOID__VOID,
10444 G_TYPE_NONE, 0);
10445
10446 /**
10447 * VteTerminal::child-exited:
10448 * @vteterminal: the object which received the signal
10449 * @status: the child's exit status
10450 *
10451 * This signal is emitted when the terminal detects that a child
10452 * watched using vte_terminal_watch_child() has exited.
10453 */
10454 g_signal_new(I_("child-exited"),
10455 G_OBJECT_CLASS_TYPE(klass),
10456 G_SIGNAL_RUN_LAST,
10457 G_STRUCT_OFFSET(VteTerminalClass, child_exited),
10458 NULL,
10459 NULL,
10460 g_cclosure_marshal_VOID__INT,
10461 G_TYPE_NONE,
10462 1, G_TYPE_INT);
10463
10464 /**
10465 * VteTerminal::window-title-changed:
10466 * @vteterminal: the object which received the signal
10467 *
10468 * Emitted when the terminal's %window_title field is modified.
10469 */
10470 g_signal_new(I_("window-title-changed"),
10471 G_OBJECT_CLASS_TYPE(klass),
10472 G_SIGNAL_RUN_LAST,
10473 G_STRUCT_OFFSET(VteTerminalClass, window_title_changed),
10474 NULL,
10475 NULL,
10476 g_cclosure_marshal_VOID__VOID,
10477 G_TYPE_NONE, 0);
10478
10479 /**
10480 * VteTerminal::icon-title-changed:
10481 * @vteterminal: the object which received the signal
10482 *
10483 * Emitted when the terminal's %icon_title field is modified.
10484 */
10485 g_signal_new(I_("icon-title-changed"),
10486 G_OBJECT_CLASS_TYPE(klass),
10487 G_SIGNAL_RUN_LAST,
10488 G_STRUCT_OFFSET(VteTerminalClass, icon_title_changed),
10489 NULL,
10490 NULL,
10491 g_cclosure_marshal_VOID__VOID,
10492 G_TYPE_NONE, 0);
10493
10494 /**
10495 * VteTerminal::current-directory-uri-changed:
10496 * @vteterminal: the object which received the signal
10497 *
10498 * Emitted when the current directory URI is modified.
10499 */
10500 g_signal_new(I_("current-directory-uri-changed"),
10501 G_OBJECT_CLASS_TYPE(klass),
10502 G_SIGNAL_RUN_LAST,
10503 0,
10504 NULL,
10505 NULL,
10506 g_cclosure_marshal_VOID__VOID,
10507 G_TYPE_NONE, 0);
10508
10509 /**
10510 * VteTerminal::current-file-uri-changed:
10511 * @vteterminal: the object which received the signal
10512 *
10513 * Emitted when the current file URI is modified.
10514 */
10515 g_signal_new(I_("current-file-uri-changed"),
10516 G_OBJECT_CLASS_TYPE(klass),
10517 G_SIGNAL_RUN_LAST,
10518 0,
10519 NULL,
10520 NULL,
10521 g_cclosure_marshal_VOID__VOID,
10522 G_TYPE_NONE, 0);
10523
10524 /**
10525 * VteTerminal::encoding-changed:
10526 * @vteterminal: the object which received the signal
10527 *
10528 * Emitted whenever the terminal's current encoding has changed, either
10529 * as a result of receiving a control sequence which toggled between the
10530 * local and UTF-8 encodings, or at the parent application's request.
10531 */
10532 g_signal_new(I_("encoding-changed"),
10533 G_OBJECT_CLASS_TYPE(klass),
10534 G_SIGNAL_RUN_LAST,
10535 G_STRUCT_OFFSET(VteTerminalClass, encoding_changed),
10536 NULL,
10537 NULL,
10538 g_cclosure_marshal_VOID__VOID,
10539 G_TYPE_NONE, 0);
10540
10541 /**
10542 * VteTerminal::commit:
10543 * @vteterminal: the object which received the signal
10544 * @text: a string of text
10545 * @size: the length of that string of text
10546 *
10547 * Emitted whenever the terminal receives input from the user and
10548 * prepares to send it to the child process. The signal is emitted even
10549 * when there is no child process.
10550 */
10551 g_signal_new(I_("commit"),
10552 G_OBJECT_CLASS_TYPE(klass),
10553 G_SIGNAL_RUN_LAST,
10554 G_STRUCT_OFFSET(VteTerminalClass, commit),
10555 NULL,
10556 NULL,
10557 _vte_marshal_VOID__STRING_UINT,
10558 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_UINT);
10559
10560 /**
10561 * VteTerminal::char-size-changed:
10562 * @vteterminal: the object which received the signal
10563 * @width: the new character cell width
10564 * @height: the new character cell height
10565 *
10566 * Emitted whenever selection of a new font causes the values of the
10567 * %char_width or %char_height fields to change.
10568 */
10569 g_signal_new(I_("char-size-changed"),
10570 G_OBJECT_CLASS_TYPE(klass),
10571 G_SIGNAL_RUN_LAST,
10572 G_STRUCT_OFFSET(VteTerminalClass, char_size_changed),
10573 NULL,
10574 NULL,
10575 _vte_marshal_VOID__UINT_UINT,
10576 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
10577
10578 /**
10579 * VteTerminal::selection-changed:
10580 * @vteterminal: the object which received the signal
10581 *
10582 * Emitted whenever the contents of terminal's selection changes.
10583 */
10584 g_signal_new (I_("selection-changed"),
10585 G_OBJECT_CLASS_TYPE(klass),
10586 G_SIGNAL_RUN_LAST,
10587 G_STRUCT_OFFSET(VteTerminalClass, selection_changed),
10588 NULL,
10589 NULL,
10590 g_cclosure_marshal_VOID__VOID,
10591 G_TYPE_NONE, 0);
10592
10593 /**
10594 * VteTerminal::contents-changed:
10595 * @vteterminal: the object which received the signal
10596 *
10597 * Emitted whenever the visible appearance of the terminal has changed.
10598 * Used primarily by #VteTerminalAccessible.
10599 */
10600 g_signal_new(I_("contents-changed"),
10601 G_OBJECT_CLASS_TYPE(klass),
10602 G_SIGNAL_RUN_LAST,
10603 G_STRUCT_OFFSET(VteTerminalClass, contents_changed),
10604 NULL,
10605 NULL,
10606 g_cclosure_marshal_VOID__VOID,
10607 G_TYPE_NONE, 0);
10608
10609 /**
10610 * VteTerminal::cursor-moved:
10611 * @vteterminal: the object which received the signal
10612 *
10613 * Emitted whenever the cursor moves to a new character cell. Used
10614 * primarily by #VteTerminalAccessible.
10615 */
10616 g_signal_new(I_("cursor-moved"),
10617 G_OBJECT_CLASS_TYPE(klass),
10618 G_SIGNAL_RUN_LAST,
10619 G_STRUCT_OFFSET(VteTerminalClass, cursor_moved),
10620 NULL,
10621 NULL,
10622 g_cclosure_marshal_VOID__VOID,
10623 G_TYPE_NONE, 0);
10624
10625 /**
10626 * VteTerminal::deiconify-window:
10627 * @vteterminal: the object which received the signal
10628 *
10629 * Emitted at the child application's request.
10630 */
10631 g_signal_new(I_("deiconify-window"),
10632 G_OBJECT_CLASS_TYPE(klass),
10633 G_SIGNAL_RUN_LAST,
10634 G_STRUCT_OFFSET(VteTerminalClass, deiconify_window),
10635 NULL,
10636 NULL,
10637 g_cclosure_marshal_VOID__VOID,
10638 G_TYPE_NONE, 0);
10639
10640 /**
10641 * VteTerminal::iconify-window:
10642 * @vteterminal: the object which received the signal
10643 *
10644 * Emitted at the child application's request.
10645 */
10646 g_signal_new(I_("iconify-window"),
10647 G_OBJECT_CLASS_TYPE(klass),
10648 G_SIGNAL_RUN_LAST,
10649 G_STRUCT_OFFSET(VteTerminalClass, iconify_window),
10650 NULL,
10651 NULL,
10652 g_cclosure_marshal_VOID__VOID,
10653 G_TYPE_NONE, 0);
10654
10655 /**
10656 * VteTerminal::raise-window:
10657 * @vteterminal: the object which received the signal
10658 *
10659 * Emitted at the child application's request.
10660 */
10661 g_signal_new(I_("raise-window"),
10662 G_OBJECT_CLASS_TYPE(klass),
10663 G_SIGNAL_RUN_LAST,
10664 G_STRUCT_OFFSET(VteTerminalClass, raise_window),
10665 NULL,
10666 NULL,
10667 g_cclosure_marshal_VOID__VOID,
10668 G_TYPE_NONE, 0);
10669
10670 /**
10671 * VteTerminal::lower-window:
10672 * @vteterminal: the object which received the signal
10673 *
10674 * Emitted at the child application's request.
10675 */
10676 g_signal_new(I_("lower-window"),
10677 G_OBJECT_CLASS_TYPE(klass),
10678 G_SIGNAL_RUN_LAST,
10679 G_STRUCT_OFFSET(VteTerminalClass, lower_window),
10680 NULL,
10681 NULL,
10682 g_cclosure_marshal_VOID__VOID,
10683 G_TYPE_NONE, 0);
10684
10685 /**
10686 * VteTerminal::refresh-window:
10687 * @vteterminal: the object which received the signal
10688 *
10689 * Emitted at the child application's request.
10690 */
10691 g_signal_new(I_("refresh-window"),
10692 G_OBJECT_CLASS_TYPE(klass),
10693 G_SIGNAL_RUN_LAST,
10694 G_STRUCT_OFFSET(VteTerminalClass, refresh_window),
10695 NULL,
10696 NULL,
10697 g_cclosure_marshal_VOID__VOID,
10698 G_TYPE_NONE, 0);
10699
10700 /**
10701 * VteTerminal::restore-window:
10702 * @vteterminal: the object which received the signal
10703 *
10704 * Emitted at the child application's request.
10705 */
10706 g_signal_new(I_("restore-window"),
10707 G_OBJECT_CLASS_TYPE(klass),
10708 G_SIGNAL_RUN_LAST,
10709 G_STRUCT_OFFSET(VteTerminalClass, restore_window),
10710 NULL,
10711 NULL,
10712 g_cclosure_marshal_VOID__VOID,
10713 G_TYPE_NONE, 0);
10714
10715 /**
10716 * VteTerminal::maximize-window:
10717 * @vteterminal: the object which received the signal
10718 *
10719 * Emitted at the child application's request.
10720 */
10721 g_signal_new(I_("maximize-window"),
10722 G_OBJECT_CLASS_TYPE(klass),
10723 G_SIGNAL_RUN_LAST,
10724 G_STRUCT_OFFSET(VteTerminalClass, maximize_window),
10725 NULL,
10726 NULL,
10727 g_cclosure_marshal_VOID__VOID,
10728 G_TYPE_NONE, 0);
10729
10730 /**
10731 * VteTerminal::resize-window:
10732 * @vteterminal: the object which received the signal
10733 * @width: the desired number of columns
10734 * @height: the desired number of rows
10735 *
10736 * Emitted at the child application's request.
10737 */
10738 g_signal_new(I_("resize-window"),
10739 G_OBJECT_CLASS_TYPE(klass),
10740 G_SIGNAL_RUN_LAST,
10741 G_STRUCT_OFFSET(VteTerminalClass, resize_window),
10742 NULL,
10743 NULL,
10744 _vte_marshal_VOID__UINT_UINT,
10745 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
10746
10747 /**
10748 * VteTerminal::move-window:
10749 * @vteterminal: the object which received the signal
10750 * @x: the terminal's desired location, X coordinate
10751 * @y: the terminal's desired location, Y coordinate
10752 *
10753 * Emitted at the child application's request.
10754 */
10755 g_signal_new(I_("move-window"),
10756 G_OBJECT_CLASS_TYPE(klass),
10757 G_SIGNAL_RUN_LAST,
10758 G_STRUCT_OFFSET(VteTerminalClass, move_window),
10759 NULL,
10760 NULL,
10761 _vte_marshal_VOID__UINT_UINT,
10762 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
10763
10764 /**
10765 * VteTerminal::increase-font-size:
10766 * @vteterminal: the object which received the signal
10767 *
10768 * Emitted when the user hits the '+' key while holding the Control key.
10769 */
10770 g_signal_new(I_("increase-font-size"),
10771 G_OBJECT_CLASS_TYPE(klass),
10772 G_SIGNAL_RUN_LAST,
10773 G_STRUCT_OFFSET(VteTerminalClass, increase_font_size),
10774 NULL,
10775 NULL,
10776 g_cclosure_marshal_VOID__VOID,
10777 G_TYPE_NONE, 0);
10778
10779 /**
10780 * VteTerminal::decrease-font-size:
10781 * @vteterminal: the object which received the signal
10782 *
10783 * Emitted when the user hits the '-' key while holding the Control key.
10784 */
10785 g_signal_new(I_("decrease-font-size"),
10786 G_OBJECT_CLASS_TYPE(klass),
10787 G_SIGNAL_RUN_LAST,
10788 G_STRUCT_OFFSET(VteTerminalClass, decrease_font_size),
10789 NULL,
10790 NULL,
10791 g_cclosure_marshal_VOID__VOID,
10792 G_TYPE_NONE, 0);
10793
10794 /**
10795 * VteTerminal::text-modified:
10796 * @vteterminal: the object which received the signal
10797 *
10798 * An internal signal used for communication between the terminal and
10799 * its accessibility peer. May not be emitted under certain
10800 * circumstances.
10801 */
10802 g_signal_new(I_("text-modified"),
10803 G_OBJECT_CLASS_TYPE(klass),
10804 G_SIGNAL_RUN_LAST,
10805 G_STRUCT_OFFSET(VteTerminalClass, text_modified),
10806 NULL,
10807 NULL,
10808 g_cclosure_marshal_VOID__VOID,
10809 G_TYPE_NONE, 0);
10810
10811 /**
10812 * VteTerminal::text-inserted:
10813 * @vteterminal: the object which received the signal
10814 *
10815 * An internal signal used for communication between the terminal and
10816 * its accessibility peer. May not be emitted under certain
10817 * circumstances.
10818 */
10819 g_signal_new(I_("text-inserted"),
10820 G_OBJECT_CLASS_TYPE(klass),
10821 G_SIGNAL_RUN_LAST,
10822 G_STRUCT_OFFSET(VteTerminalClass, text_inserted),
10823 NULL,
10824 NULL,
10825 g_cclosure_marshal_VOID__VOID,
10826 G_TYPE_NONE, 0);
10827
10828 /**
10829 * VteTerminal::text-deleted:
10830 * @vteterminal: the object which received the signal
10831 *
10832 * An internal signal used for communication between the terminal and
10833 * its accessibility peer. May not be emitted under certain
10834 * circumstances.
10835 */
10836 g_signal_new(I_("text-deleted"),
10837 G_OBJECT_CLASS_TYPE(klass),
10838 G_SIGNAL_RUN_LAST,
10839 G_STRUCT_OFFSET(VteTerminalClass, text_deleted),
10840 NULL,
10841 NULL,
10842 g_cclosure_marshal_VOID__VOID,
10843 G_TYPE_NONE, 0);
10844
10845 /**
10846 * VteTerminal::text-scrolled:
10847 * @vteterminal: the object which received the signal
10848 * @delta: the number of lines scrolled
10849 *
10850 * An internal signal used for communication between the terminal and
10851 * its accessibility peer. May not be emitted under certain
10852 * circumstances.
10853 */
10854 g_signal_new(I_("text-scrolled"),
10855 G_OBJECT_CLASS_TYPE(klass),
10856 G_SIGNAL_RUN_LAST,
10857 G_STRUCT_OFFSET(VteTerminalClass, text_scrolled),
10858 NULL,
10859 NULL,
10860 g_cclosure_marshal_VOID__INT,
10861 G_TYPE_NONE, 1, G_TYPE_INT);
10862
10863 /**
10864 * VteTerminal::copy-clipboard:
10865 * @vteterminal: the object which received the signal
10866 *
10867 * Emitted whenever vte_terminal_copy_clipboard() is called.
10868 */
10869 signals[COPY_CLIPBOARD] =
10870 g_signal_new(I_("copy-clipboard"),
10871 G_OBJECT_CLASS_TYPE(klass),
10872 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
10873 G_STRUCT_OFFSET(VteTerminalClass, copy_clipboard),
10874 NULL,
10875 NULL,
10876 g_cclosure_marshal_VOID__VOID,
10877 G_TYPE_NONE, 0);
10878
10879 /**
10880 * VteTerminal::paste-clipboard:
10881 * @vteterminal: the object which received the signal
10882 *
10883 * Emitted whenever vte_terminal_paste_clipboard() is called.
10884 */
10885 signals[PASTE_CLIPBOARD] =
10886 g_signal_new(I_("paste-clipboard"),
10887 G_OBJECT_CLASS_TYPE(klass),
10888 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
10889 G_STRUCT_OFFSET(VteTerminalClass, paste_clipboard),
10890 NULL,
10891 NULL,
10892 g_cclosure_marshal_VOID__VOID,
10893 G_TYPE_NONE, 0);
10894
10895 /**
10896 * VteTerminal::bell:
10897 * @vteterminal: the object which received the signal
10898 *
10899 * This signal is emitted when the a child sends a bell request to the
10900 * terminal.
10901 */
10902 g_signal_new(I_("bell"),
10903 G_OBJECT_CLASS_TYPE(klass),
10904 G_SIGNAL_RUN_LAST,
10905 G_STRUCT_OFFSET(VteTerminalClass, bell),
10906 NULL,
10907 NULL,
10908 g_cclosure_marshal_VOID__VOID,
10909 G_TYPE_NONE, 0);
10910
10911 /**
10912 * VteTerminal:allow-bold:
10913 *
10914 * Controls whether or not the terminal will attempt to draw bold text.
10915 * This may happen either by using a bold font variant, or by
10916 * repainting text with a different offset.
10917 */
10918 g_object_class_install_property
10919 (gobject_class,
10920 PROP_ALLOW_BOLD,
10921 g_param_spec_boolean ("allow-bold", NULL, NULL,
10922 TRUE,
10923 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10924
10925 /**
10926 * VteTerminal:audible-bell:
10927 *
10928 * Controls whether or not the terminal will beep when the child outputs the
10929 * "bl" sequence.
10930 */
10931 g_object_class_install_property
10932 (gobject_class,
10933 PROP_AUDIBLE_BELL,
10934 g_param_spec_boolean ("audible-bell", NULL, NULL,
10935 TRUE,
10936 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10937
10938 /**
10939 * VteTerminal:backspace-binding:
10940 *
10941 * *Controls what string or control sequence the terminal sends to its child
10942 * when the user presses the backspace key.
10943 */
10944 g_object_class_install_property
10945 (gobject_class,
10946 PROP_BACKSPACE_BINDING,
10947 g_param_spec_enum ("backspace-binding", NULL, NULL,
10948 VTE_TYPE_ERASE_BINDING,
10949 VTE_ERASE_AUTO,
10950 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10951
10952 /**
10953 * VteTerminal:cjk-ambiguous-width:
10954 *
10955 * This setting controls whether ambiguous-width characters are narrow or wide
10956 * when using the UTF-8 encoding (vte_terminal_set_encoding()). In all other encodings,
10957 * the width of ambiguous-width characters is fixed.
10958 *
10959 * This setting only takes effect the next time the terminal is reset, either
10960 * via escape sequence or with vte_terminal_reset().
10961 */
10962 g_object_class_install_property
10963 (gobject_class,
10964 PROP_CJK_AMBIGUOUS_WIDTH,
10965 g_param_spec_int ("cjk-ambiguous-width", NULL, NULL,
10966 1, 2, VTE_DEFAULT_UTF8_AMBIGUOUS_WIDTH,
10967 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10968
10969 /**
10970 * VteTerminal:cursor-blink-mode:
10971 *
10972 * Sets whether or not the cursor will blink. Using %VTE_CURSOR_BLINK_SYSTEM
10973 * will use the #GtkSettings::gtk-cursor-blink setting.
10974 */
10975 g_object_class_install_property
10976 (gobject_class,
10977 PROP_CURSOR_BLINK_MODE,
10978 g_param_spec_enum ("cursor-blink-mode", NULL, NULL,
10979 VTE_TYPE_CURSOR_BLINK_MODE,
10980 VTE_CURSOR_BLINK_SYSTEM,
10981 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10982
10983 /**
10984 * VteTerminal:cursor-shape:
10985 *
10986 * Controls the shape of the cursor.
10987 */
10988 g_object_class_install_property
10989 (gobject_class,
10990 PROP_CURSOR_SHAPE,
10991 g_param_spec_enum ("cursor-shape", NULL, NULL,
10992 VTE_TYPE_CURSOR_SHAPE,
10993 VTE_CURSOR_SHAPE_BLOCK,
10994 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
10995
10996 /**
10997 * VteTerminal:delete-binding:
10998 *
10999 * Controls what string or control sequence the terminal sends to its child
11000 * when the user presses the delete key.
11001 */
11002 g_object_class_install_property
11003 (gobject_class,
11004 PROP_DELETE_BINDING,
11005 g_param_spec_enum ("delete-binding", NULL, NULL,
11006 VTE_TYPE_ERASE_BINDING,
11007 VTE_ERASE_AUTO,
11008 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11009
11010 /**
11011 * VteTerminal:font-scale:
11012 *
11013 * The terminal's font scale.
11014 */
11015 g_object_class_install_property
11016 (gobject_class,
11017 PROP_FONT_SCALE,
11018 g_param_spec_double ("font-scale", NULL, NULL,
11019 VTE_FONT_SCALE_MIN,
11020 VTE_FONT_SCALE_MAX,
11021 1.,
11022 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11023
11024 /**
11025 * VteTerminal:encoding:
11026 *
11027 * Controls the encoding the terminal will expect data from the child to
11028 * be encoded with. For certain terminal types, applications executing in the
11029 * terminal can change the encoding. The default is defined by the
11030 * application's locale settings.
11031 */
11032 g_object_class_install_property
11033 (gobject_class,
11034 PROP_ENCODING,
11035 g_param_spec_string ("encoding", NULL, NULL,
11036 NULL,
11037 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11038
11039 /**
11040 * VteTerminal:font-desc:
11041 *
11042 * Specifies the font used for rendering all text displayed by the terminal,
11043 * overriding any fonts set using gtk_widget_modify_font(). The terminal
11044 * will immediately attempt to load the desired font, retrieve its
11045 * metrics, and attempt to resize itself to keep the same number of rows
11046 * and columns.
11047 */
11048 g_object_class_install_property
11049 (gobject_class,
11050 PROP_FONT_DESC,
11051 g_param_spec_boxed ("font-desc", NULL, NULL,
11052 PANGO_TYPE_FONT_DESCRIPTION,
11053 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11054
11055 /**
11056 * VteTerminal:icon-title:
11057 *
11058 * The terminal's so-called icon title, or %NULL if no icon title has been set.
11059 */
11060 g_object_class_install_property
11061 (gobject_class,
11062 PROP_ICON_TITLE,
11063 g_param_spec_string ("icon-title", NULL, NULL,
11064 NULL,
11065 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11066
11067 /**
11068 * VteTerminal:input-enabled:
11069 *
11070 * Controls whether the terminal allows user input. When user input is disabled,
11071 * key press and mouse button press and motion events are not sent to the
11072 * terminal's child.
11073 */
11074 g_object_class_install_property
11075 (gobject_class,
11076 PROP_INPUT_ENABLED,
11077 g_param_spec_boolean ("input-enabled", NULL, NULL,
11078 TRUE,
11079 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11080
11081 /**
11082 * VteTerminal:pointer-autohide:
11083 *
11084 * Controls the value of the terminal's mouse autohide setting. When autohiding
11085 * is enabled, the mouse cursor will be hidden when the user presses a key and
11086 * shown when the user moves the mouse.
11087 */
11088 g_object_class_install_property
11089 (gobject_class,
11090 PROP_MOUSE_POINTER_AUTOHIDE,
11091 g_param_spec_boolean ("pointer-autohide", NULL, NULL,
11092 FALSE,
11093 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11094
11095 /**
11096 * VteTerminal:pty:
11097 *
11098 * The PTY object for the terminal.
11099 */
11100 g_object_class_install_property
11101 (gobject_class,
11102 PROP_PTY,
11103 g_param_spec_object ("pty", NULL, NULL,
11104 VTE_TYPE_PTY,
11105 G_PARAM_READWRITE |
11106 G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11107
11108 /**
11109 * VteTerminal:rewrap-on-resize:
11110 *
11111 * Controls whether or not the terminal will rewrap its contents, including
11112 * the scrollback buffer, whenever the terminal's width changes.
11113 */
11114 g_object_class_install_property
11115 (gobject_class,
11116 PROP_REWRAP_ON_RESIZE,
11117 g_param_spec_boolean ("rewrap-on-resize", NULL, NULL,
11118 TRUE,
11119 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11120
11121 /**
11122 * VteTerminal:scrollback-lines:
11123 *
11124 * The length of the scrollback buffer used by the terminal. The size of
11125 * the scrollback buffer will be set to the larger of this value and the number
11126 * of visible rows the widget can display, so 0 can safely be used to disable
11127 * scrollback. Note that this setting only affects the normal screen buffer.
11128 * For terminal types which have an alternate screen buffer, no scrollback is
11129 * allowed on the alternate screen buffer.
11130 */
11131 g_object_class_install_property
11132 (gobject_class,
11133 PROP_SCROLLBACK_LINES,
11134 g_param_spec_uint ("scrollback-lines", NULL, NULL,
11135 0, G_MAXUINT,
11136 VTE_SCROLLBACK_INIT,
11137 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11138
11139 /**
11140 * VteTerminal:scroll-on-keystroke:
11141 *
11142 * Controls whether or not the terminal will forcibly scroll to the bottom of
11143 * the viewable history when the user presses a key. Modifier keys do not
11144 * trigger this behavior.
11145 */
11146 g_object_class_install_property
11147 (gobject_class,
11148 PROP_SCROLL_ON_KEYSTROKE,
11149 g_param_spec_boolean ("scroll-on-keystroke", NULL, NULL,
11150 FALSE,
11151 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11152
11153 /**
11154 * VteTerminal:scroll-on-output:
11155 *
11156 * Controls whether or not the terminal will forcibly scroll to the bottom of
11157 * the viewable history when the new data is received from the child.
11158 */
11159 g_object_class_install_property
11160 (gobject_class,
11161 PROP_SCROLL_ON_OUTPUT,
11162 g_param_spec_boolean ("scroll-on-output", NULL, NULL,
11163 TRUE,
11164 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11165
11166 /**
11167 * VteTerminal:window-title:
11168 *
11169 * The terminal's title.
11170 */
11171 g_object_class_install_property
11172 (gobject_class,
11173 PROP_WINDOW_TITLE,
11174 g_param_spec_string ("window-title", NULL, NULL,
11175 NULL,
11176 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11177
11178 /**
11179 * VteTerminal:current-directory-uri:
11180 *
11181 * The current directory URI, or %NULL if unset.
11182 */
11183 g_object_class_install_property
11184 (gobject_class,
11185 PROP_CURRENT_DIRECTORY_URI,
11186 g_param_spec_string ("current-directory-uri", NULL, NULL,
11187 NULL,
11188 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11189
11190 /**
11191 * VteTerminal:current-file-uri:
11192 *
11193 * The current file URI, or %NULL if unset.
11194 */
11195 g_object_class_install_property
11196 (gobject_class,
11197 PROP_CURRENT_FILE_URI,
11198 g_param_spec_string ("current-file-uri", NULL, NULL,
11199 NULL,
11200 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11201
11202 /**
11203 * VteTerminal:word-char-exceptions:
11204 *
11205 * The set of characters which will be considered parts of a word
11206 * when doing word-wise selection, in addition to the default which only
11207 * considers alphanumeric characters part of a word.
11208 *
11209 * If %NULL, a built-in set is used.
11210 *
11211 * Since: 0.40
11212 */
11213 g_object_class_install_property
11214 (gobject_class,
11215 PROP_WORD_CHAR_EXCEPTIONS,
11216 g_param_spec_string ("word-char-exceptions", NULL, NULL,
11217 NULL,
11218 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
11219
11220 /* Disable GtkWidget's keybindings except for Shift-F10 and MenuKey
11221 * which pop up the context menu.
11222 */
11223 binding_set = gtk_binding_set_by_class(vte_terminal_parent_class);
11224 gtk_binding_entry_skip(binding_set, GDK_KEY_F1, GDK_CONTROL_MASK);
11225 gtk_binding_entry_skip(binding_set, GDK_KEY_F1, GDK_SHIFT_MASK);
11226 gtk_binding_entry_skip(binding_set, GDK_KEY_KP_F1, GDK_CONTROL_MASK);
11227 gtk_binding_entry_skip(binding_set, GDK_KEY_KP_F1, GDK_SHIFT_MASK);
11228
11229
11230 process_timer = g_timer_new ();
11231
11232 klass->priv = G_TYPE_CLASS_GET_PRIVATE (klass, VTE_TYPE_TERMINAL, VteTerminalClassPrivate);
11233
11234 klass->priv->style_provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
11235 gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (klass->priv->style_provider),
11236 "VteTerminal {\n"
11237 "padding: 1px 1px 1px 1px;\n"
11238 "background-color: @theme_base_color;\n"
11239 "color: @theme_fg_color;\n"
11240 "}\n",
11241 -1, NULL);
11242
11243 /* a11y */
11244 gtk_widget_class_set_accessible_type(widget_class, VTE_TYPE_TERMINAL_ACCESSIBLE);
11245 }
11246
11247 /**
11248 * vte_terminal_set_audible_bell:
11249 * @terminal: a #VteTerminal
11250 * @is_audible: %TRUE if the terminal should beep
11251 *
11252 * Controls whether or not the terminal will beep when the child outputs the
11253 * "bl" sequence.
11254 */
11255 void
vte_terminal_set_audible_bell(VteTerminal * terminal,gboolean is_audible)11256 vte_terminal_set_audible_bell(VteTerminal *terminal, gboolean is_audible)
11257 {
11258 VteTerminalPrivate *pvt;
11259
11260 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11261
11262 pvt = terminal->pvt;
11263
11264 is_audible = is_audible != FALSE;
11265 if (is_audible == pvt->audible_bell)
11266 return;
11267
11268 pvt->audible_bell = is_audible;
11269
11270 g_object_notify (G_OBJECT (terminal), "audible-bell");
11271 }
11272
11273 /**
11274 * vte_terminal_get_audible_bell:
11275 * @terminal: a #VteTerminal
11276 *
11277 * Checks whether or not the terminal will beep when the child outputs the
11278 * "bl" sequence.
11279 *
11280 * Returns: %TRUE if audible bell is enabled, %FALSE if not
11281 */
11282 gboolean
vte_terminal_get_audible_bell(VteTerminal * terminal)11283 vte_terminal_get_audible_bell(VteTerminal *terminal)
11284 {
11285 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
11286 return terminal->pvt->audible_bell;
11287 }
11288
11289 /**
11290 * vte_terminal_set_allow_bold:
11291 * @terminal: a #VteTerminal
11292 * @allow_bold: %TRUE if the terminal should attempt to draw bold text
11293 *
11294 * Controls whether or not the terminal will attempt to draw bold text,
11295 * either by using a bold font variant or by repainting text with a different
11296 * offset.
11297 *
11298 */
11299 void
vte_terminal_set_allow_bold(VteTerminal * terminal,gboolean allow_bold)11300 vte_terminal_set_allow_bold(VteTerminal *terminal, gboolean allow_bold)
11301 {
11302 VteTerminalPrivate *pvt;
11303
11304 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11305
11306 pvt = terminal->pvt;
11307
11308 allow_bold = allow_bold != FALSE;
11309 if (allow_bold == pvt->allow_bold)
11310 return;
11311
11312 pvt->allow_bold = allow_bold;
11313 g_object_notify (G_OBJECT (terminal), "allow-bold");
11314
11315 _vte_invalidate_all (terminal);
11316 }
11317
11318 /**
11319 * vte_terminal_get_allow_bold:
11320 * @terminal: a #VteTerminal
11321 *
11322 * Checks whether or not the terminal will attempt to draw bold text by
11323 * repainting text with a one-pixel offset.
11324 *
11325 * Returns: %TRUE if bolding is enabled, %FALSE if not
11326 */
11327 gboolean
vte_terminal_get_allow_bold(VteTerminal * terminal)11328 vte_terminal_get_allow_bold(VteTerminal *terminal)
11329 {
11330 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
11331 return terminal->pvt->allow_bold;
11332 }
11333
11334 /**
11335 * vte_terminal_set_scroll_on_output:
11336 * @terminal: a #VteTerminal
11337 * @scroll: whether the terminal should scroll on output
11338 *
11339 * Controls whether or not the terminal will forcibly scroll to the bottom of
11340 * the viewable history when the new data is received from the child.
11341 */
11342 void
vte_terminal_set_scroll_on_output(VteTerminal * terminal,gboolean scroll)11343 vte_terminal_set_scroll_on_output(VteTerminal *terminal, gboolean scroll)
11344 {
11345 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11346 terminal->pvt->scroll_on_output = scroll;
11347 }
11348
11349 /**
11350 * vte_terminal_set_scroll_on_keystroke:
11351 * @terminal: a #VteTerminal
11352 * @scroll: whether the terminal should scroll on keystrokes
11353 *
11354 * Controls whether or not the terminal will forcibly scroll to the bottom of
11355 * the viewable history when the user presses a key. Modifier keys do not
11356 * trigger this behavior.
11357 */
11358 void
vte_terminal_set_scroll_on_keystroke(VteTerminal * terminal,gboolean scroll)11359 vte_terminal_set_scroll_on_keystroke(VteTerminal *terminal, gboolean scroll)
11360 {
11361 VteTerminalPrivate *pvt;
11362
11363 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11364
11365 pvt = terminal->pvt;
11366
11367 scroll = scroll != FALSE;
11368 if (scroll == pvt->scroll_on_keystroke)
11369 return;
11370
11371 pvt->scroll_on_keystroke = scroll;
11372
11373 g_object_notify (G_OBJECT (terminal), "scroll-on-keystroke");
11374 }
11375
11376 /**
11377 * vte_terminal_set_rewrap_on_resize:
11378 * @terminal: a #VteTerminal
11379 * @rewrap: %TRUE if the terminal should rewrap on resize
11380 *
11381 * Controls whether or not the terminal will rewrap its contents, including
11382 * the scrollback history, whenever the terminal's width changes.
11383 */
11384 void
vte_terminal_set_rewrap_on_resize(VteTerminal * terminal,gboolean rewrap)11385 vte_terminal_set_rewrap_on_resize(VteTerminal *terminal, gboolean rewrap)
11386 {
11387 VteTerminalPrivate *pvt;
11388
11389 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11390
11391 pvt = terminal->pvt;
11392
11393 if (rewrap == pvt->rewrap_on_resize)
11394 return;
11395
11396 pvt->rewrap_on_resize = rewrap;
11397 g_object_notify (G_OBJECT (terminal), "rewrap-on-resize");
11398 }
11399
11400 /**
11401 * vte_terminal_get_rewrap_on_resize:
11402 * @terminal: a #VteTerminal
11403 *
11404 * Checks whether or not the terminal will rewrap its contents upon resize.
11405 *
11406 * Returns: %TRUE if rewrapping is enabled, %FALSE if not
11407 */
11408 gboolean
vte_terminal_get_rewrap_on_resize(VteTerminal * terminal)11409 vte_terminal_get_rewrap_on_resize(VteTerminal *terminal)
11410 {
11411 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
11412 return terminal->pvt->rewrap_on_resize;
11413 }
11414
11415 static void
vte_terminal_real_copy_clipboard(VteTerminal * terminal)11416 vte_terminal_real_copy_clipboard(VteTerminal *terminal)
11417 {
11418 _vte_debug_print(VTE_DEBUG_SELECTION, "Copying to CLIPBOARD.\n");
11419 if (terminal->pvt->selection != NULL) {
11420 GtkClipboard *clipboard;
11421 clipboard = vte_terminal_clipboard_get(terminal,
11422 GDK_SELECTION_CLIPBOARD);
11423 gtk_clipboard_set_text(clipboard, terminal->pvt->selection, -1);
11424 }
11425 }
11426
11427 /**
11428 * vte_terminal_copy_clipboard:
11429 * @terminal: a #VteTerminal
11430 *
11431 * Places the selected text in the terminal in the #GDK_SELECTION_CLIPBOARD
11432 * selection.
11433 */
11434 void
vte_terminal_copy_clipboard(VteTerminal * terminal)11435 vte_terminal_copy_clipboard(VteTerminal *terminal)
11436 {
11437 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11438 g_signal_emit (terminal, signals[COPY_CLIPBOARD], 0);
11439 }
11440
11441 static void
vte_terminal_real_paste_clipboard(VteTerminal * terminal)11442 vte_terminal_real_paste_clipboard(VteTerminal *terminal)
11443 {
11444 _vte_debug_print(VTE_DEBUG_SELECTION, "Pasting CLIPBOARD.\n");
11445 vte_terminal_paste(terminal, GDK_SELECTION_CLIPBOARD);
11446 }
11447
11448 /**
11449 * vte_terminal_paste_clipboard:
11450 * @terminal: a #VteTerminal
11451 *
11452 * Sends the contents of the #GDK_SELECTION_CLIPBOARD selection to the
11453 * terminal's child. If necessary, the data is converted from UTF-8 to the
11454 * terminal's current encoding. It's called on paste menu item, or when
11455 * user presses Shift+Insert.
11456 */
11457 void
vte_terminal_paste_clipboard(VteTerminal * terminal)11458 vte_terminal_paste_clipboard(VteTerminal *terminal)
11459 {
11460 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11461 g_signal_emit (terminal, signals[PASTE_CLIPBOARD], 0);
11462 }
11463
11464 /**
11465 * vte_terminal_copy_primary:
11466 * @terminal: a #VteTerminal
11467 *
11468 * Places the selected text in the terminal in the #GDK_SELECTION_PRIMARY
11469 * selection.
11470 */
11471 void
vte_terminal_copy_primary(VteTerminal * terminal)11472 vte_terminal_copy_primary(VteTerminal *terminal)
11473 {
11474 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11475 _vte_debug_print(VTE_DEBUG_SELECTION, "Copying to PRIMARY.\n");
11476 vte_terminal_copy(terminal, GDK_SELECTION_PRIMARY);
11477 }
11478
11479 /**
11480 * vte_terminal_paste_primary:
11481 * @terminal: a #VteTerminal
11482 *
11483 * Sends the contents of the #GDK_SELECTION_PRIMARY selection to the terminal's
11484 * child. If necessary, the data is converted from UTF-8 to the terminal's
11485 * current encoding. The terminal will call also paste the
11486 * #GDK_SELECTION_PRIMARY selection when the user clicks with the the second
11487 * mouse button.
11488 */
11489 void
vte_terminal_paste_primary(VteTerminal * terminal)11490 vte_terminal_paste_primary(VteTerminal *terminal)
11491 {
11492 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11493 _vte_debug_print(VTE_DEBUG_SELECTION, "Pasting PRIMARY.\n");
11494 vte_terminal_paste(terminal, GDK_SELECTION_PRIMARY);
11495 }
11496
11497 /* Set up whatever background we wanted. */
11498 static void
vte_terminal_background_update(VteTerminal * terminal)11499 vte_terminal_background_update(VteTerminal *terminal)
11500 {
11501 const PangoColor *entry;
11502 GdkRGBA color;
11503
11504 /* If we're not realized yet, don't worry about it, because we get
11505 * called when we realize. */
11506 if (! gtk_widget_get_realized (&terminal->widget)) {
11507 return;
11508 }
11509
11510 _vte_debug_print(VTE_DEBUG_MISC|VTE_DEBUG_EVENTS,
11511 "Updating background color.\n");
11512
11513 entry = _vte_terminal_get_color(terminal, VTE_DEFAULT_BG);
11514 _vte_debug_print(VTE_DEBUG_STYLE,
11515 "Setting background color to (%d, %d, %d, %.3f).\n",
11516 entry->red, entry->green, entry->blue,
11517 terminal->pvt->background_alpha);
11518
11519 color.red = entry->red / 65535.;
11520 color.green = entry->green / 65535.;
11521 color.blue = entry->blue / 65535.;
11522 color.alpha = terminal->pvt->background_alpha;
11523
11524 _vte_draw_set_background_solid (terminal->pvt->draw, &color);
11525
11526 /* Force a redraw for everything. */
11527 _vte_invalidate_all (terminal);
11528 }
11529
11530 /**
11531 * vte_terminal_get_has_selection:
11532 * @terminal: a #VteTerminal
11533 *
11534 * Checks if the terminal currently contains selected text. Note that this
11535 * is different from determining if the terminal is the owner of any
11536 * #GtkClipboard items.
11537 *
11538 * Returns: %TRUE if part of the text in the terminal is selected.
11539 */
11540 gboolean
vte_terminal_get_has_selection(VteTerminal * terminal)11541 vte_terminal_get_has_selection(VteTerminal *terminal)
11542 {
11543 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
11544 return terminal->pvt->has_selection;
11545 }
11546
11547 static void
vte_terminal_update_cursor_blinks_internal(VteTerminal * terminal)11548 vte_terminal_update_cursor_blinks_internal(VteTerminal *terminal)
11549 {
11550 VteTerminalPrivate *pvt = terminal->pvt;
11551 gboolean blink = FALSE;
11552
11553 switch (_vte_terminal_decscusr_cursor_blink(terminal)) {
11554 case VTE_CURSOR_BLINK_SYSTEM:
11555 g_object_get(gtk_widget_get_settings(GTK_WIDGET(terminal)),
11556 "gtk-cursor-blink",
11557 &blink, NULL);
11558 break;
11559 case VTE_CURSOR_BLINK_ON:
11560 blink = TRUE;
11561 break;
11562 case VTE_CURSOR_BLINK_OFF:
11563 blink = FALSE;
11564 break;
11565 }
11566
11567 if (pvt->cursor_blinks == blink)
11568 return;
11569
11570 pvt->cursor_blinks = blink;
11571 _vte_check_cursor_blink (terminal);
11572 }
11573
11574 /**
11575 * vte_terminal_set_cursor_blink_mode:
11576 * @terminal: a #VteTerminal
11577 * @mode: the #VteCursorBlinkMode to use
11578 *
11579 * Sets whether or not the cursor will blink. Using %VTE_CURSOR_BLINK_SYSTEM
11580 * will use the #GtkSettings::gtk-cursor-blink setting.
11581 */
11582 void
vte_terminal_set_cursor_blink_mode(VteTerminal * terminal,VteCursorBlinkMode mode)11583 vte_terminal_set_cursor_blink_mode(VteTerminal *terminal, VteCursorBlinkMode mode)
11584 {
11585 VteTerminalPrivate *pvt;
11586
11587 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11588 pvt = terminal->pvt;
11589
11590 if (pvt->cursor_blink_mode == mode)
11591 return;
11592
11593 pvt->cursor_blink_mode = mode;
11594
11595 vte_terminal_update_cursor_blinks_internal(terminal);
11596
11597 g_object_notify(G_OBJECT(terminal), "cursor-blink-mode");
11598 }
11599
11600 /**
11601 * vte_terminal_get_cursor_blink_mode:
11602 * @terminal: a #VteTerminal
11603 *
11604 * Returns the currently set cursor blink mode.
11605 *
11606 * Return value: cursor blink mode.
11607 */
11608 VteCursorBlinkMode
vte_terminal_get_cursor_blink_mode(VteTerminal * terminal)11609 vte_terminal_get_cursor_blink_mode(VteTerminal *terminal)
11610 {
11611 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), VTE_CURSOR_BLINK_SYSTEM);
11612
11613 return terminal->pvt->cursor_blink_mode;
11614 }
11615
11616 /**
11617 * vte_terminal_set_cursor_shape:
11618 * @terminal: a #VteTerminal
11619 * @shape: the #VteCursorShape to use
11620 *
11621 * Sets the shape of the cursor drawn.
11622 */
11623 void
vte_terminal_set_cursor_shape(VteTerminal * terminal,VteCursorShape shape)11624 vte_terminal_set_cursor_shape(VteTerminal *terminal, VteCursorShape shape)
11625 {
11626 VteTerminalPrivate *pvt;
11627
11628 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11629 pvt = terminal->pvt;
11630
11631 if (pvt->cursor_shape == shape)
11632 return;
11633
11634 pvt->cursor_shape = shape;
11635 _vte_invalidate_cursor_once(terminal, FALSE);
11636
11637 g_object_notify(G_OBJECT(terminal), "cursor-shape");
11638 }
11639
11640 /**
11641 * vte_terminal_get_cursor_shape:
11642 * @terminal: a #VteTerminal
11643 *
11644 * Returns the currently set cursor shape.
11645 *
11646 * Return value: cursor shape.
11647 */
11648 VteCursorShape
vte_terminal_get_cursor_shape(VteTerminal * terminal)11649 vte_terminal_get_cursor_shape(VteTerminal *terminal)
11650 {
11651 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), VTE_CURSOR_SHAPE_BLOCK);
11652
11653 return terminal->pvt->cursor_shape;
11654 }
11655
11656 /* DECSCUSR set cursor style */
11657 void
_vte_terminal_set_cursor_style(VteTerminal * terminal,VteCursorStyle style)11658 _vte_terminal_set_cursor_style(VteTerminal *terminal, VteCursorStyle style)
11659 {
11660 VteTerminalPrivate *pvt;
11661
11662 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11663 pvt = terminal->pvt;
11664
11665 if (pvt->cursor_style == style)
11666 return;
11667
11668 pvt->cursor_style = style;
11669
11670 vte_terminal_update_cursor_blinks_internal(terminal);
11671
11672 /* and this will also make cursor shape match the DECSCUSR style */
11673 _vte_invalidate_cursor_once(terminal, FALSE);
11674 }
11675
11676 /*
11677 * _vte_terminal_decscusr_cursor_blink:
11678 * @terminal: a #VteTerminal
11679 *
11680 * Returns the cursor blink mode set by DECSCUSR. If DECSCUSR was never
11681 * called, or it set the blink mode to terminal default, this returns the
11682 * value set via API or in dconf. Internal use only.
11683 *
11684 * Return value: cursor blink mode
11685 */
11686 static VteCursorBlinkMode
_vte_terminal_decscusr_cursor_blink(VteTerminal * terminal)11687 _vte_terminal_decscusr_cursor_blink(VteTerminal *terminal)
11688 {
11689 switch (terminal->pvt->cursor_style) {
11690 default:
11691 case VTE_CURSOR_STYLE_TERMINAL_DEFAULT:
11692 return terminal->pvt->cursor_blink_mode;
11693 case VTE_CURSOR_STYLE_BLINK_BLOCK:
11694 case VTE_CURSOR_STYLE_BLINK_UNDERLINE:
11695 case VTE_CURSOR_STYLE_BLINK_IBEAM:
11696 return VTE_CURSOR_BLINK_ON;
11697 case VTE_CURSOR_STYLE_STEADY_BLOCK:
11698 case VTE_CURSOR_STYLE_STEADY_UNDERLINE:
11699 case VTE_CURSOR_STYLE_STEADY_IBEAM:
11700 return VTE_CURSOR_BLINK_OFF;
11701 }
11702 }
11703
11704 /*
11705 * _vte_terminal_decscusr_cursor_shape:
11706 * @terminal: a #VteTerminal
11707 *
11708 * Returns the cursor shape set by DECSCUSR. If DECSCUSR was never called,
11709 * or it set the cursor shape to terminal default, this returns the value
11710 * set via API. Internal use only.
11711 *
11712 * Return value: cursor shape
11713 */
11714 static VteCursorShape
_vte_terminal_decscusr_cursor_shape(VteTerminal * terminal)11715 _vte_terminal_decscusr_cursor_shape(VteTerminal *terminal)
11716 {
11717 switch (terminal->pvt->cursor_style) {
11718 default:
11719 case VTE_CURSOR_STYLE_TERMINAL_DEFAULT:
11720 return terminal->pvt->cursor_shape;
11721 case VTE_CURSOR_STYLE_BLINK_BLOCK:
11722 case VTE_CURSOR_STYLE_STEADY_BLOCK:
11723 return VTE_CURSOR_SHAPE_BLOCK;
11724 case VTE_CURSOR_STYLE_BLINK_UNDERLINE:
11725 case VTE_CURSOR_STYLE_STEADY_UNDERLINE:
11726 return VTE_CURSOR_SHAPE_UNDERLINE;
11727 case VTE_CURSOR_STYLE_BLINK_IBEAM:
11728 case VTE_CURSOR_STYLE_STEADY_IBEAM:
11729 return VTE_CURSOR_SHAPE_IBEAM;
11730 }
11731 }
11732
11733 /**
11734 * vte_terminal_set_scrollback_lines:
11735 * @terminal: a #VteTerminal
11736 * @lines: the length of the history buffer
11737 *
11738 * Sets the length of the scrollback buffer used by the terminal. The size of
11739 * the scrollback buffer will be set to the larger of this value and the number
11740 * of visible rows the widget can display, so 0 can safely be used to disable
11741 * scrollback.
11742 *
11743 * A negative value means "infinite scrollback".
11744 *
11745 * Note that this setting only affects the normal screen buffer.
11746 * For terminal types which have an alternate screen buffer, no scrollback is
11747 * allowed on the alternate screen buffer.
11748 */
11749 void
vte_terminal_set_scrollback_lines(VteTerminal * terminal,glong lines)11750 vte_terminal_set_scrollback_lines(VteTerminal *terminal, glong lines)
11751 {
11752 VteTerminalPrivate *pvt;
11753 GObject *object;
11754 glong scroll_delta, low, high, next;
11755 VteScreen *screen;
11756
11757 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11758
11759 if (lines < 0)
11760 lines = G_MAXLONG;
11761
11762 object = G_OBJECT(terminal);
11763 pvt = terminal->pvt;
11764
11765 #if 0
11766 /* FIXME: this breaks the scrollbar range, bug #562511 */
11767 if (lines == pvt->scrollback_lines)
11768 return;
11769 #endif
11770
11771 g_object_freeze_notify(object);
11772
11773 _vte_debug_print (VTE_DEBUG_MISC,
11774 "Setting scrollback lines to %ld\n", lines);
11775
11776 pvt->scrollback_lines = lines;
11777
11778 /* The main screen gets the full scrollback buffer. */
11779 screen = &terminal->pvt->normal_screen;
11780 lines = MAX (lines, terminal->pvt->row_count);
11781 next = MAX (terminal->pvt->cursor.row + 1,
11782 _vte_ring_next (screen->row_data));
11783 _vte_ring_resize (screen->row_data, lines);
11784 low = _vte_ring_delta (screen->row_data);
11785 high = lines + MIN (G_MAXLONG - lines, low - terminal->pvt->row_count + 1);
11786 screen->insert_delta = CLAMP (screen->insert_delta, low, high);
11787 screen->scroll_delta = CLAMP (screen->scroll_delta, low, screen->insert_delta);
11788 next = MIN (next, screen->insert_delta + terminal->pvt->row_count);
11789 if (_vte_ring_next (screen->row_data) > next){
11790 _vte_ring_shrink (screen->row_data, next - low);
11791 }
11792
11793 /* The alternate screen isn't allowed to scroll at all. */
11794 screen = &terminal->pvt->alternate_screen;
11795 _vte_ring_resize (screen->row_data, terminal->pvt->row_count);
11796 screen->scroll_delta = _vte_ring_delta (screen->row_data);
11797 screen->insert_delta = _vte_ring_delta (screen->row_data);
11798 if (_vte_ring_next (screen->row_data) > screen->insert_delta + terminal->pvt->row_count){
11799 _vte_ring_shrink (screen->row_data, terminal->pvt->row_count);
11800 }
11801
11802 /* Adjust the scrollbar to the new location. */
11803 /* Hack: force a change in scroll_delta even if the value remains, so that
11804 vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 676075. */
11805 scroll_delta = terminal->pvt->screen->scroll_delta;
11806 terminal->pvt->screen->scroll_delta = -1;
11807 vte_terminal_queue_adjustment_value_changed (terminal, scroll_delta);
11808 _vte_terminal_adjust_adjustments_full (terminal);
11809
11810 g_object_notify(object, "scrollback-lines");
11811
11812 g_object_thaw_notify(object);
11813 }
11814
11815 /**
11816 * vte_terminal_set_backspace_binding:
11817 * @terminal: a #VteTerminal
11818 * @binding: a #VteEraseBinding for the backspace key
11819 *
11820 * Modifies the terminal's backspace key binding, which controls what
11821 * string or control sequence the terminal sends to its child when the user
11822 * presses the backspace key.
11823 */
11824 void
vte_terminal_set_backspace_binding(VteTerminal * terminal,VteEraseBinding binding)11825 vte_terminal_set_backspace_binding(VteTerminal *terminal,
11826 VteEraseBinding binding)
11827 {
11828 VteTerminalPrivate *pvt;
11829
11830 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11831
11832 pvt = terminal->pvt;
11833
11834 if (binding == pvt->backspace_binding)
11835 return;
11836
11837 /* FIXME: should we set the pty mode to match? */
11838 pvt->backspace_binding = binding;
11839
11840 g_object_notify(G_OBJECT(terminal), "backspace-binding");
11841 }
11842
11843 /**
11844 * vte_terminal_set_delete_binding:
11845 * @terminal: a #VteTerminal
11846 * @binding: a #VteEraseBinding for the delete key
11847 *
11848 * Modifies the terminal's delete key binding, which controls what
11849 * string or control sequence the terminal sends to its child when the user
11850 * presses the delete key.
11851 */
11852 void
vte_terminal_set_delete_binding(VteTerminal * terminal,VteEraseBinding binding)11853 vte_terminal_set_delete_binding(VteTerminal *terminal,
11854 VteEraseBinding binding)
11855 {
11856 VteTerminalPrivate *pvt;
11857
11858 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11859
11860 pvt = terminal->pvt;
11861
11862 if (binding == pvt->delete_binding)
11863 return;
11864
11865 pvt->delete_binding = binding;
11866
11867 g_object_notify(G_OBJECT(terminal), "delete-binding");
11868 }
11869
11870 /**
11871 * vte_terminal_set_mouse_autohide:
11872 * @terminal: a #VteTerminal
11873 * @setting: whether the mouse pointer should autohide
11874 *
11875 * Changes the value of the terminal's mouse autohide setting. When autohiding
11876 * is enabled, the mouse cursor will be hidden when the user presses a key and
11877 * shown when the user moves the mouse. This setting can be read using
11878 * vte_terminal_get_mouse_autohide().
11879 */
11880 void
vte_terminal_set_mouse_autohide(VteTerminal * terminal,gboolean setting)11881 vte_terminal_set_mouse_autohide(VteTerminal *terminal, gboolean setting)
11882 {
11883 VteTerminalPrivate *pvt;
11884
11885 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11886
11887 pvt = terminal->pvt;
11888
11889 setting = setting != FALSE;
11890 if (setting == pvt->mouse_autohide)
11891 return;
11892
11893 pvt->mouse_autohide = setting;
11894
11895 g_object_notify(G_OBJECT(terminal), "pointer-autohide");
11896 }
11897
11898 /**
11899 * vte_terminal_get_mouse_autohide:
11900 * @terminal: a #VteTerminal
11901 *
11902 * Determines the value of the terminal's mouse autohide setting. When
11903 * autohiding is enabled, the mouse cursor will be hidden when the user presses
11904 * a key and shown when the user moves the mouse. This setting can be changed
11905 * using vte_terminal_set_mouse_autohide().
11906 *
11907 * Returns: %TRUE if autohiding is enabled, %FALSE if not
11908 */
11909 gboolean
vte_terminal_get_mouse_autohide(VteTerminal * terminal)11910 vte_terminal_get_mouse_autohide(VteTerminal *terminal)
11911 {
11912 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
11913 return terminal->pvt->mouse_autohide;
11914 }
11915
11916 /**
11917 * vte_terminal_reset:
11918 * @terminal: a #VteTerminal
11919 * @clear_tabstops: whether to reset tabstops
11920 * @clear_history: whether to empty the terminal's scrollback buffer
11921 *
11922 * Resets as much of the terminal's internal state as possible, discarding any
11923 * unprocessed input data, resetting character attributes, cursor state,
11924 * national character set state, status line, terminal modes (insert/delete),
11925 * selection state, and encoding.
11926 *
11927 */
11928 void
vte_terminal_reset(VteTerminal * terminal,gboolean clear_tabstops,gboolean clear_history)11929 vte_terminal_reset(VteTerminal *terminal,
11930 gboolean clear_tabstops,
11931 gboolean clear_history)
11932 {
11933 VteTerminalPrivate *pvt;
11934 int i;
11935
11936 g_return_if_fail(VTE_IS_TERMINAL(terminal));
11937
11938 pvt = terminal->pvt;
11939
11940 g_object_freeze_notify(G_OBJECT(terminal));
11941
11942 /* Clear the output buffer. */
11943 _vte_byte_array_clear(pvt->outgoing);
11944 /* Reset charset substitution state. */
11945 _vte_iso2022_state_free(pvt->iso2022);
11946 pvt->iso2022 = _vte_iso2022_state_new(NULL);
11947 _vte_iso2022_state_set_codeset(pvt->iso2022,
11948 pvt->encoding);
11949 /* Reset keypad/cursor key modes. */
11950 pvt->keypad_mode = VTE_KEYMODE_NORMAL;
11951 pvt->cursor_mode = VTE_KEYMODE_NORMAL;
11952 /* Enable autowrap. */
11953 pvt->autowrap = TRUE;
11954 /* Enable meta-sends-escape. */
11955 pvt->meta_sends_escape = TRUE;
11956 /* Disable margin bell. */
11957 pvt->margin_bell = FALSE;
11958 /* Disable DECCOLM mode. */
11959 pvt->deccolm_mode = FALSE;
11960 /* Reset saved settings. */
11961 if (pvt->dec_saved != NULL) {
11962 g_hash_table_destroy(pvt->dec_saved);
11963 pvt->dec_saved = g_hash_table_new(NULL, NULL);
11964 }
11965 /* Reset the color palette. Only the 256 indexed colors, not the special ones, as per xterm. */
11966 for (i = 0; i < 256; i++)
11967 terminal->pvt->palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
11968 /* Reset the default attributes. Reset the alternate attribute because
11969 * it's not a real attribute, but we need to treat it as one here. */
11970 _vte_terminal_set_default_attributes(terminal);
11971 /* Reset charset modes. */
11972 pvt->character_replacements[0] = VTE_CHARACTER_REPLACEMENT_NONE;
11973 pvt->character_replacements[1] = VTE_CHARACTER_REPLACEMENT_NONE;
11974 pvt->character_replacement = &pvt->character_replacements[0];
11975 /* Clear the scrollback buffers and reset the cursors. Switch to normal screen. */
11976 if (clear_history) {
11977 pvt->screen = &pvt->normal_screen;
11978 pvt->normal_screen.scroll_delta = pvt->normal_screen.insert_delta =
11979 _vte_ring_reset(pvt->normal_screen.row_data);
11980 pvt->alternate_screen.scroll_delta = pvt->alternate_screen.insert_delta =
11981 _vte_ring_reset(pvt->alternate_screen.row_data);
11982 pvt->cursor.row = pvt->screen->insert_delta;
11983 pvt->cursor.col = 0;
11984 /* Adjust the scrollbar to the new location. */
11985 /* Hack: force a change in scroll_delta even if the value remains, so that
11986 vte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 730599. */
11987 pvt->screen->scroll_delta = -1;
11988 vte_terminal_queue_adjustment_value_changed (terminal, pvt->screen->insert_delta);
11989 _vte_terminal_adjust_adjustments_full (terminal);
11990 }
11991 /* DECSCUSR cursor style */
11992 pvt->cursor_style = VTE_CURSOR_STYLE_TERMINAL_DEFAULT;
11993 /* Do more stuff we refer to as a "full" reset. */
11994 if (clear_tabstops) {
11995 vte_terminal_set_default_tabstops(terminal);
11996 }
11997 /* Reset restricted scrolling regions, leave insert mode, make
11998 * the cursor visible again. */
11999 pvt->scrolling_restricted = FALSE;
12000 pvt->sendrecv_mode = TRUE;
12001 pvt->insert_mode = FALSE;
12002 pvt->linefeed_mode = FALSE;
12003 pvt->origin_mode = FALSE;
12004 pvt->reverse_mode = FALSE;
12005 pvt->cursor_visible = TRUE;
12006 /* For some reason, xterm doesn't reset alternateScroll, but we do. */
12007 pvt->alternate_screen_scroll = TRUE;
12008 /* Reset the encoding. */
12009 vte_terminal_set_encoding(terminal, NULL /* UTF-8 */, NULL);
12010 g_assert_cmpstr(pvt->encoding, ==, "UTF-8");
12011 /* Reset selection. */
12012 vte_terminal_deselect_all(terminal);
12013 pvt->has_selection = FALSE;
12014 pvt->selecting = FALSE;
12015 pvt->selecting_restart = FALSE;
12016 pvt->selecting_had_delta = FALSE;
12017 if (pvt->selection != NULL) {
12018 g_free(pvt->selection);
12019 pvt->selection = NULL;
12020 memset(&pvt->selection_origin, 0,
12021 sizeof(pvt->selection_origin));
12022 memset(&pvt->selection_last, 0,
12023 sizeof(pvt->selection_last));
12024 memset(&pvt->selection_start, 0,
12025 sizeof(pvt->selection_start));
12026 memset(&pvt->selection_end, 0,
12027 sizeof(pvt->selection_end));
12028 }
12029 /* Reset mouse motion events. */
12030 pvt->mouse_tracking_mode = MOUSE_TRACKING_NONE;
12031 pvt->mouse_pressed_buttons = 0;
12032 pvt->mouse_handled_buttons = 0;
12033 pvt->mouse_last_x = 0;
12034 pvt->mouse_last_y = 0;
12035 pvt->mouse_xterm_extension = FALSE;
12036 pvt->mouse_urxvt_extension = FALSE;
12037 pvt->mouse_smooth_scroll_delta = 0.;
12038 /* Clear modifiers. */
12039 pvt->modifiers = 0;
12040 /* Reset miscellaneous stuff. */
12041 pvt->bracketed_paste_mode = FALSE;
12042 /* Reset the saved cursor. */
12043 _vte_terminal_save_cursor(terminal, &terminal->pvt->normal_screen);
12044 _vte_terminal_save_cursor(terminal, &terminal->pvt->alternate_screen);
12045 /* Cause everything to be redrawn (or cleared). */
12046 vte_terminal_maybe_scroll_to_bottom(terminal);
12047 _vte_invalidate_all(terminal);
12048
12049 g_object_thaw_notify(G_OBJECT(terminal));
12050 }
12051
12052 /**
12053 * vte_terminal_get_char_width:
12054 * @terminal: a #VteTerminal
12055 *
12056 * Returns: the width of a character cell
12057 */
12058 glong
vte_terminal_get_char_width(VteTerminal * terminal)12059 vte_terminal_get_char_width(VteTerminal *terminal)
12060 {
12061 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
12062 vte_terminal_ensure_font (terminal);
12063 return terminal->pvt->char_width;
12064 }
12065
12066 /**
12067 * vte_terminal_get_char_height:
12068 * @terminal: a #VteTerminal
12069 *
12070 * Returns: the height of a character cell
12071 */
12072 glong
vte_terminal_get_char_height(VteTerminal * terminal)12073 vte_terminal_get_char_height(VteTerminal *terminal)
12074 {
12075 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
12076 vte_terminal_ensure_font (terminal);
12077 return terminal->pvt->char_height;
12078 }
12079
12080 /**
12081 * vte_terminal_get_row_count:
12082 * @terminal: a #VteTerminal
12083 *
12084 *
12085 * Returns: the number of rows
12086 */
12087 glong
vte_terminal_get_row_count(VteTerminal * terminal)12088 vte_terminal_get_row_count(VteTerminal *terminal)
12089 {
12090 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
12091 return terminal->pvt->row_count;
12092 }
12093
12094 /**
12095 * vte_terminal_get_column_count:
12096 * @terminal: a #VteTerminal
12097 *
12098 * Returns: the number of columns
12099 */
12100 glong
vte_terminal_get_column_count(VteTerminal * terminal)12101 vte_terminal_get_column_count(VteTerminal *terminal)
12102 {
12103 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
12104 return terminal->pvt->column_count;
12105 }
12106
12107 /**
12108 * vte_terminal_get_window_title:
12109 * @terminal: a #VteTerminal
12110 *
12111 * Returns: (transfer none): the window title
12112 */
12113 const char *
vte_terminal_get_window_title(VteTerminal * terminal)12114 vte_terminal_get_window_title(VteTerminal *terminal)
12115 {
12116 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), "");
12117 return terminal->pvt->window_title;
12118 }
12119
12120 /**
12121 * vte_terminal_get_icon_title:
12122 * @terminal: a #VteTerminal
12123 *
12124 * Returns: (transfer none): the icon title
12125 */
12126 const char *
vte_terminal_get_icon_title(VteTerminal * terminal)12127 vte_terminal_get_icon_title(VteTerminal *terminal)
12128 {
12129 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), "");
12130 return terminal->pvt->icon_title;
12131 }
12132
12133 /**
12134 * vte_terminal_get_current_directory_uri:
12135 * @terminal: a #VteTerminal
12136 *
12137 * Returns: (transfer none): the URI of the current directory of the
12138 * process running in the terminal, or %NULL
12139 */
12140 const char *
vte_terminal_get_current_directory_uri(VteTerminal * terminal)12141 vte_terminal_get_current_directory_uri(VteTerminal *terminal)
12142 {
12143 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
12144 return terminal->pvt->current_directory_uri;
12145 }
12146
12147 /**
12148 * vte_terminal_get_current_file_uri:
12149 * @terminal: a #VteTerminal
12150 *
12151 * Returns: (transfer none): the URI of the current file the
12152 * process running in the terminal is operating on, or %NULL if
12153 * not set
12154 */
12155 const char *
vte_terminal_get_current_file_uri(VteTerminal * terminal)12156 vte_terminal_get_current_file_uri(VteTerminal *terminal)
12157 {
12158 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
12159 return terminal->pvt->current_file_uri;
12160 }
12161
12162 /**
12163 * vte_terminal_set_pty:
12164 * @terminal: a #VteTerminal
12165 * @pty: (allow-none): a #VtePty, or %NULL
12166 *
12167 * Sets @pty as the PTY to use in @terminal.
12168 * Use %NULL to unset the PTY.
12169 */
12170 void
vte_terminal_set_pty(VteTerminal * terminal,VtePty * pty)12171 vte_terminal_set_pty(VteTerminal *terminal,
12172 VtePty *pty)
12173 {
12174 VteTerminalPrivate *pvt;
12175 GObject *object;
12176 long flags;
12177 int pty_master;
12178
12179 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12180 g_return_if_fail(pty == NULL || VTE_IS_PTY(pty));
12181
12182 pvt = terminal->pvt;
12183 if (pvt->pty == pty)
12184 return;
12185
12186 object = G_OBJECT(terminal);
12187
12188 g_object_freeze_notify(object);
12189
12190 if (pvt->pty != NULL) {
12191 _vte_terminal_disconnect_pty_read(terminal);
12192 _vte_terminal_disconnect_pty_write(terminal);
12193
12194 if (terminal->pvt->pty_channel != NULL) {
12195 g_io_channel_unref (terminal->pvt->pty_channel);
12196 pvt->pty_channel = NULL;
12197 }
12198
12199 /* Take one last shot at processing whatever data is pending,
12200 * then flush the buffers in case we're about to run a new
12201 * command, disconnecting the timeout. */
12202 if (terminal->pvt->incoming != NULL) {
12203 vte_terminal_process_incoming(terminal);
12204 _vte_incoming_chunks_release (terminal->pvt->incoming);
12205 terminal->pvt->incoming = NULL;
12206 terminal->pvt->input_bytes = 0;
12207 }
12208 g_array_set_size(terminal->pvt->pending, 0);
12209 vte_terminal_stop_processing (terminal);
12210
12211 /* Clear the outgoing buffer as well. */
12212 _vte_byte_array_clear(terminal->pvt->outgoing);
12213
12214 vte_pty_close(pvt->pty);
12215 g_object_unref(pvt->pty);
12216 pvt->pty = NULL;
12217 }
12218
12219 if (pty == NULL) {
12220 pvt->pty = NULL;
12221 g_object_notify(object, "pty");
12222 g_object_thaw_notify(object);
12223 return;
12224 }
12225
12226 pvt->pty = g_object_ref(pty);
12227 pty_master = vte_pty_get_fd(pvt->pty);
12228
12229 pvt->pty_channel = g_io_channel_unix_new (pty_master);
12230 g_io_channel_set_close_on_unref (pvt->pty_channel, FALSE);
12231
12232 /* FIXMEchpe: vte_pty_open_unix98 does the inverse ... */
12233 /* Set the pty to be non-blocking. */
12234 flags = fcntl(pty_master, F_GETFL);
12235 if ((flags & O_NONBLOCK) == 0) {
12236 fcntl(pty_master, F_SETFL, flags | O_NONBLOCK);
12237 }
12238
12239 vte_terminal_set_size(terminal,
12240 terminal->pvt->column_count,
12241 terminal->pvt->row_count);
12242
12243 _vte_terminal_setup_utf8 (terminal);
12244
12245 /* Open channels to listen for input on. */
12246 _vte_terminal_connect_pty_read (terminal);
12247
12248 g_object_notify(object, "pty");
12249
12250 g_object_thaw_notify(object);
12251 }
12252
12253 /**
12254 * vte_terminal_get_pty:
12255 * @terminal: a #VteTerminal
12256 *
12257 * Returns the #VtePty of @terminal.
12258 *
12259 * Returns: (transfer none): a #VtePty, or %NULL
12260 */
12261 VtePty *
vte_terminal_get_pty(VteTerminal * terminal)12262 vte_terminal_get_pty(VteTerminal *terminal)
12263 {
12264 g_return_val_if_fail (VTE_IS_TERMINAL (terminal), NULL);
12265
12266 return terminal->pvt->pty;
12267 }
12268
12269 /* We need this bit of glue to ensure that accessible objects will always
12270 * get signals. */
12271 void
_vte_terminal_accessible_ref(VteTerminal * terminal)12272 _vte_terminal_accessible_ref(VteTerminal *terminal)
12273 {
12274 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12275 terminal->pvt->accessible_emit = TRUE;
12276 }
12277
12278 char *
_vte_terminal_get_selection(VteTerminal * terminal)12279 _vte_terminal_get_selection(VteTerminal *terminal)
12280 {
12281 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
12282
12283 return g_strdup (terminal->pvt->selection);
12284 }
12285
12286 void
_vte_terminal_get_start_selection(VteTerminal * terminal,long * col,long * row)12287 _vte_terminal_get_start_selection(VteTerminal *terminal, long *col, long *row)
12288 {
12289 VteVisualPosition ss;
12290
12291 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12292
12293 ss = terminal->pvt->selection_start;
12294
12295 if (col) {
12296 *col = ss.col;
12297 }
12298
12299 if (row) {
12300 *row = ss.row;
12301 }
12302 }
12303
12304 void
_vte_terminal_get_end_selection(VteTerminal * terminal,long * col,long * row)12305 _vte_terminal_get_end_selection(VteTerminal *terminal, long *col, long *row)
12306 {
12307 VteVisualPosition se;
12308
12309 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12310
12311 se = terminal->pvt->selection_end;
12312
12313 if (col) {
12314 *col = se.col;
12315 }
12316
12317 if (row) {
12318 *row = se.row;
12319 }
12320 }
12321
12322 void
_vte_terminal_select_text(VteTerminal * terminal,long start_col,long start_row,long end_col,long end_row,int start_offset,int end_offset)12323 _vte_terminal_select_text(VteTerminal *terminal,
12324 long start_col, long start_row,
12325 long end_col, long end_row,
12326 int start_offset, int end_offset)
12327 {
12328 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12329
12330 vte_terminal_deselect_all (terminal);
12331
12332 terminal->pvt->selection_type = selection_type_char;
12333 terminal->pvt->selecting_had_delta = TRUE;
12334 terminal->pvt->selection_start.col = start_col;
12335 terminal->pvt->selection_start.row = start_row;
12336 terminal->pvt->selection_end.col = end_col;
12337 terminal->pvt->selection_end.row = end_row;
12338 vte_terminal_copy_primary(terminal);
12339 vte_terminal_emit_selection_changed(terminal);
12340
12341 _vte_invalidate_region (terminal,
12342 MIN (start_col, end_col), MAX (start_col, end_col),
12343 MIN (start_row, end_row), MAX (start_row, end_row),
12344 FALSE);
12345
12346 }
12347
12348 void
_vte_terminal_remove_selection(VteTerminal * terminal)12349 _vte_terminal_remove_selection(VteTerminal *terminal)
12350 {
12351 vte_terminal_deselect_all (terminal);
12352 }
12353
12354 static void
_vte_terminal_select_empty_at(VteTerminal * terminal,long col,long row)12355 _vte_terminal_select_empty_at(VteTerminal *terminal,
12356 long col, long row)
12357 {
12358 _vte_terminal_select_text(terminal, col, row, col - 1, row, 0, 0);
12359 }
12360
12361 static void
add_update_timeout(VteTerminal * terminal)12362 add_update_timeout (VteTerminal *terminal)
12363 {
12364 if (update_timeout_tag == 0) {
12365 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12366 "Starting update timeout\n");
12367 update_timeout_tag =
12368 g_timeout_add_full (GDK_PRIORITY_REDRAW,
12369 VTE_UPDATE_TIMEOUT,
12370 update_timeout, NULL,
12371 NULL);
12372 }
12373 if (in_process_timeout == FALSE &&
12374 process_timeout_tag != 0) {
12375 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12376 "Removing process timeout\n");
12377 g_source_remove (process_timeout_tag);
12378 process_timeout_tag = 0;
12379 }
12380 if (terminal->pvt->active == NULL) {
12381 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12382 "Adding terminal to active list\n");
12383 terminal->pvt->active = active_terminals =
12384 g_list_prepend (active_terminals, terminal);
12385 }
12386
12387 }
12388 static void
reset_update_regions(VteTerminal * terminal)12389 reset_update_regions (VteTerminal *terminal)
12390 {
12391 if (terminal->pvt->update_regions != NULL) {
12392 g_slist_foreach (terminal->pvt->update_regions,
12393 (GFunc)cairo_region_destroy, NULL);
12394 g_slist_free (terminal->pvt->update_regions);
12395 terminal->pvt->update_regions = NULL;
12396 }
12397 /* the invalidated_all flag also marks whether to skip processing
12398 * due to the widget being invisible */
12399 terminal->pvt->invalidated_all =
12400 terminal->pvt->visibility_state==GDK_VISIBILITY_FULLY_OBSCURED;
12401 }
12402
12403 static void
remove_from_active_list(VteTerminal * terminal)12404 remove_from_active_list (VteTerminal *terminal)
12405 {
12406 if (terminal->pvt->active != NULL
12407 && terminal->pvt->update_regions == NULL) {
12408 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12409 "Removing terminal from active list\n");
12410 active_terminals = g_list_delete_link (active_terminals,
12411 terminal->pvt->active);
12412 terminal->pvt->active = NULL;
12413
12414 if (active_terminals == NULL) {
12415 if (in_process_timeout == FALSE &&
12416 process_timeout_tag != 0) {
12417 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12418 "Removing process timeout\n");
12419 g_source_remove (process_timeout_tag);
12420 process_timeout_tag = 0;
12421 }
12422 if (in_update_timeout == FALSE &&
12423 update_timeout_tag != 0) {
12424 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12425 "Removing update timeout\n");
12426 g_source_remove (update_timeout_tag);
12427 update_timeout_tag = 0;
12428 }
12429 }
12430 }
12431 }
12432 static void
remove_update_timeout(VteTerminal * terminal)12433 remove_update_timeout (VteTerminal *terminal)
12434 {
12435 reset_update_regions (terminal);
12436 remove_from_active_list (terminal);
12437 }
12438
12439 static void
vte_terminal_add_process_timeout(VteTerminal * terminal)12440 vte_terminal_add_process_timeout (VteTerminal *terminal)
12441 {
12442 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12443 "Adding terminal to active list\n");
12444 terminal->pvt->active = active_terminals =
12445 g_list_prepend (active_terminals, terminal);
12446 if (update_timeout_tag == 0 &&
12447 process_timeout_tag == 0) {
12448 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12449 "Starting process timeout\n");
12450 process_timeout_tag =
12451 g_timeout_add (VTE_DISPLAY_TIMEOUT,
12452 process_timeout, NULL);
12453 }
12454 }
12455 static inline gboolean
vte_terminal_is_processing(VteTerminal * terminal)12456 vte_terminal_is_processing (VteTerminal *terminal)
12457 {
12458 return terminal->pvt->active != NULL;
12459 }
12460 static inline void
vte_terminal_start_processing(VteTerminal * terminal)12461 vte_terminal_start_processing (VteTerminal *terminal)
12462 {
12463 if (!vte_terminal_is_processing (terminal)) {
12464 vte_terminal_add_process_timeout (terminal);
12465 }
12466 }
12467
12468 static void
vte_terminal_stop_processing(VteTerminal * terminal)12469 vte_terminal_stop_processing (VteTerminal *terminal)
12470 {
12471 remove_from_active_list (terminal);
12472 }
12473
12474 static inline gboolean
need_processing(VteTerminal * terminal)12475 need_processing (VteTerminal *terminal)
12476 {
12477 return _vte_incoming_chunks_length (terminal->pvt->incoming) != 0;
12478 }
12479
12480 /* Emit an "icon-title-changed" signal. */
12481 static void
vte_terminal_emit_icon_title_changed(VteTerminal * terminal)12482 vte_terminal_emit_icon_title_changed(VteTerminal *terminal)
12483 {
12484 _vte_debug_print(VTE_DEBUG_SIGNALS,
12485 "Emitting `icon-title-changed'.\n");
12486 g_signal_emit_by_name(terminal, "icon-title-changed");
12487 }
12488
12489 /* Emit a "window-title-changed" signal. */
12490 static void
vte_terminal_emit_window_title_changed(VteTerminal * terminal)12491 vte_terminal_emit_window_title_changed(VteTerminal *terminal)
12492 {
12493 _vte_debug_print(VTE_DEBUG_SIGNALS,
12494 "Emitting `window-title-changed'.\n");
12495 g_signal_emit_by_name(terminal, "window-title-changed");
12496 }
12497
12498 static void
vte_terminal_emit_current_directory_uri_changed(VteTerminal * terminal)12499 vte_terminal_emit_current_directory_uri_changed(VteTerminal *terminal)
12500 {
12501 _vte_debug_print(VTE_DEBUG_SIGNALS,
12502 "Emitting `current-directory-uri-changed'.\n");
12503 g_signal_emit_by_name(terminal, "current-directory-uri-changed");
12504 }
12505
12506 static void
vte_terminal_emit_current_file_uri_changed(VteTerminal * terminal)12507 vte_terminal_emit_current_file_uri_changed(VteTerminal *terminal)
12508 {
12509 _vte_debug_print(VTE_DEBUG_SIGNALS,
12510 "Emitting `current-file-uri-changed'.\n");
12511 g_signal_emit_by_name(terminal, "current-file-uri-changed");
12512 }
12513
12514 static void
vte_terminal_emit_pending_signals(VteTerminal * terminal)12515 vte_terminal_emit_pending_signals(VteTerminal *terminal)
12516 {
12517 GObject *object;
12518 GdkWindow *window;
12519
12520 object = G_OBJECT (terminal);
12521 window = gtk_widget_get_window (&terminal->widget);
12522
12523 g_object_freeze_notify(object);
12524
12525 vte_terminal_emit_adjustment_changed (terminal);
12526
12527 if (terminal->pvt->window_title_changed) {
12528 g_free (terminal->pvt->window_title);
12529 terminal->pvt->window_title = terminal->pvt->window_title_changed;
12530 terminal->pvt->window_title_changed = NULL;
12531
12532 if (window)
12533 gdk_window_set_title (window, terminal->pvt->window_title);
12534 vte_terminal_emit_window_title_changed(terminal);
12535 g_object_notify(object, "window-title");
12536 }
12537
12538 if (terminal->pvt->icon_title_changed) {
12539 g_free (terminal->pvt->icon_title);
12540 terminal->pvt->icon_title = terminal->pvt->icon_title_changed;
12541 terminal->pvt->icon_title_changed = NULL;
12542
12543 if (window)
12544 gdk_window_set_icon_name (window, terminal->pvt->icon_title);
12545 vte_terminal_emit_icon_title_changed(terminal);
12546 g_object_notify(object, "icon-title");
12547 }
12548
12549 if (terminal->pvt->current_directory_uri_changed) {
12550 g_free (terminal->pvt->current_directory_uri);
12551 terminal->pvt->current_directory_uri = terminal->pvt->current_directory_uri_changed;
12552 terminal->pvt->current_directory_uri_changed = NULL;
12553
12554 vte_terminal_emit_current_directory_uri_changed(terminal);
12555 g_object_notify(object, "current-directory-uri");
12556 }
12557
12558 if (terminal->pvt->current_file_uri_changed) {
12559 g_free (terminal->pvt->current_file_uri);
12560 terminal->pvt->current_file_uri = terminal->pvt->current_file_uri_changed;
12561 terminal->pvt->current_file_uri_changed = NULL;
12562
12563 vte_terminal_emit_current_file_uri_changed(terminal);
12564 g_object_notify(object, "current-file-uri");
12565 }
12566
12567 /* Flush any pending "inserted" signals. */
12568 vte_terminal_emit_cursor_moved(terminal);
12569 vte_terminal_emit_pending_text_signals(terminal);
12570 vte_terminal_emit_contents_changed (terminal);
12571
12572 g_object_thaw_notify(object);
12573 }
12574
time_process_incoming(VteTerminal * terminal)12575 static void time_process_incoming (VteTerminal *terminal)
12576 {
12577 gdouble elapsed;
12578 glong target;
12579 g_timer_reset (process_timer);
12580 vte_terminal_process_incoming (terminal);
12581 elapsed = g_timer_elapsed (process_timer, NULL) * 1000;
12582 target = VTE_MAX_PROCESS_TIME / elapsed * terminal->pvt->input_bytes;
12583 terminal->pvt->max_input_bytes =
12584 (terminal->pvt->max_input_bytes + target) / 2;
12585 }
12586
12587
12588 /* This function is called after DISPLAY_TIMEOUT ms.
12589 * It makes sure initial output is never delayed by more than DISPLAY_TIMEOUT
12590 */
12591 static gboolean
process_timeout(gpointer data)12592 process_timeout (gpointer data)
12593 {
12594 GList *l, *next;
12595 gboolean again;
12596
12597 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12598 gdk_threads_enter();
12599 G_GNUC_END_IGNORE_DEPRECATIONS;
12600
12601 in_process_timeout = TRUE;
12602
12603 _vte_debug_print (VTE_DEBUG_WORK, "<");
12604 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12605 "Process timeout: %d active\n",
12606 g_list_length (active_terminals));
12607
12608 for (l = active_terminals; l != NULL; l = next) {
12609 VteTerminal *terminal = l->data;
12610 gboolean active = FALSE;
12611
12612 next = g_list_next (l);
12613
12614 if (l != active_terminals) {
12615 _vte_debug_print (VTE_DEBUG_WORK, "T");
12616 }
12617 if (terminal->pvt->pty_channel != NULL) {
12618 if (terminal->pvt->pty_input_active ||
12619 terminal->pvt->pty_input_source == 0) {
12620 terminal->pvt->pty_input_active = FALSE;
12621 vte_terminal_io_read (terminal->pvt->pty_channel,
12622 G_IO_IN, terminal);
12623 }
12624 _vte_terminal_enable_input_source (terminal);
12625 }
12626 if (need_processing (terminal)) {
12627 active = TRUE;
12628 if (VTE_MAX_PROCESS_TIME) {
12629 time_process_incoming (terminal);
12630 } else {
12631 vte_terminal_process_incoming(terminal);
12632 }
12633 terminal->pvt->input_bytes = 0;
12634 } else
12635 vte_terminal_emit_pending_signals (terminal);
12636 if (!active && terminal->pvt->update_regions == NULL) {
12637 if (terminal->pvt->active != NULL) {
12638 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12639 "Removing terminal from active list [process]\n");
12640 active_terminals = g_list_delete_link (
12641 active_terminals,
12642 terminal->pvt->active);
12643 terminal->pvt->active = NULL;
12644 }
12645 }
12646 }
12647
12648 _vte_debug_print (VTE_DEBUG_WORK, ">");
12649
12650 if (active_terminals && update_timeout_tag == 0) {
12651 again = TRUE;
12652 } else {
12653 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12654 "Stoping process timeout\n");
12655 process_timeout_tag = 0;
12656 again = FALSE;
12657 }
12658
12659 in_process_timeout = FALSE;
12660
12661 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12662 gdk_threads_leave();
12663 G_GNUC_END_IGNORE_DEPRECATIONS;
12664
12665 if (again) {
12666 /* Force us to relinquish the CPU as the child is running
12667 * at full tilt and making us run to keep up...
12668 */
12669 g_usleep (0);
12670 } else if (update_timeout_tag == 0) {
12671 /* otherwise free up memory used to capture incoming data */
12672 prune_chunks (10);
12673 }
12674
12675 return again;
12676 }
12677
12678
12679 static gboolean
update_regions(VteTerminal * terminal)12680 update_regions (VteTerminal *terminal)
12681 {
12682 GSList *l;
12683 cairo_region_t *region;
12684 GdkWindow *window;
12685
12686 if (G_UNLIKELY(!gtk_widget_get_realized(&terminal->widget)))
12687 return FALSE;
12688 if (terminal->pvt->visibility_state == GDK_VISIBILITY_FULLY_OBSCURED) {
12689 reset_update_regions (terminal);
12690 return FALSE;
12691 }
12692
12693 if (G_UNLIKELY (!terminal->pvt->update_regions))
12694 return FALSE;
12695
12696
12697 l = terminal->pvt->update_regions;
12698 if (g_slist_next (l) != NULL) {
12699 /* amalgamate into one super-region */
12700 region = cairo_region_create ();
12701 do {
12702 cairo_region_union (region, l->data);
12703 cairo_region_destroy (l->data);
12704 } while ((l = g_slist_next (l)) != NULL);
12705 } else {
12706 region = l->data;
12707 }
12708 g_slist_free (terminal->pvt->update_regions);
12709 terminal->pvt->update_regions = NULL;
12710 terminal->pvt->invalidated_all = FALSE;
12711
12712 /* and perform the merge with the window visible area */
12713 window = gtk_widget_get_window (&terminal->widget);
12714 gdk_window_invalidate_region (window, region, FALSE);
12715 gdk_window_process_updates (window, FALSE);
12716 cairo_region_destroy (region);
12717
12718 _vte_debug_print (VTE_DEBUG_WORK, "-");
12719
12720 return TRUE;
12721 }
12722
12723 static gboolean
update_repeat_timeout(gpointer data)12724 update_repeat_timeout (gpointer data)
12725 {
12726 GList *l, *next;
12727 gboolean again;
12728
12729 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12730 gdk_threads_enter();
12731 G_GNUC_END_IGNORE_DEPRECATIONS;
12732
12733 in_update_timeout = TRUE;
12734
12735 _vte_debug_print (VTE_DEBUG_WORK, "[");
12736 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12737 "Repeat timeout: %d active\n",
12738 g_list_length (active_terminals));
12739
12740 for (l = active_terminals; l != NULL; l = next) {
12741 VteTerminal *terminal = l->data;
12742
12743 next = g_list_next (l);
12744
12745 if (l != active_terminals) {
12746 _vte_debug_print (VTE_DEBUG_WORK, "T");
12747 }
12748 if (terminal->pvt->pty_channel != NULL) {
12749 if (terminal->pvt->pty_input_active ||
12750 terminal->pvt->pty_input_source == 0) {
12751 terminal->pvt->pty_input_active = FALSE;
12752 vte_terminal_io_read (terminal->pvt->pty_channel,
12753 G_IO_IN, terminal);
12754 }
12755 _vte_terminal_enable_input_source (terminal);
12756 }
12757 vte_terminal_emit_adjustment_changed (terminal);
12758 if (need_processing (terminal)) {
12759 if (VTE_MAX_PROCESS_TIME) {
12760 time_process_incoming (terminal);
12761 } else {
12762 vte_terminal_process_incoming (terminal);
12763 }
12764 terminal->pvt->input_bytes = 0;
12765 } else
12766 vte_terminal_emit_pending_signals (terminal);
12767
12768 again = update_regions (terminal);
12769 if (!again) {
12770 if (terminal->pvt->active != NULL) {
12771 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12772 "Removing terminal from active list [update]\n");
12773 active_terminals = g_list_delete_link (
12774 active_terminals,
12775 terminal->pvt->active);
12776 terminal->pvt->active = NULL;
12777 }
12778 }
12779 }
12780
12781
12782 if (active_terminals != NULL) {
12783 /* remove the idle source, and draw non-Terminals
12784 * (except for gdk/{directfb,quartz}!)
12785 */
12786 gdk_window_process_all_updates ();
12787 }
12788
12789 _vte_debug_print (VTE_DEBUG_WORK, "]");
12790
12791 /* We only stop the timer if no update request was received in this
12792 * past cycle. Technically, always stop this timer object and maybe
12793 * reinstall a new one because we need to delay by the amount of time
12794 * it took to repaint the screen: bug 730732.
12795 */
12796 if (active_terminals == NULL) {
12797 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12798 "Stoping update timeout\n");
12799 update_timeout_tag = 0;
12800 again = FALSE;
12801 } else {
12802 update_timeout_tag =
12803 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
12804 VTE_UPDATE_REPEAT_TIMEOUT,
12805 update_repeat_timeout, NULL,
12806 NULL);
12807 again = TRUE;
12808 }
12809
12810 in_update_timeout = FALSE;
12811
12812 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12813 gdk_threads_leave();
12814 G_GNUC_END_IGNORE_DEPRECATIONS;
12815
12816 if (again) {
12817 /* Force us to relinquish the CPU as the child is running
12818 * at full tilt and making us run to keep up...
12819 */
12820 g_usleep (0);
12821 } else {
12822 /* otherwise free up memory used to capture incoming data */
12823 prune_chunks (10);
12824 }
12825
12826 return FALSE; /* If we need to go again, we already have a new timer for that. */
12827 }
12828
12829 static gboolean
update_timeout(gpointer data)12830 update_timeout (gpointer data)
12831 {
12832 GList *l, *next;
12833 gboolean redraw = FALSE;
12834
12835 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12836 gdk_threads_enter();
12837 G_GNUC_END_IGNORE_DEPRECATIONS;
12838
12839 in_update_timeout = TRUE;
12840
12841 _vte_debug_print (VTE_DEBUG_WORK, "{");
12842 _vte_debug_print (VTE_DEBUG_TIMEOUT,
12843 "Update timeout: %d active\n",
12844 g_list_length (active_terminals));
12845
12846 if (process_timeout_tag != 0) {
12847 _vte_debug_print(VTE_DEBUG_TIMEOUT,
12848 "Removing process timeout\n");
12849 g_source_remove (process_timeout_tag);
12850 process_timeout_tag = 0;
12851 }
12852
12853 for (l = active_terminals; l != NULL; l = next) {
12854 VteTerminal *terminal = l->data;
12855
12856 next = g_list_next (l);
12857
12858 if (l != active_terminals) {
12859 _vte_debug_print (VTE_DEBUG_WORK, "T");
12860 }
12861 if (terminal->pvt->pty_channel != NULL) {
12862 if (terminal->pvt->pty_input_active ||
12863 terminal->pvt->pty_input_source == 0) {
12864 terminal->pvt->pty_input_active = FALSE;
12865 vte_terminal_io_read (terminal->pvt->pty_channel,
12866 G_IO_IN, terminal);
12867 }
12868 _vte_terminal_enable_input_source (terminal);
12869 }
12870 vte_terminal_emit_adjustment_changed (terminal);
12871 if (need_processing (terminal)) {
12872 if (VTE_MAX_PROCESS_TIME) {
12873 time_process_incoming (terminal);
12874 } else {
12875 vte_terminal_process_incoming (terminal);
12876 }
12877 terminal->pvt->input_bytes = 0;
12878 } else
12879 vte_terminal_emit_pending_signals (terminal);
12880
12881 redraw |= update_regions (terminal);
12882 }
12883
12884 if (redraw) {
12885 /* remove the idle source, and draw non-Terminals
12886 * (except for gdk/{directfb,quartz}!)
12887 */
12888 gdk_window_process_all_updates ();
12889 }
12890
12891 _vte_debug_print (VTE_DEBUG_WORK, "}");
12892
12893 /* Set a timer such that we do not invalidate for a while. */
12894 /* This limits the number of times we draw to ~40fps. */
12895 update_timeout_tag =
12896 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
12897 VTE_UPDATE_REPEAT_TIMEOUT,
12898 update_repeat_timeout, NULL,
12899 NULL);
12900 in_update_timeout = FALSE;
12901
12902 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
12903 gdk_threads_leave();
12904 G_GNUC_END_IGNORE_DEPRECATIONS;
12905
12906 return FALSE;
12907 }
12908
12909 /**
12910 * vte_terminal_write_contents_sync:
12911 * @terminal: a #VteTerminal
12912 * @stream: a #GOutputStream to write to
12913 * @flags: a set of #VteWriteFlags
12914 * @cancellable: (allow-none): a #GCancellable object, or %NULL
12915 * @error: (allow-none): a #GError location to store the error occuring, or %NULL
12916 *
12917 * Write contents of the current contents of @terminal (including any
12918 * scrollback history) to @stream according to @flags.
12919 *
12920 * If @cancellable is not %NULL, then the operation can be cancelled by triggering
12921 * the cancellable object from another thread. If the operation was cancelled,
12922 * the error %G_IO_ERROR_CANCELLED will be returned in @error.
12923 *
12924 * This is a synchronous operation and will make the widget (and input
12925 * processing) during the write operation, which may take a long time
12926 * depending on scrollback history and @stream availability for writing.
12927 *
12928 * Returns: %TRUE on success, %FALSE if there was an error
12929 */
12930 gboolean
vte_terminal_write_contents_sync(VteTerminal * terminal,GOutputStream * stream,VteWriteFlags flags,GCancellable * cancellable,GError ** error)12931 vte_terminal_write_contents_sync (VteTerminal *terminal,
12932 GOutputStream *stream,
12933 VteWriteFlags flags,
12934 GCancellable *cancellable,
12935 GError **error)
12936 {
12937 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
12938 g_return_val_if_fail(G_IS_OUTPUT_STREAM(stream), FALSE);
12939 return _vte_ring_write_contents (terminal->pvt->screen->row_data,
12940 stream, flags,
12941 cancellable, error);
12942 }
12943
12944
12945 /*
12946 * Buffer search
12947 */
12948
12949 /* TODO Add properties & signals */
12950
12951 /**
12952 * vte_terminal_search_set_gregex:
12953 * @terminal: a #VteTerminal
12954 * @regex: (allow-none): a #GRegex, or %NULL
12955 * @flags: flags from #GRegexMatchFlags
12956 *
12957 * Sets the #GRegex regex to search for. Unsets the search regex when passed %NULL.
12958 */
12959 void
vte_terminal_search_set_gregex(VteTerminal * terminal,GRegex * regex,GRegexMatchFlags flags)12960 vte_terminal_search_set_gregex (VteTerminal *terminal,
12961 GRegex *regex,
12962 GRegexMatchFlags flags)
12963 {
12964 g_return_if_fail(VTE_IS_TERMINAL(terminal));
12965
12966 if (terminal->pvt->search_regex == regex)
12967 return;
12968
12969 if (terminal->pvt->search_regex) {
12970 g_regex_unref (terminal->pvt->search_regex);
12971 terminal->pvt->search_regex = NULL;
12972 }
12973
12974 if (regex)
12975 terminal->pvt->search_regex = g_regex_ref (regex);
12976 terminal->pvt->search_match_flags = flags;
12977
12978 _vte_invalidate_all (terminal);
12979 }
12980
12981 /**
12982 * vte_terminal_search_get_gregex:
12983 * @terminal: a #VteTerminal
12984 *
12985 * Returns: (transfer none): the search #GRegex regex set in @terminal, or %NULL
12986 */
12987 GRegex *
vte_terminal_search_get_gregex(VteTerminal * terminal)12988 vte_terminal_search_get_gregex (VteTerminal *terminal)
12989 {
12990 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
12991
12992 return terminal->pvt->search_regex;
12993 }
12994
12995 /**
12996 * vte_terminal_search_set_wrap_around:
12997 * @terminal: a #VteTerminal
12998 * @wrap_around: whether search should wrap
12999 *
13000 * Sets whether search should wrap around to the beginning of the
13001 * terminal content when reaching its end.
13002 */
13003 void
vte_terminal_search_set_wrap_around(VteTerminal * terminal,gboolean wrap_around)13004 vte_terminal_search_set_wrap_around (VteTerminal *terminal,
13005 gboolean wrap_around)
13006 {
13007 g_return_if_fail(VTE_IS_TERMINAL(terminal));
13008
13009 terminal->pvt->search_wrap_around = !!wrap_around;
13010 }
13011
13012 /**
13013 * vte_terminal_search_get_wrap_around:
13014 * @terminal: a #VteTerminal
13015 *
13016 * Returns: whether searching will wrap around
13017 */
13018 gboolean
vte_terminal_search_get_wrap_around(VteTerminal * terminal)13019 vte_terminal_search_get_wrap_around (VteTerminal *terminal)
13020 {
13021 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
13022
13023 return terminal->pvt->search_wrap_around;
13024 }
13025
13026 static gboolean
vte_terminal_search_rows(VteTerminal * terminal,long start_row,long end_row,gboolean backward)13027 vte_terminal_search_rows (VteTerminal *terminal,
13028 long start_row,
13029 long end_row,
13030 gboolean backward)
13031 {
13032 VteTerminalPrivate *pvt;
13033 char *row_text;
13034 GMatchInfo *match_info;
13035 GError *error = NULL;
13036 int start, end;
13037 long start_col, end_col;
13038 gchar *word;
13039 VteCharAttributes *ca;
13040 GArray *attrs;
13041 gdouble value, page_size;
13042
13043 pvt = terminal->pvt;
13044
13045 row_text = vte_terminal_get_text_range (terminal, start_row, 0, end_row, -1, NULL, NULL, NULL);
13046
13047 g_regex_match_full (pvt->search_regex, row_text, -1, 0,
13048 pvt->search_match_flags | G_REGEX_MATCH_NOTEMPTY,
13049 &match_info, &error);
13050 if (error) {
13051 g_printerr ("Error while matching: %s\n", error->message);
13052 g_error_free (error);
13053 g_match_info_free (match_info);
13054 g_free (row_text);
13055 return TRUE;
13056 }
13057
13058 if (!g_match_info_matches (match_info)) {
13059 g_match_info_free (match_info);
13060 g_free (row_text);
13061 return FALSE;
13062 }
13063
13064 word = g_match_info_fetch (match_info, 0);
13065
13066 /* Fetch text again, with attributes */
13067 g_free (row_text);
13068 if (!pvt->search_attrs)
13069 pvt->search_attrs = g_array_new (FALSE, TRUE, sizeof (VteCharAttributes));
13070 attrs = pvt->search_attrs;
13071 row_text = vte_terminal_get_text_range (terminal, start_row, 0, end_row, -1, NULL, NULL, attrs);
13072
13073 /* This gives us the offset in the buffer */
13074 g_match_info_fetch_pos (match_info, 0, &start, &end);
13075
13076 ca = &g_array_index (attrs, VteCharAttributes, start);
13077 start_row = ca->row;
13078 start_col = ca->column;
13079 ca = &g_array_index (attrs, VteCharAttributes, end - 1);
13080 end_row = ca->row;
13081 end_col = ca->column;
13082
13083 g_free (word);
13084 g_free (row_text);
13085 g_match_info_free (match_info);
13086
13087 _vte_terminal_select_text (terminal, start_col, start_row, end_col, end_row, 0, 0);
13088 /* Quite possibly the math here should not access adjustment directly... */
13089 value = gtk_adjustment_get_value(terminal->pvt->vadjustment);
13090 page_size = gtk_adjustment_get_page_size(terminal->pvt->vadjustment);
13091 if (backward) {
13092 if (end_row < value || end_row >= value + page_size)
13093 vte_terminal_queue_adjustment_value_changed_clamped (terminal, end_row - page_size + 1);
13094 } else {
13095 if (start_row < value || start_row >= value + page_size)
13096 vte_terminal_queue_adjustment_value_changed_clamped (terminal, start_row);
13097 }
13098
13099 return TRUE;
13100 }
13101
13102 static gboolean
vte_terminal_search_rows_iter(VteTerminal * terminal,long start_row,long end_row,gboolean backward)13103 vte_terminal_search_rows_iter (VteTerminal *terminal,
13104 long start_row,
13105 long end_row,
13106 gboolean backward)
13107 {
13108 const VteRowData *row;
13109 long iter_start_row, iter_end_row;
13110
13111 if (backward) {
13112 iter_start_row = end_row;
13113 while (iter_start_row > start_row) {
13114 iter_end_row = iter_start_row;
13115
13116 do {
13117 iter_start_row--;
13118 row = _vte_terminal_find_row_data (terminal, iter_start_row);
13119 } while (row && row->attr.soft_wrapped);
13120
13121 if (vte_terminal_search_rows (terminal, iter_start_row, iter_end_row, backward))
13122 return TRUE;
13123 }
13124 } else {
13125 iter_end_row = start_row;
13126 while (iter_end_row < end_row) {
13127 iter_start_row = iter_end_row;
13128
13129 do {
13130 row = _vte_terminal_find_row_data (terminal, iter_end_row);
13131 iter_end_row++;
13132 } while (row && row->attr.soft_wrapped);
13133
13134 if (vte_terminal_search_rows (terminal, iter_start_row, iter_end_row, backward))
13135 return TRUE;
13136 }
13137 }
13138
13139 return FALSE;
13140 }
13141
13142 static gboolean
vte_terminal_search_find(VteTerminal * terminal,gboolean backward)13143 vte_terminal_search_find (VteTerminal *terminal,
13144 gboolean backward)
13145 {
13146 VteTerminalPrivate *pvt;
13147 long buffer_start_row, buffer_end_row;
13148 long last_start_row, last_end_row;
13149
13150 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
13151
13152 pvt = terminal->pvt;
13153 if (!pvt->search_regex)
13154 return FALSE;
13155
13156 /* TODO
13157 * Currently We only find one result per extended line, and ignore columns
13158 * Moreover, the whole search thing is implemented very inefficiently.
13159 */
13160
13161 buffer_start_row = _vte_ring_delta (terminal->pvt->screen->row_data);
13162 buffer_end_row = _vte_ring_next (terminal->pvt->screen->row_data);
13163
13164 if (pvt->has_selection) {
13165 last_start_row = pvt->selection_start.row;
13166 last_end_row = pvt->selection_end.row + 1;
13167 } else {
13168 last_start_row = pvt->screen->scroll_delta + terminal->pvt->row_count;
13169 last_end_row = pvt->screen->scroll_delta;
13170 }
13171 last_start_row = MAX (buffer_start_row, last_start_row);
13172 last_end_row = MIN (buffer_end_row, last_end_row);
13173
13174 /* If search fails, we make an empty selection at the last searched
13175 * position... */
13176 if (backward) {
13177 if (vte_terminal_search_rows_iter (terminal, buffer_start_row, last_start_row, backward))
13178 return TRUE;
13179 if (pvt->search_wrap_around &&
13180 vte_terminal_search_rows_iter (terminal, last_end_row, buffer_end_row, backward))
13181 return TRUE;
13182 if (pvt->has_selection) {
13183 if (pvt->search_wrap_around)
13184 _vte_terminal_select_empty_at (terminal,
13185 pvt->selection_start.col,
13186 pvt->selection_start.row);
13187 else
13188 _vte_terminal_select_empty_at (terminal,
13189 -1,
13190 buffer_start_row - 1);
13191 }
13192 } else {
13193 if (vte_terminal_search_rows_iter (terminal, last_end_row, buffer_end_row, backward))
13194 return TRUE;
13195 if (pvt->search_wrap_around &&
13196 vte_terminal_search_rows_iter (terminal, buffer_start_row, last_start_row, backward))
13197 return TRUE;
13198 if (pvt->has_selection) {
13199 if (pvt->search_wrap_around)
13200 _vte_terminal_select_empty_at (terminal,
13201 pvt->selection_end.col + 1,
13202 pvt->selection_end.row);
13203 else
13204 _vte_terminal_select_empty_at (terminal,
13205 -1,
13206 buffer_end_row);
13207 }
13208 }
13209
13210 return FALSE;
13211 }
13212
13213 /**
13214 * vte_terminal_search_find_previous:
13215 * @terminal: a #VteTerminal
13216 *
13217 * Searches the previous string matching the search regex set with
13218 * vte_terminal_search_set_gregex().
13219 *
13220 * Returns: %TRUE if a match was found
13221 */
13222 gboolean
vte_terminal_search_find_previous(VteTerminal * terminal)13223 vte_terminal_search_find_previous (VteTerminal *terminal)
13224 {
13225 return vte_terminal_search_find (terminal, TRUE);
13226 }
13227
13228 /**
13229 * vte_terminal_search_find_next:
13230 * @terminal: a #VteTerminal
13231 *
13232 * Searches the next string matching the search regex set with
13233 * vte_terminal_search_set_gregex().
13234 *
13235 * Returns: %TRUE if a match was found
13236 */
13237 gboolean
vte_terminal_search_find_next(VteTerminal * terminal)13238 vte_terminal_search_find_next (VteTerminal *terminal)
13239 {
13240 return vte_terminal_search_find (terminal, FALSE);
13241 }
13242
13243 /* Just some arbitrary minimum values */
13244 #define MIN_COLUMNS (16)
13245 #define MIN_ROWS (2)
13246
13247 /**
13248 * vte_terminal_get_geometry_hints:
13249 * @terminal: a #VteTerminal
13250 * @hints: (out caller-allocates): a #GdkGeometry to fill in
13251 * @min_rows: the minimum number of rows to request
13252 * @min_columns: the minimum number of columns to request
13253 *
13254 * Fills in some @hints from @terminal's geometry. The hints
13255 * filled are those covered by the %GDK_HINT_RESIZE_INC,
13256 * %GDK_HINT_MIN_SIZE and %GDK_HINT_BASE_SIZE flags.
13257 *
13258 * See gtk_window_set_geometry_hints() for more information.
13259 *
13260 * @terminal must be realized (see gtk_widget_get_realized()).
13261 */
13262 void
vte_terminal_get_geometry_hints(VteTerminal * terminal,GdkGeometry * hints,int min_rows,int min_columns)13263 vte_terminal_get_geometry_hints(VteTerminal *terminal,
13264 GdkGeometry *hints,
13265 int min_rows,
13266 int min_columns)
13267 {
13268 VteTerminalPrivate *pvt;
13269 GtkWidget *widget;
13270 GtkBorder padding;
13271
13272 g_return_if_fail(VTE_IS_TERMINAL(terminal));
13273 g_return_if_fail(hints != NULL);
13274 widget = &terminal->widget;
13275 g_return_if_fail(gtk_widget_get_realized(widget));
13276
13277 pvt = terminal->pvt;
13278
13279 gtk_style_context_get_padding(gtk_widget_get_style_context(widget),
13280 gtk_widget_get_state_flags(widget),
13281 &padding);
13282
13283 hints->base_width = padding.left + padding.right;
13284 hints->base_height = padding.top + padding.bottom;
13285 hints->width_inc = pvt->char_width;
13286 hints->height_inc = pvt->char_height;
13287 hints->min_width = hints->base_width + hints->width_inc * min_columns;
13288 hints->min_height = hints->base_height + hints->height_inc * min_rows;
13289 }
13290
13291 /**
13292 * vte_terminal_set_geometry_hints_for_window:
13293 * @terminal: a #VteTerminal
13294 * @window: a #GtkWindow
13295 *
13296 * Sets @terminal as @window's geometry widget. See
13297 * gtk_window_set_geometry_hints() for more information.
13298 *
13299 * @terminal must be realized (see gtk_widget_get_realized()).
13300 */
13301 void
vte_terminal_set_geometry_hints_for_window(VteTerminal * terminal,GtkWindow * window)13302 vte_terminal_set_geometry_hints_for_window(VteTerminal *terminal,
13303 GtkWindow *window)
13304 {
13305 GdkGeometry hints;
13306
13307 g_return_if_fail(VTE_IS_TERMINAL(terminal));
13308 g_return_if_fail(gtk_widget_get_realized(&terminal->widget));
13309
13310 vte_terminal_get_geometry_hints(terminal, &hints, MIN_ROWS, MIN_COLUMNS);
13311 gtk_window_set_geometry_hints(window,
13312 &terminal->widget,
13313 &hints,
13314 GDK_HINT_RESIZE_INC |
13315 GDK_HINT_MIN_SIZE |
13316 GDK_HINT_BASE_SIZE);
13317 }
13318
13319 /**
13320 * vte_terminal_set_input_enabled:
13321 * @terminal: a #VteTerminal
13322 * @enabled: whether to enable user input
13323 *
13324 * Enables or disables user input. When user input is disabled,
13325 * the terminal's child will not receive any key press, or mouse button
13326 * press or motion events sent to it.
13327 */
13328 void
vte_terminal_set_input_enabled(VteTerminal * terminal,gboolean enabled)13329 vte_terminal_set_input_enabled (VteTerminal *terminal,
13330 gboolean enabled)
13331 {
13332 VteTerminalPrivate *pvt;
13333 GtkWidget *widget;
13334 GtkStyleContext *context;
13335
13336 g_return_if_fail(VTE_IS_TERMINAL(terminal));
13337
13338 pvt = terminal->pvt;
13339 widget = &terminal->widget;
13340
13341 enabled = enabled != FALSE;
13342 if (enabled == terminal->pvt->input_enabled)
13343 return;
13344
13345 pvt->input_enabled = enabled;
13346
13347 context = gtk_widget_get_style_context (widget);
13348
13349 /* FIXME: maybe hide cursor when input disabled, too? */
13350
13351 if (enabled) {
13352 if (gtk_widget_has_focus(widget))
13353 gtk_im_context_focus_in(pvt->im_context);
13354
13355 gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY);
13356 } else {
13357 vte_terminal_im_reset(terminal);
13358 if (gtk_widget_has_focus(widget))
13359 gtk_im_context_focus_out(pvt->im_context);
13360
13361 _vte_terminal_disconnect_pty_write(terminal);
13362 _vte_byte_array_clear(pvt->outgoing);
13363
13364 gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY);
13365 }
13366
13367 g_object_notify(G_OBJECT(terminal), "input-enabled");
13368 }
13369
13370 /**
13371 * vte_terminal_get_input_enabled:
13372 * @terminal: a #VteTerminal
13373 *
13374 * Returns whether the terminal allow user input.
13375 */
13376 gboolean
vte_terminal_get_input_enabled(VteTerminal * terminal)13377 vte_terminal_get_input_enabled (VteTerminal *terminal)
13378 {
13379 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
13380
13381 return terminal->pvt->input_enabled;
13382 }
13383
13384 static gboolean
process_word_char_exceptions(const char * str,gunichar ** arrayp,gsize * lenp)13385 process_word_char_exceptions(const char *str,
13386 gunichar **arrayp,
13387 gsize *lenp)
13388 {
13389 const char *p;
13390 gunichar *array, c;
13391 gsize len, i;
13392
13393 if (str == NULL)
13394 str = WORD_CHAR_EXCEPTIONS_DEFAULT;
13395
13396 len = g_utf8_strlen(str, -1);
13397 array = g_new(gunichar, len);
13398 i = 0;
13399
13400 for (p = str; *p; p = g_utf8_next_char(p)) {
13401 c = g_utf8_get_char(p);
13402
13403 /* For forward compatibility reasons, we skip
13404 * characters that aren't supposed to be here,
13405 * instead of erroring out.
13406 */
13407 /* '-' must only be used* at the start of the string */
13408 if (c == (gunichar)'-' && p != str)
13409 continue;
13410 if (!g_unichar_isgraph(c))
13411 continue;
13412 if (g_unichar_isspace(c))
13413 continue;
13414 if (g_unichar_isalnum(c))
13415 continue;
13416
13417 array[i++] = g_utf8_get_char(p);
13418 }
13419
13420 g_assert(i <= len);
13421 len = i; /* we may have skipped some characters */
13422
13423 /* Sort the result since we want to use bsearch on it */
13424 qsort(array, len, sizeof(gunichar), compare_unichar_p);
13425
13426 /* Check that no character occurs twice */
13427 for (i = 1; i < len; i++) {
13428 if (array[i-1] != array[i])
13429 continue;
13430
13431 g_free(array);
13432 return FALSE;
13433 }
13434
13435 #if 0
13436 /* Debug */
13437 for (i = 0; i < len; i++) {
13438 char utf[7];
13439 c = array[i];
13440 utf[g_unichar_to_utf8(c, utf)] = '\0';
13441 g_printerr("Word char exception: U+%04X %s\n", c, utf);
13442 }
13443 #endif
13444
13445 *lenp = len;
13446 *arrayp = array;
13447 return TRUE;
13448 }
13449
13450 /**
13451 * vte_terminal_set_word_char_exceptions:
13452 * @terminal: a #VteTerminal
13453 * @exceptions: a string of ASCII punctuation characters, or %NULL
13454 *
13455 * With this function you can provide a set of characters which will
13456 * be considered parts of a word when doing word-wise selection, in
13457 * addition to the default which only considers alphanumeric characters
13458 * part of a word.
13459 *
13460 * The characters in @exceptions must be non-alphanumeric, each character
13461 * must occur only once, and if @exceptions contains the character
13462 * U+002D HYPHEN-MINUS, it must be at the start of the string.
13463 *
13464 * Use %NULL to reset the set of exception characters to the default.
13465 *
13466 * Since: 0.40
13467 */
13468 void
vte_terminal_set_word_char_exceptions(VteTerminal * terminal,const char * exceptions)13469 vte_terminal_set_word_char_exceptions(VteTerminal *terminal,
13470 const char *exceptions)
13471 {
13472 gunichar *array;
13473 gsize len;
13474
13475 g_return_if_fail(VTE_IS_TERMINAL(terminal));
13476
13477 if (g_strcmp0(exceptions, terminal->pvt->word_char_exceptions_string) == 0)
13478 return;
13479
13480 if (!process_word_char_exceptions(exceptions, &array, &len))
13481 return;
13482
13483 g_free(terminal->pvt->word_char_exceptions_string);
13484 terminal->pvt->word_char_exceptions_string = g_strdup(exceptions);
13485
13486 g_free(terminal->pvt->word_char_exceptions);
13487 terminal->pvt->word_char_exceptions = array;
13488 terminal->pvt->word_char_exceptions_len = len;
13489
13490 g_object_notify(G_OBJECT(terminal), "word-char-exceptions");
13491 }
13492
13493 /**
13494 * vte_terminal_get_word_char_exceptions:
13495 * @terminal: a #VteTerminal
13496 *
13497 * Returns the set of characters which will be considered parts of a word
13498 * when doing word-wise selection, in addition to the default which only
13499 * considers alphanumeric characters part of a word.
13500 *
13501 * If %NULL, a built-in set is used.
13502 *
13503 * Returns: (transfer none): a string, or %NULL
13504 *
13505 * Since: 0.40
13506 */
13507 const char *
vte_terminal_get_word_char_exceptions(VteTerminal * terminal)13508 vte_terminal_get_word_char_exceptions(VteTerminal *terminal)
13509 {
13510 g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
13511
13512 return terminal->pvt->word_char_exceptions_string;
13513 }
13514
13515 /**
13516 * vte_get_major_version:
13517 *
13518 * Returns the major version of the VTE library at runtime.
13519 * Contrast this with %VTE_MAJOR_VERSION which represents
13520 * the version of the VTE library that the code was compiled
13521 * with.
13522 *
13523 * Returns: the major version
13524 *
13525 * Since: 0.40
13526 */
13527 guint
vte_get_major_version(void)13528 vte_get_major_version (void)
13529 {
13530 return VTE_MAJOR_VERSION;
13531 }
13532
13533 /**
13534 * vte_get_minor_version:
13535 *
13536 * Returns the minor version of the VTE library at runtime.
13537 * Contrast this with %VTE_MINOR_VERSION which represents
13538 * the version of the VTE library that the code was compiled
13539 * with.
13540 *
13541 * Returns: the minor version
13542 *
13543 * Since: 0.40
13544 */
13545 guint
vte_get_minor_version(void)13546 vte_get_minor_version (void)
13547 {
13548 return VTE_MINOR_VERSION;
13549 }
13550
13551 /**
13552 * vte_get_micro_version:
13553 *
13554 * Returns the micro version of the VTE library at runtime.
13555 * Contrast this with %VTE_MICRO_VERSION which represents
13556 * the version of the VTE library that the code was compiled
13557 * with.
13558 *
13559 * Returns: the micro version
13560 *
13561 * Since: 0.40
13562 */
13563 guint
vte_get_micro_version(void)13564 vte_get_micro_version (void)
13565 {
13566 return VTE_MICRO_VERSION;
13567 }
13568