POC: Make the TUI command window support the mouse (Re: [PATCHv3 1/2] Initial TUI mouse support)

Message ID bb8428c5-06fb-c86f-2786-bb6c46ae0886@palves.net
State New
Headers show
Series
  • POC: Make the TUI command window support the mouse (Re: [PATCHv3 1/2] Initial TUI mouse support)
Related show

Commit Message

Pedro Alves June 12, 2021, 2:41 a.m.
On 2021-06-11 12:02 p.m., Hannes Domani wrote:
>  Am Donnerstag, 10. Juni 2021, 20:47:01 MESZ hat Tom Tromey <tom@tromey.com> Folgendes geschrieben:

> 

>>>>>>> "Hannes" == Hannes Domani via Gdb-patches <gdb-patches@sourceware.org> writes:

>>

>> Hannes> I imagine the same is true for mouse escape sequences.

>> Hannes> So I would just disable the mouse when the command window has focus, but

>> Hannes> what do you think?

>>

>> I think this is a good fallback to have, but it would be better if we

>> could make it really work somehow.  The problem with doing this is that

>> if the command window has focus, then things like clicking in the source

>> to set a breakpoint will not work.

> 

> That would probably mean that keypad also has to be enabled when the

> command window has focus.

> I did a short test where I tried this, but then gdb crashed somewhere in

> readline, and I stopped there, because I didn't want to debug readline code.


We disable the keypad in the command window because we want to pass all escape
sequences to readline, unprocessed by ncurses, so that e.g., up/down navigates
the command history, left/right moves the cursor position, etc., just like if
you weren't in the TUI.

I first thought of fixing this by enabling the keypad, and then where we handle
KEY_UP, etc., special case the command window, and call readline functions that do
what you'd expect.  But the thing is that ends up being a poor readline emulation,
and I didn't like where this was going.  Especially when I realized that for example,
when you're in the middle for a reverse-search (c-r), KEY_UP does something different.
Etc.

I had a different idea to make this all work.  Warning, this is either a brilliant hack,
or a really nasty hack, depending on perspective.  :-)

The idea is to keep the keypad disabled in the command window.  However,
we need to enable the keypad for ncurses to process mouse escape sequences.
This is a conflict.  So here's the main trick behind the idea.  It breaks the conflict.
Create a separate ncurses screen/terminal, with newterm, which reads from a pipe instead
of from stdin.  Then, flush data from stdin into this pipe, BUT, make sure to never flush
a non-mouse escape sequence into the pipe.  It's like the pipe is stdin, but with any
non-mouse escape sequence filtered out.  This way, ncurses only either sees mouse
escape sequences or normal ASCII keys.  In order to do this, we need to use
a separate thread to flush stdin to the pipe.

I gave this a try the other weekend, and ran into issues.  Today/tonight I gave it
another go, and got it working.  It is a bit rough around the edges, but
seems to work nicely.

From a3bf7056c7dee837816807d41d33abda45cfd51d Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>

Date: Sat, 5 Jun 2021 19:11:09 +0100
Subject: [PATCH] Make the TUI command window support the mouse

Change-Id: Ie0a7d849943cfb47f4a6589e1c73341563740fa9
---
 gdb/tui/tui-io.c | 549 ++++++++++++++++++++++++++++++++++++++++++++---
 gdb/tui/tui-io.h |   3 +
 gdb/tui/tui.c    |   4 +
 3 files changed, 527 insertions(+), 29 deletions(-)


base-commit: a53755664f5f904aefd0d0b87e12f9adb6b69129
-- 
2.26.2

Comments

Simon Marchi via Gdb-patches June 12, 2021, 12:32 p.m. | #1
Am Samstag, 12. Juni 2021, 04:41:12 MESZ hat Pedro Alves <pedro@palves.net> Folgendes geschrieben:

> On 2021-06-11 12:02 p.m., Hannes Domani wrote:

> >  Am Donnerstag, 10. Juni 2021, 20:47:01 MESZ hat Tom Tromey <tom@tromey.com> Folgendes geschrieben:

> >

> >>>>>>> "Hannes" == Hannes Domani via Gdb-patches <gdb-patches@sourceware.org> writes:

> >>

> >> Hannes> I imagine the same is true for mouse escape sequences.

> >> Hannes> So I would just disable the mouse when the command window has focus, but

> >> Hannes> what do you think?

> >>

> >> I think this is a good fallback to have, but it would be better if we

> >> could make it really work somehow.  The problem with doing this is that

> >> if the command window has focus, then things like clicking in the source

> >> to set a breakpoint will not work.

> >

> > That would probably mean that keypad also has to be enabled when the

> > command window has focus.

> > I did a short test where I tried this, but then gdb crashed somewhere in

> > readline, and I stopped there, because I didn't want to debug readline code.

>

> We disable the keypad in the command window because we want to pass all escape

> sequences to readline, unprocessed by ncurses, so that e.g., up/down navigates

> the command history, left/right moves the cursor position, etc., just like if

> you weren't in the TUI.

>

> I first thought of fixing this by enabling the keypad, and then where we handle

> KEY_UP, etc., special case the command window, and call readline functions that do

> what you'd expect.  But the thing is that ends up being a poor readline emulation,

> and I didn't like where this was going.  Especially when I realized that for example,

> when you're in the middle for a reverse-search (c-r), KEY_UP does something different.

> Etc.

>

> I had a different idea to make this all work.  Warning, this is either a brilliant hack,

> or a really nasty hack, depending on perspective.  :-)

>

> The idea is to keep the keypad disabled in the command window.  However,

> we need to enable the keypad for ncurses to process mouse escape sequences.

> This is a conflict.  So here's the main trick behind the idea.  It breaks the conflict.

> Create a separate ncurses screen/terminal, with newterm, which reads from a pipe instead

> of from stdin.  Then, flush data from stdin into this pipe, BUT, make sure to never flush

> a non-mouse escape sequence into the pipe.  It's like the pipe is stdin, but with any

> non-mouse escape sequence filtered out.  This way, ncurses only either sees mouse

> escape sequences or normal ASCII keys.  In order to do this, we need to use

> a separate thread to flush stdin to the pipe.

>

> I gave this a try the other weekend, and ran into issues.  Today/tonight I gave it

> another go, and got it working.  It is a bit rough around the edges, but

> seems to work nicely.


If this works on Linux, then that's great, but Windows doesn't send mouse
escape sequences.

On the other hand, if keypad was enabled, couldn't we just forward readline
the escape sequences for the arrow keys instead?


Hannes
Pedro Alves June 12, 2021, 6:08 p.m. | #2
On 2021-06-12 1:32 p.m., Hannes Domani wrote:

> If this works on Linux, then that's great, but Windows doesn't send mouse

> escape sequences.


I was under the impression that Windows just worked without further changes, so we'd
just not compile in the new code.

> 

> On the other hand, if keypad was enabled, couldn't we just forward readline

> the escape sequences for the arrow keys instead?


Yeah, to be honest I think that is likely a better approach and worth it of a
try -- my only concern is whether the escape sequences are standard enough
across terminals?  Maybe it's all covered by ANSI and it's all we need to care
about?  I thought of the other approach because that let's us not care about
specific sequences, other than the mouse sequence, which seemed pretty much
standard from looking around.  Also, it was run to write.  :-)

Patch

diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index 7df0e2f1bd3..6c1c3f2f5f0 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -946,6 +946,34 @@  tui_initialize_io (void)
 #endif
 }
 
+/* Dispatch the correct tui function based upon the mouse event.  */
+
+static void
+tui_dispatch_mouse_event (const MEVENT &mev)
+{
+  for (tui_win_info *wi : all_tui_windows ())
+    if (mev.x > wi->x && mev.x < wi->x + wi->width - 1
+	&& mev.y > wi->y && mev.y < wi->y + wi->height - 1)
+      {
+	if ((mev.bstate & BUTTON1_CLICKED) != 0
+	    || (mev.bstate & BUTTON2_CLICKED) != 0
+	    || (mev.bstate & BUTTON3_CLICKED) != 0)
+	  {
+	    int button = (mev.bstate & BUTTON1_CLICKED) != 0 ? 1
+	      :         ((mev.bstate & BUTTON2_CLICKED) != 0 ? 2
+			 : 3);
+	    wi->click (mev.x - wi->x - 1, mev.y - wi->y - 1, button);
+	  }
+#ifdef BUTTON5_PRESSED
+	else if ((mev.bstate & BUTTON4_PRESSED) != 0)
+	  wi->backward_scroll (3);
+	else if ((mev.bstate & BUTTON5_PRESSED) != 0)
+	  wi->forward_scroll (3);
+#endif
+	break;
+      }
+}
+
 /* Dispatch the correct tui function based upon the control
    character.  */
 static unsigned int
@@ -959,7 +987,9 @@  tui_dispatch_ctrl_char (unsigned int ch)
 
   /* If no window has the focus, or if the focus window can't scroll,
      just pass the character through.  */
-  if (win_info == NULL || !win_info->can_scroll ())
+  if (win_info == NULL
+      || (win_info != TUI_CMD_WIN
+	  && !win_info->can_scroll ()))
     return ch;
 
   switch (ch)
@@ -988,30 +1018,8 @@  tui_dispatch_ctrl_char (unsigned int ch)
     case KEY_MOUSE:
 	{
 	  MEVENT mev;
-	  if (getmouse (&mev) != OK)
-	    break;
-
-	  for (tui_win_info *wi : all_tui_windows ())
-	    if (mev.x > wi->x && mev.x < wi->x + wi->width - 1
-		&& mev.y > wi->y && mev.y < wi->y + wi->height - 1)
-	      {
-		if ((mev.bstate & BUTTON1_CLICKED) != 0
-		    || (mev.bstate & BUTTON2_CLICKED) != 0
-		    || (mev.bstate & BUTTON3_CLICKED) != 0)
-		  {
-		    int button = (mev.bstate & BUTTON1_CLICKED) != 0 ? 1
-		      :         ((mev.bstate & BUTTON2_CLICKED) != 0 ? 2
-				 : 3);
-		    wi->click (mev.x - wi->x - 1, mev.y - wi->y - 1, button);
-		  }
-#ifdef BUTTON5_PRESSED
-		else if ((mev.bstate & BUTTON4_PRESSED) != 0)
-		  wi->backward_scroll (3);
-		else if ((mev.bstate & BUTTON5_PRESSED) != 0)
-		  wi->forward_scroll (3);
-#endif
-		break;
-	      }
+	  if (getmouse (&mev) == OK)
+	    tui_dispatch_mouse_event (mev);
 	}
       break;
 #endif
@@ -1067,6 +1075,251 @@  tui_inject_newline_into_command_window ()
     }
 }
 
+/* True if the mouse thread is the one reading stdin.  */
+static volatile bool mouse_thread_stdin = false;
+static volatile bool mouse_thread_stdin_out = false;
+
+/* The handle to the mouse thread.  */
+static pthread_t mouse_thread;
+
+/* Pipe used to wake up / interrupt the mouse thread.  */
+static int intr_mouse_thread[2];
+
+/* Pipe used to feed input data into the magic mouse screen.  */
+static int mouse_input_pipe[2];
+
+/* Data we've read from stdin but did not want passed to ncurses.  */
+static std::vector<int> mouse_chars;
+
+/* True if the next tut_getc_1 should drain from CHARS.  */
+static bool drain_mouse_chars;
+
+/* Enable debugging the mouse screen and thread processing.  */
+static bool debug_mouse_thread = 0;
+
+/* The escape sequence header for a mouse event.  */
+static const unsigned char mouse_seq[] = { 27, '[', 'M' };
+/* Pointer to the next expected character in a mouse escape sequence.
+   If we haven't seen any character of the sequence yet, this points
+   to the first character of MOUSE_SEQ.  If we've seen the whole
+   sequence header, this points to one-past-last of MOUSE_SEQ.  */
+static const unsigned char *mouse_seq_pos = nullptr;
+
+/* True if the last key we got out of gdb_wgetch was a KEY_MOUSE.  In
+   that case, continue reading chars using the magic mouse screen.  */
+static bool last_key_was_mouse = false;
+
+/* The mouse screen used to read mouse keys.  This screen's main
+   window has the keypad enabled so that it processes mouse escape
+   sequences.  But instead of reading from stdin, it reads from a
+   pipe.  This pipe is filled with data read from stdin, but filtered
+   -- it won't ever include non-mouse escape sequences.  This means
+   that reading from this curses screen either returns KEY_MOUSE, or
+   regular ASCII key, never KEY_UP, etc.  */
+static SCREEN *mouse_screen;
+
+/* Write CH to the mouse input pipe.  */
+
+static void
+write_mouse_pipe (int ch)
+{
+  unsigned char buf = ch;
+  write (mouse_input_pipe[1], &buf, 1);
+}
+
+/* Wake up the mouse thread.  */
+
+static void
+wake_mouse_thread ()
+{
+  unsigned char buf = '+';
+  write (intr_mouse_thread[1], &buf, 1);
+}
+
+/* Flush the INTR_MOUSE_THREAD pipe.  */
+
+static void
+flush_intr_mouse_pipe ()
+{
+  int ret;
+
+  do
+    {
+      char buf;
+      ret = read (intr_mouse_thread[0], &buf, 1);
+    }
+  while (ret >= 0 || (ret == -1 && errno == EINTR));
+}
+
+/* Flush data out of stdin, into the MOUSE_INPUT_PIPE.  Except, if we
+   see an escape sequence that isn't a mouse escape sequence, don't
+   put it in the pipe.  The idea is that we never want ncurses to see
+   non-mouse escape sequences.  We want to pass those directly to
+   readline.  E.g., we don't want ncurses to return KEY_UP, we want
+   readline instead to see the "key up" escape sequence, and call
+   "previous-history".  */
+
+static void *
+mouse_thread_fn (void *)
+{
+  /* XXXX: Block signals in parent before spawning this.  */
+
+  while (1)
+    {
+      fd_set readfds;
+
+      FD_ZERO (&readfds);
+      FD_SET (intr_mouse_thread[0], &readfds);
+
+      if (mouse_thread_stdin)
+	FD_SET (0, &readfds);
+
+      if (select (intr_mouse_thread[0] + 1, &readfds, NULL, NULL, NULL) == -1)
+	{
+	  if (errno == EINTR)
+	    continue;
+
+	  /* XXX: shouldn't throw...  */
+	  perror_with_name (("select"));
+	}
+
+      if (FD_ISSET (0, &readfds))
+	{
+	  unsigned char ch;
+	  int n = read (0, &ch, 1);
+	  if (n == 1)
+	    {
+	      if (ch == *mouse_seq_pos)
+		{
+		  /* Looks like another character part of a mouse
+		     escape sequence.  */
+		  mouse_seq_pos++;
+
+		  if (mouse_seq_pos == mouse_seq + sizeof (mouse_seq))
+		    {
+		      /* Yup, we saw "ESC [ M".  */
+
+		      if (debug_mouse_thread)
+			fprintf (stderr, "thread: got mouse sequence header\n");
+
+		      for (size_t i = 0; i < sizeof (mouse_seq); i++)
+			{
+			  write_mouse_pipe (mouse_seq[i]);
+			  if (debug_mouse_thread)
+			    fprintf (stderr, "thread: %c (%d) ",
+				     mouse_seq[i], mouse_seq[i]);
+			}
+
+		      mouse_seq_pos = mouse_seq;
+		    }
+		}
+	      else if (mouse_seq_pos > mouse_seq)
+		{
+		  if (debug_mouse_thread)
+		    fprintf (stderr,
+			     "thread: non-mouse escape sequence, STOP!\n");
+
+		  /* Store the sequence in MOUSE_CHARS instead of in
+		     the pipe.  We don't want ncurses to see it.  */
+		  for (size_t i = 0; i < mouse_seq_pos - mouse_seq; i++)
+		    {
+		      mouse_chars.push_back (mouse_seq[i]);
+		      if (debug_mouse_thread)
+			fprintf (stderr, "thread: %c (%d) ",
+				 mouse_seq[i], mouse_seq[i]);
+		    }
+		  mouse_chars.push_back (ch);
+		  if (debug_mouse_thread)
+		    fprintf (stderr, "thread: %c (%d) ", ch, ch);
+
+		  /* The mainline code is blocked in wgetch, and we
+		     need to wake it up, but we don't want to let
+		     ncurses see this sequence.  Solve this by putting
+		     a 0/NIL character in the pipe.  That pass through
+		     wgetch and we will end up returning 0 to
+		     readline, which readline interprets as "no op",
+		     exactly what we want.  Phew!  */
+		  write_mouse_pipe (0);
+
+		  /* Reset the sequence position pointer, and stop
+		     reading stdin until the mainline code tells us
+		     otherwise.  */
+		  mouse_seq_pos = mouse_seq;
+		  mouse_thread_stdin = false;
+		}
+	      else
+		{
+		  write_mouse_pipe (ch);
+
+		  if (debug_mouse_thread)
+		    fprintf (stderr, "thread: %c (%d) ", ch, ch);
+		}
+	    }
+	  else
+	    {
+	      if (debug_mouse_thread)
+		fprintf (stderr, "thread: => -1!");
+	    }
+	  if (debug_mouse_thread)
+	    fflush (stderr);
+	}
+
+      if (FD_ISSET (intr_mouse_thread[0], &readfds))
+	{
+	  int ret;
+
+	  if (debug_mouse_thread)
+	    fprintf (stderr, "break thread\n");
+
+	  do
+	    {
+	      char buf;
+	      ret = read (intr_mouse_thread[0], &buf, 1);
+	    }
+	  while (ret == -1 && errno == EINTR);
+
+	  mouse_thread_stdin_out = mouse_thread_stdin;
+	  continue;
+	}
+    }
+
+  return nullptr;
+}
+
+/* Initialize the special mouse screen, and all auxiliary bits.  */
+
+void
+init_mouse_screen ()
+{
+  /* Pseudo-stdin pipe for the ncurses mouse "terminal".  */
+  if (gdb_pipe_cloexec (mouse_input_pipe) != 0)
+    error (_("Cannot create pipe for mouse"));
+
+  /* The self-trick pipe used to wake up / interrupt the mouse
+     thread.  */
+  if (gdb_pipe_cloexec (intr_mouse_thread) != 0)
+    error (_("Cannot create pipe for mouse interruption"));
+  fcntl (intr_mouse_thread[0], F_SETFL, O_NONBLOCK);
+  fcntl (intr_mouse_thread[1], F_SETFL, O_NONBLOCK);
+
+  /* Create the mouse terminal.  It reads from the mouse input pipe,
+     and writes nowhere.  */
+  FILE *in = fdopen (mouse_input_pipe[0], "r");
+  FILE *out = fopen ("/dev/null", "w+");
+  setvbuf (in, NULL, _IONBF, BUFSIZ);
+
+  mouse_screen = newterm (NULL, out, in);
+  mousemask (ALL_MOUSE_EVENTS, NULL);
+
+  /* Enable the keypad, we want to use this terminal to process mouse
+     escape codes.  */
+  WINDOW *w = stdscr;
+  keypad (w, TRUE);
+
+  /* Spawn the mouse thread.  */
+  pthread_create (&mouse_thread, nullptr, mouse_thread_fn, nullptr);
+}
+
 /* Main worker for tui_getc.  Get a character from the command window.
    This is called from the readline package, but wrapped in a
    try/catch by tui_getc.  */
@@ -1084,14 +1337,251 @@  tui_getc_1 (FILE *fp)
   tui_readline_output (0, 0);
 #endif
 
-  ch = gdb_wgetch (w);
+  if (tui_win_with_focus () == TUI_CMD_WIN && current_ui->command_editing)
+    {
+      if (drain_mouse_chars)
+	{
+	  ch = mouse_chars.front ();
+	  mouse_chars.erase (mouse_chars.begin ());
+	  if (debug_mouse_thread)
+	    fprintf (stderr, "drain: got %c (%d), ", ch, ch);
+	  if (mouse_chars.empty ())
+	    {
+	      drain_mouse_chars = false;
+	      if (debug_mouse_thread)
+		fprintf (stderr, " : done\r\n");
+	    }
+	  else
+	    call_stdin_event_handler_again_p = 1;
+	  return ch;
+	}
+      else if (!last_key_was_mouse)
+	{
+	  ch = gdb_wgetch (w);
+	}
+
+      if (last_key_was_mouse || key_is_start_sequence (ch))
+	{
+	  /* Process this key sequence with the special mouse ncurses
+	     screen, which has the keypad enabled, and reads from
+	     MOUSE_INPUT_PIPE.  */
+
+	  /* Set the current screen to the mouse screen.  */
+	  WINDOW *prev_stdscr = stdscr;
+	  SCREEN *prev_s = set_term (mouse_screen);
+	  /* The mouse WINDOW.  */
+	  WINDOW *mw = stdscr;
+
+	  if (last_key_was_mouse)
+	    {
+	      if (debug_mouse_thread)
+		fprintf (stderr, "last key was mouse\r\n");
+	    }
+
+	  /* If we're just starting a sequence, block waiting for the
+	     whole sequence.  If we're here because we returned a
+	     mouse event before and we're now draining the curses
+	     buffer, disable blocking.  */
+	  if (last_key_was_mouse)
+	    nodelay (mw, TRUE);
+	  else
+	    nodelay (mw, FALSE);
+
+	  flush_intr_mouse_pipe ();
+
+	  /* If we're starting a new sequence, reset the sequence
+	     position.  If we last saw a mouse key, then the mouse
+	     thread may have read a partial mouse event out of stdin,
+	     so we need to let it continue the sequence where it left
+	     it last.  */
+	  if (!last_key_was_mouse)
+	    mouse_seq_pos = &mouse_seq[1];
+
+	  /* Set the thread reading from stdin.  */
+	  mouse_thread_stdin = true;
+	  wake_mouse_thread ();
+	  while (!mouse_thread_stdin_out)
+	    ;
+
+	  using namespace std::chrono;
+
+	  steady_clock::time_point before = steady_clock::now ();
+
+	  /* Read one cooked key out of the mouse window.  */
+	  ch = gdb_wgetch (mw);
+
+	  steady_clock::time_point after = steady_clock::now ();
+
+	  auto diff = after - before;
+	  seconds s = duration_cast<seconds> (diff);
+	  microseconds us = duration_cast<microseconds> (diff - s);
+	  if (debug_mouse_thread)
+	    fprintf (stderr, "wgetch took: %ld.%06ld\n",
+		     (long) s.count (),
+		     (long) us.count ());
+
+	  /* Tell the thread to stop reading stdin, and wait until it
+	     acknowledges it.  */
+	  mouse_thread_stdin = false;
+	  wake_mouse_thread ();
+	  while (mouse_thread_stdin_out)
+	    ;
+
+	  if (ch == ERR)
+	    {
+	      /* We get here if the previous key returned was a mouse
+		 key, and thus disabled blocked in order to flush all
+		 keys.  ERR means there are no more keys in the curses
+		 buffer.  */
+	      if (debug_mouse_thread)
+		fprintf (stderr, "got ERR\r\n");
+
+	      /* Stop draining curses.  */
+	      last_key_was_mouse = false;
+
+	      /* If the thread saw a non-mouse escape sequence, we
+		 need to drain it next.  */
+	      if (!mouse_chars.empty ())
+		{
+		  drain_mouse_chars = true;
+		  call_stdin_event_handler_again_p = 1;
+		}
+
+	      ch = 0;
+	    }
+	  else if (ch == KEY_MOUSE)
+	    {
+	      if (debug_mouse_thread)
+		fprintf (stderr, "KEY_MOUSE\n");
+
+	      /* Process this mouse key, and set up to drain other
+		 keys that ncurses may have read into its internal
+		 buffer already.  */
+	      last_key_was_mouse = true;
+	      call_stdin_event_handler_again_p = 1;
+
+	      /* Get the mouse event.  This must be called with the
+		 mouse screen as current.  */
+	      MEVENT mev;
+	      if (getmouse (&mev) == OK)
+		{
+		  /* Now handle the mouse event.  This must be done
+		     with the normal screen as current.  */
+		  set_term (prev_s);
+		  stdscr = prev_stdscr;
+
+		  /* Handle prev/next/up/down here.  */
+		  tui_dispatch_mouse_event (mev);
+		}
+
+	      return 0;
+	    }
+	  else
+	    {
+	      if (debug_mouse_thread)
+		fprintf (stderr, "not KEY_MOUSE, %c (%d)\n", ch, ch);
+
+	      last_key_was_mouse = false;
+
+	      /* Drain data buffered on the ncurses side and/or in the
+		 pipe.  */
+	      nodelay (mw, TRUE);
+
+	      /* Data already in MOUSE_CHARS must be returned to
+		 readline _after_ the data ncurses already fetched
+		 from the pipe into its own internal buffer.  IOW, the
+		 ncurses buffered data must be prepended into
+		 MOUSE_CHARS.  Start by draining ncurses data into a
+		 temporary vector.  */
+	      std::vector<int> tmp_chars;
+	      int ch2 = gdb_wgetch (mw);
+	      if (ch2 != ERR)
+		{
+		  if (debug_mouse_thread)
+		    fprintf (stderr, "more data!: %c (%d)", ch2, ch2);
+		  gdb_assert (ch2 != KEY_MOUSE);
+		  tmp_chars.push_back (ch2);
+		  while (1)
+		    {
+		      ch2 = gdb_wgetch (mw);
+		      if (ch2 == ERR)
+			{
+			  if (debug_mouse_thread)
+			    fprintf (stderr, ", ERR");
+			  break;
+			}
+		      else
+			{
+			  if (debug_mouse_thread)
+			    fprintf (stderr, ", %c (%d)", ch2, ch2);
+			  gdb_assert (ch2 != KEY_MOUSE);
+			  tmp_chars.push_back (ch2);
+			}
+		    }
+		  if (debug_mouse_thread)
+		    {
+		      fprintf (stderr, "\n");
+		      fflush (stderr);
+		    }
+		}
+
+	      nodelay (mw, FALSE);
+
+	      /* Now append the data that was already in MOUSE_CHARS
+		 in the temporary vector.  */
+	      tmp_chars.insert (tmp_chars.end (),
+				mouse_chars.begin (), mouse_chars.end ());
+
+	      /* And make the result the new MOUSE_CHARS.  */
+	      mouse_chars = std::move (tmp_chars);
+
+	      if (debug_mouse_thread)
+		fprintf (stderr, "got %c (%d), ", ch, ch);
+	      if (!mouse_chars.empty ())
+		{
+		  drain_mouse_chars = true;
+		  call_stdin_event_handler_again_p = 1;
+		}
+	    }
+
+	  /* Restore the regular screen as current.  */
+	  set_term (prev_s);
+	  stdscr = prev_stdscr;
+	}
+      else
+	{
+	  if (debug_mouse_thread)
+	    fprintf (stderr, "tui_get_c_1: not escape: %c (%d)\n", ch, ch);
+	}
+    }
+  else
+    {
+      using namespace std::chrono;
+
+      steady_clock::time_point before = steady_clock::now ();
+
+      ch = gdb_wgetch (w);
+
+      steady_clock::time_point after = steady_clock::now ();
+
+      auto diff = after - before;
+      seconds s = duration_cast<seconds> (diff);
+      microseconds us = duration_cast<microseconds> (diff - s);
+#if 0
+      fprintf (stderr, "wgetch took: %ld.%06ld\n",
+	       (long) s.count (),
+	       (long) us.count ());
+#endif
+
+      /* Handle prev/next/up/down here.  */
+      ch = tui_dispatch_ctrl_char (ch);
+    }
 
-  /* Handle prev/next/up/down here.  */
-  ch = tui_dispatch_ctrl_char (ch);
-  
   if (ch == KEY_BACKSPACE)
     return '\b';
 
+#if 0
+  /* XXX: see about re-enabling this.  */
   if (current_ui->command_editing && key_is_start_sequence (ch))
     {
       int ch_pending;
@@ -1117,6 +1607,7 @@  tui_getc_1 (FILE *fp)
 	  call_stdin_event_handler_again_p = 1;
 	}
     }
+#endif
 
   return ch;
 }
diff --git a/gdb/tui/tui-io.h b/gdb/tui/tui-io.h
index 760532140f1..e4e9bc745b9 100644
--- a/gdb/tui/tui-io.h
+++ b/gdb/tui/tui-io.h
@@ -60,4 +60,7 @@  extern cli_ui_out *tui_old_uiout;
    next line.  */
 extern void tui_inject_newline_into_command_window ();
 
+/* Initialize the mouse screen.  */
+extern void init_mouse_screen ();
+
 #endif /* TUI_TUI_IO_H */
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index 529fc62c9ac..490cdb69dda 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -383,6 +383,10 @@  tui_enable (void)
       if (!gdb_stderr->isatty ())
 	error (_("Cannot enable the TUI when output is not a terminal"));
 
+      /* This must be done before the newterm below, otherwise we end
+	 up with a messed up (main) terminal.  */
+      init_mouse_screen ();
+
       s = newterm (NULL, stdout, stdin);
 #ifdef __MINGW32__
       /* The MinGW port of ncurses requires $TERM to be unset in order