diff --git a/src/pty.cc b/src/pty.cc
index 82f7771..63ba3d4 100644
--- a/src/pty.cc
+++ b/src/pty.cc
@@ -715,6 +715,7 @@ static int
 _vte_pty_getpt(GError **error)
 {
 	int fd, flags, rv;
+        int one = 1;
 #if defined(HAVE_POSIX_OPENPT)
 	fd = posix_openpt(O_RDWR | O_NOCTTY);
 #elif defined(HAVE_GETPT)
@@ -759,6 +760,10 @@ _vte_pty_getpt(GError **error)
                 return -1;
         }
 
+        /* tty_ioctl(4) -> every read() gives an extra byte at the beginning
+         * notifying us of stop/start (^S/^Q) events. */
+        ioctl(fd, TIOCPKT, &one);
+
 	return fd;
 }
 
diff --git a/src/vte.cc b/src/vte.cc
index 8df3902..1efb036 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -4237,7 +4237,15 @@ vte_terminal_io_read(GIOChannel *channel,
 			bp = chunk->data + chunk->len;
 			len = 0;
 			do {
-				int ret = read (fd, bp, rem);
+                                /* We'd like to read (fd, bp, rem); but due to TIOCPKT mode
+                                 * there's an extra input byte returned at the beginning.
+                                 * We need to see what that byte is, but otherwise drop it
+                                 * and write continuously to chunk->data. */
+                                char cpkt_header;
+                                char save = bp[-1];
+                                int ret = read (fd, bp - 1, rem + 1);
+                                cpkt_header = bp[-1];
+                                bp[-1] = save;
 				switch (ret){
 					case -1:
 						err = errno;
@@ -4246,6 +4254,16 @@ vte_terminal_io_read(GIOChannel *channel,
 						eof = TRUE;
 						goto out;
 					default:
+                                                ret--;
+                                                if (cpkt_header & TIOCPKT_STOP) {
+                                                        _vte_debug_print(VTE_DEBUG_IO, "Output stopped (^S)\n");
+                                                        terminal->pvt->scroll_locked = TRUE;
+                                                        _vte_invalidate_cursor_once(terminal, FALSE);
+                                                } else if (cpkt_header & TIOCPKT_START) {
+                                                        _vte_debug_print(VTE_DEBUG_IO, "Output started (^Q)\n");
+                                                        terminal->pvt->scroll_locked = FALSE;
+                                                        _vte_invalidate_cursor_once(terminal, FALSE);
+                                                }
 						bp += ret;
 						rem -= ret;
 						len += ret;
@@ -9997,6 +10015,14 @@ vte_terminal_paint_cursor(VteTerminal *terminal)
 	x = item.x;
 	y = item.y;
 
+        if (terminal->pvt->scroll_locked) {
+                vte_terminal_fill_rectangle(terminal, &bg,
+                                            x, y, width / 3, height);
+                vte_terminal_fill_rectangle(terminal, &bg,
+                                            x + width - width / 3, y, width / 3, height);
+                return;
+        }
+
         switch (_vte_terminal_decscusr_cursor_shape(terminal)) {
 
 		case VTE_CURSOR_SHAPE_IBEAM: {
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index d585365..27629c0 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -96,6 +96,7 @@ typedef struct _vte_incoming_chunk _vte_incoming_chunk_t;
 struct _vte_incoming_chunk{
         _vte_incoming_chunk_t *next;
         guint len;
+        guchar dataminusone;    /* Hack: Keep it right before data, so that data[-1] is valid and usable */
         guchar data[VTE_INPUT_CHUNK_SIZE - 2 * sizeof(void *)];
 };
 
@@ -403,4 +404,6 @@ public:
         guint hscroll_policy : 1; /* unused */
 
         guint vscroll_policy : 1;
+
+        gboolean scroll_locked;
 };
