diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index 56ae775..306102c 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -22,6 +22,8 @@ vte_terminal_set_font_scale
 vte_terminal_get_font_scale
 vte_terminal_set_audible_bell
 vte_terminal_get_audible_bell
+vte_terminal_set_allow_blink
+vte_terminal_get_allow_blink
 vte_terminal_set_allow_bold
 vte_terminal_get_allow_bold
 vte_terminal_set_scroll_on_output
@@ -92,6 +94,8 @@ vte_terminal_get_geometry_hints
 vte_terminal_set_geometry_hints_for_window
 
 <SUBSECTION Standard>
+VTE_TYPE_ALLOW_BLINK
+vte_blink_get_type
 VTE_TYPE_CURSOR_BLINK_MODE
 vte_cursor_blink_mode_get_type
 VTE_TYPE_CURSOR_SHAPE
diff --git a/src/app.vala b/src/app.vala
index 4fef749..bf11eb0 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -333,6 +333,7 @@ class Window : Gtk.ApplicationWindow
 
     terminal.set_audible_bell(App.Options.audible);
     terminal.set_cjk_ambiguous_width(App.Options.get_cjk_ambiguous_width());
+    terminal.set_allow_blink(App.Options.get_blink_mode());
     terminal.set_cursor_blink_mode(App.Options.get_cursor_blink_mode());
     terminal.set_cursor_shape(App.Options.get_cursor_shape());
     terminal.set_mouse_autohide(true);
@@ -821,6 +822,7 @@ class App : Gtk.Application
     public static bool audible = false;
     public static string? command = null;
     private static string? cjk_ambiguous_width_string = null;
+    private static string? blink_mode_string = null;
     private static string? cursor_blink_mode_string = null;
     private static string? cursor_background_color_string = null;
     private static string? cursor_foreground_color_string = null;
@@ -957,6 +959,17 @@ class App : Gtk.Application
       return get_color(hl_fg_color_string);
     }
 
+    public static Vte.Blink get_blink_mode()
+    {
+      Vte.Blink value;
+      if (blink_mode_string != null)
+        value = (Vte.Blink)parse_enum(typeof(Vte.Blink),
+                                      blink_mode_string);
+      else
+        value = Vte.Blink.ALWAYS;
+      return value;
+    }
+
     public static Vte.CursorBlinkMode get_cursor_blink_mode()
     {
       Vte.CursorBlinkMode value;
@@ -986,6 +999,8 @@ class App : Gtk.Application
         "Execute a command in the terminal", null },
       { "cjk-width", 0, 0, OptionArg.STRING, ref cjk_ambiguous_width_string,
         "Specify the cjk ambiguous width to use for UTF-8 encoding", "NARROW|WIDE" },
+      { "blink", 0, 0, OptionArg.STRING, ref blink_mode_string,
+        "Blink mode (always|focused|never)", "MODE" },
       { "cursor-blink", 0, 0, OptionArg.STRING, ref cursor_blink_mode_string,
         "Cursor blink mode (system|on|off)", "MODE" },
       { "cursor-background-color", 0, 0, OptionArg.STRING, ref cursor_background_color_string,
diff --git a/src/vte.cc b/src/vte.cc
index 609d4e2..a1f4b5d 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -437,6 +437,9 @@ VteTerminalPrivate::invalidate_all()
 	_vte_debug_print (VTE_DEBUG_WORK, "*");
 	_vte_debug_print (VTE_DEBUG_UPDATES, "Invalidating all.\n");
 
+        /* this sometimes saves us a full repaint after blinking has stopped */
+        remove_blink_timeout();
+
 	/* replace invalid regions with one covering the whole terminal */
 	reset_update_rects();
 	m_invalidated_all = TRUE;
@@ -4510,6 +4513,16 @@ VteTerminalPrivate::check_cursor_blink()
 }
 
 void
+VteTerminalPrivate::remove_blink_timeout()
+{
+	if (m_contents_blink_tag == 0)
+		return;
+
+	g_source_remove(m_contents_blink_tag);
+	m_contents_blink_tag = 0;
+}
+
+void
 VteTerminalPrivate::beep()
 {
 	if (m_audible_bell) {
@@ -7803,6 +7816,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_contents_blink_tag = 0;
 	m_outgoing = _vte_byte_array_new();
 	m_outgoing_conv = VTE_INVALID_CONV;
 	m_conv_buffer = _vte_byte_array_new();
@@ -7844,6 +7858,7 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) :
 	m_meta_sends_escape = TRUE;
 	m_audible_bell = TRUE;
 	m_bell_margin = 10;
+        m_allow_blink = VTE_BLINK_ALWAYS;
 	m_allow_bold = TRUE;
         m_deccolm_mode = FALSE;
         m_rewrap_on_resize = TRUE;
@@ -8075,12 +8090,15 @@ VteTerminalPrivate::widget_unrealize()
 		gtk_widget_unmap(m_widget);
 	}
 
-	/* Remove the blink timeout function. */
+	/* Remove the cursor blink timeout function. */
 	remove_cursor_timeout();
 
 	/* Cancel any pending redraws. */
 	remove_update_timeout(this);
 
+        /* Remove the contents blink timeout function. */
+        remove_blink_timeout();
+
 	/* Cancel any pending signals */
 	m_contents_changed_pending = FALSE;
 	m_cursor_moved_pending = FALSE;
@@ -8109,7 +8127,7 @@ void
 VteTerminalPrivate::widget_settings_notify()
 {
         gboolean blink;
-        int blink_time = 1000;
+        int blink_time = VTE_BLINK_TIME_DEFAULT;
         int blink_timeout = G_MAXINT;
 
         g_object_get(gtk_widget_get_settings(m_widget),
@@ -8509,6 +8527,9 @@ VteTerminalPrivate::determine_colors(VteCellAttr const* attr,
 	}
 
 	/* Invisible? */
+        /* Note: 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 = back;
 	}
@@ -8539,6 +8560,14 @@ VteTerminalPrivate::determine_cursor_colors(VteCell const* cell,
                          fore, back);
 }
 
+static gboolean
+invalidate_all_cb(VteTerminalPrivate *that)
+{
+        that->m_contents_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,
@@ -8551,6 +8580,7 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
                                bool italic,
                                bool underline,
                                bool strikethrough,
+                               bool blink,
                                bool hilite,
                                bool boxed,
                                int column_width,
@@ -8559,6 +8589,7 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
 	int i, x, y;
 	gint columns = 0;
 	vte::color::rgb fg, bg;
+	bool skip_drawing_text = FALSE;
 
 	g_assert(n > 0);
 	_VTE_DEBUG_IF(VTE_DEBUG_CELLS) {
@@ -8569,9 +8600,9 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
 		}
 		tmp = g_string_free (str, FALSE);
 		g_printerr ("draw_cells('%s', fore=%d, back=%d, bold=%d,"
-				" ul=%d, strike=%d, hilite=%d, boxed=%d)\n",
+				" ul=%d, strike=%d, blink=%d, hilite=%d, boxed=%d)\n",
 				tmp, fore, back, bold,
-				underline, strikethrough, hilite, boxed);
+				underline, strikethrough, blink, hilite, boxed);
 		g_free (tmp);
 	}
 
@@ -8579,6 +8610,28 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
 	rgb_from_index(fore, fg);
 	rgb_from_index(back, bg);
 
+        if (blink &&
+            (m_allow_blink == VTE_BLINK_ALWAYS ||
+             (m_allow_blink == VTE_BLINK_FOCUSED && m_has_focus))) {
+                unsigned int blink_time = VTE_BLINK_TIME_DEFAULT;
+                guint64 now;
+
+                g_object_get(gtk_widget_get_settings(m_widget),
+                        "gtk-cursor-blink-time", &blink_time,
+                        nullptr);
+                now = g_get_monotonic_time() / 1000;
+
+                if (now % blink_time >= blink_time / 2)
+                        skip_drawing_text = TRUE;
+                /* TODO: We could go for a bounding box of blinking text instead of forcing a full repaint. */
+                if (m_contents_blink_tag == 0)
+                        m_contents_blink_tag = g_timeout_add_full(G_PRIORITY_LOW,
+                                                                  blink_time / 2 - now % (blink_time / 2),
+                                                                  (GSourceFunc)invalidate_all_cb,
+                                                                  this,
+                                                                  NULL);
+        }
+
 	i = 0;
 	do {
 		columns = 0;
@@ -8598,6 +8651,14 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
 		}
 	} while (i < n);
 
+        /* This is for the "off" state of blinking text. Invisible text could also be handled here,
+         * but it's not, it's actually 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 must bail out. */
+        if (skip_drawing_text)
+                return;
+
 	_vte_draw_text(m_draw,
 			items, n,
 			&fg, VTE_DRAW_OPAQUE,
@@ -8869,6 +8930,7 @@ VteTerminalPrivate::draw_cells_with_attributes(struct _vte_draw_text_request *it
 					cells[j].attr.italic,
 					cells[j].attr.underline,
 					cells[j].attr.strikethrough,
+                                        cells[j].attr.blink,
 					FALSE, FALSE, column_width, height);
 		j += g_unichar_to_utf8(items[i].c, scratch_buf);
 	}
@@ -8896,7 +8958,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
         long x, y;
 	guint fore, nfore, back, nback;
 	gboolean underline, nunderline, bold, nbold, italic, nitalic, hilite, nhilite,
-		 selected, nselected, strikethrough, nstrikethrough;
+		 selected, nselected, strikethrough, nstrikethrough, blink, nblink;
 	guint item_count;
 	const VteCell *cell;
 	VteRowData const* row_data;
@@ -9047,6 +9109,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 			strikethrough = cell->attr.strikethrough;
 			bold = cell->attr.bold;
 			italic = cell->attr.italic;
+			blink = cell->attr.blink;
 			if (m_show_match) {
 				hilite = m_match_span.contains(row, i);
 			} else {
@@ -9111,6 +9174,10 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 					if (nstrikethrough != strikethrough) {
 						break;
 					}
+                                        nblink = cell->attr.blink;
+                                        if (nblink != blink) {
+                                                break;
+                                        }
 					/* Break up matched/not-matched text. */
 					nhilite = false;
 					if (m_show_match) {
@@ -9162,7 +9229,7 @@ fg_draw:
 					item_count,
 					fore, back, FALSE, FALSE,
 					bold, italic, underline,
-					strikethrough, hilite, FALSE,
+					strikethrough, blink, hilite, FALSE,
 					column_width, row_height);
 			item_count = 1;
 			/* We'll need to continue at the first cell which didn't
@@ -9367,6 +9434,7 @@ VteTerminalPrivate::paint_cursor()
                                                         cell->attr.italic,
                                                         cell->attr.underline,
                                                         cell->attr.strikethrough,
+                                                        cell->attr.blink,
                                                         FALSE,
                                                         FALSE,
                                                         width,
@@ -9452,6 +9520,7 @@ VteTerminalPrivate::paint_im_preedit_string()
 						FALSE,
 						FALSE,
 						FALSE,
+                                                FALSE,
 						FALSE,
 						TRUE,
 						width, height);
@@ -9713,6 +9782,18 @@ VteTerminalPrivate::set_audible_bell(bool setting)
 }
 
 bool
+VteTerminalPrivate::set_allow_blink(VteBlink setting)
+{
+        if (setting == m_allow_blink)
+                return false;
+
+        m_allow_blink = 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 1006963..2ca4344 100644
--- a/src/vte/vteenums.h
+++ b/src/vte/vteenums.h
@@ -59,6 +59,24 @@ typedef enum {
 } VteCursorShape;
 
 /**
+ * VteBlink:
+ * @VTE_BLINK_NEVER: Do not blink the contents.
+ * @VTE_BLINK_ALWAYS: Allow blinking contents. This is the default.
+ * @VTE_BLINK_FOCUSED: Allow blinking contents only if the widget is focused.
+ *
+ * An enumerated type which can be used to indicate whether the terminal allows
+ * the contents to be blinked. Note: This is for the actual text contents only,
+ * not for the cursor.
+ *
+ * Since: 0.46
+ */
+typedef enum {
+        VTE_BLINK_NEVER,
+        VTE_BLINK_ALWAYS,
+        VTE_BLINK_FOCUSED
+} VteBlink;
+
+/**
  * 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 7836271..323e9e4 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -203,6 +203,11 @@ gdouble vte_terminal_get_font_scale(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
 
 /* Set various on-off settings. */
 _VTE_PUBLIC
+void vte_terminal_set_allow_blink(VteTerminal *terminal,
+                                  VteBlink allow_blink) _VTE_GNUC_NONNULL(1);
+_VTE_PUBLIC
+VteBlink vte_terminal_get_allow_blink(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/vteapp.c b/src/vteapp.c
index 96a3732..8f73fb7 100644
--- a/src/vteapp.c
+++ b/src/vteapp.c
@@ -620,6 +620,7 @@ main(int argc, char **argv)
 	const char *working_directory = NULL;
 	const char *output_file = NULL;
 	char *pty_flags_string = NULL;
+	char *blink_mode_string = NULL;
 	char *cursor_blink_mode_string = NULL;
 	char *cursor_shape_string = NULL;
 	char *scrollbar_policy_string = NULL;
@@ -723,6 +724,11 @@ main(int argc, char **argv)
 			"Specify the number of scrollback-lines", NULL
 		},
 		{
+			"blink", 0, 0,
+			G_OPTION_ARG_STRING, &blink_mode_string,
+			"Blink mode (always|focused|never)", "MODE"
+		},
+		{
 			"cursor-blink", 0, 0,
 			G_OPTION_ARG_STRING, &cursor_blink_mode_string,
 			"Cursor blink mode (system|on|off)", "MODE"
@@ -812,6 +818,7 @@ main(int argc, char **argv)
 	};
 	GOptionContext *context;
 	GError *error = NULL;
+	VteBlink blink_mode = VTE_BLINK_ALWAYS;
 	VteCursorBlinkMode cursor_blink_mode = VTE_CURSOR_BLINK_SYSTEM;
 	VteCursorShape cursor_shape = VTE_CURSOR_SHAPE_BLOCK;
 	GtkPolicyType scrollbar_policy = GTK_POLICY_ALWAYS;
@@ -839,6 +846,10 @@ main(int argc, char **argv)
 		return 1;
 	}
 
+	if (blink_mode_string) {
+		blink_mode = parse_enum(VTE_TYPE_BLINK, blink_mode_string);
+		g_free(blink_mode_string);
+	}
 	if (cursor_blink_mode_string) {
 		cursor_blink_mode = parse_enum(VTE_TYPE_CURSOR_BLINK_MODE, cursor_blink_mode_string);
 		g_free(cursor_blink_mode_string);
@@ -979,6 +990,7 @@ main(int argc, char **argv)
 
 	/* Set some defaults. */
 	vte_terminal_set_audible_bell(terminal, audible);
+	vte_terminal_set_allow_blink(terminal, blink_mode);
 	vte_terminal_set_cursor_blink_mode(terminal, cursor_blink_mode);
 	vte_terminal_set_scroll_on_output(terminal, FALSE);
 	vte_terminal_set_scroll_on_keystroke(terminal, TRUE);
diff --git a/src/vtedefines.hh b/src/vtedefines.hh
index 7f56291..7aa3f55 100644
--- a/src/vtedefines.hh
+++ b/src/vtedefines.hh
@@ -82,6 +82,7 @@
 #define VTE_UPDATE_TIMEOUT		15
 #define VTE_UPDATE_REPEAT_TIMEOUT	30
 #define VTE_MAX_PROCESS_TIME		100
+#define VTE_BLINK_TIME_DEFAULT          1000
 #define VTE_CELL_BBOX_SLACK		1
 #define VTE_DEFAULT_UTF8_AMBIGUOUS_WIDTH 1
 
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index b9de9f2..063586a 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -413,6 +413,9 @@ vte_terminal_get_property (GObject *object,
                 case PROP_VSCROLL_POLICY:
                         g_value_set_enum (value, impl->m_vscroll_policy);
                         break;
+                case PROP_ALLOW_BLINK:
+                        g_value_set_enum (value, vte_terminal_get_allow_blink (terminal));
+                        break;
                 case PROP_ALLOW_BOLD:
                         g_value_set_boolean (value, vte_terminal_get_allow_bold (terminal));
                         break;
@@ -508,6 +511,9 @@ vte_terminal_set_property (GObject *object,
                 case PROP_VSCROLL_POLICY:
                         vte_terminal_set_vscroll_policy(terminal, (GtkScrollablePolicy)g_value_get_enum(value));
                         break;
+                case PROP_ALLOW_BLINK:
+                        vte_terminal_set_allow_blink (terminal, (VteBlink)g_value_get_enum (value));
+                        break;
                 case PROP_ALLOW_BOLD:
                         vte_terminal_set_allow_bold (terminal, g_value_get_boolean (value));
                         break;
@@ -1206,6 +1212,19 @@ vte_terminal_class_init(VteTerminalClass *klass)
                              G_TYPE_NONE, 0);
 
         /**
+         * VteTerminal:allow-blink:
+         *
+         * Controls whether or not the terminal will allow blinking text.
+         *
+         * Since: 0.46
+         */
+        pspecs[PROP_ALLOW_BLINK] =
+                g_param_spec_enum ("allow-blink", NULL, NULL,
+                                   VTE_TYPE_BLINK,
+                                   VTE_BLINK_ALWAYS,
+                                   (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
+
+        /**
          * VteTerminal:allow-bold:
          *
          * Controls whether or not the terminal will attempt to draw bold text.
@@ -2512,6 +2531,42 @@ vte_terminal_set_size(VteTerminal *terminal,
 }
 
 /**
+ * vte_terminal_get_allow_blink:
+ * @terminal: a #VteTerminal
+ *
+ * Checks whether or not the terminal will allow blinking text.
+ *
+ * Returns: the blinking setting
+ *
+ * Since: 0.46
+ */
+VteBlink
+vte_terminal_get_allow_blink(VteTerminal *terminal)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), VTE_BLINK_ALWAYS);
+        return IMPL(terminal)->m_allow_blink;
+}
+
+/**
+ * vte_terminal_set_allow_blink:
+ * @terminal: a #VteTerminal
+ * @allow_blink: the #VteBlink to use
+ *
+ * Controls whether or not the terminal will allow blinking text.
+ *
+ * Since: 0.46
+ */
+void
+vte_terminal_set_allow_blink(VteTerminal *terminal,
+                             VteBlink allow_blink)
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        if (IMPL(terminal)->set_allow_blink(allow_blink))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ALLOW_BLINK]);
+}
+
+/**
  * vte_terminal_get_allow_bold:
  * @terminal: a #VteTerminal
  *
diff --git a/src/vtegtk.hh b/src/vtegtk.hh
index 5340725..8a59765 100644
--- a/src/vtegtk.hh
+++ b/src/vtegtk.hh
@@ -62,6 +62,7 @@ extern guint signals[LAST_SIGNAL];
 
 enum {
         PROP_0,
+        PROP_ALLOW_BLINK,
         PROP_ALLOW_BOLD,
         PROP_AUDIBLE_BELL,
         PROP_BACKSPACE_BINDING,
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index cff2157..a8f0bef 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -406,6 +406,10 @@ public:
         gboolean m_cursor_visible;
         gboolean m_has_focus;               /* is the terminal window focused */
 
+        /* Contents blinking */
+        VteBlink m_allow_blink;
+        guint m_contents_blink_tag;
+
         /* DECSCUSR cursor style (shape and blinking possibly overridden
          * via escape sequence) */
         VteCursorStyle m_cursor_style;
@@ -598,6 +602,8 @@ public:
         VteCursorBlinkMode decscusr_cursor_blink();
         VteCursorShape decscusr_cursor_shape();
 
+        void remove_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
@@ -689,6 +695,7 @@ public:
                         bool italic,
                         bool underline,
                         bool strikethrough,
+                        bool blink,
                         bool hilite,
                         bool boxed,
                         int column_width,
@@ -1056,6 +1063,7 @@ public:
                          int source);
 
         bool set_audible_bell(bool setting);
+        bool set_allow_blink(VteBlink setting);
         bool set_allow_bold(bool setting);
         bool set_backspace_binding(VteEraseBinding binding);
         bool set_background_alpha(double alpha);
