vte.c 441 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
static void _vte_check_cursor_blink(VteTerminal *terminal);
138

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

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

149
enum {
150
        PROP_0,
151
152
153
#if GTK_CHECK_VERSION (2, 91, 2)
        PROP_HADJUSTMENT,
        PROP_VADJUSTMENT,
154
155
        PROP_HSCROLL_POLICY,
        PROP_VSCROLL_POLICY,
156
#endif
157
158
159
160
161
162
163
164
165
166
167
168
169
170
        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,
171
        PROP_FONT_DESC,
172
173
174
        PROP_ICON_TITLE,
        PROP_MOUSE_POINTER_AUTOHIDE,
        PROP_PTY,
175
        PROP_PTY_OBJECT,
176
177
178
179
180
181
182
        PROP_SCROLL_BACKGROUND,
        PROP_SCROLLBACK_LINES,
        PROP_SCROLL_ON_KEYSTROKE,
        PROP_SCROLL_ON_OUTPUT,
        PROP_WINDOW_TITLE,
        PROP_WORD_CHARS,
        PROP_VISIBLE_BELL
183
184
};

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

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

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/* 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)
{
215
216
217
	chunk->next = free_chunks;
	chunk->len = free_chunks ? free_chunks->len + 1 : 0;
	free_chunks = chunk;
218
219
}
static void
220
prune_chunks (guint len)
221
{
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
	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) {
238
		struct _vte_incoming_chunk *next = chunk->next;
239
		g_free (chunk);
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
284
		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;
}

285

286
287
288
289
290
291
292
293
294
295
296
297
#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
298
299
300
301
302
303
304
305
#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
306
#endif /* GTK 3.0 */
Kjartan Maraas's avatar
Kjartan Maraas committed
307

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

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

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

324

325
VteRowData *
326
_vte_terminal_ring_insert (VteTerminal *terminal, glong position, gboolean fill)
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
{
	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
346
void
347
_vte_terminal_ring_remove (VteTerminal *terminal, glong position)
Behdad Esfahbod's avatar
Minor    
Behdad Esfahbod committed
348
{
349
	_vte_ring_remove (terminal->pvt->screen->row_data, position);
Behdad Esfahbod's avatar
Minor    
Behdad Esfahbod committed
350
}
351

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

	screen = terminal->pvt->screen;

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

365
/* Cause certain cells to be repainted. */
366
367
368
369
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
370
{
371
	VteRegionRectangle rect;
Chris Wilson's avatar
Chris Wilson committed
372
	glong i;
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
373

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

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

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

390
391
392
393
394
	/* 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;

395
396
397
398
399
400
	/* 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
401
	/* Clamp the start values to reasonable numbers. */
402
	i = row_start + row_count;
403
	row_start = MAX (0, row_start);
404
	row_count = CLAMP (i - row_start, 0, terminal->row_count);
405

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

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

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

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

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

447
448
449
450
451
452
453
454
	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 {
455
		gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
456
	}
457

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

461
static void
462
463
464
465
_vte_invalidate_region (VteTerminal *terminal,
			glong scolumn, glong ecolumn,
			glong srow, glong erow,
			gboolean block)
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
{
	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);
	}
}


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

493
	g_assert(VTE_IS_TERMINAL(terminal));
494

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

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

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

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

514
515
516
517
518
519
520
	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 {
521
		gdk_window_invalidate_rect (gtk_widget_get_window (&terminal->widget), &rect, FALSE);
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