/* $Id: VBoxDbgConsole.cpp 57358 2015-08-14 15:16:38Z vboxsync $ */ /** @file * VBox Debugger GUI - Console. */ /* * Copyright (C) 2006-2015 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DBGG #include "VBoxDbgConsole.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * * V B o x D b g C o n s o l e O u t p u t * V B o x D b g C o n s o l e O u t p u t * V B o x D b g C o n s o l e O u t p u t * * */ VBoxDbgConsoleOutput::VBoxDbgConsoleOutput(QWidget *pParent/* = NULL*/, const char *pszName/* = NULL*/) : QTextEdit(pParent), m_uCurLine(0), m_uCurPos(0), m_hGUIThread(RTThreadNativeSelf()) { setReadOnly(true); setUndoRedoEnabled(false); setOverwriteMode(false); setPlainText(""); setTextInteractionFlags(Qt::TextBrowserInteraction); setAutoFormatting(QTextEdit::AutoAll); setTabChangesFocus(true); setAcceptRichText(false); /* * Font. * Create actions for font menu items. */ m_pCourierFontAction = new QAction(tr("Courier"), this); m_pCourierFontAction->setCheckable(true); m_pCourierFontAction->setShortcut(Qt::ControlModifier + Qt::Key_D); connect(m_pCourierFontAction, SIGNAL(triggered()), this, SLOT(setFontCourier())); m_pMonospaceFontAction = new QAction(tr("Monospace"), this); m_pMonospaceFontAction->setCheckable(true); m_pMonospaceFontAction->setShortcut(Qt::ControlModifier + Qt::Key_M); connect(m_pMonospaceFontAction, SIGNAL(triggered()), this, SLOT(setFontMonospace())); /* Create action group for grouping of exclusive font menu items. */ QActionGroup *pActionFontGroup = new QActionGroup(this); pActionFontGroup->addAction(m_pCourierFontAction); pActionFontGroup->addAction(m_pMonospaceFontAction); pActionFontGroup->setExclusive(true); /* * Color scheme. * Create actions for color-scheme menu items. */ m_pGreenOnBlackAction = new QAction(tr("Green On Black"), this); m_pGreenOnBlackAction->setCheckable(true); m_pGreenOnBlackAction->setShortcut(Qt::ControlModifier + Qt::Key_1); connect(m_pGreenOnBlackAction, SIGNAL(triggered()), this, SLOT(setColorGreenOnBlack())); m_pBlackOnWhiteAction = new QAction(tr("Black On White"), this); m_pBlackOnWhiteAction->setCheckable(true); m_pBlackOnWhiteAction->setShortcut(Qt::ControlModifier + Qt::Key_2); connect(m_pBlackOnWhiteAction, SIGNAL(triggered()), this, SLOT(setColorBlackOnWhite())); /* Create action group for grouping of exclusive color-scheme menu items. */ QActionGroup *pActionColorGroup = new QActionGroup(this); pActionColorGroup->addAction(m_pGreenOnBlackAction); pActionColorGroup->addAction(m_pBlackOnWhiteAction); pActionColorGroup->setExclusive(true); /* * Set the defaults (which syncs with the menu item checked state). */ setFontCourier(); setColorGreenOnBlack(); NOREF(pszName); } VBoxDbgConsoleOutput::~VBoxDbgConsoleOutput() { Assert(m_hGUIThread == RTThreadNativeSelf()); } void VBoxDbgConsoleOutput::contextMenuEvent(QContextMenuEvent *pEvent) { /* * Create the context menu and add the menu items. */ QMenu *pMenu = createStandardContextMenu(); QMenu *pColorMenu = pMenu->addMenu(tr("Co&lor Scheme")); pColorMenu->addAction(m_pGreenOnBlackAction); pColorMenu->addAction(m_pBlackOnWhiteAction); QMenu *pFontMenu = pMenu->addMenu(tr("&Font Family")); pFontMenu->addAction(m_pCourierFontAction); pFontMenu->addAction(m_pMonospaceFontAction); pMenu->exec(pEvent->globalPos()); delete pMenu; } void VBoxDbgConsoleOutput::setColorGreenOnBlack() { setStyleSheet("QTextEdit { background-color: black; color: rgb(0, 224, 0) }"); m_enmColorScheme = kGreenOnBlack; /* This is used both as a trigger as well as called independently from code. When used as a trigger, the checked is done automatically by Qt. */ if (!m_pGreenOnBlackAction->isChecked()) m_pGreenOnBlackAction->setChecked(true); } void VBoxDbgConsoleOutput::setColorBlackOnWhite() { setStyleSheet("QTextEdit { background-color: white; color: black }"); m_enmColorScheme = kBlackOnWhite; if (!m_pBlackOnWhiteAction->isChecked()) m_pBlackOnWhiteAction->setChecked(true); } void VBoxDbgConsoleOutput::setFontCourier() { #ifdef Q_WS_MAC QFont Font("Monaco", 10, QFont::Normal, FALSE); Font.setStyleStrategy(QFont::NoAntialias); #else QFont Font = font(); Font.setStyleHint(QFont::TypeWriter); Font.setFamily("Courier [Monotype]"); #endif setFont(Font); if (!m_pCourierFontAction->isChecked()) m_pCourierFontAction->setChecked(true); } void VBoxDbgConsoleOutput::setFontMonospace() { QFont Font = font(); Font.setStyleHint(QFont::TypeWriter); Font.setStyleStrategy(QFont::PreferAntialias); Font.setFamily("Monospace [Monotype]"); setFont(Font); if (!m_pMonospaceFontAction->isChecked()) m_pMonospaceFontAction->setChecked(true); } void VBoxDbgConsoleOutput::appendText(const QString &rStr, bool fClearSelection) { Assert(m_hGUIThread == RTThreadNativeSelf()); if (rStr.isEmpty() || rStr.isNull() || !rStr.length()) return; /* * Insert all in one go and make sure it's visible. * * We need to move the cursor and unselect any selected text before * inserting anything, otherwise, text will disappear. */ QTextCursor Cursor = textCursor(); if (!fClearSelection && Cursor.hasSelection()) { QTextCursor SavedCursor = Cursor; Cursor.clearSelection(); Cursor.movePosition(QTextCursor::End); Cursor.insertText(rStr); setTextCursor(SavedCursor); } else { if (Cursor.hasSelection()) Cursor.clearSelection(); if (!Cursor.atEnd()) Cursor.movePosition(QTextCursor::End); Cursor.insertText(rStr); setTextCursor(Cursor); ensureCursorVisible(); } } /* * * V B o x D b g C o n s o l e I n p u t * V B o x D b g C o n s o l e I n p u t * V B o x D b g C o n s o l e I n p u t * * */ VBoxDbgConsoleInput::VBoxDbgConsoleInput(QWidget *pParent/* = NULL*/, const char *pszName/* = NULL*/) : QComboBox(pParent), m_hGUIThread(RTThreadNativeSelf()) { addItem(""); /* invariant: empty command line is the last item */ setEditable(true); setInsertPolicy(NoInsert); setAutoCompletion(false); setMaxCount(50); const QLineEdit *pEdit = lineEdit(); if (pEdit) connect(pEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed())); NOREF(pszName); } VBoxDbgConsoleInput::~VBoxDbgConsoleInput() { Assert(m_hGUIThread == RTThreadNativeSelf()); } void VBoxDbgConsoleInput::setLineEdit(QLineEdit *pEdit) { Assert(m_hGUIThread == RTThreadNativeSelf()); QComboBox::setLineEdit(pEdit); if (lineEdit() == pEdit && pEdit) connect(pEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed())); } void VBoxDbgConsoleInput::returnPressed() { Assert(m_hGUIThread == RTThreadNativeSelf()); QString strCommand = currentText(); /* TODO: trim whitespace? */ if (strCommand.isEmpty()) return; /* deal with the current command. */ emit commandSubmitted(strCommand); /* * Add current command to history. */ bool fNeedsAppending = true; /* invariant: empty line at the end */ int iLastItem = count() - 1; Assert(itemText(iLastItem).isEmpty()); /* have previous command? check duplicate. */ if (iLastItem > 0) { const QString strPrevCommand(itemText(iLastItem - 1)); if (strCommand == strPrevCommand) fNeedsAppending = false; } if (fNeedsAppending) { /* history full? drop the oldest command. */ if (count() == maxCount()) { removeItem(0); --iLastItem; } /* insert before the empty line. */ insertItem(iLastItem, strCommand); } /* invariant: empty line at the end */ int iNewLastItem = count() - 1; Assert(itemText(iNewLastItem).isEmpty()); /* select empty line to present "new" command line to the user */ setCurrentIndex(iNewLastItem); } /* * * V B o x D b g C o n s o l e * V B o x D b g C o n s o l e * V B o x D b g C o n s o l e * * */ VBoxDbgConsole::VBoxDbgConsole(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent/* = NULL*/) : VBoxDbgBaseWindow(a_pDbgGui, a_pParent), m_pOutput(NULL), m_pInput(NULL), m_fInputRestoreFocus(false), m_pszInputBuf(NULL), m_cbInputBuf(0), m_cbInputBufAlloc(0), m_pszOutputBuf(NULL), m_cbOutputBuf(0), m_cbOutputBufAlloc(0), m_pTimer(NULL), m_fUpdatePending(false), m_Thread(NIL_RTTHREAD), m_EventSem(NIL_RTSEMEVENT), m_fTerminate(false), m_fThreadTerminated(false) { setWindowTitle("VBoxDbg - Console"); /* * Create the output text box. */ m_pOutput = new VBoxDbgConsoleOutput(this); /* try figure a suitable size */ QLabel *pLabel = new QLabel( "11111111111111111111111111111111111111111111111111111111111111111111111111111112222222222", this); pLabel->setFont(m_pOutput->font()); QSize Size = pLabel->sizeHint(); delete pLabel; Size.setWidth((int)(Size.width() * 1.10)); Size.setHeight(Size.width() / 2); resize(Size); /* * Create the input combo box (with a label). */ QHBoxLayout *pLayout = new QHBoxLayout(); //pLayout->setSizeConstraint(QLayout::SetMaximumSize); pLabel = new QLabel(" Command "); pLayout->addWidget(pLabel); pLabel->setMaximumSize(pLabel->sizeHint()); pLabel->setAlignment(Qt::AlignCenter); m_pInput = new VBoxDbgConsoleInput(NULL); pLayout->addWidget(m_pInput); m_pInput->setDuplicatesEnabled(false); connect(m_pInput, SIGNAL(commandSubmitted(const QString &)), this, SLOT(commandSubmitted(const QString &))); # if 0//def Q_WS_MAC pLabel = new QLabel(" "); pLayout->addWidget(pLabel); pLabel->setMaximumSize(20, m_pInput->sizeHint().height() + 6); pLabel->setMinimumSize(20, m_pInput->sizeHint().height() + 6); # endif QWidget *pHBox = new QWidget(this); pHBox->setLayout(pLayout); m_pInput->setEnabled(false); /* (we'll get a ready notification) */ /* * Vertical layout box on the whole widget. */ QVBoxLayout *pVLayout = new QVBoxLayout(); pVLayout->setContentsMargins(0, 0, 0, 0); pVLayout->setSpacing(5); pVLayout->addWidget(m_pOutput); pVLayout->addWidget(pHBox); setLayout(pVLayout); /* * The tab order is from input to output, not the other way around as it is by default. */ setTabOrder(m_pInput, m_pOutput); m_fInputRestoreFocus = true; /* hack */ /* * Setup the timer. */ m_pTimer = new QTimer(this); connect(m_pTimer, SIGNAL(timeout()), SLOT(updateOutput())); /* * Init the backend structure. */ m_Back.Core.pfnInput = backInput; m_Back.Core.pfnRead = backRead; m_Back.Core.pfnWrite = backWrite; m_Back.Core.pfnSetReady = backSetReady; m_Back.pSelf = this; /* * Create the critical section, the event semaphore and the debug console thread. */ int rc = RTCritSectInit(&m_Lock); AssertRC(rc); rc = RTSemEventCreate(&m_EventSem); AssertRC(rc); rc = RTThreadCreate(&m_Thread, backThread, this, 0, RTTHREADTYPE_DEBUGGER, RTTHREADFLAGS_WAITABLE, "VBoxDbgC"); AssertRC(rc); if (RT_FAILURE(rc)) m_Thread = NIL_RTTHREAD; /* * Shortcuts. */ m_pFocusToInput = new QAction("", this); m_pFocusToInput->setShortcut(QKeySequence("Ctrl+L")); addAction(m_pFocusToInput); connect(m_pFocusToInput, SIGNAL(triggered(bool)), this, SLOT(actFocusToInput())); m_pFocusToOutput = new QAction("", this); m_pFocusToOutput->setShortcut(QKeySequence("Ctrl+O")); addAction(m_pFocusToOutput); connect(m_pFocusToOutput, SIGNAL(triggered(bool)), this, SLOT(actFocusToOutput())); addAction(m_pOutput->m_pBlackOnWhiteAction); addAction(m_pOutput->m_pGreenOnBlackAction); addAction(m_pOutput->m_pCourierFontAction); addAction(m_pOutput->m_pMonospaceFontAction); } VBoxDbgConsole::~VBoxDbgConsole() { Assert(isGUIThread()); /* * Wait for the thread. */ ASMAtomicWriteBool(&m_fTerminate, true); RTSemEventSignal(m_EventSem); if (m_Thread != NIL_RTTHREAD) { int rc = RTThreadWait(m_Thread, 15000, NULL); AssertRC(rc); m_Thread = NIL_RTTHREAD; } /* * Free resources. */ delete m_pTimer; m_pTimer = NULL; RTCritSectDelete(&m_Lock); RTSemEventDestroy(m_EventSem); m_EventSem = 0; m_pOutput = NULL; m_pInput = NULL; if (m_pszInputBuf) { RTMemFree(m_pszInputBuf); m_pszInputBuf = NULL; } m_cbInputBuf = 0; m_cbInputBufAlloc = 0; delete m_pFocusToInput; m_pFocusToInput = NULL; delete m_pFocusToOutput; m_pFocusToOutput = NULL; } void VBoxDbgConsole::commandSubmitted(const QString &rCommand) { Assert(isGUIThread()); lock(); RTSemEventSignal(m_EventSem); QByteArray Utf8Array = rCommand.toUtf8(); const char *psz = Utf8Array.constData(); size_t cb = strlen(psz); /* * Make sure we've got space for the input. */ if (cb + m_cbInputBuf >= m_cbInputBufAlloc) { size_t cbNew = RT_ALIGN_Z(cb + m_cbInputBufAlloc + 1, 128); void *pv = RTMemRealloc(m_pszInputBuf, cbNew); if (!pv) { unlock(); return; } m_pszInputBuf = (char *)pv; m_cbInputBufAlloc = cbNew; } /* * Add the input and output it. */ memcpy(m_pszInputBuf + m_cbInputBuf, psz, cb); m_cbInputBuf += cb; m_pszInputBuf[m_cbInputBuf++] = '\n'; m_pOutput->appendText(rCommand + "\n", true /*fClearSelection*/); m_pOutput->ensureCursorVisible(); m_fInputRestoreFocus = m_pInput->hasFocus(); /* dirty focus hack */ m_pInput->setEnabled(false); Log(("VBoxDbgConsole::commandSubmitted: %s (input-enabled=%RTbool)\n", psz, m_pInput->isEnabled())); unlock(); } void VBoxDbgConsole::updateOutput() { Assert(isGUIThread()); lock(); m_fUpdatePending = false; if (m_cbOutputBuf) { m_pOutput->appendText(QString::fromUtf8((const char *)m_pszOutputBuf, (int)m_cbOutputBuf), false /*fClearSelection*/); m_cbOutputBuf = 0; } unlock(); } /** * Lock the object. */ void VBoxDbgConsole::lock() { RTCritSectEnter(&m_Lock); } /** * Unlocks the object. */ void VBoxDbgConsole::unlock() { RTCritSectLeave(&m_Lock); } /** * Checks if there is input. * * @returns true if there is input ready. * @returns false if there not input ready. * @param pBack Pointer to VBoxDbgConsole::m_Back. * @param cMillies Number of milliseconds to wait on input data. */ /*static*/ DECLCALLBACK(bool) VBoxDbgConsole::backInput(PDBGCBACK pBack, uint32_t cMillies) { VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(pBack); pThis->lock(); bool fRc = true; if (!pThis->m_cbInputBuf) { /* * Wait outside the lock for the requested time, then check again. */ pThis->unlock(); RTSemEventWait(pThis->m_EventSem, cMillies); pThis->lock(); fRc = pThis->m_cbInputBuf || ASMAtomicUoReadBool(&pThis->m_fTerminate); } pThis->unlock(); return fRc; } /** * Read input. * * @returns VBox status code. * @param pBack Pointer to VBoxDbgConsole::m_Back. * @param pvBuf Where to put the bytes we read. * @param cbBuf Maximum nymber of bytes to read. * @param pcbRead Where to store the number of bytes actually read. * If NULL the entire buffer must be filled for a * successful return. */ /*static*/ DECLCALLBACK(int) VBoxDbgConsole::backRead(PDBGCBACK pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead) { VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(pBack); Assert(pcbRead); /** @todo implement this bit */ if (pcbRead) *pcbRead = 0; pThis->lock(); int rc = VINF_SUCCESS; if (!ASMAtomicUoReadBool(&pThis->m_fTerminate)) { if (pThis->m_cbInputBuf) { const char *psz = pThis->m_pszInputBuf; size_t cbRead = RT_MIN(pThis->m_cbInputBuf, cbBuf); memcpy(pvBuf, psz, cbRead); psz += cbRead; pThis->m_cbInputBuf -= cbRead; if (*psz) memmove(pThis->m_pszInputBuf, psz, pThis->m_cbInputBuf); pThis->m_pszInputBuf[pThis->m_cbInputBuf] = '\0'; *pcbRead = cbRead; } } else rc = VERR_GENERAL_FAILURE; pThis->unlock(); return rc; } /** * Write (output). * * @returns VBox status code. * @param pBack Pointer to VBoxDbgConsole::m_Back. * @param pvBuf What to write. * @param cbBuf Number of bytes to write. * @param pcbWritten Where to store the number of bytes actually written. * If NULL the entire buffer must be successfully written. */ /*static*/ DECLCALLBACK(int) VBoxDbgConsole::backWrite(PDBGCBACK pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten) { VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(pBack); int rc = VINF_SUCCESS; pThis->lock(); if (cbBuf + pThis->m_cbOutputBuf >= pThis->m_cbOutputBufAlloc) { size_t cbNew = RT_ALIGN_Z(cbBuf + pThis->m_cbOutputBufAlloc + 1, 1024); void *pv = RTMemRealloc(pThis->m_pszOutputBuf, cbNew); if (!pv) { pThis->unlock(); if (pcbWritten) *pcbWritten = 0; return VERR_NO_MEMORY; } pThis->m_pszOutputBuf = (char *)pv; pThis->m_cbOutputBufAlloc = cbNew; } /* * Add the output. */ memcpy(pThis->m_pszOutputBuf + pThis->m_cbOutputBuf, pvBuf, cbBuf); pThis->m_cbOutputBuf += cbBuf; pThis->m_pszOutputBuf[pThis->m_cbOutputBuf] = '\0'; if (pcbWritten) *pcbWritten = cbBuf; if (ASMAtomicUoReadBool(&pThis->m_fTerminate)) rc = VERR_GENERAL_FAILURE; /* * Tell the GUI thread to draw this text. * We cannot do it from here without frequent crashes. */ if (!pThis->m_fUpdatePending) QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kUpdate)); pThis->unlock(); return rc; } /*static*/ DECLCALLBACK(void) VBoxDbgConsole::backSetReady(PDBGCBACK pBack, bool fReady) { VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(pBack); if (fReady) QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kInputEnable)); } /** * The Debugger Console Thread * * @returns VBox status code (ignored). * @param Thread The thread handle. * @param pvUser Pointer to the VBoxDbgConsole object.s */ /*static*/ DECLCALLBACK(int) VBoxDbgConsole::backThread(RTTHREAD Thread, void *pvUser) { VBoxDbgConsole *pThis = (VBoxDbgConsole *)pvUser; LogFlow(("backThread: Thread=%p pvUser=%p\n", (void *)Thread, pvUser)); NOREF(Thread); /* * Create and execute the console. */ int rc = pThis->dbgcCreate(&pThis->m_Back.Core, 0); ASMAtomicUoWriteBool(&pThis->m_fThreadTerminated, true); if (!ASMAtomicUoReadBool(&pThis->m_fTerminate)) QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(rc == VINF_SUCCESS ? VBoxDbgConsoleEvent::kTerminatedUser : VBoxDbgConsoleEvent::kTerminatedOther)); LogFlow(("backThread: returns %Rrc (m_fTerminate=%RTbool)\n", rc, ASMAtomicUoReadBool(&pThis->m_fTerminate))); return rc; } bool VBoxDbgConsole::event(QEvent *pGenEvent) { Assert(isGUIThread()); if (pGenEvent->type() == (QEvent::Type)VBoxDbgConsoleEvent::kEventNumber) { VBoxDbgConsoleEvent *pEvent = (VBoxDbgConsoleEvent *)pGenEvent; switch (pEvent->command()) { /* make update pending. */ case VBoxDbgConsoleEvent::kUpdate: lock(); if (!m_fUpdatePending) { m_fUpdatePending = true; m_pTimer->setSingleShot(true); m_pTimer->start(10); } unlock(); break; /* Re-enable the input field and restore focus. */ case VBoxDbgConsoleEvent::kInputEnable: Log(("VBoxDbgConsole: kInputEnable (input-enabled=%RTbool)\n", m_pInput->isEnabled())); m_pInput->setEnabled(true); if ( m_fInputRestoreFocus && !m_pInput->hasFocus()) m_pInput->setFocus(); /* this is a hack. */ m_fInputRestoreFocus = false; break; /* The thread terminated by user command (exit, quit, bye). */ case VBoxDbgConsoleEvent::kTerminatedUser: Log(("VBoxDbgConsole: kTerminatedUser (input-enabled=%RTbool)\n", m_pInput->isEnabled())); m_pInput->setEnabled(false); close(); break; /* The thread terminated for some unknown reason., disable input */ case VBoxDbgConsoleEvent::kTerminatedOther: Log(("VBoxDbgConsole: kTerminatedOther (input-enabled=%RTbool)\n", m_pInput->isEnabled())); m_pInput->setEnabled(false); break; /* paranoia */ default: AssertMsgFailed(("command=%d\n", pEvent->command())); break; } return true; } return VBoxDbgBaseWindow::event(pGenEvent); } void VBoxDbgConsole::closeEvent(QCloseEvent *a_pCloseEvt) { if (m_fThreadTerminated) { a_pCloseEvt->accept(); delete this; } } void VBoxDbgConsole::actFocusToInput() { if (!m_pInput->hasFocus()) m_pInput->setFocus(Qt::ShortcutFocusReason); } void VBoxDbgConsole::actFocusToOutput() { if (!m_pOutput->hasFocus()) m_pOutput->setFocus(Qt::ShortcutFocusReason); }