diff --git a/src/pty.cc b/src/pty.cc
index 82f7771..51eef70 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,19 @@ _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. */
+        rv = ioctl(fd, TIOCPKT, &one);
+        if (rv < 0) {
+                int errsv = errno;
+                g_set_error(error, VTE_PTY_ERROR,
+                            VTE_PTY_ERROR_PTY98_FAILED,
+                            "%s failed: %s", "ioctl(TIOCPKT)", g_strerror(errno));
+                close(fd);
+                errno = errsv;
+                return -1;
+        }
+
 	return fd;
 }
 
diff --git a/src/vte.cc b/src/vte.cc
index 76114fe..1e72752 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -4405,6 +4405,56 @@ vte_terminal_emit_pending_text_signals(VteTerminal *terminal)
 	}
 }
 
+static void
+_vte_terminal_output_stopped(VteTerminal *terminal)
+{
+        // TODO: emit signal
+        _vte_debug_print(VTE_DEBUG_IO, "Output stopped (^S)\n");
+        _vte_invalidate_cursor_once(terminal, FALSE);
+}
+
+static void
+_vte_terminal_output_started(VteTerminal *terminal)
+{
+        // TODO: emit signal
+        _vte_debug_print(VTE_DEBUG_IO, "Output started (^Q)\n");
+        _vte_invalidate_cursor_once(terminal, FALSE);
+}
+
+static void
+_vte_terminal_noecho_enabled(VteTerminal *terminal)
+{
+        // TODO: emit signal
+        _vte_debug_print(VTE_DEBUG_IO, "Password mode (icanon, no echo) entered\n");
+        _vte_invalidate_cursor_once(terminal, FALSE);
+}
+
+static void
+_vte_terminal_noecho_disabled(VteTerminal *terminal)
+{
+        // TODO: emit signal
+        _vte_debug_print(VTE_DEBUG_IO, "Password mode (icanon, no echo) left\n");
+        _vte_invalidate_cursor_once(terminal, FALSE);
+}
+
+static void
+_vte_terminal_check_noecho(VteTerminal *terminal)
+{
+        struct termios t;
+        gboolean new_noecho;
+        if (terminal->pvt->pty == NULL) return;
+        if (tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &t) != 0) return;
+        new_noecho = ((t.c_lflag & ICANON) && !(t.c_lflag & ECHO));
+        if (new_noecho != terminal->pvt->noecho) {
+                terminal->pvt->noecho = new_noecho;
+                if (new_noecho) {
+                        _vte_terminal_noecho_enabled(terminal);
+                } else {
+                        _vte_terminal_noecho_disabled(terminal);
+                }
+        }
+}
+
 /* Process incoming data, first converting it to unicode characters, and then
  * processing control sequences. */
 static void
@@ -4824,6 +4874,8 @@ next_match:
 						   &rect);
 	}
 
+	_vte_terminal_check_noecho(terminal);
+
 	_vte_debug_print (VTE_DEBUG_WORK, ")");
 	_vte_debug_print (VTE_DEBUG_IO,
 			"%ld chars and %ld bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",
@@ -4913,7 +4965,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 pkt_header;
+                                char save = bp[-1];
+                                int ret = read (fd, bp - 1, rem + 1);
+                                pkt_header = bp[-1];
+                                bp[-1] = save;
 				switch (ret){
 					case -1:
 						err = errno;
@@ -4922,6 +4982,14 @@ vte_terminal_io_read(GIOChannel *channel,
 						eof = TRUE;
 						goto out;
 					default:
+                                                ret--;
+                                                if (pkt_header & TIOCPKT_STOP) {
+                                                        terminal->pvt->scroll_locked = TRUE;
+                                                        _vte_terminal_output_stopped(terminal);
+                                                } else if (pkt_header & TIOCPKT_START) {
+                                                        terminal->pvt->scroll_locked = FALSE;
+                                                        _vte_terminal_output_started(terminal);
+                                                }
 						bp += ret;
 						rem -= ret;
 						len += ret;
@@ -5200,6 +5268,7 @@ vte_terminal_send(VteTerminal *terminal, const char *encoding,
 			 * become available for writing, set that up here. */
 			_vte_terminal_connect_pty_write(terminal);
 		}
+		_vte_terminal_check_noecho(terminal);
 		if (crcount > 0) {
 			g_free(cooked);
 		}
@@ -10741,6 +10810,24 @@ vte_terminal_paint_cursor(VteTerminal *terminal)
 	x = item.x;
 	y = item.y;
 
+        if (terminal->pvt->scroll_locked) {
+                // TODO: better cursor shape
+                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;
+        }
+
+        if (terminal->pvt->noecho) {
+                // TODO: better cursor shape
+                vte_terminal_fill_rectangle(terminal, &bg,
+                                            x, y, width, height / 3);
+                vte_terminal_fill_rectangle(terminal, &bg,
+                                            x, y + height - height / 3, width, height / 3);
+                return;
+        }
+
         switch (_vte_terminal_decscusr_cursor_shape(terminal)) {
 
 		case VTE_CURSOR_SHAPE_IBEAM: {
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 7573ada..ba231a1 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -115,6 +115,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 *)];
 };
 
@@ -423,4 +424,7 @@ public:
         guint hscroll_policy : 1; /* unused */
 
         guint vscroll_policy : 1;
+
+        gboolean scroll_locked;
+        gboolean noecho;
 };
