vte.c 440 KB
Newer Older
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
1
/*
2
 * Copyright (C) 2001-2004,2009,2010 Red Hat, Inc.
3
 * Copyright © 2008, 2009, 2010 Christian Persch
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

Christian Persch's avatar
Christian Persch committed
20
21
22
23
24
25
26
/**
 * SECTION: vte-terminal
 * @short_description: A terminal widget implementation
 *
 * A VteTerminal is a terminal emulator implemented as a GTK2 widget.
 */

27
#include <config.h>
28

29
30
#include <math.h>

31
32
#include "vte.h"
#include "vte-private.h"
33
#include "vte-gtk-compat.h"
34

35
#ifdef HAVE_WCHAR_H
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
36
#include <wchar.h>
37
#endif
38
39
40
#ifdef HAVE_SYS_SYSLIMITS_H
#include <sys/syslimits.h>
#endif
41
42
43
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
44
#include <glib.h>
Chris Wilson's avatar
Chris Wilson committed
45
#include <glib/gstdio.h>
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
46
47
48
#include <glib-object.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
49
#include <pango/pango.h>
50
#include "iso2022.h"
51
#include "keymap.h"
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
52
#include "marshal.h"
53
#include "matcher.h"
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
54
#include "pty.h"
55
#include "vteaccess.h"
56
#include "vteint.h"
57
58
#include "vtepty.h"
#include "vtepty-private.h"
59
#include "vteregex.h"
60
#include "vtetc.h"
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
61

62
63
64
65
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

66
67
68
69
70
71
72
#if GTK_CHECK_VERSION (2, 90, 7)
#define GDK_KEY(symbol) GDK_KEY_##symbol
#else
#include <gdk/gdkkeysyms.h>
#define GDK_KEY(symbol) GDK_##symbol
#endif

73
#ifndef HAVE_WINT_T
74
typedef gunichar wint_t;
75
76
#endif

77
78
79
80
#ifndef howmany
#define howmany(x, y) (((x) + ((y) - 1)) / (y))
#endif

81
82
83
#define STATIC_PARAMS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)


84
static void vte_terminal_set_visibility (VteTerminal *terminal, GdkVisibilityState state);
85
86
static void vte_terminal_set_termcap(VteTerminal *terminal, const char *path,
				     gboolean reset);
87
static void vte_terminal_paste(VteTerminal *terminal, GdkAtom board);
88
89
static void vte_terminal_real_copy_clipboard(VteTerminal *terminal);
static void vte_terminal_real_paste_clipboard(VteTerminal *terminal);
90
static gboolean vte_terminal_io_read(GIOChannel *channel,
91
				     GIOCondition condition,
Chris Wilson's avatar
Chris Wilson committed
92
				     VteTerminal *terminal);
93
static gboolean vte_terminal_io_write(GIOChannel *channel,
94
				      GIOCondition condition,
Chris Wilson's avatar
Chris Wilson committed
95
				      VteTerminal *terminal);
96
static void vte_terminal_match_hilite_clear(VteTerminal *terminal);
Chris Wilson's avatar
Chris Wilson committed
97
static void vte_terminal_match_hilite_hide(VteTerminal *terminal);
98
99
static void vte_terminal_match_hilite_show(VteTerminal *terminal, long x, long y);
static void vte_terminal_match_hilite_update(VteTerminal *terminal, long x, long y);
100
static void vte_terminal_match_contents_clear(VteTerminal *terminal);
Chris Wilson's avatar
Chris Wilson committed
101
static gboolean vte_terminal_background_update(VteTerminal *data);
102
static void vte_terminal_queue_background_update(VteTerminal *terminal);
Chris Wilson's avatar
Chris Wilson committed
103
static void vte_terminal_process_incoming(VteTerminal *terminal);
104
static void vte_terminal_emit_pending_signals(VteTerminal *terminal);
105
static gboolean vte_cell_is_selected(VteTerminal *terminal,
106
				     glong col, glong row, gpointer data);
107
108
109
110
111
112
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,
113
						       VteSelectionFunc is_selected,
114
						       gpointer data,
115
116
						       GArray *attributes,
						       gboolean include_trailing_spaces);
117
118
static char *vte_terminal_get_text_maybe_wrapped(VteTerminal *terminal,
						 gboolean wrap,
119
						 VteSelectionFunc is_selected,
120
						 gpointer data,
121
122
						 GArray *attributes,
						 gboolean include_trailing_spaces);
123
124
static void _vte_terminal_disconnect_pty_read(VteTerminal *terminal);
static void _vte_terminal_disconnect_pty_write(VteTerminal *terminal);
125
static void vte_terminal_stop_processing (VteTerminal *terminal);
126

127
128
129
static inline gboolean vte_terminal_is_processing (VteTerminal *terminal);
static inline void vte_terminal_start_processing (VteTerminal *terminal);
static void vte_terminal_add_process_timeout (VteTerminal *terminal);
Chris Wilson's avatar
Chris Wilson committed
130
131
static void add_update_timeout (VteTerminal *terminal);
static void remove_update_timeout (VteTerminal *terminal);
132
static void reset_update_regions (VteTerminal *terminal);
133
static void vte_terminal_set_cursor_blinks_internal(VteTerminal *terminal, gboolean blink);
134
135
136
static void vte_terminal_set_font_full_internal(VteTerminal *terminal,
                                                const PangoFontDescription *font_desc,
                                                VteTerminalAntiAlias antialias);
137

138
139
140
static gboolean process_timeout (gpointer data);
static gboolean update_timeout (gpointer data);

141
142
143
144
145
146
147
enum {
    COPY_CLIPBOARD,
    PASTE_CLIPBOARD,
    LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];

148
enum {
149
        PROP_0,
150
151
152
#if GTK_CHECK_VERSION (2, 91, 2)
        PROP_HADJUSTMENT,
        PROP_VADJUSTMENT,
153
154
        PROP_HSCROLL_POLICY,
        PROP_VSCROLL_POLICY,
155
#endif
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        PROP_ALLOW_BOLD,
        PROP_AUDIBLE_BELL,
        PROP_BACKGROUND_IMAGE_FILE,
        PROP_BACKGROUND_IMAGE_PIXBUF,
        PROP_BACKGROUND_OPACITY,
        PROP_BACKGROUND_SATURATION,
        PROP_BACKGROUND_TINT_COLOR,
        PROP_BACKGROUND_TRANSPARENT,
        PROP_BACKSPACE_BINDING,
        PROP_CURSOR_BLINK_MODE,
        PROP_CURSOR_SHAPE,
        PROP_DELETE_BINDING,
        PROP_EMULATION,
        PROP_ENCODING,
170
        PROP_FONT_DESC,
171
172
173
        PROP_ICON_TITLE,
        PROP_MOUSE_POINTER_AUTOHIDE,
        PROP_PTY,
174
        PROP_PTY_OBJECT,
175
176
177
178
179
180
181
        PROP_SCROLL_BACKGROUND,
        PROP_SCROLLBACK_LINES,
        PROP_SCROLL_ON_KEYSTROKE,
        PROP_SCROLL_ON_OUTPUT,
        PROP_WINDOW_TITLE,
        PROP_WORD_CHARS,
        PROP_VISIBLE_BELL
182
183
};

184
/* these static variables are guarded by the GDK mutex */
185
static guint process_timeout_tag = 0;
186
static gboolean in_process_timeout;
187
static guint update_timeout_tag = 0;
188
static gboolean in_update_timeout;
189
static GList *active_terminals;
190
static GTimer *process_timer;
191

192
193
static const GtkBorder default_inner_border = { 1, 1, 1, 1 };

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/* process incoming data without copying */
static struct _vte_incoming_chunk *free_chunks;
static struct _vte_incoming_chunk *
get_chunk (void)
{
	struct _vte_incoming_chunk *chunk = NULL;
	if (free_chunks) {
		chunk = free_chunks;
		free_chunks = free_chunks->next;
	}
	if (chunk == NULL) {
		chunk = g_new (struct _vte_incoming_chunk, 1);
	}
	chunk->next = NULL;
	chunk->len = 0;
	return chunk;
}
static void
release_chunk (struct _vte_incoming_chunk *chunk)
{
214
215
216
	chunk->next = free_chunks;
	chunk->len = free_chunks ? free_chunks->len + 1 : 0;
	free_chunks = chunk;
217
218
}
static void
219
prune_chunks (guint len)
220
{
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
	struct _vte_incoming_chunk *chunk = NULL;
	if (len && free_chunks != NULL) {
	    if (free_chunks->len > len) {
		struct _vte_incoming_chunk *last;
		chunk = free_chunks;
		while (free_chunks->len > len) {
		    last = free_chunks;
		    free_chunks = free_chunks->next;
		}
		last->next = NULL;
	    }
	} else {
	    chunk = free_chunks;
	    free_chunks = NULL;
	}
	while (chunk != NULL) {
237
		struct _vte_incoming_chunk *next = chunk->next;
238
		g_free (chunk);
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
		chunk = next;
	}
}
static void
_vte_incoming_chunks_release (struct _vte_incoming_chunk *chunk)
{
	while (chunk) {
		struct _vte_incoming_chunk *next = chunk->next;
		release_chunk (chunk);
		chunk = next;
	}
}
static gsize
_vte_incoming_chunks_length (struct _vte_incoming_chunk *chunk)
{
	gsize len = 0;
	while (chunk) {
		len += chunk->len;
		chunk = chunk->next;
	}
	return len;
}
static gsize
_vte_incoming_chunks_count (struct _vte_incoming_chunk *chunk)
{
	gsize cnt = 0;
	while (chunk) {
		cnt ++;
		chunk = chunk->next;
	}
	return cnt;
}
static struct _vte_incoming_chunk *
_vte_incoming_chunks_reverse(struct _vte_incoming_chunk *chunk)
{
	struct _vte_incoming_chunk *prev = NULL;
	while (chunk) {
		struct _vte_incoming_chunk *next = chunk->next;
		chunk->next = prev;
		prev = chunk;
		chunk = next;
	}
	return prev;
}

284

285
286
287
288
289
290
291
292
293
294
295
296
#if GTK_CHECK_VERSION (2, 91, 2)
#ifdef VTE_DEBUG
G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET,
                        G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL)
                        if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) {
                                g_printerr("vte_terminal_get_type()\n");
                        })
#else
G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET,
                        G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
#endif
#else
Chris Wilson's avatar
Chris Wilson committed
297
298
299
300
301
302
303
304
#ifdef VTE_DEBUG
G_DEFINE_TYPE_WITH_CODE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET,
		if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) {
			g_printerr("vte_terminal_get_type()\n");
		})
#else
G_DEFINE_TYPE(VteTerminal, vte_terminal, GTK_TYPE_WIDGET)
#endif
305
#endif /* GTK 3.0 */
Kjartan Maraas's avatar
Kjartan Maraas committed
306

307
/* Indexes in the "palette" color array for the dim colors.
308
 * Only the first %VTE_LEGACY_COLOR_SET_SIZE colors have dim versions.  */
309
310
static const guchar corresponding_dim_index[] = {16,88,28,100,18,90,30,102};

311
static void
312
vte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
313
{
314
	if (array->len >= final_size)
315
316
		return;

Chris Wilson's avatar
Chris Wilson committed
317
318
	final_size -= array->len;
	do {
319
		g_array_append_vals(array, item, 1);
Chris Wilson's avatar
Chris Wilson committed
320
	} while (--final_size);
321
322
}

323

324
VteRowData *
325
_vte_terminal_ring_insert (VteTerminal *terminal, glong position, gboolean fill)
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
{
	VteRowData *row;
	VteRing *ring = terminal->pvt->screen->row_data;
	while (G_UNLIKELY (_vte_ring_next (ring) < position)) {
		row = _vte_ring_append (ring);
		_vte_row_data_fill (row, &terminal->pvt->screen->fill_defaults, terminal->column_count);
	}
	row = _vte_ring_insert (ring, position);
	if (fill)
		_vte_row_data_fill (row, &terminal->pvt->screen->fill_defaults, terminal->column_count);
	return row;
}

VteRowData *
_vte_terminal_ring_append (VteTerminal *terminal, gboolean fill)
{
	return _vte_terminal_ring_insert (terminal, _vte_ring_next (terminal->pvt->screen->row_data), fill);
}

Behdad Esfahbod's avatar
Minor    
Behdad Esfahbod committed
345
void
346
_vte_terminal_ring_remove (VteTerminal *terminal, glong position)
Behdad Esfahbod's avatar
Minor    
Behdad Esfahbod committed
347
{
348
	_vte_ring_remove (terminal->pvt->screen->row_data, position);
Behdad Esfahbod's avatar
Minor    
Behdad Esfahbod committed
349
}
350

Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
351
/* Reset defaults for character insertion. */
352
353
void
_vte_terminal_set_default_attributes(VteTerminal *terminal)
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
354
{
Chris Wilson's avatar
Chris Wilson committed
355
356
357
358
	VteScreen *screen;

	screen = terminal->pvt->screen;

359
	screen->defaults = basic_cell.cell;
Chris Wilson's avatar
Chris Wilson committed
360
361
	screen->color_defaults = screen->defaults;
	screen->fill_defaults = screen->defaults;
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
362
363
}

364
/* Cause certain cells to be repainted. */
365
366
367
368
void
_vte_invalidate_cells(VteTerminal *terminal,
		      glong column_start, gint column_count,
		      glong row_start, gint row_count)
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
369
{
370
	VteRegionRectangle rect;
Chris Wilson's avatar
Chris Wilson committed
371
	glong i;
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
372

373
374
375
376
	if (!column_count || !row_count) {
		return;
	}

377
378
	if (G_UNLIKELY (! gtk_widget_is_drawable (&terminal->widget)
				|| terminal->pvt->invalidated_all)) {
379
380
		return;
	}
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
381

382
	_vte_debug_print (VTE_DEBUG_UPDATES,
383
			"Invalidating cells at (%ld,%ld+%ld)x(%d,%d).\n",
384
			column_start, row_start,
385
			(long)terminal->pvt->screen->scroll_delta,
386
387
			column_count, row_count);
	_vte_debug_print (VTE_DEBUG_WORK, "?");
388

389
390
391
392
393
	/* Subtract the scrolling offset from the row start so that the
	 * resulting rectangle is relative to the visible portion of the
	 * buffer. */
	row_start -= terminal->pvt->screen->scroll_delta;

394
395
396
397
398
399
	/* Ensure the start of region is on screen */
	if (column_start > terminal->column_count ||
			row_start > terminal->row_count) {
		return;
	}

Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
400
	/* Clamp the start values to reasonable numbers. */
401
	i = row_start + row_count;
402
	row_start = MAX (0, row_start);
403
	row_count = CLAMP (i - row_start, 0, terminal->row_count);
404

405
	i = column_start + column_count;
406
	column_start = MAX (0, column_start);
407
	column_count = CLAMP (i - column_start, 0 , terminal->column_count);
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
408

409
410
411
	if (!column_count || !row_count) {
		return;
	}
412
	if (column_count == terminal->column_count &&
413
414
415
416
			row_count == terminal->row_count) {
		_vte_invalidate_all (terminal);
		return;
	}
417

Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
418
	/* Convert the column and row start and end to pixel values
419
	 * by multiplying by the size of a character cell.
420
	 * Always include the extra pixel border and overlap pixel.
421
	 */
422
423
	rect.x = column_start * terminal->char_width - 1;
	if (column_start != 0) {
424
		rect.x += terminal->pvt->inner_border.left;
425
	}
426
	rect.width = (column_start + column_count) * terminal->char_width + 3 + terminal->pvt->inner_border.left;
427
	if (column_start + column_count == terminal->column_count) {
428
		rect.width += terminal->pvt->inner_border.right;
429
430
	}
	rect.width -= rect.x;
431

432
433
	rect.y = row_start * terminal->char_height - 1;
	if (row_start != 0) {
434
		rect.y += terminal->pvt->inner_border.top;
435
	}
436
	rect.height = (row_start + row_count) * terminal->char_height + 2 + terminal->pvt->inner_border.top;
437
	if (row_start + row_count == terminal->row_count) {
438
		rect.height += terminal->pvt->inner_border.bottom;
439
440
	}
	rect.height -= rect.y;
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
441

442
443
444
445
	_vte_debug_print (VTE_DEBUG_UPDATES,
			"Invalidating pixels at (%d,%d)x(%d,%d).\n",
			rect.x, rect.y, rect.width, rect.height);

446
447
448
449
450
451
452
453
	if (terminal->pvt->active != NULL) {
		terminal->pvt->update_regions = g_slist_prepend (
				terminal->pvt->update_regions,
				gdk_region_rectangle (&rect));
		/* Wait a bit before doing any invalidation, just in
		 * case updates are coming in really soon. */
		add_update_timeout (terminal);
	} else {
454
		gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
455
	}
456

457
	_vte_debug_print (VTE_DEBUG_WORK, "!");
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
458
459
}

460
static void
461
462
463
464
_vte_invalidate_region (VteTerminal *terminal,
			glong scolumn, glong ecolumn,
			glong srow, glong erow,
			gboolean block)
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
{
	if (block || srow == erow) {
		_vte_invalidate_cells(terminal,
				scolumn, ecolumn - scolumn + 1,
				srow, erow - srow + 1);
	} else {
		_vte_invalidate_cells(terminal,
				scolumn,
				terminal->column_count - scolumn,
				srow, 1);
		_vte_invalidate_cells(terminal,
				0, terminal->column_count,
				srow + 1, erow - srow - 1);
		_vte_invalidate_cells(terminal,
				0, ecolumn + 1,
				erow, 1);
	}
}


485
/* Redraw the entire visible portion of the window. */
486
487
void
_vte_invalidate_all(VteTerminal *terminal)
488
{
489
	VteRegionRectangle rect;
490
	GtkAllocation allocation;
491

492
	g_assert(VTE_IS_TERMINAL(terminal));
493

494
	if (! gtk_widget_is_drawable (&terminal->widget)) {
495
496
		return;
	}
497
	if (terminal->pvt->invalidated_all) {
498
499
		return;
	}
500

501
	_vte_debug_print (VTE_DEBUG_WORK, "*");
502
	_vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating all.\n");
503

504
505
	gtk_widget_get_allocation (&terminal->widget, &allocation);

506
507
	/* replace invalid regions with one covering the whole terminal */
	reset_update_regions (terminal);
508
	rect.x = rect.y = 0;
509
510
	rect.width = allocation.width;
	rect.height = allocation.height;
511
	terminal->pvt->invalidated_all = TRUE;
512

513
514
515
516
517
518
519
	if (terminal->pvt->active != NULL) {
		terminal->pvt->update_regions = g_slist_prepend (NULL,
				gdk_region_rectangle (&rect));
		/* Wait a bit before doing any invalidation, just in
		 * case updates are coming in really soon. */
		add_update_timeout (terminal);
	} else {
520
		gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
521
	}
522
523
}

524

525
526
/* Scroll a rectangular region up or down by a fixed number of lines,
 * negative = up, positive = down. */
527
void
528
529
_vte_terminal_scroll_region (VteTerminal *terminal,
			     long row, glong count, glong delta)
530
531
532
533
534
535
{
	if ((delta == 0) || (count == 0)) {
		/* Shenanigans! */
		return;
	}

536
	if (terminal->pvt->scroll_background || count >= terminal->row_count) {
537
		/* We have to repaint the entire window. */
538
		_vte_invalidate_all(terminal);
539
540
541
	} else {
		/* We have to repaint the area which is to be
		 * scrolled. */
542
		_vte_invalidate_cells(terminal,
543
544
				     0, terminal->column_count,
				     row, count);
545
546
547
	}
}

548
549
550
551
552
553
554
555
556
557
558
559
/* Find the row in the given position in the backscroll buffer. */
static inline const VteRowData *
_vte_terminal_find_row_data (VteTerminal *terminal, glong row)
{
	const VteRowData *rowdata = NULL;
	VteScreen *screen = terminal->pvt->screen;
	if (G_LIKELY (_vte_ring_contains (screen->row_data, row))) {
		rowdata = _vte_ring_index (screen->row_data, row);
	}
	return rowdata;
}

Chris Wilson's avatar
Chris Wilson committed
560
561
/* Find the row in the given position in the backscroll buffer. */
static inline VteRowData *
562
_vte_terminal_find_row_data_writable (VteTerminal *terminal, glong row)
Chris Wilson's avatar
Chris Wilson committed
563
564
565
{
	VteRowData *rowdata = NULL;
	VteScreen *screen = terminal->pvt->screen;
566
567
	if (G_LIKELY (_vte_ring_contains (screen->row_data, row))) {
		rowdata = _vte_ring_index_writable (screen->row_data, row);
Chris Wilson's avatar
Chris Wilson committed
568
569
570
	}
	return rowdata;
}
571

572
/* Find the character an the given position in the backscroll buffer. */
573
static const VteCell *
574
vte_terminal_find_charcell(VteTerminal *terminal, gulong col, glong row)
575
{
576
	const VteRowData *rowdata;
577
	const VteCell *ret = NULL;
578
	VteScreen *screen;
579
	screen = terminal->pvt->screen;
580
581
	if (_vte_ring_contains (screen->row_data, row)) {
		rowdata = _vte_ring_index (screen->row_data, row);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
582
		ret = _vte_row_data_get (rowdata, col);
Chris Wilson's avatar
Chris Wilson committed
583
584
585
586
	}
	return ret;
}

Behdad Esfahbod's avatar
Behdad Esfahbod committed
587
588
589
static glong
find_start_column (VteTerminal *terminal, glong col, glong row)
{
590
	const VteRowData *row_data = _vte_terminal_find_row_data (terminal, row);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
591
592
593
	if (G_UNLIKELY (col < 0))
		return col;
	if (row_data != NULL) {
594
		const VteCell *cell = _vte_row_data_get (row_data, col);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
595
		while (col > 0 && cell != NULL && cell->attr.fragment) {
Behdad Esfahbod's avatar
Behdad Esfahbod committed
596
597
598
599
600
601
602
603
			cell = _vte_row_data_get (row_data, --col);
		}
	}
	return MAX(col, 0);
}
static glong
find_end_column (VteTerminal *terminal, glong col, glong row)
{
604
	const VteRowData *row_data = _vte_terminal_find_row_data (terminal, row);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
605
606
607
608
	gint columns = 0;
	if (G_UNLIKELY (col < 0))
		return col;
	if (row_data != NULL) {
609
		const VteCell *cell = _vte_row_data_get (row_data, col);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
610
		while (col > 0 && cell != NULL && cell->attr.fragment) {
Behdad Esfahbod's avatar
Behdad Esfahbod committed
611
612
613
614
615
616
617
618
619
620
			cell = _vte_row_data_get (row_data, --col);
		}
		if (cell) {
			columns = cell->attr.columns - 1;
		}
	}
	return MIN(col + columns, terminal->column_count);
}


621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
/* Determine the width of the portion of the preedit string which lies
 * to the left of the cursor, or the entire string, in columns. */
static gssize
vte_terminal_preedit_width(VteTerminal *terminal, gboolean left_only)
{
	gunichar c;
	int i;
	gssize ret = 0;
	const char *preedit = NULL;

	if (terminal->pvt->im_preedit != NULL) {
		preedit = terminal->pvt->im_preedit;
		for (i = 0;
		     (preedit != NULL) &&
		     (preedit[0] != '\0') &&
		     (!left_only || (i < terminal->pvt->im_preedit_cursor));
		     i++) {
			c = g_utf8_get_char(preedit);
639
			ret += _vte_iso2022_unichar_width(terminal->pvt->iso2022, c);
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
			preedit = g_utf8_next_char(preedit);
		}
	}

	return ret;
}

/* Determine the length of the portion of the preedit string which lies
 * to the left of the cursor, or the entire string, in gunichars. */
static gssize
vte_terminal_preedit_length(VteTerminal *terminal, gboolean left_only)
{
	int i = 0;
	const char *preedit = NULL;

	if (terminal->pvt->im_preedit != NULL) {
		preedit = terminal->pvt->im_preedit;
		for (i = 0;
		     (preedit != NULL) &&
		     (preedit[0] != '\0') &&
		     (!left_only || (i < terminal->pvt->im_preedit_cursor));
		     i++) {
			preedit = g_utf8_next_char(preedit);
		}
	}

	return i;
}

669
670
/* Cause the cell to be redrawn. */
void
671
_vte_invalidate_cell(VteTerminal *terminal, glong col, glong row)
672
{
673
	const VteRowData *row_data;
674
675
	int columns;

676
677
	if (G_UNLIKELY (! gtk_widget_is_drawable (&terminal->widget)
				|| terminal->pvt->invalidated_all)) {
678
679
680
681
682
		return;
	}

	columns = 1;
	row_data = _vte_terminal_find_row_data(terminal, row);
Chris Wilson's avatar
Chris Wilson committed
683
	if (row_data != NULL) {
684
		const VteCell *cell;
685
		cell = _vte_row_data_get (row_data, col);
Chris Wilson's avatar
Chris Wilson committed
686
		if (cell != NULL) {
687
			while (cell->attr.fragment && col> 0) {
688
				cell = _vte_row_data_get (row_data, --col);
Chris Wilson's avatar
Chris Wilson committed
689
			}
690
			columns = cell->attr.columns;
691
692
693
			if (cell->c != 0 &&
					_vte_draw_get_char_width (
						terminal->pvt->draw,
Chris Wilson's avatar
Chris Wilson committed
694
						cell->c,
695
						columns, cell->attr.bold) >
Chris Wilson's avatar
Chris Wilson committed
696
697
698
699
					terminal->char_width * columns) {
				columns++;
			}
		}
700
701
	}

702
703
704
	_vte_debug_print(VTE_DEBUG_UPDATES,
			"Invalidating cell at (%ld,%ld-%ld).\n",
			row, col, col + columns);
705
706
707
	_vte_invalidate_cells(terminal,
			col, columns,
			row, 1);
708
709
}

710
/* Cause the cursor to be redrawn. */
711
void
Chris Wilson's avatar
Chris Wilson committed
712
_vte_invalidate_cursor_once(VteTerminal *terminal, gboolean periodic)
713
{
714
	VteScreen *screen;
715
	const VteCell *cell;
716
	gssize preedit_width;
Chris Wilson's avatar
Chris Wilson committed
717
718
	glong column, row;
	gint columns;
719

720
	if (terminal->pvt->invalidated_all) {
721
722
723
		return;
	}

724
725
726
727
728
729
	if (periodic) {
		if (!terminal->pvt->cursor_blinks) {
			return;
		}
	}

730
	if (terminal->pvt->cursor_visible && gtk_widget_is_drawable (&terminal->widget)) {
731
		preedit_width = vte_terminal_preedit_width(terminal, FALSE);
732
733

		screen = terminal->pvt->screen;
734
		row = screen->cursor_current.row;
735
		column = screen->cursor_current.col;
736
		columns = 1;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
737
738
		column = find_start_column (terminal, column, row);
		cell = vte_terminal_find_charcell(terminal, column, row);
739
		if (cell != NULL) {
740
			columns = cell->attr.columns;
741
742
743
744
			if (cell->c != 0 &&
					_vte_draw_get_char_width (
						terminal->pvt->draw,
						cell->c,
745
						columns, cell->attr.bold) >
746
747
748
			    terminal->char_width * columns) {
				columns++;
			}
749
		}
750
751
752
		if (preedit_width > 0) {
			columns += preedit_width;
			columns++; /* one more for the preedit cursor */
753
		}
754

755
756
757
		_vte_debug_print(VTE_DEBUG_UPDATES,
				"Invalidating cursor at (%ld,%ld-%ld).\n",
				row, column, column + columns);
758
759
760
		_vte_invalidate_cells(terminal,
				     column, columns,
				     row, 1);
761
	}
762
763
}

764
765
/* Invalidate the cursor repeatedly. */
static gboolean
Chris Wilson's avatar
Chris Wilson committed
766
vte_invalidate_cursor_periodic (VteTerminal *terminal)
767
{
768
        VteTerminalPrivate *pvt = terminal->pvt;
769

770
771
	pvt->cursor_blink_state = !pvt->cursor_blink_state;
	pvt->cursor_blink_time += pvt->cursor_blink_cycle;
772
773
774
775
776
777

	_vte_invalidate_cursor_once(terminal, TRUE);

	/* only disable the blink if the cursor is currently shown.
	 * else, wait until next time.
	 */
778
779
780
	if (pvt->cursor_blink_time / 1000 >= pvt->cursor_blink_timeout &&
	    pvt->cursor_blink_state) {
                pvt->cursor_blink_tag = 0;
781
		return FALSE;
782
        }
783

784
785
786
787
788
	pvt->cursor_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
						   terminal->pvt->cursor_blink_cycle,
						   (GSourceFunc)vte_invalidate_cursor_periodic,
						   terminal,
						   NULL);
789
	return FALSE;
790
791
}

792
/* Emit a "selection_changed" signal. */
793
static void
794
vte_terminal_emit_selection_changed(VteTerminal *terminal)
795
{
796
	_vte_debug_print(VTE_DEBUG_SIGNALS,
Chris Wilson's avatar
Chris Wilson committed
797
			"Emitting `selection-changed'.\n");
798
	g_signal_emit_by_name(terminal, "selection-changed");
799
800
}

801
802
/* Emit a "commit" signal. */
static void
803
vte_terminal_emit_commit(VteTerminal *terminal, const gchar *text, guint length)
804
{
805
	const char *result = NULL;
806
	char *wrapped = NULL;
807
808
809
810

	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `commit' of %d bytes.\n", length);

811
	if (length == (guint)-1) {
812
		length = strlen(text);
813
		result = text;
814
	} else {
Chris Wilson's avatar
Chris Wilson committed
815
		result = wrapped = g_slice_alloc(length + 1);
816
		memcpy(wrapped, text, length);
Chris Wilson's avatar
Chris Wilson committed
817
		wrapped[length] = '\0';
818
	}
819
820
821

	g_signal_emit_by_name(terminal, "commit", result, length);

Chris Wilson's avatar
Chris Wilson committed
822
823
	if(wrapped)
		g_slice_free1(length+1, wrapped);
824
825
}

826
827
828
829
/* Emit an "emulation-changed" signal. */
static void
vte_terminal_emit_emulation_changed(VteTerminal *terminal)
{
830
831
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `emulation-changed'.\n");
832
	g_signal_emit_by_name(terminal, "emulation-changed");
833
834
        g_object_notify(G_OBJECT(terminal), "emulation");

835
836
837
838
839
840
}

/* Emit an "encoding-changed" signal. */
static void
vte_terminal_emit_encoding_changed(VteTerminal *terminal)
{
841
842
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `encoding-changed'.\n");
843
	g_signal_emit_by_name(terminal, "encoding-changed");
844
        g_object_notify(G_OBJECT(terminal), "encoding");
845
846
}

847
848
849
850
/* Emit a "child-exited" signal. */
static void
vte_terminal_emit_child_exited(VteTerminal *terminal)
{
851
852
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `child-exited'.\n");
853
854
855
	g_signal_emit_by_name(terminal, "child-exited");
}

856
/* Emit a "contents_changed" signal. */
857
858
859
860
static void
vte_terminal_emit_contents_changed(VteTerminal *terminal)
{
	if (terminal->pvt->contents_changed_pending) {
861
		/* Update dingus match set. */
862
		vte_terminal_match_contents_clear(terminal);
863
864
865
866
867
		if (terminal->pvt->mouse_cursor_visible) {
			vte_terminal_match_hilite_update(terminal,
					terminal->pvt->mouse_last_x,
					terminal->pvt->mouse_last_y);
		}
868

869
870
871
872
873
874
		_vte_debug_print(VTE_DEBUG_SIGNALS,
				"Emitting `contents-changed'.\n");
		g_signal_emit_by_name(terminal, "contents-changed");
		terminal->pvt->contents_changed_pending = FALSE;
	}
}
875
void
876
_vte_terminal_queue_contents_changed(VteTerminal *terminal)
877
{
878
	_vte_debug_print(VTE_DEBUG_SIGNALS,
879
880
			"Queueing `contents-changed'.\n");
	terminal->pvt->contents_changed_pending = TRUE;
881
882
883
884
885
}

/* Emit a "cursor_moved" signal. */
static void
vte_terminal_emit_cursor_moved(VteTerminal *terminal)
886
887
888
889
890
891
892
893
894
895
{
	if (terminal->pvt->cursor_moved_pending) {
		_vte_debug_print(VTE_DEBUG_SIGNALS,
				"Emitting `cursor-moved'.\n");
		g_signal_emit_by_name(terminal, "cursor-moved");
		terminal->pvt->cursor_moved_pending = FALSE;
	}
}
static void
vte_terminal_queue_cursor_moved(VteTerminal *terminal)
896
{
897
	_vte_debug_print(VTE_DEBUG_SIGNALS,
898
899
			"Queueing `cursor-moved'.\n");
	terminal->pvt->cursor_moved_pending = TRUE;
900
901
}

902
static gboolean
903
904
vte_terminal_emit_eof(VteTerminal *terminal)
{
905
906
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `eof'.\n");
907
	GDK_THREADS_ENTER ();
908
	g_signal_emit_by_name(terminal, "eof");
909
910
911
912
913
914
915
916
917
918
919
920
921
922
	GDK_THREADS_LEAVE ();

	return FALSE;
}
/* Emit a "eof" signal. */
static void
vte_terminal_queue_eof(VteTerminal *terminal)
{
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Queueing `eof'.\n");
	g_idle_add_full (G_PRIORITY_HIGH,
		(GSourceFunc) vte_terminal_emit_eof,
		g_object_ref (terminal),
		g_object_unref);
923
924
925
926
927
928
929
}

/* Emit a "char-size-changed" signal. */
static void
vte_terminal_emit_char_size_changed(VteTerminal *terminal,
				    guint width, guint height)
{
930
931
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `char-size-changed'.\n");
932
933
	g_signal_emit_by_name(terminal, "char-size-changed",
			      width, height);
934
/*         g_object_notify(G_OBJECT(terminal), "char-size"); */
935
936
}

937
/* Emit a "status-line-changed" signal. */
938
static void
939
_vte_terminal_emit_status_line_changed(VteTerminal *terminal)
940
{
941
942
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `status-line-changed'.\n");
943
	g_signal_emit_by_name(terminal, "status-line-changed");
944
/*         g_object_notify(G_OBJECT(terminal), "status-line"); */
945
946
}

947
948
949
950
/* Emit an "increase-font-size" signal. */
static void
vte_terminal_emit_increase_font_size(VteTerminal *terminal)
{
951
952
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `increase-font-size'.\n");
953
	g_signal_emit_by_name(terminal, "increase-font-size");
954
        g_object_notify(G_OBJECT(terminal), "font-scale");
955
956
957
958
959
960
}

/* Emit a "decrease-font-size" signal. */
static void
vte_terminal_emit_decrease_font_size(VteTerminal *terminal)
{
961
962
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `decrease-font-size'.\n");
963
	g_signal_emit_by_name(terminal, "decrease-font-size");
964
        g_object_notify(G_OBJECT(terminal), "font-scale");
965
966
}

967
/* Emit a "text-inserted" signal. */
968
969
void
_vte_terminal_emit_text_inserted(VteTerminal *terminal)
970
{
971
	if (!terminal->pvt->accessible_emit) {
972
973
		return;
	}
974
975
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `text-inserted'.\n");
976
977
978
979
	g_signal_emit_by_name(terminal, "text-inserted");
}

/* Emit a "text-deleted" signal. */
980
981
void
_vte_terminal_emit_text_deleted(VteTerminal *terminal)
982
{
983
	if (!terminal->pvt->accessible_emit) {
984
985
		return;
	}
986
987
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `text-deleted'.\n");
988
989
990
991
992
993
994
	g_signal_emit_by_name(terminal, "text-deleted");
}

/* Emit a "text-modified" signal. */
static void
vte_terminal_emit_text_modified(VteTerminal *terminal)
{
995
	if (!terminal->pvt->accessible_emit) {
996
997
		return;
	}
998
999
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `text-modified'.\n");
1000
1001
1002
1003
1004
1005
1006
	g_signal_emit_by_name(terminal, "text-modified");
}

/* Emit a "text-scrolled" signal. */
static void
vte_terminal_emit_text_scrolled(VteTerminal *terminal, gint delta)
{
1007
	if (!terminal->pvt->accessible_emit) {
1008
1009
		return;
	}
1010
1011
	_vte_debug_print(VTE_DEBUG_SIGNALS,
			"Emitting `text-scrolled'(%d).\n", delta);
1012
1013
1014
	g_signal_emit_by_name(terminal, "text-scrolled", delta);
}

1015
1016
1017
1018
/* Deselect anything which is selected and refresh the screen if needed. */
static void
vte_terminal_deselect_all(VteTerminal *terminal)
{
1019
	if (terminal->pvt->has_selection) {
1020
		gint sx, sy, ex, ey;
1021

1022
1023
		_vte_debug_print(VTE_DEBUG_SELECTION,
				"Deselecting all text.\n");
1024
1025

		terminal->pvt->has_selection = FALSE;
1026
1027
		/* Don't free the current selection, as we need to keep
		 * hold of it for async copying from the clipboard. */
1028

1029
		vte_terminal_emit_selection_changed(terminal);
1030

1031
1032
1033
1034
		sx = terminal->pvt->selection_start.col;
		sy = terminal->pvt->selection_start.row;
		ex = terminal->pvt->selection_end.col;
		ey = terminal->pvt->selection_end.row;
1035
1036
1037
1038
		_vte_invalidate_region(terminal,
				MIN (sx, ex), MAX (sx, ex),
				MIN (sy, ey),   MAX (sy, ey),
				FALSE);