diff options
author | Egmont Koblinger <egmont@gmail.com> | 2017-12-23 22:41:24 +0100 |
---|---|---|
committer | Egmont Koblinger <egmont@gmail.com> | 2017-12-23 22:41:24 +0100 |
commit | 38396ef88ff44b447399b6abc562a22e07a27684 (patch) | |
tree | 75a113bf5d896089b2a1c03cacf2d4cc1fcd1a14 | |
parent | 68944c293d1004208846b9e9b18e536be3291365 (diff) | |
download | vte-38396ef88ff44b447399b6abc562a22e07a27684.tar.gz |
widget: Add support for blinking text
Also add an API to enable/disable this feature depending on the focused
or unfocused state of the widget.
https://bugzilla.gnome.org/show_bug.cgi?id=579964
-rw-r--r-- | doc/reference/vte-sections.txt | 5 | ||||
-rw-r--r-- | src/app/app.cc | 15 | ||||
-rw-r--r-- | src/vte.cc | 126 | ||||
-rw-r--r-- | src/vte/vteenums.h | 19 | ||||
-rw-r--r-- | src/vte/vteterminal.h | 5 | ||||
-rw-r--r-- | src/vtegtk.cc | 55 | ||||
-rw-r--r-- | src/vtegtk.hh | 1 | ||||
-rw-r--r-- | src/vteinternal.hh | 11 |
8 files changed, 231 insertions, 6 deletions
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt index b87d249f..a63fa343 100644 --- a/doc/reference/vte-sections.txt +++ b/doc/reference/vte-sections.txt @@ -5,6 +5,7 @@ VteTerminal VteCursorBlinkMode VteCursorShape VteEraseBinding +VteTextBlinkMode VteFormat VteWriteFlags VteSelectionFunc @@ -52,6 +53,8 @@ vte_terminal_set_cursor_shape vte_terminal_get_cursor_shape vte_terminal_get_cursor_blink_mode vte_terminal_set_cursor_blink_mode +vte_terminal_get_text_blink_mode +vte_terminal_set_text_blink_mode vte_terminal_set_scrollback_lines vte_terminal_get_scrollback_lines vte_terminal_set_font @@ -113,6 +116,8 @@ VTE_TYPE_CURSOR_SHAPE vte_cursor_shape_get_type VTE_TYPE_ERASE_BINDING vte_erase_binding_get_type +VTE_TYPE_TEXT_BLINK_MODE +vte_text_blink_mode_get_type VTE_TYPE_FORMAT vte_format_get_type VTE_TYPE_WRITE_FLAGS diff --git a/src/app/app.cc b/src/app/app.cc index 636df478..d63adb6b 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -93,6 +93,7 @@ public: double cell_width_scale{1.0}; VteCursorBlinkMode cursor_blink_mode{VTE_CURSOR_BLINK_SYSTEM}; VteCursorShape cursor_shape{VTE_CURSOR_SHAPE_BLOCK}; + VteTextBlinkMode text_blink_mode{VTE_TEXT_BLINK_ALWAYS}; ~Options() { g_clear_object(&background_pixbuf); @@ -288,6 +289,17 @@ private: } static gboolean + parse_text_blink(char const* option, char const* value, void* data, GError** error) + { + Options* that = static_cast<Options*>(data); + int v; + auto rv = that->parse_enum(VTE_TYPE_TEXT_BLINK_MODE, value, v, error); + if (rv) + that->text_blink_mode = VteTextBlinkMode(v); + return rv; + } + + static gboolean parse_verbosity(char const* option, char const* value, void* data, GError** error) { Options* that = static_cast<Options*>(data); @@ -341,6 +353,8 @@ public: "Set background image extend", "EXTEND" }, { "background-operator", 0, 0, G_OPTION_ARG_CALLBACK, (void*)parse_background_operator, "Set background draw operator", "OPERATOR" }, + { "blink", 0, 0, G_OPTION_ARG_CALLBACK, (void*)parse_text_blink, + "Text blink mode (never|focused|unfocused|always)", "MODE" }, { "cell-height-scale", 0, 0, G_OPTION_ARG_DOUBLE, &cell_height_scale, "Add extra line spacing", "1.0..2.0" }, { "cell-width-scale", 0, 0, G_OPTION_ARG_DOUBLE, &cell_width_scale, @@ -1850,6 +1864,7 @@ vteapp_window_constructed(GObject *object) vte_terminal_set_scroll_on_output(window->terminal, false); vte_terminal_set_scroll_on_keystroke(window->terminal, true); vte_terminal_set_scrollback_lines(window->terminal, options.scrollback_lines); + vte_terminal_set_text_blink_mode(window->terminal, options.text_blink_mode); /* Style */ if (options.font_string != nullptr) { @@ -4583,6 +4583,16 @@ VteTerminalPrivate::check_cursor_blink() } void +VteTerminalPrivate::remove_text_blink_timeout() +{ + if (m_text_blink_tag == 0) + return; + + g_source_remove (m_text_blink_tag); + m_text_blink_tag = 0; +} + +void VteTerminalPrivate::beep() { if (m_audible_bell) { @@ -7423,6 +7433,14 @@ VteTerminalPrivate::widget_focus_in(GdkEventFocus *event) m_cursor_blink_state = TRUE; m_has_focus = TRUE; + /* If blinking gets enabled now, do a full repaint. + * If blinking gets disabled, only repaint if there's blinking stuff present + * (we could further optimize by checking its current phase). */ + if (m_text_blink_mode == VTE_TEXT_BLINK_FOCUSED || + (m_text_blink_mode == VTE_TEXT_BLINK_UNFOCUSED && m_text_blink_tag != 0)) { + invalidate_all(); + } + check_cursor_blink(); gtk_im_context_focus_in(m_im_context); @@ -7446,6 +7464,14 @@ VteTerminalPrivate::widget_focus_out(GdkEventFocus *event) maybe_end_selection(); + /* If blinking gets enabled now, do a full repaint. + * If blinking gets disabled, only repaint if there's blinking stuff present + * (we could further optimize by checking its current phase). */ + if (m_text_blink_mode == VTE_TEXT_BLINK_UNFOCUSED || + (m_text_blink_mode == VTE_TEXT_BLINK_FOCUSED && m_text_blink_tag != 0)) { + invalidate_all(); + } + gtk_im_context_focus_out(m_im_context); invalidate_cursor_once(); @@ -8157,6 +8183,7 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) : m_pending = g_array_new(FALSE, TRUE, sizeof(gunichar)); m_max_input_bytes = VTE_MAX_INPUT_READ; m_cursor_blink_tag = 0; + m_text_blink_tag = 0; m_outgoing = _vte_byte_array_new(); m_outgoing_conv = VTE_INVALID_CONV; m_conv_buffer = _vte_byte_array_new(); @@ -8198,6 +8225,7 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) : set_delete_binding(VTE_ERASE_AUTO); m_meta_sends_escape = TRUE; m_audible_bell = TRUE; + m_text_blink_mode = VTE_TEXT_BLINK_ALWAYS; m_allow_bold = TRUE; m_bold_is_bright = TRUE; m_deccolm_mode = FALSE; @@ -8450,9 +8478,12 @@ VteTerminalPrivate::widget_unrealize() gtk_widget_unmap(m_widget); } - /* Remove the blink timeout function. */ + /* Remove the cursor blink timeout function. */ remove_cursor_timeout(); + /* Remove the contents blink timeout function. */ + remove_text_blink_timeout(); + /* Cancel any pending redraws. */ remove_update_timeout(this); @@ -8501,6 +8532,16 @@ VteTerminalPrivate::widget_settings_notify() m_cursor_blink_timeout = blink_timeout; update_cursor_blinks(); + + /* Misuse gtk-cursor-blink-time for text blinking as well. This might change in the future. */ + m_text_blink_cycle = m_cursor_blink_cycle; + if (m_text_blink_tag != 0) { + /* The current phase might have changed, and an already installed + * timer to blink might fire too late. So remove the timer and + * repaint the contents (which will install a correct new timer). */ + remove_text_blink_timeout(); + invalidate_all(); + } } void @@ -8886,6 +8927,9 @@ VteTerminalPrivate::determine_colors(VteCellAttr const* attr, } /* Invisible? */ + /* FIXME: This is dead code, this is not where we actually handle invisibile. + * Instead, draw_cells() is not called from draw_rows(). + * That is required for the foreground to be transparent if so is the background. */ if (attr->invisible) { fore = deco = back; } @@ -8919,6 +8963,14 @@ VteTerminalPrivate::determine_cursor_colors(VteCell const* cell, fore, back, deco); } +static gboolean +invalidate_text_blink_cb(VteTerminalPrivate *that) +{ + that->m_text_blink_tag = 0; + that->invalidate_all(); + return G_SOURCE_REMOVE; +} + /* Draw a string of characters with similar attributes. */ void VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items, @@ -8933,6 +8985,7 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items, guint underline, bool strikethrough, bool overline, + bool blink, bool hyperlink, bool hilite, bool boxed, @@ -8952,10 +9005,10 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items, } tmp = g_string_free (str, FALSE); g_printerr ("draw_cells('%s', fore=%d, back=%d, deco=%d, bold=%d," - " ul=%d, strike=%d, ol=%d" + " ul=%d, strike=%d, ol=%d, blink=%d," " hyperlink=%d, hilite=%d, boxed=%d)\n", tmp, fore, back, deco, bold, - underline, strikethrough, overline, + underline, strikethrough, overline, blink, hyperlink, hilite, boxed); g_free (tmp); } @@ -8984,6 +9037,21 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items, } } while (i < n); + if (blink) { + /* Notify the caller that cells with the "blink" attribute were encountered (regardless of + * whether they're actually painted or skipped now), so that the caller can set up a timer + * to make them blink if it wishes to. */ + m_text_to_blink = true; + + /* This is for the "off" state of blinking text. Invisible text could also be handled here, + * but it's not, it's handled outside by not even calling this method. + * Setting fg = bg and painting the text would not work for two reasons: it'd be opaque + * even if the background is translucent, and this method can be called with a continuous + * run of identical fg, yet different bg colored cells. So we simply bail out. */ + if (!m_text_blink_state) + return; + } + /* Draw whatever SFX are required. Do this before drawing the letters, * so that if the descent of a letter crosses an underline of a different color, * it's the letter's color that wins. Other kinds of decorations always have the @@ -9304,6 +9372,7 @@ VteTerminalPrivate::draw_cells_with_attributes(struct _vte_draw_text_request *it cells[j].attr.underline, cells[j].attr.strikethrough, cells[j].attr.overline, + cells[j].attr.blink, m_allow_hyperlink && cells[j].attr.hyperlink_idx != 0, FALSE, FALSE, column_width, height); j += g_unichar_to_utf8(items[i].c, scratch_buf); @@ -9334,7 +9403,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_, gboolean bold, nbold, italic, nitalic, hyperlink, nhyperlink, hilite, nhilite, selected, nselected, strikethrough, nstrikethrough, - overline, noverline, invisible, ninvisible; + overline, noverline, invisible, ninvisible, blink, nblink; guint item_count; const VteCell *cell; VteRowData const* row_data; @@ -9490,6 +9559,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_, hyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0); bold = cell->attr.bold; italic = cell->attr.italic; + blink = cell->attr.blink; if (cell->attr.hyperlink_idx != 0 && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) { hilite = true; } else if (m_hyperlink_hover_idx == 0 && m_show_match) { @@ -9562,6 +9632,10 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_, if (noverline != overline) { break; } + nblink = cell->attr.blink; + if (nblink != blink) { + break; + } nhyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0); if (nhyperlink != hyperlink) { break; @@ -9622,8 +9696,8 @@ fg_draw: items, item_count, fore, back, deco, FALSE, FALSE, - bold, italic, underline, - strikethrough, overline, hyperlink, hilite, FALSE, + bold, italic, underline, strikethrough, + overline, blink, hyperlink, hilite, FALSE, column_width, row_height); item_count = 1; /* We'll need to continue at the first cell which didn't @@ -9856,6 +9930,7 @@ VteTerminalPrivate::paint_cursor() cell->attr.underline, cell->attr.strikethrough, cell->attr.overline, + cell->attr.blink, m_allow_hyperlink && cell->attr.hyperlink_idx != 0, FALSE, FALSE, @@ -9947,6 +10022,7 @@ VteTerminalPrivate::paint_im_preedit_string() 0, /* underline */ FALSE, /* strikethrough */ FALSE, /* overline */ + FALSE, /* blink */ FALSE, /* hyperlink */ FALSE, /* hilite */ TRUE, /* boxed */ @@ -9963,6 +10039,8 @@ VteTerminalPrivate::widget_draw(cairo_t *cr) cairo_region_t *region; int allocated_width, allocated_height; int extra_area_for_cursor; + bool text_blink_enabled_now; + gint64 now = 0; if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect)) return; @@ -10029,6 +10107,17 @@ VteTerminalPrivate::widget_draw(cairo_t *cr) cairo_region_destroy(rr); } + /* Whether blinking text should be visible now */ + m_text_blink_state = true; + text_blink_enabled_now = m_text_blink_mode & (m_has_focus ? VTE_TEXT_BLINK_FOCUSED : VTE_TEXT_BLINK_UNFOCUSED); + if (text_blink_enabled_now) { + now = g_get_monotonic_time() / 1000; + if (now % (m_text_blink_cycle * 2) >= m_text_blink_cycle) + m_text_blink_state = false; + } + /* Painting will flip this if it encounters any cell with blink attribute */ + m_text_to_blink = false; + /* and now paint them */ for (n = 0; n < n_rectangles; n++) { paint_area(&rectangles[n]); @@ -10057,6 +10146,19 @@ VteTerminalPrivate::widget_draw(cairo_t *cr) cairo_region_destroy (region); + /* If painting encountered any cell with blink attribute, we might need to set up a timer. + * Blinking is implemented using a one-shot (not repeating) timer that keeps getting reinstalled + * here as long as blinking cells are encountered during (re)painting. This way there's no need + * for an explicit step to stop the timer when blinking cells are no longer present, this happens + * implicitly by the timer not getting reinstalled anymore (often after a final unnecessary but + * harmless repaint). */ + if (G_UNLIKELY (m_text_to_blink && text_blink_enabled_now && m_text_blink_tag == 0)) + m_text_blink_tag = g_timeout_add_full(G_PRIORITY_LOW, + m_text_blink_cycle - now % m_text_blink_cycle, + (GSourceFunc)invalidate_text_blink_cb, + this, + NULL); + m_invalidated_all = FALSE; } @@ -10210,6 +10312,18 @@ VteTerminalPrivate::set_audible_bell(bool setting) } bool +VteTerminalPrivate::set_text_blink_mode(VteTextBlinkMode setting) +{ + if (setting == m_text_blink_mode) + return false; + + m_text_blink_mode = setting; + invalidate_all(); + + return true; +} + +bool VteTerminalPrivate::set_allow_bold(bool setting) { if (setting == m_allow_bold) diff --git a/src/vte/vteenums.h b/src/vte/vteenums.h index 0e71715c..54c4ee08 100644 --- a/src/vte/vteenums.h +++ b/src/vte/vteenums.h @@ -59,6 +59,25 @@ typedef enum { } VteCursorShape; /** + * VteTextBlinkMode: + * @VTE_TEXT_BLINK_NEVER: Do not blink the text. + * @VTE_TEXT_BLINK_FOCUSED: Allow blinking text only if the terminal is focused. + * @VTE_TEXT_BLINK_UNFOCUSED: Allow blinking text only if the terminal is unfocused. + * @VTE_TEXT_BLINK_ALWAYS: Allow blinking text. This is the default. + * + * An enumerated type which can be used to indicate whether the terminal allows + * the text contents to be blinked. + * + * Since: 0.52 + */ +typedef enum { + VTE_TEXT_BLINK_NEVER = 0, + VTE_TEXT_BLINK_FOCUSED = 1, + VTE_TEXT_BLINK_UNFOCUSED = 2, + VTE_TEXT_BLINK_ALWAYS = 3 +} VteTextBlinkMode; + +/** * VteEraseBinding: * @VTE_ERASE_AUTO: For backspace, attempt to determine the right value from the terminal's IO settings. For delete, use the control sequence. * @VTE_ERASE_ASCII_BACKSPACE: Send an ASCII backspace character (0x08). diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h index 7d85474d..b96999f3 100644 --- a/src/vte/vteterminal.h +++ b/src/vte/vteterminal.h @@ -223,6 +223,11 @@ double vte_terminal_get_cell_height_scale(VteTerminal *terminal) _VTE_GNUC_NONNU /* Set various on-off settings. */ _VTE_PUBLIC +void vte_terminal_set_text_blink_mode(VteTerminal *terminal, + VteTextBlinkMode text_blink_mode) _VTE_GNUC_NONNULL(1); +_VTE_PUBLIC +VteTextBlinkMode vte_terminal_get_text_blink_mode(VteTerminal *terminal) _VTE_GNUC_NONNULL(1); +_VTE_PUBLIC void vte_terminal_set_audible_bell(VteTerminal *terminal, gboolean is_audible) _VTE_GNUC_NONNULL(1); _VTE_PUBLIC diff --git a/src/vtegtk.cc b/src/vtegtk.cc index a7dcd9bf..83fdfc8a 100644 --- a/src/vtegtk.cc +++ b/src/vtegtk.cc @@ -499,6 +499,9 @@ vte_terminal_get_property (GObject *object, case PROP_SCROLL_ON_OUTPUT: g_value_set_boolean (value, vte_terminal_get_scroll_on_output(terminal)); break; + case PROP_TEXT_BLINK_MODE: + g_value_set_enum (value, vte_terminal_get_text_blink_mode (terminal)); + break; case PROP_WINDOW_TITLE: g_value_set_string (value, vte_terminal_get_window_title (terminal)); break; @@ -597,6 +600,9 @@ vte_terminal_set_property (GObject *object, case PROP_SCROLL_ON_OUTPUT: vte_terminal_set_scroll_on_output (terminal, g_value_get_boolean (value)); break; + case PROP_TEXT_BLINK_MODE: + vte_terminal_set_text_blink_mode (terminal, (VteTextBlinkMode)g_value_get_enum (value)); + break; case PROP_WORD_CHAR_EXCEPTIONS: vte_terminal_set_word_char_exceptions (terminal, g_value_get_string (value)); break; @@ -1547,6 +1553,19 @@ vte_terminal_class_init(VteTerminalClass *klass) (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); /** + * VteTerminal:text-blink-mode: + * + * Controls whether or not the terminal will allow blinking text. + * + * Since: 0.52 + */ + pspecs[PROP_TEXT_BLINK_MODE] = + g_param_spec_enum ("text-blink-mode", NULL, NULL, + VTE_TYPE_TEXT_BLINK_MODE, + VTE_TEXT_BLINK_ALWAYS, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); + + /** * VteTerminal:window-title: * * The terminal's title. @@ -2903,6 +2922,42 @@ vte_terminal_set_size(VteTerminal *terminal, } /** + * vte_terminal_get_text_blink_mode: + * @terminal: a #VteTerminal + * + * Checks whether or not the terminal will allow blinking text. + * + * Returns: the blinking setting + * + * Since: 0.52 + */ +VteTextBlinkMode +vte_terminal_get_text_blink_mode(VteTerminal *terminal) +{ + g_return_val_if_fail(VTE_IS_TERMINAL(terminal), VTE_TEXT_BLINK_ALWAYS); + return IMPL(terminal)->m_text_blink_mode; +} + +/** + * vte_terminal_set_text_blink_mode: + * @terminal: a #VteTerminal + * @text_blink_mode: the #VteTextBlinkMode to use + * + * Controls whether or not the terminal will allow blinking text. + * + * Since: 0.52 + */ +void +vte_terminal_set_text_blink_mode(VteTerminal *terminal, + VteTextBlinkMode text_blink_mode) +{ + g_return_if_fail(VTE_IS_TERMINAL(terminal)); + + if (IMPL(terminal)->set_text_blink_mode(text_blink_mode)) + g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_TEXT_BLINK_MODE]); +} + +/** * vte_terminal_get_allow_bold: * @terminal: a #VteTerminal * diff --git a/src/vtegtk.hh b/src/vtegtk.hh index 7adadfe7..c49754ef 100644 --- a/src/vtegtk.hh +++ b/src/vtegtk.hh @@ -88,6 +88,7 @@ enum { PROP_SCROLLBACK_LINES, PROP_SCROLL_ON_KEYSTROKE, PROP_SCROLL_ON_OUTPUT, + PROP_TEXT_BLINK_MODE, PROP_WINDOW_TITLE, PROP_WORD_CHAR_EXCEPTIONS, LAST_PROP, diff --git a/src/vteinternal.hh b/src/vteinternal.hh index 446204c3..5d8731e0 100644 --- a/src/vteinternal.hh +++ b/src/vteinternal.hh @@ -519,6 +519,13 @@ public: gboolean m_cursor_visible; gboolean m_has_focus; /* is the terminal window focused */ + /* Contents blinking */ + VteTextBlinkMode m_text_blink_mode; + gint m_text_blink_cycle; /* gtk-cursor-blink-time / 2 */ + bool m_text_blink_state; /* whether blinking text should be visible at this very moment */ + bool m_text_to_blink; /* drawing signals here if it encounters any cell with blink attribute */ + guint m_text_blink_tag; /* timeout ID for redrawing due to blinking */ + /* DECSCUSR cursor style (shape and blinking possibly overridden * via escape sequence) */ VteCursorStyle m_cursor_style; @@ -749,6 +756,8 @@ public: VteCursorBlinkMode decscusr_cursor_blink(); VteCursorShape decscusr_cursor_shape(); + void remove_text_blink_timeout(); + /* The allocation of the widget */ cairo_rectangle_int_t m_allocated_rect; /* The usable view area. This is the allocation, minus the padding, but @@ -844,6 +853,7 @@ public: guint underline, bool strikethrough, bool overline, + bool blink, bool hyperlink, bool hilite, bool boxed, @@ -1222,6 +1232,7 @@ public: int source); bool set_audible_bell(bool setting); + bool set_text_blink_mode(VteTextBlinkMode setting); bool set_allow_bold(bool setting); bool set_allow_hyperlink(bool setting); bool set_backspace_binding(VteEraseBinding binding); |