diff options
author | Christian Persch <chpe@gnome.org> | 2014-04-11 11:38:53 +0200 |
---|---|---|
committer | Christian Persch <chpe@gnome.org> | 2014-04-11 19:43:59 +0200 |
commit | a71e0eb84d1b88f764d13b5bb38e3faaded672d2 (patch) | |
tree | 9283c7a657633af91d252db399dfbd72f1d4ff2c | |
parent | 7f765481c24765dfb3d02932666686103e2a4e07 (diff) | |
parent | 0beed99e3000e8b60d8f0ab0e47f0f0865268d0c (diff) | |
download | vte-a71e0eb84d1b88f764d13b5bb38e3faaded672d2.tar.gz |
Merge remote-tracking branch 'nomeata/master-html-copy-paste' into work-master
Works, but needs much cleanup before it can be merged.
Conflicts:
src/vte-private.h
src/vte.c
-rw-r--r-- | src/vte-private.h | 24 | ||||
-rw-r--r-- | src/vte.c | 356 | ||||
-rw-r--r-- | src/vte.h | 3 |
3 files changed, 306 insertions, 77 deletions
diff --git a/src/vte-private.h b/src/vte-private.h index 72bd2320..1ccc44b7 100644 --- a/src/vte-private.h +++ b/src/vte-private.h @@ -132,10 +132,20 @@ typedef enum { MOUSE_TRACKING_ALL_MOTION_TRACKING } MouseTrackingMode; -struct _vte_regex_match { - int rm_so; - int rm_eo; -}; +/* For unified handling of PRIMARY and CLIPBOARD selection */ +typedef enum { + VTE_SELECTION_PRIMARY, + VTE_SELECTION_CLIPBOARD, + LAST_VTE_SELECTION +} VteSelection; + +/* Used in the GtkClipboard API, to distinguish requests for HTML and TEXT + * contents of a clipboard */ +typedef enum { + VTE_TARGET_TEXT, + VTE_TARGET_HTML, + LAST_VTE_TARGET +} VteSelectionTarget; /* A match regex, with a tag. */ struct vte_match_regex { @@ -271,7 +281,6 @@ struct _VteTerminalPrivate { gboolean selecting_restart; gboolean selecting_had_delta; gboolean selection_block_mode; - char *selection; enum vte_selection_type { selection_type_char, selection_type_word, @@ -282,6 +291,11 @@ struct _VteTerminalPrivate { } selection_origin, selection_last; VteVisualPosition selection_start, selection_end; + /* Clipboard data information. */ + char *selection_text[LAST_VTE_SELECTION]; + char *selection_html[LAST_VTE_SELECTION]; + GtkClipboard *clipboard[LAST_VTE_SELECTION]; + /* Miscellaneous options. */ VteEraseBinding backspace_binding, delete_binding; gboolean meta_sends_escape; @@ -133,6 +133,11 @@ static void _vte_check_cursor_blink(VteTerminal *terminal); static gboolean process_timeout (gpointer data); static gboolean update_timeout (gpointer data); static cairo_region_t *vte_cairo_get_clip_region (cairo_t *cr); +static void vte_terminal_determine_colors_internal(VteTerminal *terminal, + const VteCellAttr *attr, + gboolean selected, + gboolean cursor, + guint *pfore, guint *pback); enum { COPY_CLIPBOARD, @@ -4009,8 +4014,9 @@ next_match: vte_cell_is_selected, NULL, NULL); - if ((selection == NULL) || (terminal->pvt->selection == NULL) || - (strcmp(selection, terminal->pvt->selection) != 0)) { + if ((selection == NULL) || + (terminal->pvt->selection_text[VTE_SELECTION_PRIMARY] == NULL) || + (strcmp(selection, terminal->pvt->selection_text[VTE_SELECTION_PRIMARY]) != 0)) { vte_terminal_deselect_all(terminal); } g_free(selection); @@ -5863,6 +5869,13 @@ vte_terminal_match_hilite(VteTerminal *terminal, long x, long y) vte_terminal_match_hilite_update(terminal, x, y); } +static GtkClipboard * +vte_terminal_clipboard_get(VteTerminal *terminal, GdkAtom board) +{ + GdkDisplay *display; + display = gtk_widget_get_display(&terminal->widget); + return gtk_clipboard_get_for_display(display, board); +} /* Note that the clipboard has cleared. */ static void @@ -5870,9 +5883,11 @@ vte_terminal_clear_cb(GtkClipboard *clipboard, gpointer owner) { VteTerminal *terminal; terminal = owner; - if (terminal->pvt->has_selection) { - _vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n"); - vte_terminal_deselect_all(terminal); + if (clipboard == vte_terminal_clipboard_get(terminal, GDK_SELECTION_PRIMARY)) { + if (terminal->pvt->has_selection) { + _vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n"); + vte_terminal_deselect_all(terminal); + } } } @@ -5881,20 +5896,42 @@ static void vte_terminal_copy_cb(GtkClipboard *clipboard, GtkSelectionData *data, guint info, gpointer owner) { + VteSelection sel; VteTerminal *terminal; terminal = owner; - if (terminal->pvt->selection != NULL) { - _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) { - int i; - g_printerr("Setting selection (%"G_GSIZE_FORMAT" UTF-8 bytes.)\n", - strlen(terminal->pvt->selection)); - for (i = 0; terminal->pvt->selection[i] != '\0'; i++) { - g_printerr("0x%04x\n", - terminal->pvt->selection[i]); + for (sel = 0; sel < LAST_VTE_SELECTION; sel ++) { + if (clipboard == terminal->pvt->clipboard[sel] && terminal->pvt->selection_text[sel] != NULL) { + _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) { + int i; + g_printerr("Setting selection %d (%"G_GSIZE_FORMAT" UTF-8 bytes.)\n", + sel, + strlen(terminal->pvt->selection_text[sel])); + for (i = 0; terminal->pvt->selection_text[sel][i] != '\0'; i++) { + g_printerr("0x%04x\n", + terminal->pvt->selection_text[sel][i]); + } + } + if (info == VTE_TARGET_TEXT) { + gtk_selection_data_set_text(data, terminal->pvt->selection_text[sel], -1); + } else { + gsize len; + gchar *selection; + + g_assert(info == VTE_TARGET_HTML); + + /* Mozilla asks that we start our text/html with the Unicode byte order mark */ + /* (Comment found in gtkimhtml.c of pidgin fame) */ + selection = g_convert(terminal->pvt->selection_html[sel], + -1, "UTF-16", "UTF-8", NULL, &len, NULL); + gtk_selection_data_set(data, + gdk_atom_intern("text/html", FALSE), + 16, + (const guchar *)selection, + len); + g_free(selection); } } - gtk_selection_data_set_text(data, terminal->pvt->selection, -1); - } + } } /* Convert the internal color code (either index or RGB, see vte-private.h) into RGB. */ @@ -6190,6 +6227,164 @@ vte_terminal_get_text_include_trailing_spaces(VteTerminal *terminal, TRUE); } +/* + * Compares the visual attributes of a VteCellAttr for equality, but ignores + * attributes that tend to change from character to character or are otherwise + * strange (in particular: fragment, columns). + */ +static gboolean +vte_terminal_cellattr_equal(const VteCellAttr *attr1, const VteCellAttr *attr2) { + return (attr1->bold == attr2->bold && + attr1->fore == attr2->fore && + attr1->back == attr2->back && + attr1->standout == attr2->standout && + attr1->underline == attr2->underline && + attr1->strikethrough == attr2->strikethrough && + attr1->reverse == attr2->reverse && + attr1->blink == attr2->blink && + attr1->half == attr2->half && + attr1->invisible == attr2->invisible); +} + +/* + * Wraps a given string according to the VteCellAttr in HTML tags. Used + * old-style HTML (and not CSS) for better compatibility with, for example, + * evolution's mail editor component. + */ +static gchar * +vte_terminal_cellattr_to_html(VteTerminal *terminal, const VteCellAttr *attr, const gchar *text) { + GString *string; + guint fore, back; + + g_assert (terminal->pvt->palette_initialized); + + string = g_string_new(text); + + vte_terminal_determine_colors_internal (terminal, attr, + FALSE, FALSE, + &fore, &back); + + if (attr->bold || attr->standout) { + g_string_prepend(string, "<b>"); + g_string_append(string, "</b>"); + } + if (attr->fore != VTE_DEFAULT_FG || attr->reverse) { + PangoColor color; + char *tag; + + vte_terminal_get_rgb_from_index(terminal, attr->fore, &color); + tag = g_strdup_printf("<font color=\"#%02X%02X%02X\">", + color.red >> 8, + color.green >> 8, + color.blue >> 8); + g_string_prepend(string, tag); + g_free(tag); + g_string_append(string, "</font>"); + } + if (attr->back != VTE_DEFAULT_BG || attr->reverse) { + PangoColor color; + char *tag; + + vte_terminal_get_rgb_from_index(terminal, attr->back, &color); + tag = g_strdup_printf("<span style=\"background-color:#%02X%02X%02X\">", + color.red >> 8, + color.green >> 8, + color.blue >> 8); + g_string_prepend(string, tag); + g_free(tag); + g_string_append(string, "</span>"); + } + if (attr->underline) { + g_string_prepend(string, "<u>"); + g_string_append(string, "</u>"); + } + if (attr->strikethrough) { + g_string_prepend(string, "<strike>"); + g_string_append(string, "</strike>"); + } + if (attr->blink) { + g_string_prepend(string, "<blink>"); + g_string_append(string, "</blink>"); + } + // reverse and invisible are not supported + + return g_string_free(string, FALSE); +} + +/* + * Similar to vte_terminal_find_charcell, but takes a VteCharAttribute for + * indexing and returns the VteCellAttr. + */ +static const VteCellAttr * +vte_terminal_char_to_cell_attr(VteTerminal *terminal, VteCharAttributes *attr) +{ + const VteCell *cell; + + cell = vte_terminal_find_charcell(terminal, attr->column, attr->row); + if (cell) + return &cell->attr; + return NULL; +} + + +/** + * vte_terminal_attributes_to_html: + * @terminal: a #VteTerminal + * @text: A string as returned by the vte_terminal_get_* family of functions. + * @attrs: (array) (element-type Vte.CharAttributes): text attributes, as created by vte_terminal_get_* + * + * Marks the given text up according to the given attributes, using HTML <span> + * commands, and wraps the string in a <pre> element. The attributes have to be + * "fresh" in the sense that the terminal must not have changed since they were + * obtained using the vte_terminal_get* function. + * + * Returns: (transfer full): a newly allocated text string, or %NULL. + */ +char * +vte_terminal_attributes_to_html(VteTerminal *terminal, const gchar *text, GArray *attrs) { + GString *string; + guint from,to; + const VteCellAttr *attr; + char *escaped, *marked; + + g_assert(strlen(text) == attrs->len); + + // Initial size fits perfectly if the text has no attributes and no + // characters that need to be escaped + string = g_string_sized_new (strlen(text) + 11); + + g_string_append(string, "<pre>"); + // Find streches with equal attributes. Newlines are treated specially, + // so that the <span> do not cover multiple lines. + from = to = 0; + while (text[from] != '\0') { + g_assert(from == to); + if (text[from] == '\n') { + g_string_append_c(string, '\n'); + from = ++to; + } else { + attr = vte_terminal_char_to_cell_attr(terminal, + &g_array_index(attrs, VteCharAttributes, from)); + while (text[to] != '\0' && text[to] != '\n' && + vte_terminal_cellattr_equal(attr, + vte_terminal_char_to_cell_attr(terminal, + &g_array_index(attrs, VteCharAttributes, to)))) + { + to++; + } + escaped = g_markup_escape_text(text + from, to - from); + marked = vte_terminal_cellattr_to_html(terminal, attr, escaped); + g_string_append(string, marked); + g_free(escaped); + g_free(marked); + from = to; + } + } + g_string_append(string, "</pre>"); + + return g_string_free(string, FALSE); +} + /** * vte_terminal_get_cursor_position: * @terminal: a #VteTerminal @@ -6212,28 +6407,24 @@ vte_terminal_get_cursor_position(VteTerminal *terminal, } } -static GtkClipboard * -vte_terminal_clipboard_get(VteTerminal *terminal, GdkAtom board) -{ - GdkDisplay *display; - display = gtk_widget_get_display(&terminal->widget); - return gtk_clipboard_get_for_display(display, board); -} - /* Place the selected text onto the clipboard. Do this asynchronously so that * we get notified when the selection we placed on the clipboard is replaced. */ static void -vte_terminal_copy(VteTerminal *terminal, GdkAtom board) +vte_terminal_copy(VteTerminal *terminal, VteSelection sel) { GtkClipboard *clipboard; static GtkTargetEntry *targets = NULL; static gint n_targets = 0; + GArray *attributes; - clipboard = vte_terminal_clipboard_get(terminal, board); + clipboard = terminal->pvt->clipboard[sel]; + + attributes = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes)); /* Chuck old selected text and retrieve the newly-selected text. */ - g_free(terminal->pvt->selection); - terminal->pvt->selection = + g_free(terminal->pvt->selection_text[sel]); + g_free(terminal->pvt->selection_html[sel]); + terminal->pvt->selection_text[sel] = vte_terminal_get_text_range(terminal, terminal->pvt->selection_start.row, 0, @@ -6241,18 +6432,29 @@ vte_terminal_copy(VteTerminal *terminal, GdkAtom board) terminal->pvt->column_count, vte_cell_is_selected, NULL, - NULL); - terminal->pvt->has_selection = TRUE; + attributes); + terminal->pvt->selection_html[sel] = + vte_terminal_attributes_to_html(terminal, + terminal->pvt->selection_text[sel], + attributes); + g_array_free (attributes, TRUE); + + if (sel == VTE_SELECTION_PRIMARY) + terminal->pvt->has_selection = TRUE; /* Place the text on the clipboard. */ - if (terminal->pvt->selection != NULL) { + if (terminal->pvt->selection_text[sel] != NULL) { _vte_debug_print(VTE_DEBUG_SELECTION, "Assuming ownership of selection.\n"); if (!targets) { GtkTargetList *list; list = gtk_target_list_new (NULL, 0); - gtk_target_list_add_text_targets (list, 0); + gtk_target_list_add_text_targets (list, VTE_TARGET_TEXT); + gtk_target_list_add (list, + gdk_atom_intern("text/html", FALSE), + 0, + VTE_TARGET_HTML); targets = gtk_target_table_new_from_list (list, &n_targets); gtk_target_list_unref (list); } @@ -8126,6 +8328,7 @@ vte_terminal_init(VteTerminal *terminal) { VteTerminalPrivate *pvt; GtkStyleContext *context; + GdkDisplay *display; _vte_debug_print(VTE_DEBUG_LIFECYCLE, "vte_terminal_init()\n"); @@ -8213,6 +8416,11 @@ vte_terminal_init(VteTerminal *terminal) pvt->scrollback_lines = -1; /* force update in vte_terminal_set_scrollback_lines */ vte_terminal_set_scrollback_lines(terminal, VTE_SCROLLBACK_INIT); + /* Selection info. */ + display = gtk_widget_get_display(&terminal->widget); + pvt->clipboard[VTE_SELECTION_PRIMARY] = gtk_clipboard_get_for_display(display, GDK_SELECTION_PRIMARY); + pvt->clipboard[VTE_SELECTION_CLIPBOARD] = gtk_clipboard_get_for_display(display, GDK_SELECTION_CLIPBOARD); + /* Miscellaneous options. */ vte_terminal_set_backspace_binding(terminal, VTE_ERASE_AUTO); vte_terminal_set_delete_binding(terminal, VTE_ERASE_AUTO); @@ -8565,6 +8773,7 @@ vte_terminal_finalize(GObject *object) VteTerminalPrivate *pvt = terminal->pvt; GtkClipboard *clipboard; GtkSettings *settings; + VteSelection sel; struct vte_match_regex *regex; guint i; @@ -8624,15 +8833,17 @@ vte_terminal_finalize(GObject *object) /* Free any selected text, but if we currently own the selection, * throw the text onto the clipboard without an owner so that it * doesn't just disappear. */ - if (terminal->pvt->selection != NULL) { - clipboard = vte_terminal_clipboard_get(terminal, - GDK_SELECTION_PRIMARY); - if (gtk_clipboard_get_owner(clipboard) == object) { - gtk_clipboard_set_text(clipboard, - terminal->pvt->selection, - -1); + for (sel = VTE_SELECTION_PRIMARY; sel < LAST_VTE_SELECTION; sel++) { + if (terminal->pvt->selection_text[sel] != NULL) { + clipboard = terminal->pvt->clipboard[sel]; + if (gtk_clipboard_get_owner(clipboard) == object) { + gtk_clipboard_set_text(clipboard, + terminal->pvt->selection_text[sel], + -1); + } + g_free(terminal->pvt->selection_text[sel]); + g_free(terminal->pvt->selection_html[sel]); } - g_free(terminal->pvt->selection); } /* Clear the output histories. */ @@ -8837,19 +9048,18 @@ swap (guint *a, guint *b) static void vte_terminal_determine_colors_internal(VteTerminal *terminal, - const VteCell *cell, + const VteCellAttr *attr, gboolean selected, gboolean cursor, guint *pfore, guint *pback) { guint fore, back; - if (!cell) - cell = &basic_cell.cell; + g_assert(attr); /* Start with cell colors */ - fore = cell->attr.fore; - back = cell->attr.back; + fore = attr->fore; + back = attr->back; /* Reverse-mode switches default fore and back colors */ if (G_UNLIKELY (terminal->pvt->screen->reverse_mode)) { @@ -8860,7 +9070,7 @@ vte_terminal_determine_colors_internal(VteTerminal *terminal, } /* Handle bold by using set bold color or brightening */ - if (cell->attr.bold) { + if (attr->bold) { if (fore == VTE_DEFAULT_FG) fore = VTE_BOLD_FG; else if (fore >= VTE_LEGACY_COLORS_OFFSET && fore < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) { @@ -8869,7 +9079,7 @@ vte_terminal_determine_colors_internal(VteTerminal *terminal, } /* Handle half similarly */ - if (cell->attr.half) { + if (attr->half) { if (fore == VTE_DEFAULT_FG) fore = VTE_DIM_FG; else if (fore >= VTE_LEGACY_COLORS_OFFSET && fore < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) @@ -8877,13 +9087,13 @@ vte_terminal_determine_colors_internal(VteTerminal *terminal, } /* And standout */ - if (cell->attr.standout) { + if (attr->standout) { if (back >= VTE_LEGACY_COLORS_OFFSET && back < VTE_LEGACY_COLORS_OFFSET + VTE_LEGACY_COLOR_SET_SIZE) back += VTE_COLOR_BRIGHT_OFFSET; } /* Reverse cell? */ - if (cell->attr.reverse) { + if (attr->reverse) { swap (&fore, &back); } @@ -8913,7 +9123,7 @@ vte_terminal_determine_colors_internal(VteTerminal *terminal, } /* Invisible? */ - if (cell && cell->attr.invisible) { + if (attr->invisible) { fore = back; } @@ -8927,7 +9137,7 @@ vte_terminal_determine_colors (VteTerminal *terminal, gboolean highlight, guint *fore, guint *back) { - vte_terminal_determine_colors_internal (terminal, cell, + vte_terminal_determine_colors_internal (terminal, cell ? &cell->attr : &basic_cell.cell.attr, highlight, FALSE, fore, back); } @@ -8938,7 +9148,7 @@ vte_terminal_determine_cursor_colors (VteTerminal *terminal, gboolean highlight, guint *fore, guint *back) { - vte_terminal_determine_colors_internal (terminal, cell, + vte_terminal_determine_colors_internal (terminal, cell ? &cell->attr : &basic_cell.cell.attr, highlight, TRUE, fore, back); } @@ -12121,16 +12331,12 @@ vte_terminal_get_rewrap_on_resize(VteTerminal *terminal) return terminal->pvt->rewrap_on_resize; } +/* Place the selected text onto the CLIPBOARD clipboard. Do this + * asynchronously, so that we can support the html target as well */ static void vte_terminal_real_copy_clipboard(VteTerminal *terminal) { - _vte_debug_print(VTE_DEBUG_SELECTION, "Copying to CLIPBOARD.\n"); - if (terminal->pvt->selection != NULL) { - GtkClipboard *clipboard; - clipboard = vte_terminal_clipboard_get(terminal, - GDK_SELECTION_CLIPBOARD); - gtk_clipboard_set_text(clipboard, terminal->pvt->selection, -1); - } + vte_terminal_copy(terminal, VTE_SELECTION_CLIPBOARD); } /** @@ -12182,7 +12388,7 @@ vte_terminal_copy_primary(VteTerminal *terminal) { g_return_if_fail(VTE_IS_TERMINAL(terminal)); _vte_debug_print(VTE_DEBUG_SELECTION, "Copying to PRIMARY.\n"); - vte_terminal_copy(terminal, GDK_SELECTION_PRIMARY); + vte_terminal_copy(terminal, VTE_SELECTION_PRIMARY); } /** @@ -12576,6 +12782,7 @@ vte_terminal_reset(VteTerminal *terminal, { VteTerminalPrivate *pvt; int i; + VteSelection sel; g_return_if_fail(VTE_IS_TERMINAL(terminal)); @@ -12699,18 +12906,23 @@ vte_terminal_reset(VteTerminal *terminal, pvt->selecting = FALSE; pvt->selecting_restart = FALSE; pvt->selecting_had_delta = FALSE; - if (pvt->selection != NULL) { - g_free(pvt->selection); - pvt->selection = NULL; - memset(&pvt->selection_origin, 0, - sizeof(pvt->selection_origin)); - memset(&pvt->selection_last, 0, - sizeof(pvt->selection_last)); - memset(&pvt->selection_start, 0, - sizeof(pvt->selection_start)); - memset(&pvt->selection_end, 0, - sizeof(pvt->selection_end)); - } + for (sel = VTE_SELECTION_PRIMARY; sel < LAST_VTE_SELECTION; sel++) { + if (pvt->selection_text[sel] != NULL) { + g_free(pvt->selection_text[sel]); + g_free(pvt->selection_html[sel]); + pvt->selection_text[sel] = NULL; + pvt->selection_html[sel] = NULL; + } + } + memset(&pvt->selection_origin, 0, + sizeof(pvt->selection_origin)); + memset(&pvt->selection_last, 0, + sizeof(pvt->selection_last)); + memset(&pvt->selection_start, 0, + sizeof(pvt->selection_start)); + memset(&pvt->selection_end, 0, + sizeof(pvt->selection_end)); + /* Reset mouse motion events. */ pvt->mouse_tracking_mode = MOUSE_TRACKING_NONE; pvt->mouse_last_button = 0; @@ -12986,7 +13198,7 @@ _vte_terminal_get_selection(VteTerminal *terminal) { g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL); - return g_strdup (terminal->pvt->selection); + return g_strdup (terminal->pvt->selection_text[VTE_SELECTION_PRIMARY]); } void @@ -318,6 +318,9 @@ char *vte_terminal_get_text_range(VteTerminal *terminal, VteSelectionFunc is_selected, gpointer user_data, GArray *attributes); +char *vte_terminal_attributes_to_html(VteTerminal *terminal, + const gchar *text, + GArray *attributes); void vte_terminal_get_cursor_position(VteTerminal *terminal, glong *column, glong *row); |