/* Copyright (C) 2023 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 "defs.h" #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 throught 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); interp_pre_command_loop (top_level_interpreter ()); /* 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); }