/* Copyright (C) 2023-2024 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include "ui.h"
#include "cli/cli-cmds.h"
#include "event-top.h"
#include "gdbsupport/buildargv.h"
#include "gdbsupport/filestuff.h"
#include "gdbsupport/gdb_file.h"
#include "gdbsupport/scoped_fd.h"
#include "interps.h"
#include "pager.h"
#include "main.h"
#include "top.h"
/* See top.h. */
struct ui *main_ui;
struct ui *current_ui;
struct ui *ui_list;
/* The highest UI number ever assigned. */
static int highest_ui_num;
/* See top.h. */
ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
: num (++highest_ui_num),
stdin_stream (instream_),
instream (instream_),
outstream (outstream_),
errstream (errstream_),
input_fd (fileno (instream)),
m_input_interactive_p (ISATTY (instream)),
m_gdb_stdout (new pager_file (new stdio_file (outstream))),
m_gdb_stdin (new stdio_file (instream)),
m_gdb_stderr (new stderr_file (errstream)),
m_gdb_stdlog (new timestamped_file (m_gdb_stderr))
{
unbuffer_stream (instream_);
if (ui_list == NULL)
ui_list = this;
else
{
struct ui *last;
for (last = ui_list; last->next != NULL; last = last->next)
;
last->next = this;
}
}
ui::~ui ()
{
struct ui *ui, *uiprev;
uiprev = NULL;
for (ui = ui_list; ui != NULL; uiprev = ui, ui = ui->next)
if (ui == this)
break;
gdb_assert (ui != NULL);
if (uiprev != NULL)
uiprev->next = next;
else
ui_list = next;
delete m_gdb_stdin;
delete m_gdb_stdout;
delete m_gdb_stderr;
}
/* Returns whether GDB is running on an interactive terminal. */
bool
ui::input_interactive_p () const
{
if (batch_flag)
return false;
if (interactive_mode != AUTO_BOOLEAN_AUTO)
return interactive_mode == AUTO_BOOLEAN_TRUE;
return m_input_interactive_p;
}
/* When there is an event ready on the stdin file descriptor, instead
of calling readline directly through the callback function, or
instead of calling gdb_readline_no_editing_callback, give gdb a
chance to detect errors and do something. */
static void
stdin_event_handler (int error, gdb_client_data client_data)
{
struct ui *ui = (struct ui *) client_data;
if (error)
{
/* Switch to the main UI, so diagnostics always go there. */
current_ui = main_ui;
ui->unregister_file_handler ();
if (main_ui == ui)
{
/* If stdin died, we may as well kill gdb. */
gdb_printf (gdb_stderr, _("error detected on stdin\n"));
quit_command ((char *) 0, 0);
}
else
{
/* Simply delete the UI. */
delete ui;
}
}
else
{
/* Switch to the UI whose input descriptor woke up the event
loop. */
current_ui = ui;
/* This makes sure a ^C immediately followed by further input is
always processed in that order. E.g,. with input like
"^Cprint 1\n", the SIGINT handler runs, marks the async
signal handler, and then select/poll may return with stdin
ready, instead of -1/EINTR. The
gdb.base/double-prompt-target-event-error.exp test exercises
this. */
QUIT;
do
{
call_stdin_event_handler_again_p = 0;
ui->call_readline (client_data);
}
while (call_stdin_event_handler_again_p != 0);
}
}
/* See top.h. */
void
ui::register_file_handler ()
{
if (input_fd != -1)
add_file_handler (input_fd, stdin_event_handler, this,
string_printf ("ui-%d", num), true);
}
/* See top.h. */
void
ui::unregister_file_handler ()
{
if (input_fd != -1)
delete_file_handler (input_fd);
}
/* Open file named NAME for read/write, making sure not to make it the
controlling terminal. */
static gdb_file_up
open_terminal_stream (const char *name)
{
scoped_fd fd = gdb_open_cloexec (name, O_RDWR | O_NOCTTY, 0);
if (fd.get () < 0)
perror_with_name (_("opening terminal failed"));
return fd.to_file ("w+");
}
/* Implementation of the "new-ui" command. */
static void
new_ui_command (const char *args, int from_tty)
{
int argc;
const char *interpreter_name;
const char *tty_name;
dont_repeat ();
gdb_argv argv (args);
argc = argv.count ();
if (argc < 2)
error (_("Usage: new-ui INTERPRETER TTY"));
interpreter_name = argv[0];
tty_name = argv[1];
{
scoped_restore save_ui = make_scoped_restore (¤t_ui);
/* Open specified terminal. Note: we used to open it three times,
once for each of stdin/stdout/stderr, but that does not work
with Windows named pipes. */
gdb_file_up stream = open_terminal_stream (tty_name);
std::unique_ptr ui
(new struct ui (stream.get (), stream.get (), stream.get ()));
ui->async = 1;
current_ui = ui.get ();
set_top_level_interpreter (interpreter_name, true);
top_level_interpreter ()->pre_command_loop ();
/* Make sure the file is not closed. */
stream.release ();
ui.release ();
}
gdb_printf ("New UI allocated\n");
}
void _initialize_ui ();
void
_initialize_ui ()
{
cmd_list_element *c = add_cmd ("new-ui", class_support, new_ui_command, _("\
Create a new UI.\n\
Usage: new-ui INTERPRETER TTY\n\
The first argument is the name of the interpreter to run.\n\
The second argument is the terminal the UI runs on."), &cmdlist);
set_cmd_completer (c, interpreter_completer);
}