1 /*
2  * Copyright (C) 2002,2003 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 /*
20  * SECTION: vte-access
21  * @short_description: Accessibility peer of #VteTerminal
22  *
23  * The accessibility peer of a #VteTerminal, implementing GNOME's accessibility
24  * framework.
25  */
26 
27 #include "config.h"
28 
29 #include <atk/atk.h>
30 #include <gtk/gtk.h>
31 #include <gtk/gtk-a11y.h>
32 #include <string.h>
33 #include "debug.h"
34 #include "vte.h"
35 #include "vteaccess.h"
36 #include "vteint.h"
37 #include "vte-private.h"
38 
39 #ifdef HAVE_LOCALE_H
40 #include <locale.h>
41 #endif
42 #include <glib/gi18n-lib.h>
43 
44 enum {
45         ACTION_MENU,
46         LAST_ACTION
47 };
48 
49 typedef struct _VteTerminalAccessiblePrivate {
50 	gboolean snapshot_contents_invalid;	/* This data is stale. */
51 	gboolean snapshot_caret_invalid;	/* This data is stale. */
52 	GString *snapshot_text;		/* Pointer to UTF-8 text. */
53 	GArray *snapshot_characters;	/* Offsets to character begin points. */
54 	GArray *snapshot_attributes;	/* Attributes, per byte. */
55 	GArray *snapshot_linebreaks;	/* Offsets to line breaks. */
56 	gint snapshot_caret;       /* Location of the cursor (in characters). */
57         gboolean text_caret_moved_pending;
58 
59 	char *action_descriptions[LAST_ACTION];
60 } VteTerminalAccessiblePrivate;
61 
62 enum direction {
63 	direction_previous = -1,
64 	direction_current = 0,
65 	direction_next = 1
66 };
67 
68 static gunichar vte_terminal_accessible_get_character_at_offset(AtkText *text,
69 								gint offset);
70 
71 static const char *vte_terminal_accessible_action_names[] = {
72         "menu",
73         NULL
74 };
75 
76 static const char *vte_terminal_accessible_action_descriptions[] = {
77         "Popup context menu",
78         NULL
79 };
80 
81 static void vte_terminal_accessible_text_iface_init(AtkTextIface *iface);
82 static void vte_terminal_accessible_component_iface_init(AtkComponentIface *component);
83 static void vte_terminal_accessible_action_iface_init(AtkActionIface *action);
84 
G_DEFINE_TYPE_WITH_CODE(VteTerminalAccessible,_vte_terminal_accessible,GTK_TYPE_WIDGET_ACCESSIBLE,G_ADD_PRIVATE (VteTerminalAccessible)G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,vte_terminal_accessible_text_iface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,vte_terminal_accessible_component_iface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,vte_terminal_accessible_action_iface_init))85 G_DEFINE_TYPE_WITH_CODE (VteTerminalAccessible, _vte_terminal_accessible, GTK_TYPE_WIDGET_ACCESSIBLE,
86                          G_ADD_PRIVATE (VteTerminalAccessible)
87                          G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, vte_terminal_accessible_text_iface_init)
88                          G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, vte_terminal_accessible_component_iface_init)
89                          G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, vte_terminal_accessible_action_iface_init))
90 
91 static gint
92 offset_from_xy (VteTerminalAccessiblePrivate *priv,
93 		gint x, gint y)
94 {
95 	gint offset;
96 	gint linebreak;
97 	gint next_linebreak;
98 
99 	if (y >= (gint) priv->snapshot_linebreaks->len)
100 		y = priv->snapshot_linebreaks->len -1;
101 
102 	linebreak = g_array_index (priv->snapshot_linebreaks, int, y);
103 	if (y + 1 == (gint) priv->snapshot_linebreaks->len)
104 		next_linebreak = priv->snapshot_characters->len;
105 	else
106 		next_linebreak = g_array_index (priv->snapshot_linebreaks, int, y + 1);
107 
108 	offset = linebreak + x;
109 	if (offset >= next_linebreak)
110 		offset = next_linebreak -1;
111 	return offset;
112 }
113 
114 static void
xy_from_offset(VteTerminalAccessiblePrivate * priv,guint offset,gint * x,gint * y)115 xy_from_offset (VteTerminalAccessiblePrivate *priv,
116 		guint offset, gint *x, gint *y)
117 {
118 	guint i, linebreak;
119 	gint cur_x, cur_y;
120 	gint cur_offset = 0;
121 
122 	cur_x = -1;
123 	cur_y = -1;
124 	for (i = 0; i < priv->snapshot_linebreaks->len; i++) {
125 		linebreak = g_array_index (priv->snapshot_linebreaks, int, i);
126 		if (offset < linebreak) {
127 			cur_x = offset - cur_offset;
128 			cur_y = i - 1;
129 			break;
130 
131 		}  else {
132 			cur_offset = linebreak;
133 		}
134 	}
135 	if (i == priv->snapshot_linebreaks->len) {
136 		if (offset <= priv->snapshot_characters->len) {
137 			cur_x = offset - cur_offset;
138 			cur_y = i - 1;
139 		}
140 	}
141 	*x = cur_x;
142 	*y = cur_y;
143 }
144 
145 /* "Oh yeah, that's selected.  Sure." callback. */
146 static gboolean
all_selected(VteTerminal * terminal,glong column,glong row,gpointer data)147 all_selected(VteTerminal *terminal, glong column, glong row, gpointer data)
148 {
149 	return TRUE;
150 }
151 
152 static void
emit_text_caret_moved(GObject * object,glong caret)153 emit_text_caret_moved(GObject *object, glong caret)
154 {
155 	_vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
156 			"Accessibility peer emitting "
157 			"`text-caret-moved'.\n");
158 	g_signal_emit_by_name(object, "text-caret-moved", caret);
159 }
160 
161 static void
emit_text_changed_insert(GObject * object,const char * text,glong offset,glong len)162 emit_text_changed_insert(GObject *object,
163 			 const char *text, glong offset, glong len)
164 {
165 	glong start, count;
166 	if (len == 0) {
167 		return;
168 	}
169 	/* Convert the byte offsets to character offsets. */
170 	start = g_utf8_pointer_to_offset (text, text + offset);
171 	count = g_utf8_pointer_to_offset (text + offset, text + offset + len);
172 	_vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
173 			"Accessibility peer emitting "
174 			"`text-changed::insert' (%ld, %ld) (%ld, %ld).\n"
175 			"Inserted text was `%.*s'.\n",
176 			offset, len, start, count,
177 			(int) len, text + offset);
178 	g_signal_emit_by_name(object, "text-changed::insert", start, count);
179 }
180 
181 static void
emit_text_changed_delete(GObject * object,const char * text,glong offset,glong len)182 emit_text_changed_delete(GObject *object,
183 			 const char *text, glong offset, glong len)
184 {
185 	glong start, count;
186 	if (len == 0) {
187 		return;
188 	}
189 	/* Convert the byte offsets to characters. */
190 	start = g_utf8_pointer_to_offset (text, text + offset);
191 	count = g_utf8_pointer_to_offset (text + offset, text + offset + len);
192 	_vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
193 			"Accessibility peer emitting "
194 			"`text-changed::delete' (%ld, %ld) (%ld, %ld).\n"
195 			"Deleted text was `%.*s'.\n",
196 			offset, len, start, count,
197 			(int) len, text + offset);
198 	g_signal_emit_by_name(object, "text-changed::delete", start, count);
199 }
200 
201 static void
vte_terminal_accessible_update_private_data_if_needed(VteTerminalAccessible * accessible,GString ** old_text,GArray ** old_characters)202 vte_terminal_accessible_update_private_data_if_needed(VteTerminalAccessible *accessible,
203                                                       GString **old_text,
204                                                       GArray **old_characters)
205 {
206 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
207         VteTerminal *terminal;
208 	struct _VteCharAttributes attrs;
209 	char *next, *tmp;
210 	long row, offset, caret;
211 	long ccol, crow;
212 	guint i;
213 
214 	/* If nothing's changed, just return immediately. */
215 	if ((priv->snapshot_contents_invalid == FALSE) &&
216 	    (priv->snapshot_caret_invalid == FALSE)) {
217                 if (old_text) {
218 			if (priv->snapshot_text) {
219                                 *old_text = g_string_new_len(priv->snapshot_text->str,
220                                                              priv->snapshot_text->len);
221 			} else {
222                                 *old_text = g_string_new("");
223 			}
224                 }
225                 if (old_characters) {
226                         if (priv->snapshot_characters) {
227                                 *old_characters = g_array_sized_new(FALSE, FALSE, sizeof(int),
228                                                                     priv->snapshot_characters->len);
229                                 g_array_append_vals(*old_characters,
230                                                     priv->snapshot_characters->data,
231                                                     priv->snapshot_characters->len);
232                         } else {
233                                 *old_characters = g_array_new(FALSE, FALSE, sizeof(int));
234 			}
235 		}
236 		return;
237 	}
238 
239 	/* Re-read the contents of the widget if the contents have changed. */
240 	terminal = VTE_TERMINAL(gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)));
241 	if (priv->snapshot_contents_invalid) {
242 		/* Free the outdated snapshot data, unless the caller
243 		 * wants it. */
244                 if (old_text) {
245 			if (priv->snapshot_text != NULL) {
246                                 *old_text = priv->snapshot_text;
247 			} else {
248                                 *old_text = g_string_new("");
249 			}
250 		} else {
251 			if (priv->snapshot_text != NULL) {
252 				g_string_free(priv->snapshot_text, TRUE);
253 			}
254 		}
255 		priv->snapshot_text = NULL;
256 
257                 /* Free the character offsets unless the caller wants it,
258                  * and allocate a new array to hold them. */
259                 if (old_characters) {
260                         if (priv->snapshot_characters != NULL) {
261                                 *old_characters = priv->snapshot_characters;
262                         } else {
263                                 *old_characters = g_array_new(FALSE, FALSE, sizeof(int));
264                         }
265                 } else {
266                         if (priv->snapshot_characters != NULL) {
267                                 g_array_free(priv->snapshot_characters, TRUE);
268                         }
269 		}
270 		priv->snapshot_characters = g_array_new(FALSE, FALSE, sizeof(int));
271 
272 		/* Free the attribute lists and allocate a new array to hold
273 		 * them. */
274 		if (priv->snapshot_attributes != NULL) {
275 			g_array_free(priv->snapshot_attributes, TRUE);
276 		}
277 		priv->snapshot_attributes = g_array_new(FALSE, FALSE,
278 							sizeof(struct _VteCharAttributes));
279 
280 		/* Free the linebreak offsets and allocate a new array to hold
281 		 * them. */
282 		if (priv->snapshot_linebreaks != NULL) {
283 			g_array_free(priv->snapshot_linebreaks, TRUE);
284 		}
285 		priv->snapshot_linebreaks = g_array_new(FALSE, FALSE, sizeof(int));
286 
287 		/* Get a new view of the uber-label. */
288 		tmp = vte_terminal_get_text_include_trailing_spaces(terminal,
289 								    all_selected,
290 								    NULL,
291 								    priv->snapshot_attributes);
292 		if (tmp == NULL) {
293 			/* Aaargh!  We're screwed. */
294 			return;
295 		}
296 		priv->snapshot_text = g_string_new_len(tmp,
297 						       priv->snapshot_attributes->len);
298 		g_free(tmp);
299 
300 		/* Get the offsets to the beginnings of each character. */
301 		i = 0;
302 		next = priv->snapshot_text->str;
303 		while (i < priv->snapshot_attributes->len) {
304 			g_array_append_val(priv->snapshot_characters, i);
305 			next = g_utf8_next_char(next);
306 			if (next == NULL) {
307 				break;
308 			} else {
309 				i = next - priv->snapshot_text->str;
310 			}
311 		}
312 		/* Find offsets for the beginning of lines. */
313 		for (i = 0, row = 0; i < priv->snapshot_characters->len; i++) {
314 			/* Get the attributes for the current cell. */
315 			offset = g_array_index(priv->snapshot_characters,
316 					       int, i);
317 			attrs = g_array_index(priv->snapshot_attributes,
318 					      struct _VteCharAttributes,
319 					      offset);
320 			/* If this character is on a row different from the row
321 			 * the character we looked at previously was on, then
322 			 * it's a new line and we need to keep track of where
323 			 * it is. */
324 			if ((i == 0) || (attrs.row != row)) {
325 				_vte_debug_print(VTE_DEBUG_ALLY,
326 						"Row %d/%ld begins at %u.\n",
327 						priv->snapshot_linebreaks->len,
328 						attrs.row, i);
329 				g_array_append_val(priv->snapshot_linebreaks, i);
330 			}
331 			row = attrs.row;
332 		}
333 		/* Add the final line break. */
334 		g_array_append_val(priv->snapshot_linebreaks, i);
335 		/* We're finished updating this. */
336 		priv->snapshot_contents_invalid = FALSE;
337 	}
338 
339 	/* Update the caret position. */
340 	vte_terminal_get_cursor_position(terminal, &ccol, &crow);
341 	_vte_debug_print(VTE_DEBUG_ALLY,
342 			"Cursor at (%ld, " "%ld).\n", ccol, crow);
343 
344 	/* Get the offsets to the beginnings of each line. */
345 	caret = 0;
346 	for (i = 0; i < priv->snapshot_characters->len; i++) {
347 		/* Get the attributes for the current cell. */
348 		offset = g_array_index(priv->snapshot_characters,
349 				       int, i);
350 		attrs = g_array_index(priv->snapshot_attributes,
351 				      struct _VteCharAttributes,
352 				      offset);
353 		/* If this cell is "before" the cursor, move the
354 		 * caret to be "here". */
355 		if ((attrs.row < crow) ||
356 		    ((attrs.row == crow) && (attrs.column < ccol))) {
357 			caret = i + 1;
358 		}
359 	}
360 
361         /* Make a note that we'll need to notify observers if the caret moved.
362          * But only notify them after sending text-changed. */
363 	if (caret != priv->snapshot_caret) {
364 		priv->snapshot_caret = caret;
365                 priv->text_caret_moved_pending = TRUE;
366 	}
367 
368 	/* Done updating the caret position, whether we needed to or not. */
369 	priv->snapshot_caret_invalid = FALSE;
370 
371 	_vte_debug_print(VTE_DEBUG_ALLY,
372 			"Refreshed accessibility snapshot, "
373 			"%ld cells, %ld characters.\n",
374 			(long)priv->snapshot_attributes->len,
375 			(long)priv->snapshot_characters->len);
376 }
377 
378 static void
vte_terminal_accessible_maybe_emit_text_caret_moved(VteTerminalAccessible * accessible)379 vte_terminal_accessible_maybe_emit_text_caret_moved(VteTerminalAccessible *accessible)
380 {
381         VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
382 
383         if (priv->text_caret_moved_pending) {
384                 emit_text_caret_moved(G_OBJECT(accessible), priv->snapshot_caret);
385                 priv->text_caret_moved_pending = FALSE;
386         }
387 }
388 
389 /* A signal handler to catch "text-inserted/deleted/modified" signals. */
390 static void
vte_terminal_accessible_text_modified(VteTerminal * terminal,gpointer data)391 vte_terminal_accessible_text_modified(VteTerminal *terminal, gpointer data)
392 {
393         VteTerminalAccessible *accessible = data;
394 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
395         GString *old_text;
396         GArray *old_characters;
397 	char *old, *current;
398 	glong offset, caret_offset, olen, clen;
399 	gint old_snapshot_caret;
400 
401 	old_snapshot_caret = priv->snapshot_caret;
402 	priv->snapshot_contents_invalid = TRUE;
403 	vte_terminal_accessible_update_private_data_if_needed(accessible,
404                                                               &old_text,
405                                                               &old_characters);
406         g_assert(old_text != NULL);
407         g_assert(old_characters != NULL);
408 
409 	current = priv->snapshot_text->str;
410 	clen = priv->snapshot_text->len;
411         old = old_text->str;
412         olen = old_text->len;
413 
414 	if ((guint) priv->snapshot_caret < priv->snapshot_characters->len) {
415 		caret_offset = g_array_index(priv->snapshot_characters,
416 				int, priv->snapshot_caret);
417 	} else {
418 		/* caret was not in the line */
419 		caret_offset = clen;
420 	}
421 
422 	/* Find the offset where they don't match. */
423 	offset = 0;
424 	while ((offset < olen) && (offset < clen)) {
425 		if (old[offset] != current[offset]) {
426 			break;
427 		}
428 		offset++;
429 	}
430 
431         /* Check if we just backspaced over a space. */
432 	if ((olen == offset) &&
433 		       	(caret_offset < olen && old[caret_offset] == ' ') &&
434 			(old_snapshot_caret == priv->snapshot_caret + 1)) {
435                 GString *saved_text = priv->snapshot_text;
436                 GArray *saved_characters = priv->snapshot_characters;
437 
438                 priv->snapshot_text = old_text;
439                 priv->snapshot_characters = old_characters;
440 		emit_text_changed_delete(G_OBJECT(accessible),
441 					 old, caret_offset, 1);
442                 priv->snapshot_text = saved_text;
443                 priv->snapshot_characters = saved_characters;
444 	}
445 
446 
447 	/* At least one of them had better have more data, right? */
448 	if ((offset < olen) || (offset < clen)) {
449 		/* Back up from both end points until we find the *last* point
450 		 * where they differed. */
451 		gchar *op = old + olen;
452 		gchar *cp = current + clen;
453 		while (op > old + offset && cp > current + offset) {
454 			gchar *opp = g_utf8_prev_char (op);
455 			gchar *cpp = g_utf8_prev_char (cp);
456 			if (g_utf8_get_char (opp) != g_utf8_get_char (cpp)) {
457 				break;
458 			}
459 			op = opp;
460 			cp = cpp;
461 		}
462 		/* recompute the respective lengths */
463 		olen = op - old;
464 		clen = cp - current;
465 		/* At least one of them has to have text the other
466 		 * doesn't. */
467 		g_assert((clen > offset) || (olen > offset));
468 		g_assert((clen >= 0) && (olen >= 0));
469 		/* Now emit a deleted signal for text that was in the old
470 		 * string but isn't in the new one... */
471 		if (olen > offset) {
472                         GString *saved_text = priv->snapshot_text;
473                         GArray *saved_characters = priv->snapshot_characters;
474 
475                         priv->snapshot_text = old_text;
476                         priv->snapshot_characters = old_characters;
477 			emit_text_changed_delete(G_OBJECT(accessible),
478 						 old,
479 						 offset,
480 						 olen - offset);
481                         priv->snapshot_text = saved_text;
482                         priv->snapshot_characters = saved_characters;
483 		}
484 		/* .. and an inserted signal for text that wasn't in the old
485 		 * string but is in the new one. */
486 		if (clen > offset) {
487 			emit_text_changed_insert(G_OBJECT(accessible),
488 						 current,
489 						 offset,
490 						 clen - offset);
491 		}
492 	}
493 
494         vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
495 
496         g_string_free(old_text, TRUE);
497         g_array_free(old_characters, TRUE);
498 }
499 
500 /* A signal handler to catch "text-scrolled" signals. */
501 static void
vte_terminal_accessible_text_scrolled(VteTerminal * terminal,gint howmuch,gpointer data)502 vte_terminal_accessible_text_scrolled(VteTerminal *terminal,
503 				      gint howmuch,
504 				      gpointer data)
505 {
506         VteTerminalAccessible *accessible = data;
507 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
508 	struct _VteCharAttributes attr;
509 	long delta, row_count;
510 	guint i, len;
511 
512 	g_assert(howmuch != 0);
513 
514         row_count = vte_terminal_get_row_count(terminal);
515 	if (((howmuch < 0) && (howmuch <= -row_count)) ||
516 	    ((howmuch > 0) && (howmuch >= row_count))) {
517 		/* All of the text was removed. */
518 		if (priv->snapshot_text != NULL) {
519 			if (priv->snapshot_text->str != NULL) {
520 				emit_text_changed_delete(G_OBJECT(accessible),
521 							 priv->snapshot_text->str,
522 							 0,
523 							 priv->snapshot_text->len);
524 			}
525 		}
526 		priv->snapshot_contents_invalid = TRUE;
527 		vte_terminal_accessible_update_private_data_if_needed(accessible,
528 								      NULL,
529 								      NULL);
530 		/* All of the present text was added. */
531 		if (priv->snapshot_text != NULL) {
532 			if (priv->snapshot_text->str != NULL) {
533 				emit_text_changed_insert(G_OBJECT(accessible),
534 							 priv->snapshot_text->str,
535 							 0,
536 							 priv->snapshot_text->len);
537 			}
538 		}
539                 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
540 		return;
541 	}
542 	/* Find the start point. */
543 	delta = 0;
544 	if (priv->snapshot_attributes != NULL) {
545 		if (priv->snapshot_attributes->len > 0) {
546 			attr = g_array_index(priv->snapshot_attributes,
547 					     struct _VteCharAttributes,
548 					     0);
549 			delta = attr.row;
550 		}
551 	}
552 	/* We scrolled up, so text was added at the top and removed
553 	 * from the bottom. */
554 	if ((howmuch < 0) && (howmuch > -row_count)) {
555 		gboolean inserted = FALSE;
556 		howmuch = -howmuch;
557 		if (priv->snapshot_attributes != NULL &&
558 				priv->snapshot_text != NULL) {
559 			/* Find the first byte that scrolled off. */
560 			for (i = 0; i < priv->snapshot_attributes->len; i++) {
561 				attr = g_array_index(priv->snapshot_attributes,
562 						struct _VteCharAttributes,
563 						i);
564 				if (attr.row >= delta + row_count - howmuch) {
565 					break;
566 				}
567 			}
568 			if (i < priv->snapshot_attributes->len) {
569 				/* The rest of the string was deleted -- make a note. */
570 				emit_text_changed_delete(G_OBJECT(accessible),
571 						priv->snapshot_text->str,
572 						i,
573 						priv->snapshot_attributes->len - i);
574 			}
575 			inserted = TRUE;
576 		}
577 		/* Refresh.  Note that i is now the length of the data which
578 		 * we expect to have left over. */
579 		priv->snapshot_contents_invalid = TRUE;
580 		vte_terminal_accessible_update_private_data_if_needed(accessible,
581 								      NULL,
582 								      NULL);
583 		/* If we now have more text than before, the initial portion
584 		 * was added. */
585 		if (inserted) {
586 			len = priv->snapshot_text->len;
587 			if (len > i) {
588 				emit_text_changed_insert(G_OBJECT(accessible),
589 							 priv->snapshot_text->str,
590 							 0,
591 							 len - i);
592 			}
593 		}
594                 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
595 		return;
596 	}
597 	/* We scrolled down, so text was added at the bottom and removed
598 	 * from the top. */
599 	if ((howmuch > 0) && (howmuch < row_count)) {
600 		gboolean inserted = FALSE;
601 		if (priv->snapshot_attributes != NULL &&
602 				priv->snapshot_text != NULL) {
603 			/* Find the first byte that wasn't scrolled off the top. */
604 			for (i = 0; i < priv->snapshot_attributes->len; i++) {
605 				attr = g_array_index(priv->snapshot_attributes,
606 						struct _VteCharAttributes,
607 						i);
608 				if (attr.row >= delta + howmuch) {
609 					break;
610 				}
611 			}
612 			/* That many bytes disappeared -- make a note. */
613 			emit_text_changed_delete(G_OBJECT(accessible),
614 					priv->snapshot_text->str,
615 					0,
616 					i);
617 			/* Figure out how much text was left, and refresh. */
618 			i = strlen(priv->snapshot_text->str + i);
619 			inserted = TRUE;
620 		}
621 		priv->snapshot_contents_invalid = TRUE;
622 		vte_terminal_accessible_update_private_data_if_needed(accessible,
623 								      NULL,
624 								      NULL);
625 		/* Any newly-added string data is new, so note that it was
626 		 * inserted. */
627 		if (inserted) {
628 			len = priv->snapshot_text->len;
629 			if (len > i) {
630 				/* snapshot_text always contains a trailing '\n',
631 				 * insertion happens in front of it: bug 657960 */
632 				g_assert(i >= 1);
633 				emit_text_changed_insert(G_OBJECT(accessible),
634 							 priv->snapshot_text->str,
635 							 i - 1,
636 							 len - i);
637 			}
638 		}
639                 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
640 		return;
641 	}
642 	g_assert_not_reached();
643 }
644 
645 /* A signal handler to catch "cursor-moved" signals. */
646 static void
vte_terminal_accessible_invalidate_cursor(VteTerminal * terminal,gpointer data)647 vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data)
648 {
649         VteTerminalAccessible *accessible = data;
650 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
651 
652 	_vte_debug_print(VTE_DEBUG_ALLY,
653 			"Invalidating accessibility cursor.\n");
654 	priv->snapshot_caret_invalid = TRUE;
655 	vte_terminal_accessible_update_private_data_if_needed(accessible,
656 							      NULL, NULL);
657         vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
658 }
659 
660 /* Handle title changes by resetting the description. */
661 static void
vte_terminal_accessible_title_changed(VteTerminal * terminal,gpointer data)662 vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data)
663 {
664         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
665 
666 	atk_object_set_description(ATK_OBJECT(accessible), vte_terminal_get_window_title(terminal));
667 }
668 
669 /* Reflect visibility-notify events. */
670 static gboolean
vte_terminal_accessible_visibility_notify(VteTerminal * terminal,GdkEventVisibility * event,gpointer data)671 vte_terminal_accessible_visibility_notify(VteTerminal *terminal,
672 					  GdkEventVisibility *event,
673 					  gpointer data)
674 {
675         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
676 	GtkWidget *widget;
677 	gboolean visible;
678 
679 	visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED;
680 	/* The VISIBLE state indicates that this widget is "visible". */
681 	atk_object_notify_state_change(ATK_OBJECT(accessible),
682 				       ATK_STATE_VISIBLE,
683 				       visible);
684 	widget = &terminal->widget;
685 	while (visible) {
686 		if (gtk_widget_get_toplevel(widget) == widget) {
687 			break;
688 		}
689 		if (widget == NULL) {
690 			break;
691 		}
692 		visible = visible && (gtk_widget_get_visible(widget));
693 		widget = gtk_widget_get_parent(widget);
694 	}
695 	/* The SHOWING state indicates that this widget, and all of its
696 	 * parents up to the toplevel, are "visible". */
697 	atk_object_notify_state_change(ATK_OBJECT(accessible),
698 				       ATK_STATE_SHOWING,
699 				       visible);
700 
701 	return FALSE;
702 }
703 
704 static void
vte_terminal_accessible_selection_changed(VteTerminal * terminal,gpointer data)705 vte_terminal_accessible_selection_changed (VteTerminal *terminal,
706 					   gpointer data)
707 {
708         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
709 
710 	g_signal_emit_by_name (accessible, "text_selection_changed");
711 }
712 
713 static void
vte_terminal_accessible_initialize(AtkObject * obj,gpointer data)714 vte_terminal_accessible_initialize (AtkObject *obj, gpointer data)
715 {
716 	VteTerminal *terminal = VTE_TERMINAL (data);
717         const char *window_title;
718 
719 	ATK_OBJECT_CLASS (_vte_terminal_accessible_parent_class)->initialize (obj, data);
720 
721 	_vte_terminal_accessible_ref(terminal);
722 
723 	g_signal_connect(terminal, "text-inserted",
724 			 G_CALLBACK(vte_terminal_accessible_text_modified),
725 			 obj);
726 	g_signal_connect(terminal, "text-deleted",
727 			 G_CALLBACK(vte_terminal_accessible_text_modified),
728 			 obj);
729 	g_signal_connect(terminal, "text-modified",
730 			 G_CALLBACK(vte_terminal_accessible_text_modified),
731 			 obj);
732 	g_signal_connect(terminal, "text-scrolled",
733 			 G_CALLBACK(vte_terminal_accessible_text_scrolled),
734 			 obj);
735 	g_signal_connect(terminal, "cursor-moved",
736 			 G_CALLBACK(vte_terminal_accessible_invalidate_cursor),
737 			 obj);
738 	g_signal_connect(terminal, "window-title-changed",
739 			 G_CALLBACK(vte_terminal_accessible_title_changed),
740 			 obj);
741 
742 	g_signal_connect(terminal, "visibility-notify-event",
743 		G_CALLBACK(vte_terminal_accessible_visibility_notify), obj);
744 	g_signal_connect(terminal, "selection-changed",
745 		G_CALLBACK(vte_terminal_accessible_selection_changed), obj);
746 
747 	atk_object_set_name(obj, "Terminal");
748         window_title = vte_terminal_get_window_title(terminal);
749 	atk_object_set_description(obj, window_title ? window_title : "");
750 
751 	atk_object_notify_state_change(obj,
752 				       ATK_STATE_FOCUSABLE, TRUE);
753 	atk_object_notify_state_change(obj,
754 				       ATK_STATE_EXPANDABLE, FALSE);
755 	atk_object_notify_state_change(obj,
756 				       ATK_STATE_RESIZABLE, TRUE);
757         atk_object_set_role(obj, ATK_ROLE_TERMINAL);
758 }
759 
760 static void
_vte_terminal_accessible_init(VteTerminalAccessible * accessible)761 _vte_terminal_accessible_init (VteTerminalAccessible *accessible)
762 {
763         VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private (accessible);
764 
765 	_vte_debug_print(VTE_DEBUG_ALLY, "Initialising accessible peer.\n");
766 
767 	priv->snapshot_text = NULL;
768 	priv->snapshot_characters = NULL;
769 	priv->snapshot_attributes = NULL;
770 	priv->snapshot_linebreaks = NULL;
771 	priv->snapshot_caret = -1;
772 	priv->snapshot_contents_invalid = TRUE;
773 	priv->snapshot_caret_invalid = TRUE;
774         priv->text_caret_moved_pending = FALSE;
775 }
776 
777 static void
vte_terminal_accessible_finalize(GObject * object)778 vte_terminal_accessible_finalize(GObject *object)
779 {
780         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(object);
781 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
782         GtkWidget *widget;
783 	gint i;
784 
785 	_vte_debug_print(VTE_DEBUG_ALLY, "Finalizing accessible peer.\n");
786 
787         widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible));
788 
789 	if (widget != NULL) {
790 		g_signal_handlers_disconnect_matched(widget,
791 						     G_SIGNAL_MATCH_FUNC |
792 						     G_SIGNAL_MATCH_DATA,
793 						     0, 0, NULL,
794 						     vte_terminal_accessible_text_modified,
795 						     object);
796 		g_signal_handlers_disconnect_matched(widget,
797 						     G_SIGNAL_MATCH_FUNC |
798 						     G_SIGNAL_MATCH_DATA,
799 						     0, 0, NULL,
800 						     vte_terminal_accessible_text_scrolled,
801 						     object);
802 		g_signal_handlers_disconnect_matched(widget,
803 						     G_SIGNAL_MATCH_FUNC |
804 						     G_SIGNAL_MATCH_DATA,
805 						     0, 0, NULL,
806 						     vte_terminal_accessible_invalidate_cursor,
807 						     object);
808 		g_signal_handlers_disconnect_matched(widget,
809 						     G_SIGNAL_MATCH_FUNC |
810 						     G_SIGNAL_MATCH_DATA,
811 						     0, 0, NULL,
812 						     vte_terminal_accessible_title_changed,
813 						     object);
814 		g_signal_handlers_disconnect_matched(widget,
815 						     G_SIGNAL_MATCH_FUNC |
816 						     G_SIGNAL_MATCH_DATA,
817 						     0, 0, NULL,
818 						     vte_terminal_accessible_visibility_notify,
819 						     object);
820 	}
821 
822 	if (priv->snapshot_text != NULL) {
823 		g_string_free(priv->snapshot_text, TRUE);
824 	}
825 	if (priv->snapshot_characters != NULL) {
826 		g_array_free(priv->snapshot_characters, TRUE);
827 	}
828 	if (priv->snapshot_attributes != NULL) {
829 		g_array_free(priv->snapshot_attributes, TRUE);
830 	}
831 	if (priv->snapshot_linebreaks != NULL) {
832 		g_array_free(priv->snapshot_linebreaks, TRUE);
833 	}
834 	for (i = 0; i < LAST_ACTION; i++) {
835 		g_free (priv->action_descriptions[i]);
836 	}
837 
838 	G_OBJECT_CLASS(_vte_terminal_accessible_parent_class)->finalize(object);
839 }
840 
841 static gchar *
vte_terminal_accessible_get_text(AtkText * text,gint start_offset,gint end_offset)842 vte_terminal_accessible_get_text(AtkText *text,
843 				 gint start_offset, gint end_offset)
844 {
845         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
846 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
847 	int start, end;
848 	gchar *ret;
849 
850 	g_assert(VTE_IS_TERMINAL_ACCESSIBLE(accessible));
851 
852         /* Swap around if start is greater than end */
853         if (start_offset > end_offset && end_offset != -1) {
854                 gint tmp;
855 
856                 tmp = start_offset;
857                 start_offset = end_offset;
858                 end_offset = tmp;
859         }
860 
861 	g_assert((start_offset >= 0) && (end_offset >= -1));
862 
863 	vte_terminal_accessible_update_private_data_if_needed(accessible,
864 							      NULL, NULL);
865 
866 	_vte_debug_print(VTE_DEBUG_ALLY,
867 			"Getting text from %d to %d of %d.\n",
868 			start_offset, end_offset,
869 			priv->snapshot_characters->len);
870 
871 	/* If the requested area is after all of the text, just return an
872 	 * empty string. */
873 	if (start_offset >= (int) priv->snapshot_characters->len) {
874 		return g_strdup("");
875 	}
876 
877 	/* Map the offsets to, er, offsets. */
878 	start = g_array_index(priv->snapshot_characters, int, start_offset);
879 	if ((end_offset == -1) || (end_offset >= (int) priv->snapshot_characters->len) ) {
880 		/* Get everything up to the end of the buffer. */
881 		end = priv->snapshot_text->len;
882 	} else {
883 		/* Map the stopping point. */
884 		end = g_array_index(priv->snapshot_characters, int, end_offset);
885 	}
886 	if (end <= start) {
887 		ret = g_strdup ("");
888 	} else {
889 		ret = g_malloc(end - start + 1);
890 		memcpy(ret, priv->snapshot_text->str + start, end - start);
891 		ret[end - start] = '\0';
892 	}
893 	return ret;
894 }
895 
896 /* Map a subsection of the text with before/at/after char/word/line specs
897  * into a run of Unicode characters.  (The interface is specifying characters,
898  * not bytes, plus that saves us from having to deal with parts of multibyte
899  * characters, which are icky.) */
900 static gchar *
vte_terminal_accessible_get_text_somewhere(AtkText * text,gint offset,AtkTextBoundary boundary_type,enum direction direction,gint * start_offset,gint * end_offset)901 vte_terminal_accessible_get_text_somewhere(AtkText *text,
902 					   gint offset,
903 					   AtkTextBoundary boundary_type,
904 					   enum direction direction,
905 					   gint *start_offset,
906 					   gint *end_offset)
907 {
908         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
909 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
910         VteTerminal *terminal;
911 	gunichar current, prev, next;
912 	guint start, end, line;
913 
914 	vte_terminal_accessible_update_private_data_if_needed(accessible,
915 							      NULL, NULL);
916 
917         terminal = VTE_TERMINAL(gtk_accessible_get_widget (GTK_ACCESSIBLE(text)));
918 
919 	_vte_debug_print(VTE_DEBUG_ALLY,
920 			"Getting %s %s at %d of %d.\n",
921 			(direction == direction_current) ? "this" :
922 			((direction == direction_next) ? "next" : "previous"),
923 			(boundary_type == ATK_TEXT_BOUNDARY_CHAR) ? "char" :
924 			((boundary_type == ATK_TEXT_BOUNDARY_LINE_START) ? "line (start)" :
925 			((boundary_type == ATK_TEXT_BOUNDARY_LINE_END) ? "line (end)" :
926 			((boundary_type == ATK_TEXT_BOUNDARY_WORD_START) ? "word (start)" :
927 			((boundary_type == ATK_TEXT_BOUNDARY_WORD_END) ? "word (end)" :
928 			((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_START) ? "sentence (start)" :
929 			((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_END) ? "sentence (end)" : "unknown")))))),
930 			offset, priv->snapshot_attributes->len);
931 	g_assert(priv->snapshot_text != NULL);
932 	g_assert(priv->snapshot_characters != NULL);
933 	if (offset >= (int) priv->snapshot_characters->len) {
934 		return g_strdup("");
935 	}
936 	g_assert(offset < (int) priv->snapshot_characters->len);
937 	g_assert(offset >= 0);
938 
939 	switch (boundary_type) {
940 		case ATK_TEXT_BOUNDARY_CHAR:
941 			/* We're either looking at the character at this
942 			 * position, the one before it, or the one after it. */
943 			offset += direction;
944 			start = MAX(offset, 0);
945 			end = MIN(offset + 1, (int) priv->snapshot_attributes->len);
946 			break;
947 		case ATK_TEXT_BOUNDARY_WORD_START:
948 			/* Back up to the previous non-word-word transition. */
949 			while (offset > 0) {
950 				prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
951 				if (_vte_terminal_is_word_char(terminal, prev)) {
952 					offset--;
953 				} else {
954 					break;
955 				}
956 			}
957 			start = offset;
958 			/* If we started in a word and we're looking for the
959 			 * word before this one, keep searching by backing up
960 			 * to the previous non-word character and then searching
961 			 * for the word-start before that. */
962 			if (direction == direction_previous) {
963 				while (offset > 0) {
964 					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
965 					if (!_vte_terminal_is_word_char(terminal, prev)) {
966 						offset--;
967 					} else {
968 						break;
969 					}
970 				}
971 				while (offset > 0) {
972 					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
973 					if (_vte_terminal_is_word_char(terminal, prev)) {
974 						offset--;
975 					} else {
976 						break;
977 					}
978 				}
979 				start = offset;
980 			}
981 			/* If we're looking for the word after this one,
982 			 * search forward by scanning forward for the next
983 			 * non-word character, then the next word character
984 			 * after that. */
985 			if (direction == direction_next) {
986 				while (offset < (int) priv->snapshot_characters->len) {
987 					next = vte_terminal_accessible_get_character_at_offset(text, offset);
988 					if (_vte_terminal_is_word_char(terminal, next)) {
989 						offset++;
990 					} else {
991 						break;
992 					}
993 				}
994 				while (offset < (int) priv->snapshot_characters->len) {
995 					next = vte_terminal_accessible_get_character_at_offset(text, offset);
996 					if (!_vte_terminal_is_word_char(terminal, next)) {
997 						offset++;
998 					} else {
999 						break;
1000 					}
1001 				}
1002 				start = offset;
1003 			}
1004 			/* Now find the end of this word. */
1005 			while (offset < (int) priv->snapshot_characters->len) {
1006 				current = vte_terminal_accessible_get_character_at_offset(text, offset);
1007 				if (_vte_terminal_is_word_char(terminal, current)) {
1008 					offset++;
1009 				} else {
1010 					break;
1011 				}
1012 
1013 			}
1014 			/* Now find the next non-word-word transition */
1015 			while (offset < (int) priv->snapshot_characters->len) {
1016 				next = vte_terminal_accessible_get_character_at_offset(text, offset);
1017 				if (!_vte_terminal_is_word_char(terminal, next)) {
1018 					offset++;
1019 				} else {
1020 					break;
1021 				}
1022 			}
1023 			end = offset;
1024 			break;
1025 		case ATK_TEXT_BOUNDARY_WORD_END:
1026 			/* Back up to the previous word-non-word transition. */
1027 			current = vte_terminal_accessible_get_character_at_offset(text, offset);
1028 			while (offset > 0) {
1029 				prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1030 				if (_vte_terminal_is_word_char(terminal, prev) &&
1031 				    !_vte_terminal_is_word_char(terminal, current)) {
1032 					break;
1033 				} else {
1034 					offset--;
1035 					current = prev;
1036 				}
1037 			}
1038 			start = offset;
1039 			/* If we're looking for the word end before this one,
1040 			 * keep searching by backing up to the previous word
1041 			 * character and then searching for the word-end
1042 			 * before that. */
1043 			if (direction == direction_previous) {
1044 				while (offset > 0) {
1045 					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1046 					if (_vte_terminal_is_word_char(terminal, prev)) {
1047 						offset--;
1048 					} else {
1049 						break;
1050 					}
1051 				}
1052 				current = vte_terminal_accessible_get_character_at_offset(text, offset);
1053 				while (offset > 0) {
1054 					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1055 					if (_vte_terminal_is_word_char(terminal, prev) &&
1056 					    !_vte_terminal_is_word_char(terminal, current)) {
1057 						break;
1058 					} else {
1059 						offset--;
1060 						current = prev;
1061 					}
1062 				}
1063 				start = offset;
1064 			}
1065 			/* If we're looking for the word end after this one,
1066 			 * search forward by scanning forward for the next
1067 			 * word character, then the next non-word character
1068 			 * after that. */
1069 			if (direction == direction_next) {
1070 				while (offset < (int) priv->snapshot_characters->len) {
1071 					current = vte_terminal_accessible_get_character_at_offset(text, offset);
1072 					if (!_vte_terminal_is_word_char(terminal, current)) {
1073 						offset++;
1074 					} else {
1075 						break;
1076 					}
1077 				}
1078 				while (offset < (int) priv->snapshot_characters->len) {
1079 					current = vte_terminal_accessible_get_character_at_offset(text, offset);
1080 					if (_vte_terminal_is_word_char(terminal, current)) {
1081 						offset++;
1082 					} else {
1083 						break;
1084 					}
1085 				}
1086 				start = offset;
1087 			}
1088 			/* Now find the next word end. */
1089 			while (offset < (int) priv->snapshot_characters->len) {
1090 				current = vte_terminal_accessible_get_character_at_offset(text, offset);
1091 				if (!_vte_terminal_is_word_char(terminal, current)) {
1092 					offset++;
1093 				} else {
1094 					break;
1095 				}
1096 			}
1097 			while (offset < (int) priv->snapshot_characters->len) {
1098 				current = vte_terminal_accessible_get_character_at_offset(text, offset);
1099 				if (_vte_terminal_is_word_char(terminal, current)) {
1100 					offset++;
1101 				} else {
1102 					break;
1103 				}
1104 			}
1105 			end = offset;
1106 			break;
1107 		case ATK_TEXT_BOUNDARY_LINE_START:
1108 		case ATK_TEXT_BOUNDARY_LINE_END:
1109 			/* Figure out which line we're on.  If the start of the
1110 			 * i'th line is before the offset, then i could be the
1111 			 * line we're looking for. */
1112 			line = 0;
1113 			for (line = 0;
1114 			     line < priv->snapshot_linebreaks->len;
1115 			     line++) {
1116 				if (g_array_index(priv->snapshot_linebreaks,
1117 						  int, line) > offset) {
1118 					line--;
1119 					break;
1120 				}
1121 			}
1122 			_vte_debug_print(VTE_DEBUG_ALLY,
1123 					"Character %d is on line %d.\n",
1124 					offset, line);
1125 			/* Perturb the line number to handle before/at/after. */
1126 			line += direction;
1127 			line = MIN(line, priv->snapshot_linebreaks->len - 1);
1128 			/* Read the offsets for this line. */
1129 			start = g_array_index(priv->snapshot_linebreaks,
1130 						      int, line);
1131 			line++;
1132 			line = MIN(line, priv->snapshot_linebreaks->len - 1);
1133 			end = g_array_index(priv->snapshot_linebreaks,
1134 						    int, line);
1135 			_vte_debug_print(VTE_DEBUG_ALLY,
1136 					"Line runs from %d to %d.\n",
1137 					start, end);
1138 			break;
1139 		case ATK_TEXT_BOUNDARY_SENTENCE_START:
1140 		case ATK_TEXT_BOUNDARY_SENTENCE_END:
1141 			/* This doesn't make sense.  Fall through. */
1142 		default:
1143 			start = end = 0;
1144 			break;
1145 	}
1146 	*start_offset = start = MIN(start, priv->snapshot_characters->len - 1);
1147 	*end_offset = end = CLAMP(end, start, priv->snapshot_characters->len);
1148 	return vte_terminal_accessible_get_text(text, start, end);
1149 }
1150 
1151 static gchar *
vte_terminal_accessible_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1152 vte_terminal_accessible_get_text_before_offset(AtkText *text, gint offset,
1153 					       AtkTextBoundary boundary_type,
1154 					       gint *start_offset,
1155 					       gint *end_offset)
1156 {
1157         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1158 
1159 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1160 							      NULL, NULL);
1161 	return vte_terminal_accessible_get_text_somewhere(text,
1162 							  offset,
1163 							  boundary_type,
1164 							  -1,
1165 							  start_offset,
1166 							  end_offset);
1167 }
1168 
1169 static gchar *
vte_terminal_accessible_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1170 vte_terminal_accessible_get_text_after_offset(AtkText *text, gint offset,
1171 					      AtkTextBoundary boundary_type,
1172 					      gint *start_offset,
1173 					      gint *end_offset)
1174 {
1175         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1176 
1177 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1178 							      NULL, NULL);
1179 	return vte_terminal_accessible_get_text_somewhere(text,
1180 							  offset,
1181 							  boundary_type,
1182 							  1,
1183 							  start_offset,
1184 							  end_offset);
1185 }
1186 
1187 static gchar *
vte_terminal_accessible_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1188 vte_terminal_accessible_get_text_at_offset(AtkText *text, gint offset,
1189 					   AtkTextBoundary boundary_type,
1190 					   gint *start_offset,
1191 					   gint *end_offset)
1192 {
1193         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1194 
1195 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1196 							      NULL, NULL);
1197 	return vte_terminal_accessible_get_text_somewhere(text,
1198 							  offset,
1199 							  boundary_type,
1200 							  0,
1201 							  start_offset,
1202 							  end_offset);
1203 }
1204 
1205 static gunichar
vte_terminal_accessible_get_character_at_offset(AtkText * text,gint offset)1206 vte_terminal_accessible_get_character_at_offset(AtkText *text, gint offset)
1207 {
1208         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1209 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1210 	char *unichar;
1211 	gunichar ret;
1212 
1213 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1214 							      NULL, NULL);
1215 
1216 	g_assert(offset < (int) priv->snapshot_characters->len);
1217 
1218 	unichar = vte_terminal_accessible_get_text(text, offset, offset + 1);
1219 	ret = g_utf8_get_char(unichar);
1220 	g_free(unichar);
1221 
1222 	return ret;
1223 }
1224 
1225 static gint
vte_terminal_accessible_get_caret_offset(AtkText * text)1226 vte_terminal_accessible_get_caret_offset(AtkText *text)
1227 {
1228         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1229 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1230 
1231 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1232 							      NULL, NULL);
1233 
1234 	return priv->snapshot_caret;
1235 }
1236 
1237 static AtkAttributeSet *
get_attribute_set(struct _VteCharAttributes attr)1238 get_attribute_set (struct _VteCharAttributes attr)
1239 {
1240 	AtkAttributeSet *set = NULL;
1241 	AtkAttribute *at;
1242 
1243 	if (attr.underline) {
1244 		at = g_new (AtkAttribute, 1);
1245 		at->name = g_strdup ("underline");
1246 		at->value = g_strdup ("true");
1247 		set = g_slist_append (set, at);
1248 	}
1249 	if (attr.strikethrough) {
1250 		at = g_new (AtkAttribute, 1);
1251 		at->name = g_strdup ("strikethrough");
1252 		at->value = g_strdup ("true");
1253 		set = g_slist_append (set, at);
1254 	}
1255 	at = g_new (AtkAttribute, 1);
1256 	at->name = g_strdup ("fg-color");
1257 	at->value = g_strdup_printf ("%u,%u,%u",
1258 				     attr.fore.red, attr.fore.green, attr.fore.blue);
1259 	set = g_slist_append (set, at);
1260 
1261 	at = g_new (AtkAttribute, 1);
1262 	at->name = g_strdup ("bg-color");
1263 	at->value = g_strdup_printf ("%u,%u,%u",
1264 				     attr.back.red, attr.back.green, attr.back.blue);
1265 	set = g_slist_append (set, at);
1266 
1267 	return set;
1268 }
1269 
1270 static gboolean
_pango_color_equal(const PangoColor * a,const PangoColor * b)1271 _pango_color_equal(const PangoColor *a,
1272                    const PangoColor *b)
1273 {
1274         return a->red   == b->red &&
1275                a->green == b->green &&
1276                a->blue  == b->blue;
1277 }
1278 
1279 static AtkAttributeSet *
vte_terminal_accessible_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)1280 vte_terminal_accessible_get_run_attributes(AtkText *text, gint offset,
1281 					   gint *start_offset, gint *end_offset)
1282 {
1283         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1284 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1285 	guint i;
1286 	struct _VteCharAttributes cur_attr;
1287 	struct _VteCharAttributes attr;
1288 
1289 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1290 							      NULL, NULL);
1291 
1292 	attr = g_array_index (priv->snapshot_attributes,
1293 			      struct _VteCharAttributes,
1294 			      offset);
1295 	*start_offset = 0;
1296 	for (i = offset; i--;) {
1297 		cur_attr = g_array_index (priv->snapshot_attributes,
1298 				      struct _VteCharAttributes,
1299 				      i);
1300 		if (!_pango_color_equal (&cur_attr.fore, &attr.fore) ||
1301 		    !_pango_color_equal (&cur_attr.back, &attr.back) ||
1302 		    cur_attr.underline != attr.underline ||
1303 		    cur_attr.strikethrough != attr.strikethrough) {
1304 			*start_offset = i + 1;
1305 			break;
1306 		}
1307 	}
1308 	*end_offset = priv->snapshot_attributes->len - 1;
1309 	for (i = offset + 1; i < priv->snapshot_attributes->len; i++) {
1310 		cur_attr = g_array_index (priv->snapshot_attributes,
1311 				      struct _VteCharAttributes,
1312 				      i);
1313 		if (!_pango_color_equal (&cur_attr.fore, &attr.fore) ||
1314 		    !_pango_color_equal (&cur_attr.back, &attr.back) ||
1315 		    cur_attr.underline != attr.underline ||
1316 		    cur_attr.strikethrough != attr.strikethrough) {
1317 			*end_offset = i - 1;
1318 			break;
1319 		}
1320 	}
1321 
1322 	return get_attribute_set (attr);
1323 }
1324 
1325 static AtkAttributeSet *
vte_terminal_accessible_get_default_attributes(AtkText * text)1326 vte_terminal_accessible_get_default_attributes(AtkText *text)
1327 {
1328 	return NULL;
1329 }
1330 
1331 static void
vte_terminal_accessible_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)1332 vte_terminal_accessible_get_character_extents(AtkText *text, gint offset,
1333 					      gint *x, gint *y,
1334 					      gint *width, gint *height,
1335 					      AtkCoordType coords)
1336 {
1337         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1338 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1339 	VteTerminal *terminal;
1340 	glong char_width, char_height;
1341 	gint base_x, base_y, w, h;
1342 
1343 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1344 							      NULL, NULL);
1345 	terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text)));
1346 
1347 	atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords);
1348 	xy_from_offset (priv, offset, x, y);
1349 	char_width = vte_terminal_get_char_width (terminal);
1350 	char_height = vte_terminal_get_char_height (terminal);
1351 	*x *= char_width;
1352 	*y *= char_height;
1353 	*width = char_width;
1354 	*height = char_height;
1355 	*x += base_x;
1356 	*y += base_y;
1357 }
1358 
1359 static gint
vte_terminal_accessible_get_character_count(AtkText * text)1360 vte_terminal_accessible_get_character_count(AtkText *text)
1361 {
1362         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1363 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1364 
1365 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1366 							      NULL, NULL);
1367 
1368 	return priv->snapshot_attributes->len;
1369 }
1370 
1371 static gint
vte_terminal_accessible_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)1372 vte_terminal_accessible_get_offset_at_point(AtkText *text,
1373 					    gint x, gint y,
1374 					    AtkCoordType coords)
1375 {
1376         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1377 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1378 	VteTerminal *terminal;
1379 	glong char_width, char_height;
1380 	gint base_x, base_y, w, h;
1381 
1382 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1383 							      NULL, NULL);
1384 	terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text)));
1385 
1386 	atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords);
1387         /* FIXME: use _vte_terminal_xy_to_grid */
1388 	char_width = vte_terminal_get_char_width (terminal);
1389 	char_height = vte_terminal_get_char_height (terminal);
1390 	x -= base_x;
1391 	y -= base_y;
1392 	x /= char_width;
1393 	y /= char_height;
1394 	return offset_from_xy (priv, x, y);
1395 }
1396 
1397 static gint
vte_terminal_accessible_get_n_selections(AtkText * text)1398 vte_terminal_accessible_get_n_selections(AtkText *text)
1399 {
1400         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1401 	GtkWidget *widget;
1402 	VteTerminal *terminal;
1403 
1404 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1405 							      NULL, NULL);
1406 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1407 	if (widget == NULL) {
1408 		/* State is defunct */
1409 		return -1;
1410 	}
1411 
1412 	terminal = VTE_TERMINAL (widget);
1413 	return (vte_terminal_get_has_selection (terminal)) ? 1 : 0;
1414 }
1415 
1416 static gchar *
vte_terminal_accessible_get_selection(AtkText * text,gint selection_number,gint * start_offset,gint * end_offset)1417 vte_terminal_accessible_get_selection(AtkText *text, gint selection_number,
1418 				      gint *start_offset, gint *end_offset)
1419 {
1420         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1421 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1422 	GtkWidget *widget;
1423 	VteTerminal *terminal;
1424 	long start_x, start_y, end_x, end_y;
1425 
1426 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1427 							      NULL, NULL);
1428 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1429 	if (widget == NULL) {
1430 		/* State is defunct */
1431 		return NULL;
1432 	}
1433 
1434 	terminal = VTE_TERMINAL (widget);
1435 	if (!vte_terminal_get_has_selection (terminal)) {
1436 		return NULL;
1437 	}
1438 	if (selection_number != 0) {
1439 		return NULL;
1440 	}
1441 
1442 	_vte_terminal_get_start_selection (terminal, &start_x, &start_y);
1443 
1444 	*start_offset = offset_from_xy (priv, start_x, start_y);
1445 	_vte_terminal_get_end_selection (terminal, &end_x, &end_y);
1446 	*end_offset = offset_from_xy (priv, end_x, end_y);
1447 	return _vte_terminal_get_selection (terminal);
1448 }
1449 
1450 static gboolean
vte_terminal_accessible_add_selection(AtkText * text,gint start_offset,gint end_offset)1451 vte_terminal_accessible_add_selection(AtkText *text,
1452 				      gint start_offset, gint end_offset)
1453 {
1454         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1455 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1456 	GtkWidget *widget;
1457 	VteTerminal *terminal;
1458 	gint start_x, start_y, end_x, end_y;
1459 
1460 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1461 							      NULL, NULL);
1462 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1463 	if (widget == NULL) {
1464 		/* State is defunct */
1465 		return FALSE;
1466 	}
1467 
1468 	terminal = VTE_TERMINAL (widget);
1469 	g_assert(!vte_terminal_get_has_selection (terminal));
1470 
1471 	xy_from_offset (priv, start_offset, &start_x, &start_y);
1472 	xy_from_offset (priv, end_offset, &end_x, &end_y);
1473 	_vte_terminal_select_text (terminal, start_x, start_y, end_x, end_y, start_offset, end_offset);
1474 	return TRUE;
1475 }
1476 
1477 static gboolean
vte_terminal_accessible_remove_selection(AtkText * text,gint selection_number)1478 vte_terminal_accessible_remove_selection(AtkText *text,
1479 					 gint selection_number)
1480 {
1481         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1482 	GtkWidget *widget;
1483 	VteTerminal *terminal;
1484 
1485 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1486 							      NULL, NULL);
1487 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1488 	if (widget == NULL) {
1489 		/* State is defunct */
1490 		return FALSE;
1491 	}
1492 
1493 	terminal = VTE_TERMINAL (widget);
1494 	if (selection_number == 0 && vte_terminal_get_has_selection (terminal)) {
1495 		_vte_terminal_remove_selection (terminal);
1496 		return TRUE;
1497 	} else {
1498 		return FALSE;
1499 	}
1500 }
1501 
1502 static gboolean
vte_terminal_accessible_set_selection(AtkText * text,gint selection_number,gint start_offset,gint end_offset)1503 vte_terminal_accessible_set_selection(AtkText *text, gint selection_number,
1504 				      gint start_offset, gint end_offset)
1505 {
1506         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1507 	GtkWidget *widget;
1508 	VteTerminal *terminal;
1509 
1510 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1511 							      NULL, NULL);
1512 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1513 	if (widget == NULL) {
1514 		/* State is defunct */
1515 		return FALSE;
1516 	}
1517 
1518 	terminal = VTE_TERMINAL (widget);
1519 	if (selection_number != 0) {
1520 		return FALSE;
1521 	}
1522 	if (vte_terminal_get_has_selection (terminal)) {
1523 		_vte_terminal_remove_selection (terminal);
1524 	}
1525 
1526 	return vte_terminal_accessible_add_selection (text, start_offset, end_offset);
1527 }
1528 
1529 static gboolean
vte_terminal_accessible_set_caret_offset(AtkText * text,gint offset)1530 vte_terminal_accessible_set_caret_offset(AtkText *text, gint offset)
1531 {
1532         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1533 
1534 	vte_terminal_accessible_update_private_data_if_needed(accessible,
1535 							      NULL, NULL);
1536 	/* Whoa, very not allowed. */
1537 	return FALSE;
1538 }
1539 
1540 static void
vte_terminal_accessible_text_iface_init(AtkTextIface * text)1541 vte_terminal_accessible_text_iface_init(AtkTextIface *text)
1542 {
1543 	text->get_text = vte_terminal_accessible_get_text;
1544 	text->get_text_after_offset = vte_terminal_accessible_get_text_after_offset;
1545 	text->get_text_at_offset = vte_terminal_accessible_get_text_at_offset;
1546 	text->get_character_at_offset = vte_terminal_accessible_get_character_at_offset;
1547 	text->get_text_before_offset = vte_terminal_accessible_get_text_before_offset;
1548 	text->get_caret_offset = vte_terminal_accessible_get_caret_offset;
1549 	text->get_run_attributes = vte_terminal_accessible_get_run_attributes;
1550 	text->get_default_attributes = vte_terminal_accessible_get_default_attributes;
1551 	text->get_character_extents = vte_terminal_accessible_get_character_extents;
1552 	text->get_character_count = vte_terminal_accessible_get_character_count;
1553 	text->get_offset_at_point = vte_terminal_accessible_get_offset_at_point;
1554 	text->get_n_selections = vte_terminal_accessible_get_n_selections;
1555 	text->get_selection = vte_terminal_accessible_get_selection;
1556 	text->add_selection = vte_terminal_accessible_add_selection;
1557 	text->remove_selection = vte_terminal_accessible_remove_selection;
1558 	text->set_selection = vte_terminal_accessible_set_selection;
1559 	text->set_caret_offset = vte_terminal_accessible_set_caret_offset;
1560 }
1561 
1562 static gboolean
vte_terminal_accessible_set_extents(AtkComponent * component,gint x,gint y,gint width,gint height,AtkCoordType coord_type)1563 vte_terminal_accessible_set_extents(AtkComponent *component,
1564 				    gint x, gint y,
1565 				    gint width, gint height,
1566 				    AtkCoordType coord_type)
1567 {
1568 	/* FIXME?  We can change the size, but our position is controlled
1569 	 * by the parent container. */
1570 	return FALSE;
1571 }
1572 
1573 static gboolean
vte_terminal_accessible_set_position(AtkComponent * component,gint x,gint y,AtkCoordType coord_type)1574 vte_terminal_accessible_set_position(AtkComponent *component,
1575 				     gint x, gint y,
1576 				     AtkCoordType coord_type)
1577 {
1578 	/* Controlled by the parent container, if there is one. */
1579 	return FALSE;
1580 }
1581 
1582 static gboolean
vte_terminal_accessible_set_size(AtkComponent * component,gint width,gint height)1583 vte_terminal_accessible_set_size(AtkComponent *component,
1584 				 gint width, gint height)
1585 {
1586         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(component);
1587 	VteTerminal *terminal;
1588 	long columns, rows;
1589 	GtkWidget *widget;
1590 
1591 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible));
1592 	if (widget == NULL) {
1593 		return FALSE;
1594 	}
1595 	terminal = VTE_TERMINAL(widget);
1596 
1597         /* If the size is an exact multiple of the cell size, use that,
1598          * otherwise round down. */
1599         (void) _vte_terminal_size_to_grid_size(terminal, width, height, &columns, &rows);
1600 
1601 	vte_terminal_set_size(terminal, columns, rows);
1602 	return (vte_terminal_get_row_count (terminal) == rows) &&
1603 	       (vte_terminal_get_column_count (terminal) == columns);
1604 }
1605 
1606 static AtkObject *
vte_terminal_accessible_ref_accessible_at_point(AtkComponent * component,gint x,gint y,AtkCoordType coord_type)1607 vte_terminal_accessible_ref_accessible_at_point(AtkComponent *component,
1608 						gint x, gint y,
1609 						AtkCoordType coord_type)
1610 {
1611 	/* There are no children. */
1612 	return NULL;
1613 }
1614 
1615 static void
vte_terminal_accessible_component_iface_init(AtkComponentIface * component)1616 vte_terminal_accessible_component_iface_init(AtkComponentIface *component)
1617 {
1618 	component->ref_accessible_at_point = vte_terminal_accessible_ref_accessible_at_point;
1619 	component->set_extents = vte_terminal_accessible_set_extents;
1620 	component->set_position = vte_terminal_accessible_set_position;
1621 	component->set_size = vte_terminal_accessible_set_size;
1622 }
1623 
1624 /* AtkAction interface */
1625 
1626 static gboolean
vte_terminal_accessible_do_action(AtkAction * accessible,int i)1627 vte_terminal_accessible_do_action (AtkAction *accessible, int i)
1628 {
1629 	GtkWidget *widget;
1630 	gboolean retval = FALSE;
1631 
1632 	g_return_val_if_fail (i < LAST_ACTION, FALSE);
1633 
1634 	widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
1635 	if (!widget) {
1636 		return FALSE;
1637 	}
1638 
1639         switch (i) {
1640         case ACTION_MENU :
1641 		g_signal_emit_by_name (widget, "popup_menu", &retval);
1642                 break;
1643         default :
1644                 g_warning ("Invalid action passed to VteTerminalAccessible::do_action");
1645                 return FALSE;
1646         }
1647         return retval;
1648 }
1649 
1650 static int
vte_terminal_accessible_get_n_actions(AtkAction * accessible)1651 vte_terminal_accessible_get_n_actions (AtkAction *accessible)
1652 {
1653 	return LAST_ACTION;
1654 }
1655 
1656 static const char *
vte_terminal_accessible_action_get_description(AtkAction * action,int i)1657 vte_terminal_accessible_action_get_description (AtkAction *action, int i)
1658 {
1659         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action);
1660 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1661 
1662         g_return_val_if_fail (i < LAST_ACTION, NULL);
1663 
1664         if (priv->action_descriptions[i]) {
1665                 return priv->action_descriptions[i];
1666         } else {
1667                 return vte_terminal_accessible_action_descriptions[i];
1668         }
1669 }
1670 
1671 static const char *
vte_terminal_accessible_action_get_name(AtkAction * accessible,int i)1672 vte_terminal_accessible_action_get_name (AtkAction *accessible, int i)
1673 {
1674         g_return_val_if_fail (i < LAST_ACTION, NULL);
1675 
1676         return vte_terminal_accessible_action_names[i];
1677 }
1678 
1679 static const char *
vte_terminal_accessible_action_get_keybinding(AtkAction * accessible,int i)1680 vte_terminal_accessible_action_get_keybinding (AtkAction *accessible, int i)
1681 {
1682         g_return_val_if_fail (i < LAST_ACTION, NULL);
1683 
1684         return NULL;
1685 }
1686 
1687 static gboolean
vte_terminal_accessible_action_set_description(AtkAction * action,int i,const char * description)1688 vte_terminal_accessible_action_set_description (AtkAction *action,
1689                                                 int i,
1690                                                 const char *description)
1691 {
1692         VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action);
1693 	VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1694 
1695         g_return_val_if_fail (i < LAST_ACTION, FALSE);
1696 
1697         if (priv->action_descriptions[i]) {
1698                 g_free (priv->action_descriptions[i]);
1699         }
1700         priv->action_descriptions[i] = g_strdup (description);
1701 
1702         return TRUE;
1703 }
1704 
1705 static void
vte_terminal_accessible_action_iface_init(AtkActionIface * action)1706 vte_terminal_accessible_action_iface_init(AtkActionIface *action)
1707 {
1708 	action->do_action = vte_terminal_accessible_do_action;
1709 	action->get_n_actions = vte_terminal_accessible_get_n_actions;
1710 	action->get_description = vte_terminal_accessible_action_get_description;
1711 	action->get_name = vte_terminal_accessible_action_get_name;
1712 	action->get_keybinding = vte_terminal_accessible_action_get_keybinding;
1713 	action->set_description = vte_terminal_accessible_action_set_description;
1714 }
1715 
1716 static void
_vte_terminal_accessible_class_init(VteTerminalAccessibleClass * klass)1717 _vte_terminal_accessible_class_init(VteTerminalAccessibleClass *klass)
1718 {
1719 	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1720 	AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
1721 
1722 	gobject_class->finalize = vte_terminal_accessible_finalize;
1723 
1724 	class->initialize = vte_terminal_accessible_initialize;
1725 }
1726