3 * VBox Debugger GUI - Console.
7 * Copyright (C) 2006-2010 Oracle Corporation
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19 /*******************************************************************************
21 *******************************************************************************/
22 #define LOG_GROUP LOG_GROUP_DBGG
23 #include "VBoxDbgConsole.h"
26 #include <QApplication>
29 #include <QHBoxLayout>
31 #include <QContextMenuEvent>
34 #include <VBox/vmm/cfgm.h>
37 #include <iprt/thread.h>
40 #include <iprt/assert.h>
42 #include <iprt/alloc.h>
43 #include <iprt/string.h>
50 * V B o x D b g C o n s o l e O u t p u t
51 * V B o x D b g C o n s o l e O u t p u t
52 * V B o x D b g C o n s o l e O u t p u t
58 VBoxDbgConsoleOutput::VBoxDbgConsoleOutput(QWidget
*pParent
/* = NULL*/, const char *pszName
/* = NULL*/)
59 : QTextEdit(pParent
), m_uCurLine(0), m_uCurPos(0), m_hGUIThread(RTThreadNativeSelf())
62 setUndoRedoEnabled(false);
63 setOverwriteMode(false);
65 setTextInteractionFlags(Qt::TextBrowserInteraction
);
66 setAutoFormatting(QTextEdit::AutoAll
);
67 setTabChangesFocus(true);
68 setAcceptRichText(false);
71 QFont
Font("Monaco", 10, QFont::Normal
, FALSE
);
72 Font
.setStyleStrategy(QFont::NoAntialias
);
75 Font
.setStyleHint(QFont::TypeWriter
);
76 Font
.setFamily("Courier [Monotype]");
81 QPalette
Pal(palette());
82 Pal
.setColor(QPalette::All
, QPalette::Base
, QColor(Qt::black
));
84 setTextColor(QColor(qRgb(0, 0xe0, 0)));
89 VBoxDbgConsoleOutput::~VBoxDbgConsoleOutput()
91 Assert(m_hGUIThread
== RTThreadNativeSelf());
96 VBoxDbgConsoleOutput::appendText(const QString
&rStr
, bool fClearSelection
)
98 Assert(m_hGUIThread
== RTThreadNativeSelf());
100 if (rStr
.isEmpty() || rStr
.isNull() || !rStr
.length())
104 * Insert all in one go and make sure it's visible.
106 * We need to move the cursor and unselect any selected text before
107 * inserting anything, otherwise, text will disappear.
109 QTextCursor Cursor
= textCursor();
110 if (!fClearSelection
&& Cursor
.hasSelection())
112 QTextCursor SavedCursor
= Cursor
;
113 Cursor
.clearSelection();
114 Cursor
.movePosition(QTextCursor::End
);
116 Cursor
.insertText(rStr
);
118 setTextCursor(SavedCursor
);
122 if (Cursor
.hasSelection())
123 Cursor
.clearSelection();
125 Cursor
.movePosition(QTextCursor::End
);
127 Cursor
.insertText(rStr
);
129 setTextCursor(Cursor
);
130 ensureCursorVisible();
139 * V B o x D b g C o n s o l e I n p u t
140 * V B o x D b g C o n s o l e I n p u t
141 * V B o x D b g C o n s o l e I n p u t
147 VBoxDbgConsoleInput::VBoxDbgConsoleInput(QWidget
*pParent
/* = NULL*/, const char *pszName
/* = NULL*/)
148 : QComboBox(pParent
), m_hGUIThread(RTThreadNativeSelf())
150 addItem(""); /* invariant: empty command line is the last item */
153 setInsertPolicy(NoInsert
);
154 setAutoCompletion(false);
156 const QLineEdit
*pEdit
= lineEdit();
158 connect(pEdit
, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
164 VBoxDbgConsoleInput::~VBoxDbgConsoleInput()
166 Assert(m_hGUIThread
== RTThreadNativeSelf());
171 VBoxDbgConsoleInput::setLineEdit(QLineEdit
*pEdit
)
173 Assert(m_hGUIThread
== RTThreadNativeSelf());
174 QComboBox::setLineEdit(pEdit
);
175 if (lineEdit() == pEdit
&& pEdit
)
176 connect(pEdit
, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
181 VBoxDbgConsoleInput::returnPressed()
183 Assert(m_hGUIThread
== RTThreadNativeSelf());
185 QString strCommand
= currentText();
186 /* TODO: trim whitespace? */
187 if (strCommand
.isEmpty())
190 /* deal with the current command. */
191 emit
commandSubmitted(strCommand
);
195 * Add current command to history.
197 bool fNeedsAppending
= true;
199 /* invariant: empty line at the end */
200 int iLastItem
= count() - 1;
201 Assert(itemText(iLastItem
).isEmpty());
203 /* have previous command? check duplicate. */
206 const QString
strPrevCommand(itemText(iLastItem
- 1));
207 if (strCommand
== strPrevCommand
)
208 fNeedsAppending
= false;
213 /* history full? drop the oldest command. */
214 if (count() == maxCount())
220 /* insert before the empty line. */
221 insertItem(iLastItem
, strCommand
);
224 /* invariant: empty line at the end */
225 int iNewLastItem
= count() - 1;
226 Assert(itemText(iNewLastItem
).isEmpty());
228 /* select empty line to present "new" command line to the user */
229 setCurrentIndex(iNewLastItem
);
239 * V B o x D b g C o n s o l e
240 * V B o x D b g C o n s o l e
241 * V B o x D b g C o n s o l e
247 VBoxDbgConsole::VBoxDbgConsole(VBoxDbgGui
*a_pDbgGui
, QWidget
*a_pParent
/* = NULL*/)
248 : VBoxDbgBaseWindow(a_pDbgGui
, a_pParent
), m_pOutput(NULL
), m_pInput(NULL
), m_fInputRestoreFocus(false),
249 m_pszInputBuf(NULL
), m_cbInputBuf(0), m_cbInputBufAlloc(0),
250 m_pszOutputBuf(NULL
), m_cbOutputBuf(0), m_cbOutputBufAlloc(0),
251 m_pTimer(NULL
), m_fUpdatePending(false), m_Thread(NIL_RTTHREAD
), m_EventSem(NIL_RTSEMEVENT
),
252 m_fTerminate(false), m_fThreadTerminated(false)
254 setWindowTitle("VBoxDbg - Console");
257 * Create the output text box.
259 m_pOutput
= new VBoxDbgConsoleOutput(this);
261 /* try figure a suitable size */
262 QLabel
*pLabel
= new QLabel( "11111111111111111111111111111111111111111111111111111111111111111111111111111112222222222", this);
263 pLabel
->setFont(m_pOutput
->font());
264 QSize Size
= pLabel
->sizeHint();
266 Size
.setWidth((int)(Size
.width() * 1.10));
267 Size
.setHeight(Size
.width() / 2);
271 * Create the input combo box (with a label).
273 QHBoxLayout
*pLayout
= new QHBoxLayout();
274 //pLayout->setSizeConstraint(QLayout::SetMaximumSize);
276 pLabel
= new QLabel(" Command ");
277 pLayout
->addWidget(pLabel
);
278 pLabel
->setMaximumSize(pLabel
->sizeHint());
279 pLabel
->setAlignment(Qt::AlignCenter
);
281 m_pInput
= new VBoxDbgConsoleInput(NULL
);
282 pLayout
->addWidget(m_pInput
);
283 m_pInput
->setDuplicatesEnabled(false);
284 connect(m_pInput
, SIGNAL(commandSubmitted(const QString
&)), this, SLOT(commandSubmitted(const QString
&)));
287 pLabel
= new QLabel(" ");
288 pLayout
->addWidget(pLabel
);
289 pLabel
->setMaximumSize(20, m_pInput
->sizeHint().height() + 6);
290 pLabel
->setMinimumSize(20, m_pInput
->sizeHint().height() + 6);
293 QWidget
*pHBox
= new QWidget(this);
294 pHBox
->setLayout(pLayout
);
296 m_pInput
->setEnabled(false); /* (we'll get a ready notification) */
300 * Vertical layout box on the whole widget.
302 QVBoxLayout
*pVLayout
= new QVBoxLayout();
303 pVLayout
->setContentsMargins(0, 0, 0, 0);
304 pVLayout
->setSpacing(5);
305 pVLayout
->addWidget(m_pOutput
);
306 pVLayout
->addWidget(pHBox
);
310 * The tab order is from input to output, not the other way around as it is by default.
312 setTabOrder(m_pInput
, m_pOutput
);
313 m_fInputRestoreFocus
= true; /* hack */
318 m_pTimer
= new QTimer(this);
319 connect(m_pTimer
, SIGNAL(timeout()), SLOT(updateOutput()));
322 * Init the backend structure.
324 m_Back
.Core
.pfnInput
= backInput
;
325 m_Back
.Core
.pfnRead
= backRead
;
326 m_Back
.Core
.pfnWrite
= backWrite
;
327 m_Back
.Core
.pfnSetReady
= backSetReady
;
331 * Create the critical section, the event semaphore and the debug console thread.
333 int rc
= RTCritSectInit(&m_Lock
);
336 rc
= RTSemEventCreate(&m_EventSem
);
339 rc
= RTThreadCreate(&m_Thread
, backThread
, this, 0, RTTHREADTYPE_DEBUGGER
, RTTHREADFLAGS_WAITABLE
, "VBoxDbgC");
342 m_Thread
= NIL_RTTHREAD
;
347 m_pFocusToInput
= new QAction("", this);
348 m_pFocusToInput
->setShortcut(QKeySequence("Ctrl+L"));
349 addAction(m_pFocusToInput
);
350 connect(m_pFocusToInput
, SIGNAL(triggered(bool)), this, SLOT(actFocusToInput()));
352 m_pFocusToOutput
= new QAction("", this);
353 m_pFocusToOutput
->setShortcut(QKeySequence("Ctrl+O"));
354 addAction(m_pFocusToOutput
);
355 connect(m_pFocusToOutput
, SIGNAL(triggered(bool)), this, SLOT(actFocusToOutput()));
359 VBoxDbgConsole::~VBoxDbgConsole()
361 Assert(isGUIThread());
364 * Wait for the thread.
366 ASMAtomicWriteBool(&m_fTerminate
, true);
367 RTSemEventSignal(m_EventSem
);
368 if (m_Thread
!= NIL_RTTHREAD
)
370 int rc
= RTThreadWait(m_Thread
, 15000, NULL
);
372 m_Thread
= NIL_RTTHREAD
;
380 RTCritSectDelete(&m_Lock
);
381 RTSemEventDestroy(m_EventSem
);
387 RTMemFree(m_pszInputBuf
);
388 m_pszInputBuf
= NULL
;
391 m_cbInputBufAlloc
= 0;
393 delete m_pFocusToInput
;
394 m_pFocusToInput
= NULL
;
395 delete m_pFocusToOutput
;
396 m_pFocusToOutput
= NULL
;
401 VBoxDbgConsole::commandSubmitted(const QString
&rCommand
)
403 Assert(isGUIThread());
406 RTSemEventSignal(m_EventSem
);
408 QByteArray Utf8Array
= rCommand
.toUtf8();
409 const char *psz
= Utf8Array
.constData();
410 size_t cb
= strlen(psz
);
413 * Make sure we've got space for the input.
415 if (cb
+ m_cbInputBuf
>= m_cbInputBufAlloc
)
417 size_t cbNew
= RT_ALIGN_Z(cb
+ m_cbInputBufAlloc
+ 1, 128);
418 void *pv
= RTMemRealloc(m_pszInputBuf
, cbNew
);
424 m_pszInputBuf
= (char *)pv
;
425 m_cbInputBufAlloc
= cbNew
;
429 * Add the input and output it.
431 memcpy(m_pszInputBuf
+ m_cbInputBuf
, psz
, cb
);
433 m_pszInputBuf
[m_cbInputBuf
++] = '\n';
435 m_pOutput
->appendText(rCommand
+ "\n", true /*fClearSelection*/);
436 m_pOutput
->ensureCursorVisible();
438 m_fInputRestoreFocus
= m_pInput
->hasFocus(); /* dirty focus hack */
439 m_pInput
->setEnabled(false);
441 Log(("VBoxDbgConsole::commandSubmitted: %s (input-enabled=%RTbool)\n", psz
, m_pInput
->isEnabled()));
447 VBoxDbgConsole::updateOutput()
449 Assert(isGUIThread());
452 m_fUpdatePending
= false;
455 m_pOutput
->appendText(QString::fromUtf8((const char *)m_pszOutputBuf
, (int)m_cbOutputBuf
), false /*fClearSelection*/);
466 VBoxDbgConsole::lock()
468 RTCritSectEnter(&m_Lock
);
473 * Unlocks the object.
476 VBoxDbgConsole::unlock()
478 RTCritSectLeave(&m_Lock
);
484 * Checks if there is input.
486 * @returns true if there is input ready.
487 * @returns false if there not input ready.
488 * @param pBack Pointer to VBoxDbgConsole::m_Back.
489 * @param cMillies Number of milliseconds to wait on input data.
491 /*static*/ DECLCALLBACK(bool)
492 VBoxDbgConsole::backInput(PDBGCBACK pBack
, uint32_t cMillies
)
494 VBoxDbgConsole
*pThis
= VBOXDBGCONSOLE_FROM_DBGCBACK(pBack
);
498 if (!pThis
->m_cbInputBuf
)
501 * Wait outside the lock for the requested time, then check again.
504 RTSemEventWait(pThis
->m_EventSem
, cMillies
);
506 fRc
= pThis
->m_cbInputBuf
507 || ASMAtomicUoReadBool(&pThis
->m_fTerminate
);
518 * @returns VBox status code.
519 * @param pBack Pointer to VBoxDbgConsole::m_Back.
520 * @param pvBuf Where to put the bytes we read.
521 * @param cbBuf Maximum nymber of bytes to read.
522 * @param pcbRead Where to store the number of bytes actually read.
523 * If NULL the entire buffer must be filled for a
526 /*static*/ DECLCALLBACK(int)
527 VBoxDbgConsole::backRead(PDBGCBACK pBack
, void *pvBuf
, size_t cbBuf
, size_t *pcbRead
)
529 VBoxDbgConsole
*pThis
= VBOXDBGCONSOLE_FROM_DBGCBACK(pBack
);
530 Assert(pcbRead
); /** @todo implement this bit */
535 int rc
= VINF_SUCCESS
;
536 if (!ASMAtomicUoReadBool(&pThis
->m_fTerminate
))
538 if (pThis
->m_cbInputBuf
)
540 const char *psz
= pThis
->m_pszInputBuf
;
541 size_t cbRead
= RT_MIN(pThis
->m_cbInputBuf
, cbBuf
);
542 memcpy(pvBuf
, psz
, cbRead
);
544 pThis
->m_cbInputBuf
-= cbRead
;
546 memmove(pThis
->m_pszInputBuf
, psz
, pThis
->m_cbInputBuf
);
547 pThis
->m_pszInputBuf
[pThis
->m_cbInputBuf
] = '\0';
552 rc
= VERR_GENERAL_FAILURE
;
561 * @returns VBox status code.
562 * @param pBack Pointer to VBoxDbgConsole::m_Back.
563 * @param pvBuf What to write.
564 * @param cbBuf Number of bytes to write.
565 * @param pcbWritten Where to store the number of bytes actually written.
566 * If NULL the entire buffer must be successfully written.
568 /*static*/ DECLCALLBACK(int)
569 VBoxDbgConsole::backWrite(PDBGCBACK pBack
, const void *pvBuf
, size_t cbBuf
, size_t *pcbWritten
)
571 VBoxDbgConsole
*pThis
= VBOXDBGCONSOLE_FROM_DBGCBACK(pBack
);
572 int rc
= VINF_SUCCESS
;
575 if (cbBuf
+ pThis
->m_cbOutputBuf
>= pThis
->m_cbOutputBufAlloc
)
577 size_t cbNew
= RT_ALIGN_Z(cbBuf
+ pThis
->m_cbOutputBufAlloc
+ 1, 1024);
578 void *pv
= RTMemRealloc(pThis
->m_pszOutputBuf
, cbNew
);
584 return VERR_NO_MEMORY
;
586 pThis
->m_pszOutputBuf
= (char *)pv
;
587 pThis
->m_cbOutputBufAlloc
= cbNew
;
593 memcpy(pThis
->m_pszOutputBuf
+ pThis
->m_cbOutputBuf
, pvBuf
, cbBuf
);
594 pThis
->m_cbOutputBuf
+= cbBuf
;
595 pThis
->m_pszOutputBuf
[pThis
->m_cbOutputBuf
] = '\0';
599 if (ASMAtomicUoReadBool(&pThis
->m_fTerminate
))
600 rc
= VERR_GENERAL_FAILURE
;
603 * Tell the GUI thread to draw this text.
604 * We cannot do it from here without frequent crashes.
606 if (!pThis
->m_fUpdatePending
)
607 QApplication::postEvent(pThis
, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kUpdate
));
615 /*static*/ DECLCALLBACK(void)
616 VBoxDbgConsole::backSetReady(PDBGCBACK pBack
, bool fReady
)
618 VBoxDbgConsole
*pThis
= VBOXDBGCONSOLE_FROM_DBGCBACK(pBack
);
620 QApplication::postEvent(pThis
, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kInputEnable
));
625 * The Debugger Console Thread
627 * @returns VBox status code (ignored).
628 * @param Thread The thread handle.
629 * @param pvUser Pointer to the VBoxDbgConsole object.s
631 /*static*/ DECLCALLBACK(int)
632 VBoxDbgConsole::backThread(RTTHREAD Thread
, void *pvUser
)
634 VBoxDbgConsole
*pThis
= (VBoxDbgConsole
*)pvUser
;
635 LogFlow(("backThread: Thread=%p pvUser=%p\n", (void *)Thread
, pvUser
));
640 * Create and execute the console.
642 int rc
= pThis
->dbgcCreate(&pThis
->m_Back
.Core
, 0);
644 ASMAtomicUoWriteBool(&pThis
->m_fThreadTerminated
, true);
645 if (!ASMAtomicUoReadBool(&pThis
->m_fTerminate
))
646 QApplication::postEvent(pThis
, new VBoxDbgConsoleEvent(rc
== VINF_SUCCESS
647 ? VBoxDbgConsoleEvent::kTerminatedUser
648 : VBoxDbgConsoleEvent::kTerminatedOther
));
649 LogFlow(("backThread: returns %Rrc (m_fTerminate=%RTbool)\n", rc
, ASMAtomicUoReadBool(&pThis
->m_fTerminate
)));
655 VBoxDbgConsole::event(QEvent
*pGenEvent
)
657 Assert(isGUIThread());
658 if (pGenEvent
->type() == (QEvent::Type
)VBoxDbgConsoleEvent::kEventNumber
)
660 VBoxDbgConsoleEvent
*pEvent
= (VBoxDbgConsoleEvent
*)pGenEvent
;
662 switch (pEvent
->command())
664 /* make update pending. */
665 case VBoxDbgConsoleEvent::kUpdate
:
667 if (!m_fUpdatePending
)
669 m_fUpdatePending
= true;
670 m_pTimer
->setSingleShot(true);
676 /* Re-enable the input field and restore focus. */
677 case VBoxDbgConsoleEvent::kInputEnable
:
678 Log(("VBoxDbgConsole: kInputEnable (input-enabled=%RTbool)\n", m_pInput
->isEnabled()));
679 m_pInput
->setEnabled(true);
680 if ( m_fInputRestoreFocus
681 && !m_pInput
->hasFocus())
682 m_pInput
->setFocus(); /* this is a hack. */
683 m_fInputRestoreFocus
= false;
686 /* The thread terminated by user command (exit, quit, bye). */
687 case VBoxDbgConsoleEvent::kTerminatedUser
:
688 Log(("VBoxDbgConsole: kTerminatedUser (input-enabled=%RTbool)\n", m_pInput
->isEnabled()));
689 m_pInput
->setEnabled(false);
693 /* The thread terminated for some unknown reason., disable input */
694 case VBoxDbgConsoleEvent::kTerminatedOther
:
695 Log(("VBoxDbgConsole: kTerminatedOther (input-enabled=%RTbool)\n", m_pInput
->isEnabled()));
696 m_pInput
->setEnabled(false);
701 AssertMsgFailed(("command=%d\n", pEvent
->command()));
707 return VBoxDbgBaseWindow::event(pGenEvent
);
712 VBoxDbgConsole::closeEvent(QCloseEvent
*a_pCloseEvt
)
714 if (m_fThreadTerminated
)
716 a_pCloseEvt
->accept();
723 VBoxDbgConsole::actFocusToInput()
725 if (!m_pInput
->hasFocus())
726 m_pInput
->setFocus(Qt::ShortcutFocusReason
);
731 VBoxDbgConsole::actFocusToOutput()
733 if (!m_pOutput
->hasFocus())
734 m_pOutput
->setFocus(Qt::ShortcutFocusReason
);