/* TUI layout window management. Copyright (C) 1998-2020 Free Software Foundation, Inc. Contributed by Hewlett-Packard Company. 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 "arch-utils.h" #include "command.h" #include "symtab.h" #include "frame.h" #include "source.h" #include #include "tui/tui.h" #include "tui/tui-command.h" #include "tui/tui-data.h" #include "tui/tui-wingeneral.h" #include "tui/tui-stack.h" #include "tui/tui-regs.h" #include "tui/tui-win.h" #include "tui/tui-winsource.h" #include "tui/tui-disasm.h" #include "tui/tui-layout.h" #include "tui/tui-source.h" #include "gdb_curses.h" static void show_layout (enum tui_layout_type); static enum tui_layout_type next_layout (void); static enum tui_layout_type prev_layout (void); static void tui_layout_command (const char *, int); static void extract_display_start_addr (struct gdbarch **, CORE_ADDR *); /* The pre-defined layouts. */ static tui_layout_split *standard_layouts[UNDEFINED_LAYOUT]; /* The layout that is currently applied. */ static std::unique_ptr applied_layout; static enum tui_layout_type current_layout = UNDEFINED_LAYOUT; /* Accessor for the current layout. */ enum tui_layout_type tui_current_layout (void) { return current_layout; } /* See tui-layout.h. */ void tui_apply_current_layout () { applied_layout->apply (0, 0, tui_term_width (), tui_term_height ()); } /* See tui-layout. */ void tui_adjust_window_height (struct tui_win_info *win, int new_height) { applied_layout->adjust_size (win->name (), new_height); } /* Show the screen layout defined. */ static void show_layout (enum tui_layout_type layout) { enum tui_layout_type cur_layout = tui_current_layout (); if (layout != cur_layout) { tui_make_all_invisible (); applied_layout = standard_layouts[layout]->clone (); tui_apply_current_layout (); current_layout = layout; tui_delete_invisible_windows (); } } /* Function to set the layout to SRC_COMMAND, DISASSEM_COMMAND, SRC_DISASSEM_COMMAND, SRC_DATA_COMMAND, or DISASSEM_DATA_COMMAND. */ void tui_set_layout (enum tui_layout_type layout_type) { gdb_assert (layout_type != UNDEFINED_LAYOUT); enum tui_layout_type cur_layout = tui_current_layout (); struct gdbarch *gdbarch; CORE_ADDR addr; struct tui_win_info *win_with_focus = tui_win_with_focus (); extract_display_start_addr (&gdbarch, &addr); enum tui_layout_type new_layout = layout_type; if (new_layout != cur_layout) { show_layout (new_layout); /* Now determine where focus should be. */ if (win_with_focus != TUI_CMD_WIN) { switch (new_layout) { case SRC_COMMAND: tui_set_win_focus_to (TUI_SRC_WIN); break; case DISASSEM_COMMAND: /* The previous layout was not showing code. This can happen if there is no source available: 1. if the source file is in another dir OR 2. if target was compiled without -g We still want to show the assembly though! */ tui_get_begin_asm_address (&gdbarch, &addr); tui_set_win_focus_to (TUI_DISASM_WIN); break; case SRC_DISASSEM_COMMAND: /* The previous layout was not showing code. This can happen if there is no source available: 1. if the source file is in another dir OR 2. if target was compiled without -g We still want to show the assembly though! */ tui_get_begin_asm_address (&gdbarch, &addr); if (win_with_focus == TUI_SRC_WIN) tui_set_win_focus_to (TUI_SRC_WIN); else tui_set_win_focus_to (TUI_DISASM_WIN); break; case SRC_DATA_COMMAND: if (win_with_focus != TUI_DATA_WIN) tui_set_win_focus_to (TUI_SRC_WIN); else tui_set_win_focus_to (TUI_DATA_WIN); break; case DISASSEM_DATA_COMMAND: /* The previous layout was not showing code. This can happen if there is no source available: 1. if the source file is in another dir OR 2. if target was compiled without -g We still want to show the assembly though! */ tui_get_begin_asm_address (&gdbarch, &addr); if (win_with_focus != TUI_DATA_WIN) tui_set_win_focus_to (TUI_DISASM_WIN); else tui_set_win_focus_to (TUI_DATA_WIN); break; default: break; } } /* * Now update the window content. */ tui_update_source_windows_with_addr (gdbarch, addr); if (new_layout == SRC_DATA_COMMAND || new_layout == DISASSEM_DATA_COMMAND) TUI_DATA_WIN->show_registers (TUI_DATA_WIN->get_current_group ()); } } /* Add the specified window to the layout in a logical way. This means setting up the most logical layout given the window to be added. */ void tui_add_win_to_layout (enum tui_win_type type) { enum tui_layout_type cur_layout = tui_current_layout (); switch (type) { case SRC_WIN: if (cur_layout != SRC_COMMAND && cur_layout != SRC_DISASSEM_COMMAND && cur_layout != SRC_DATA_COMMAND) { if (cur_layout == DISASSEM_DATA_COMMAND) tui_set_layout (SRC_DATA_COMMAND); else tui_set_layout (SRC_COMMAND); } break; case DISASSEM_WIN: if (cur_layout != DISASSEM_COMMAND && cur_layout != SRC_DISASSEM_COMMAND && cur_layout != DISASSEM_DATA_COMMAND) { if (cur_layout == SRC_DATA_COMMAND) tui_set_layout (DISASSEM_DATA_COMMAND); else tui_set_layout (DISASSEM_COMMAND); } break; case DATA_WIN: if (cur_layout != SRC_DATA_COMMAND && cur_layout != DISASSEM_DATA_COMMAND) { if (cur_layout == DISASSEM_COMMAND) tui_set_layout (DISASSEM_DATA_COMMAND); else tui_set_layout (SRC_DATA_COMMAND); } break; default: break; } } /* Complete possible layout names. TEXT is the complete text entered so far, WORD is the word currently being completed. */ static void layout_completer (struct cmd_list_element *ignore, completion_tracker &tracker, const char *text, const char *word) { static const char *layout_names [] = { "src", "asm", "split", "regs", "next", "prev", NULL }; complete_on_enum (tracker, layout_names, text, word); } /* Function to set the layout to SRC, ASM, SPLIT, NEXT, PREV, DATA, or REGS. */ static void tui_layout_command (const char *layout_name, int from_tty) { enum tui_layout_type new_layout = UNDEFINED_LAYOUT; enum tui_layout_type cur_layout = tui_current_layout (); if (layout_name == NULL || *layout_name == '\0') error (_("Usage: layout prev | next | LAYOUT-NAME")); /* First check for ambiguous input. */ if (strcmp (layout_name, "s") == 0) error (_("Ambiguous command input.")); if (subset_compare (layout_name, "src")) new_layout = SRC_COMMAND; else if (subset_compare (layout_name, "asm")) new_layout = DISASSEM_COMMAND; else if (subset_compare (layout_name, "split")) new_layout = SRC_DISASSEM_COMMAND; else if (subset_compare (layout_name, "regs")) { if (cur_layout == SRC_COMMAND || cur_layout == SRC_DATA_COMMAND) new_layout = SRC_DATA_COMMAND; else new_layout = DISASSEM_DATA_COMMAND; } else if (subset_compare (layout_name, "next")) new_layout = next_layout (); else if (subset_compare (layout_name, "prev")) new_layout = prev_layout (); else error (_("Unrecognized layout: %s"), layout_name); /* Make sure the curses mode is enabled. */ tui_enable (); tui_set_layout (new_layout); } static void extract_display_start_addr (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p) { enum tui_layout_type cur_layout = tui_current_layout (); struct gdbarch *gdbarch = get_current_arch (); CORE_ADDR addr; CORE_ADDR pc; struct symtab_and_line cursal = get_current_source_symtab_and_line (); switch (cur_layout) { case SRC_COMMAND: case SRC_DATA_COMMAND: gdbarch = TUI_SRC_WIN->gdbarch; find_line_pc (cursal.symtab, TUI_SRC_WIN->start_line_or_addr.u.line_no, &pc); addr = pc; break; case DISASSEM_COMMAND: case SRC_DISASSEM_COMMAND: case DISASSEM_DATA_COMMAND: gdbarch = TUI_DISASM_WIN->gdbarch; addr = TUI_DISASM_WIN->start_line_or_addr.u.addr; break; default: addr = 0; break; } *gdbarch_p = gdbarch; *addr_p = addr; } /* Answer the previous layout to cycle to. */ static enum tui_layout_type next_layout (void) { int new_layout; new_layout = tui_current_layout (); if (new_layout == UNDEFINED_LAYOUT) new_layout = SRC_COMMAND; else { new_layout++; if (new_layout == UNDEFINED_LAYOUT) new_layout = SRC_COMMAND; } return (enum tui_layout_type) new_layout; } /* Answer the next layout to cycle to. */ static enum tui_layout_type prev_layout (void) { int new_layout; new_layout = tui_current_layout (); if (new_layout == SRC_COMMAND) new_layout = DISASSEM_DATA_COMMAND; else { new_layout--; if (new_layout == UNDEFINED_LAYOUT) new_layout = DISASSEM_DATA_COMMAND; } return (enum tui_layout_type) new_layout; } void tui_gen_win_info::resize (int height_, int width_, int origin_x_, int origin_y_) { if (width == width_ && height == height_ && x == origin_x_ && y == origin_y_ && handle != nullptr) return; width = width_; height = height_; x = origin_x_; y = origin_y_; if (handle != nullptr) { #ifdef HAVE_WRESIZE wresize (handle.get (), height, width); mvwin (handle.get (), y, x); wmove (handle.get (), 0, 0); #else handle.reset (nullptr); #endif } if (handle == nullptr) make_window (); rerender (); } /* Helper function that returns a TUI window, given its name. */ static tui_gen_win_info * tui_get_window_by_name (const std::string &name) { if (name == "src") { if (tui_win_list[SRC_WIN] == nullptr) tui_win_list[SRC_WIN] = new tui_source_window (); return tui_win_list[SRC_WIN]; } else if (name == "cmd") { if (tui_win_list[CMD_WIN] == nullptr) tui_win_list[CMD_WIN] = new tui_cmd_window (); return tui_win_list[CMD_WIN]; } else if (name == "regs") { if (tui_win_list[DATA_WIN] == nullptr) tui_win_list[DATA_WIN] = new tui_data_window (); return tui_win_list[DATA_WIN]; } else if (name == "asm") { if (tui_win_list[DISASSEM_WIN] == nullptr) tui_win_list[DISASSEM_WIN] = new tui_disasm_window (); return tui_win_list[DISASSEM_WIN]; } else { gdb_assert (name == "locator"); return tui_locator_win_info_ptr (); } } /* See tui-layout.h. */ std::unique_ptr tui_layout_window::clone () const { tui_layout_window *result = new tui_layout_window (m_contents.c_str ()); return std::unique_ptr (result); } /* See tui-layout.h. */ void tui_layout_window::apply (int x_, int y_, int width_, int height_) { x = x_; y = y_; width = width_; height = height_; gdb_assert (m_window != nullptr); m_window->resize (height, width, x, y); } /* See tui-layout.h. */ void tui_layout_window::get_sizes (int *min_height, int *max_height) { if (m_window == nullptr) m_window = tui_get_window_by_name (m_contents); *min_height = m_window->min_height (); *max_height = m_window->max_height (); } /* See tui-layout.h. */ bool tui_layout_window::top_boxed_p () const { gdb_assert (m_window != nullptr); return m_window->can_box (); } /* See tui-layout.h. */ bool tui_layout_window::bottom_boxed_p () const { gdb_assert (m_window != nullptr); return m_window->can_box (); } /* See tui-layout.h. */ tui_layout_split * tui_layout_split::add_split (int weight) { tui_layout_split *result = new tui_layout_split (); split s = {weight, std::unique_ptr (result)}; m_splits.push_back (std::move (s)); return result; } /* See tui-layout.h. */ void tui_layout_split::add_window (const char *name, int weight) { tui_layout_window *result = new tui_layout_window (name); split s = {weight, std::unique_ptr (result)}; m_splits.push_back (std::move (s)); } /* See tui-layout.h. */ std::unique_ptr tui_layout_split::clone () const { tui_layout_split *result = new tui_layout_split (); for (const split &item : m_splits) { std::unique_ptr next = item.layout->clone (); split s = {item.weight, std::move (next)}; result->m_splits.push_back (std::move (s)); } return std::unique_ptr (result); } /* See tui-layout.h. */ void tui_layout_split::get_sizes (int *min_height, int *max_height) { *min_height = 0; *max_height = 0; for (const split &item : m_splits) { int new_min, new_max; item.layout->get_sizes (&new_min, &new_max); *min_height += new_min; *max_height += new_max; } } /* See tui-layout.h. */ bool tui_layout_split::top_boxed_p () const { if (m_splits.empty ()) return false; return m_splits[0].layout->top_boxed_p (); } /* See tui-layout.h. */ bool tui_layout_split::bottom_boxed_p () const { if (m_splits.empty ()) return false; return m_splits.back ().layout->top_boxed_p (); } /* See tui-layout.h. */ void tui_layout_split::set_weights_from_heights () { for (int i = 0; i < m_splits.size (); ++i) m_splits[i].weight = m_splits[i].layout->height; } /* See tui-layout.h. */ bool tui_layout_split::adjust_size (const char *name, int new_height) { /* Look through the children. If one is a layout holding the named window, we're done; or if one actually is the named window, update it. */ int found_index = -1; for (int i = 0; i < m_splits.size (); ++i) { if (m_splits[i].layout->adjust_size (name, new_height)) return true; const char *win_name = m_splits[i].layout->get_name (); if (win_name != nullptr && strcmp (name, win_name) == 0) { found_index = i; break; } } if (found_index == -1) return false; if (m_splits[found_index].layout->height == new_height) return true; set_weights_from_heights (); int delta = m_splits[found_index].weight - new_height; m_splits[found_index].weight = new_height; /* Distribute the "delta" over the next window; but if the next window cannot hold it all, keep going until we either find a window that does, or until we loop all the way around. */ for (int i = 0; delta != 0 && i < m_splits.size () - 1; ++i) { int index = (found_index + 1 + i) % m_splits.size (); int new_min, new_max; m_splits[index].layout->get_sizes (&new_min, &new_max); if (delta < 0) { /* The primary window grew, so we are trying to shrink other windows. */ int available = m_splits[index].weight - new_min; int shrink_by = std::min (available, -delta); m_splits[index].weight -= shrink_by; delta += shrink_by; } else { /* The primary window shrank, so we are trying to grow other windows. */ int available = new_max - m_splits[index].weight; int grow_by = std::min (available, delta); m_splits[index].weight += grow_by; delta -= grow_by; } } if (delta != 0) { warning (_("Invalid window height specified")); /* Effectively undo any modifications made here. */ set_weights_from_heights (); } else { /* Simply re-apply the updated layout. */ apply (x, y, width, height); } return true; } /* See tui-layout.h. */ void tui_layout_split::apply (int x_, int y_, int width_, int height_) { x = x_; y = y_; width = width_; height = height_; struct height_info { int height; int min_height; int max_height; /* True if this window will share a box border with the previous window in the list. */ bool share_box; }; std::vector info (m_splits.size ()); /* Step 1: Find the min and max height of each sub-layout. Fixed-sized layouts are given their desired height, and then the remaining space is distributed among the remaining windows according to the weights given. */ int available_height = height; int last_index = -1; int total_weight = 0; for (int i = 0; i < m_splits.size (); ++i) { bool cmd_win_already_exists = TUI_CMD_WIN != nullptr; /* Always call get_sizes, to ensure that the window is instantiated. This is a bit gross but less gross than adding special cases for this in other places. */ m_splits[i].layout->get_sizes (&info[i].min_height, &info[i].max_height); if (!m_applied && cmd_win_already_exists && m_splits[i].layout->get_name () != nullptr && strcmp (m_splits[i].layout->get_name (), "cmd") == 0) { /* If this layout has never been applied, then it means the user just changed the layout. In this situation, it's desirable to keep the size of the command window the same. Setting the min and max heights this way ensures that the resizing step, below, does the right thing with this window. */ info[i].min_height = TUI_CMD_WIN->height; info[i].max_height = TUI_CMD_WIN->height; } if (info[i].min_height == info[i].max_height) available_height -= info[i].min_height; else { last_index = i; total_weight += m_splits[i].weight; } /* Two adjacent boxed windows will share a border, making a bit more height available. */ if (i > 0 && m_splits[i - 1].layout->bottom_boxed_p () && m_splits[i].layout->top_boxed_p ()) info[i].share_box = true; } /* Step 2: Compute the height of each sub-layout. Fixed-sized items are given their fixed size, while others are resized according to their weight. */ int used_height = 0; for (int i = 0; i < m_splits.size (); ++i) { /* Compute the height and clamp to the allowable range. */ info[i].height = available_height * m_splits[i].weight / total_weight; if (info[i].height > info[i].max_height) info[i].height = info[i].max_height; if (info[i].height < info[i].min_height) info[i].height = info[i].min_height; /* If there is any leftover height, just redistribute it to the last resizeable window, by dropping it from the allocated height. We could try to be fancier here perhaps, by redistributing this height among all windows, not just the last window. */ if (info[i].min_height != info[i].max_height) { used_height += info[i].height; if (info[i].share_box) --used_height; } } /* Allocate any leftover height. */ if (available_height >= used_height && last_index != -1) info[last_index].height += available_height - used_height; /* Step 3: Resize. */ int height_accum = 0; for (int i = 0; i < m_splits.size (); ++i) { /* If we fall off the bottom, just make allocations overlap. GIGO. */ if (height_accum + info[i].height > height) height_accum = height - info[i].height; else if (info[i].share_box) --height_accum; m_splits[i].layout->apply (x, y + height_accum, width, info[i].height); height_accum += info[i].height; } m_applied = true; } static void initialize_layouts () { standard_layouts[SRC_COMMAND] = new tui_layout_split (); standard_layouts[SRC_COMMAND]->add_window ("src", 2); standard_layouts[SRC_COMMAND]->add_window ("locator", 0); standard_layouts[SRC_COMMAND]->add_window ("cmd", 1); standard_layouts[DISASSEM_COMMAND] = new tui_layout_split (); standard_layouts[DISASSEM_COMMAND]->add_window ("asm", 2); standard_layouts[DISASSEM_COMMAND]->add_window ("locator", 0); standard_layouts[DISASSEM_COMMAND]->add_window ("cmd", 1); standard_layouts[SRC_DATA_COMMAND] = new tui_layout_split (); standard_layouts[SRC_DATA_COMMAND]->add_window ("regs", 1); standard_layouts[SRC_DATA_COMMAND]->add_window ("src", 1); standard_layouts[SRC_DATA_COMMAND]->add_window ("locator", 0); standard_layouts[SRC_DATA_COMMAND]->add_window ("cmd", 1); standard_layouts[DISASSEM_DATA_COMMAND] = new tui_layout_split (); standard_layouts[DISASSEM_DATA_COMMAND]->add_window ("regs", 1); standard_layouts[DISASSEM_DATA_COMMAND]->add_window ("asm", 1); standard_layouts[DISASSEM_DATA_COMMAND]->add_window ("locator", 0); standard_layouts[DISASSEM_DATA_COMMAND]->add_window ("cmd", 1); standard_layouts[SRC_DISASSEM_COMMAND] = new tui_layout_split (); standard_layouts[SRC_DISASSEM_COMMAND]->add_window ("src", 1); standard_layouts[SRC_DISASSEM_COMMAND]->add_window ("asm", 1); standard_layouts[SRC_DISASSEM_COMMAND]->add_window ("locator", 0); standard_layouts[SRC_DISASSEM_COMMAND]->add_window ("cmd", 1); } /* Function to initialize gdb commands, for tui window layout manipulation. */ void _initialize_tui_layout (void) { struct cmd_list_element *cmd; cmd = add_com ("layout", class_tui, tui_layout_command, _("\ Change the layout of windows.\n\ Usage: layout prev | next | LAYOUT-NAME\n\ Layout names are:\n\ src : Displays source and command windows.\n\ asm : Displays disassembly and command windows.\n\ split : Displays source, disassembly and command windows.\n\ regs : Displays register window. If existing layout\n\ is source/command or assembly/command, the \n\ register window is displayed. If the\n\ source/assembly/command (split) is displayed, \n\ the register window is displayed with \n\ the window that has current logical focus.")); set_cmd_completer (cmd, layout_completer); initialize_layouts (); }