Files
src-1.2/engine/shared/library/sharedDebug/src/linux/DebugMonitor.cpp
T
2015-04-02 22:57:39 -05:00

450 lines
13 KiB
C++

// ======================================================================
//
// DebugMonitor.cpp
// Copyright 2002, Sony Online Entertainment.h
// All rights reserved.
//
// ======================================================================
#include "sharedDebug/FirstSharedDebug.h"
#include "sharedDebug/DebugMonitor.h"
#ifdef _DEBUG
#include "sharedDebug/ConfigSharedDebugLinux.h"
#include "sharedDebug/Profiler.h"
#include "sharedFoundation/ConfigSharedFoundation.h"
#include <curses.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
// ======================================================================
namespace DebugMonitorNamespace
{
bool s_installed;
SCREEN *s_initialScreen;
WINDOW *s_initialWindow;
FILE *s_ttyOutputFile;
FILE *s_ttyInputFile;
SCREEN *s_outputScreen;
WINDOW *s_outputWindow;
}
using namespace DebugMonitorNamespace;
// ======================================================================
// Install the debug monitor subsystem
//
// Remarks:
//
// This routine will first attempt to install the selected debug monitor.
void DebugMonitor::install(void)
{
DEBUG_FATAL(s_installed, ("DebugMonitor already installed."));
//-- Check if user wants to use a TTY for the DebugMonitor.
if (!ConfigSharedDebugLinux::getUseTty())
{
// Do not use the DebugMonitor.
DEBUG_REPORT_LOG(ConfigSharedDebugLinux::getLogTtySetup(), ("DebugMonitor: skipping install, ConfigSharedDebugLinux/useTty is false.\n"));
return;
}
//-- Open and retrieve the name of the TTY file for output from the TTY Specification file.
char const *const configFilename = ConfigSharedDebugLinux::getDebugMonitorOutputFilename();
if (!configFilename || (*configFilename == '\0'))
{
DEBUG_WARNING(true, ("DebugMonitor(): skipping installation, no debugMonitorOutputFilename specified."));
return;
}
char ttyFilename[1024];
if (configFilename[0] != '@')
{
// filename is the real output filename (usually a device) for DebugMonitor output.
strncpy(ttyFilename, configFilename, sizeof(ttyFilename) - 1);
ttyFilename[sizeof(ttyFilename)-1] = 0;
}
else
{
// filename is actually an indirection filename, the contents of this file contains the real filename.
FILE *ttySpecifierFile = fopen(configFilename + 1, "r");
if (!ttySpecifierFile)
{
DEBUG_WARNING(true, ("DebugMonitor: failed to open tty specifier file [%s]: [%s].", configFilename + 1, strerror(errno)));
return;
}
// Retrieve the TTY file name.
fgets(ttyFilename, sizeof(ttyFilename), ttySpecifierFile);
fclose(ttySpecifierFile);
}
// Strip trailing newline from TTY filename.
const int filenameLength = strlen(ttyFilename);
if (filenameLength > 0)
{
// chop off trailing newline if present.
if (ttyFilename[filenameLength - 1] == '\n')
ttyFilename[filenameLength - 1] = '\0';
}
//-- Open the TTY file for output.
DEBUG_REPORT_LOG(ConfigSharedDebugLinux::getLogTtySetup(), ("DebugMonitor: opening TTY file [%s] for reading and writing.\n", ttyFilename));
FILE *s_ttyOutputFile = fopen(ttyFilename, "w");
if (!s_ttyOutputFile)
{
DEBUG_WARNING(true, ("DebugMonitor: failed to open tty file for output [%s]: [%s].", ttyFilename, strerror(errno)));
return;
}
//-- Open the TTY file for input.
FILE *s_ttyInputFile = fopen(ttyFilename, "r");
if (!s_ttyInputFile)
{
DEBUG_WARNING(true, ("DebugMonitor: failed to open tty file for input [%s]: [%s].", ttyFilename, strerror(errno)));
fclose(s_ttyOutputFile);
return;
}
#if 1
// NOTE: -TRF- I would not expect this chunk of termios setup
// code to be necessary. My expectation is that it should
// be handled by the curs_inopts (see man page) options I have
// selected below. Alas, these are needed.
//-- Setup profiler window input mode.
const int inputFd = STDIN_FILENO;
// Get terminal attributes.
termios terminalAttributes;
if (tcgetattr(inputFd, &terminalAttributes) != 0)
{
DEBUG_WARNING(true, ("DebugMonitor: tcgetattr failed [%s].", strerror(errno)));
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
// Turn off line-processing mode. We want a character at a time.
terminalAttributes.c_lflag &= ~ICANON;
// Turn off echo.
terminalAttributes.c_lflag &= ~ECHO;
// Specify that read should return immediately (non-blocking).
terminalAttributes.c_cc[VMIN] = 0;
terminalAttributes.c_cc[VTIME] = 0;
// Set terminal attributes.
if (tcsetattr(inputFd, TCSAFLUSH, &terminalAttributes) != 0)
{
DEBUG_WARNING(true, ("DebugMonitor: tcsetattr failed [%s].", strerror(errno)));
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
#endif
//-- Create a curses screen to represent the DebugMonitor output tty.
// NOTE: for now the curses input is hooked up to the application's
// standard input. Later we may want to change that to
// handle input from the output terminal, particularly to
// handle profiler modifications when the output window
// is active.
s_outputScreen = newterm(NULL, s_ttyOutputFile, stdin);
if (!s_outputScreen)
{
DEBUG_WARNING(true, ("DebugMonitor: newterm() failed [%s].", strerror(errno)));
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
//-- Initialize the curses library.
s_initialWindow = initscr();
if (!s_initialWindow)
{
DEBUG_WARNING(true, ("DebugMonitor: initscr(): failed [%s].", strerror(errno)));
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
//-- Restore the stdout window to its previous state. We won't use stdout.
endwin();
//-- Set the default terminal to be the output TTY's terminal screen.
s_initialScreen = set_term(s_outputScreen);
if (!s_initialScreen)
{
DEBUG_WARNING(true, ("DebugMonitor: set_term(): failed [%s].", strerror(errno)));
delscreen(s_outputScreen);
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
//-- Create the curses WINDOW at the full size of the TTY.
s_outputWindow = newwin(0, 0, 0, 0);
if (!s_outputWindow)
{
DEBUG_WARNING(true, ("DebugMonitor: newwin(): failed [%s].", strerror(errno)));
delscreen(s_outputScreen);
fclose(s_ttyOutputFile);
fclose(s_ttyInputFile);
return;
}
//-- Destroy curses data structures associated with the stdout TTY
delwin(s_initialWindow);
delscreen(s_initialScreen);
//-- Additional profiler input setup.
// Allow translation of arrow keys.
if (keypad(s_outputWindow, true) == ERR)
{
DEBUG_WARNING(true, ("DebugMonitor: keypad() failed [%s].", strerror(errno)));
return;
}
//-- Add the removal routine to the ExitChain.
s_installed = true;
// NOTE: this can't go on the exit chain because the MemoryManager
// will write to it way late in processing.
//ExitChain::add(remove, "DebugMonitor");
}
// ----------------------------------------------------------------------
void DebugMonitor::remove(void)
{
if (!s_installed)
{
// Exit silently rather than FATAL. There's any number of reasons why
// we might not be installed, and the ExitChain does not call remove,
// so this should be valid.
return;
}
s_installed = false;
//-- Restore the TTY's state.
endwin();
//-- Delete curses data structures associated with the output terminal.
delwin(s_outputWindow);
s_outputWindow = 0;
delscreen(s_outputScreen);
s_outputScreen = 0;
//-- Close file handle to output terminal.
fclose(s_ttyOutputFile);
s_ttyOutputFile = 0;
fclose(s_ttyInputFile);
s_ttyInputFile = 0;
}
// ----------------------------------------------------------------------
/**
* Set the debug window's z-order.
*/
void DebugMonitor::setBehindWindow(HWND window)
{
UNREF(window);
}
// ----------------------------------------------------------------------
/**
* Clear the debug monitor and home the cursor.
*
* If the mono monitor is not installed, this routine does nothing.
*
* This routine will clear the contents of the debug monitor, reset the screen
* offset to 0, and move the cursor to the upper left corner of the screen.
*
* @see DebugMonitor::home(), DebugMonitor::clearToCursor()
*/
void DebugMonitor::clearScreen(void)
{
//-- Ignore request if DebugMonitor isn't installed.
if (!s_installed)
return;
//-- Tell curses to clear the virtual screen. The actual
// clear will not take effect until the next showOutput() call
// is made.
IGNORE_RETURN(wclear(s_outputWindow));
//-- Goto upper left corner.
gotoXY(0, 0);
}
// ----------------------------------------------------------------------
/**
* Clear the debug monitor to the current cursor position and home the cursor.
*
* If the debug monitor is not installed, this routine does nothing.
*
* This routine will clear the contents of the debug monitor only up to the
* cursor position. If the cursor is not very far down on the screen,
* this routine may be significantly more efficient clearing the screen.
*
* It will also move the cursor to the upper left corner of the screen.
*
* @see DebugMonitor::clearScreen(), DebugMonitor::home()
*/
void DebugMonitor::clearToCursor(void)
{
//-- Ignore request if DebugMonitor isn't installed.
if (!s_installed)
return;
//-- I don't see this functionality in curses. Just clear
// the entire screen.
clearScreen();
}
// ----------------------------------------------------------------------
/**
* Position the cursor on the debug monitor screen.
*
* If the debug monitor is not installed, this routine does nothing.
*
* All printing happens at the cursor position.
*
* @param x New X position for the cursor
* @param y New Y position for the cursor
*/
void DebugMonitor::gotoXY(int x, int y)
{
//-- Ignore request if DebugMonitor isn't installed.
if (!s_installed)
return;
//-- Move the cursor position to the specified location.
const int result = wmove(s_outputWindow, y, x);
DEBUG_REPORT_LOG(result == ERR, ("DebugMonitor: wmove(%d, %d) failed [%s].\n", y, x, strerror(errno)));
UNREF(result);
}
// ----------------------------------------------------------------------
/**
* Display a string on the debug monitor.
* * If the debug monitor is not installed, this routine does nothing.
*
* Printing occurs from the cursor position.
*
* Newline characters '\n' will cause the cursor position to advance to the
* beginning of the next line. If the cursor is already on the last line of
* the screen, the screen will scroll up one line and the cursor will move to
* the beginning of the last line.
*
* The backspace character '\b' will cause the cursor to move one character
* backwards. If at the beginning of the line, the cursor will move to the
* end of the previous line. If already on the first line of the screen, the
* cursor position and screen contents will be unchanged.
*
* All other characters are placed directly into the text frame buffer.
* After each character, the cursor will be logically advanced one
* character forward. If the cursor was on the last column, it will advance
* to the next line. If the cursor was already on the last line, the screen
* will be scrolled up one line and the cursor will move to the beginning of
* the last line.
*
* @param string String to display on the debug monitor
*/
void DebugMonitor::print(const char *string)
{
//-- Ignore request if DebugMonitor isn't installed.
if (!s_installed)
return;
const int result = wprintw(s_outputWindow, const_cast<char*>(string));
DEBUG_REPORT_LOG(result == ERR, ("WARNING: DebugMonitor: wprintw failed [%s].\n", strerror(errno)));
UNREF(result);
}
// ----------------------------------------------------------------------
/**
* Ensure all changes to the DebugMonitor have taken effect by the time
* this function returns.
*
* Note: some platforms may do nothing here. The Win32 platform does not
* require flushing. The Linux platform does. Call it assuming
* that it is needed. It will be a no-op when not required.
*/
void DebugMonitor::flushOutput()
{
//-- Ignore request if DebugMonitor isn't installed.
if (!s_installed)
return;
//-- Tell curses to refresh the output window so that
// the virtual curses display is rendered to the real
// terminal display.
const int result = wrefresh(s_outputWindow);
DEBUG_REPORT_LOG(result == ERR, ("WARNING: DebugMonitor: wrefresh failed [%s].\n", strerror(errno)));
UNREF(result);
//-- Handle tty input here. This is somewhat hacky. Unix tty input
// and output currently are intimately tied to the profiler. Later
// this can be separated out so we can handle multiple TTYs for
// multiple purposes, such as for handling a TTY-based popup debug
// menu for on-the-fly options changing.
const int input = wgetch(s_outputWindow);
if (input != ERR)
{
#if 0
DEBUG_REPORT_LOG(true, ("DebugMonitor: received key [index=%d].\n", input));
#endif
//-- Handle input.
switch (input)
{
case '8':
case KEY_UP:
Profiler::selectionMoveUp();
break;
case '9':
case KEY_DOWN:
Profiler::selectionMoveDown();
break;
case '0':
case 10:
case KEY_LEFT:
case KEY_RIGHT:
Profiler::selectionToggleExpanded();
break;
default:
// Nothing to do.
break;
}
}
}
// ======================================================================
#endif