GNU Octave  3.8.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
QWinTerminalImpl.cpp
Go to the documentation of this file.
1 /*
2 
3 Copyright (C) 2011, 2013 Michael Goffioul.
4 
5 This file is part of QConsole.
6 
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11 
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with this program. If not,
19 see <http://www.gnu.org/licenses/>.
20 
21 */
22 
23 #include <QApplication>
24 #include <QClipboard>
25 #include <QColor>
26 #include <QFont>
27 #include <QHBoxLayout>
28 #include <QPaintEvent>
29 #include <QPainter>
30 #include <QResizeEvent>
31 #include <QScrollBar>
32 #include <QtDebug>
33 #include <QThread>
34 #include <QTimer>
35 #include <QToolTip>
36 #include <QCursor>
37 #include <QMessageBox>
38 
39 #include <fcntl.h>
40 #include <io.h>
41 #include <stdio.h>
42 #include <stdarg.h>
43 #define WIN32_LEAN_AND_MEAN
44 #if ! defined (_WIN32_WINNT) && ! defined (NTDDI_VERSION)
45 #define _WIN32_WINNT 0x0500
46 #endif
47 #include <windows.h>
48 #include <cstring>
49 #include <csignal>
50 #include <limits>
51 
52 #include "QWinTerminalImpl.h"
53 #include "QTerminalColors.h"
54 
55 // Uncomment to log activity to LOGFILENAME
56 // #define DEBUG_QCONSOLE
57 #define LOGFILENAME "QConsole.log"
58 // Uncomment to create hidden console window
59 #define HIDDEN_CONSOLE
60 
61 #ifdef _MSC_VER
62 # pragma warning(disable : 4996)
63 #endif
64 
65 //////////////////////////////////////////////////////////////////////////////
66 
67 class QConsoleView : public QWidget
68 {
69 public:
70  QConsoleView (QWinTerminalImpl* parent = 0) : QWidget (parent), q (parent) { }
71  ~QConsoleView (void) { }
72 
73 protected:
74  void paintEvent (QPaintEvent* event) { q->viewPaintEvent (this, event); }
75  void resizeEvent (QResizeEvent* event) { q->viewResizeEvent (this, event); }
76 
77 private:
79 };
80 
81 //////////////////////////////////////////////////////////////////////////////
82 
83 class QConsoleThread : public QThread
84 {
85 public:
86  QConsoleThread (QWinTerminalImpl* console) : QThread (console), q (console) { }
87 
88 protected:
89  void run (void)
90  { q->start (); }
91 
92 private:
94 };
95 
96 //////////////////////////////////////////////////////////////////////////////
97 
98 static QString translateKey (QKeyEvent *ev)
99 {
100  QString esc = "\x1b";
101  QString s;
102 
103  if (ev->key () == Qt::Key_Delete)
104  s = esc + "[C\b";
105  else if (!ev->text ().isEmpty ())
106  s = ev->text ();
107  else
108  {
109 
110  switch (ev->key ())
111  {
112  case Qt::Key_Up:
113  s = esc + "[A";
114  break;
115 
116  case Qt::Key_Down:
117  s = esc + "[B";
118  break;
119 
120  case Qt::Key_Right:
121  s = esc + "[C";
122  break;
123 
124  case Qt::Key_Left:
125  s = esc + "[D";
126  break;
127 
128  case Qt::Key_Home:
129  s = esc + "[H";
130  break;
131 
132  case Qt::Key_End:
133  s = esc + "[F";
134  break;
135 
136  case Qt::Key_Insert:
137  s = esc + "[2~";
138  break;
139 
140  case Qt::Key_PageUp:
141  s = esc + "[5~";
142  break;
143 
144  case Qt::Key_PageDown:
145  s = esc + "[6~";
146  break;
147 
148  case Qt::Key_Escape:
149  s = esc;
150  break;
151 
152  default:
153  break;
154  }
155  }
156 
157  return s;
158 }
159 
161 {
162  friend class QWinTerminalImpl;
163 
164 public:
165 
167  {
171  };
172 
173  QConsolePrivate (QWinTerminalImpl* parent, const QString& cmd = QString ());
174  ~QConsolePrivate (void);
175 
176  void updateConsoleSize (bool sync = false);
177  void syncConsoleParameters (void);
178  void grabConsoleBuffer (CHAR_INFO* buf = 0);
179  void updateScrollBar (void);
180  void setScrollValue (int value);
181  void updateConsoleView (bool grab = true);
182  void monitorConsole (void);
183  void startCommand (void);
184  void sendConsoleText (const QString& s);
185  QRect cursorRect (void);
186 
187  void log (const char* fmt, ...);
188 
189  void closeStandardIO (int fd, DWORD stdHandleId, const char* name);
190  void setupStandardIO (DWORD stdHandleId, int fd, const char* name,
191  const char* devName);
192 
193  QPoint posToCell (const QPoint& pt);
194  QString getSelection (void);
195  void updateSelection (void);
196  void clearSelection (void);
197 
198  QColor backgroundColor (void) const;
199  QColor foregroundColor (void) const;
200  QColor selectionColor (void) const;
201  QColor cursorColor (void) const;
202 
203  void setBackgroundColor (const QColor& color);
204  void setForegroundColor (const QColor& color);
205  void setSelectionColor (const QColor& color);
206  void setCursorColor (bool useForegroundColor, const QColor& color);
207 
208  void drawTextBackground (QPainter& p, int cx1, int cy1, int cx2, int cy2,
209  int cw, int ch);
210 
211  void drawSelection (QPainter& p, int cx1, int cy1, int cx2, int cy2,
212  int cw, int ch);
213 
214  void drawCursor (QPainter& p);
215 
216  void drawText (QPainter& p, int cx1, int cy1, int cx2, int cy2,
217  int cw, int ch);
218 
219 private:
221 
222 private:
223  QFont m_font;
224  QString m_command;
227  QString m_title;
228 
229  QSize m_charSize;
232  QPoint m_cursorPos;
237 
241 
244 
245  HANDLE m_stdOut;
247  CHAR_INFO* m_buffer;
248  CHAR_INFO* m_tmpBuffer;
249  HANDLE m_process;
250 
252  QScrollBar* m_scrollBar;
255 
256  // The delay in milliseconds between redrawing blinking text.
257  static const int BLINK_DELAY = 500;
258 };
259 
260 static void maybeSwapPoints (QPoint& begin, QPoint& end)
261 {
262  if (end.y () < begin.y ()
263  || (end.y () == begin.y () && end.x () < begin.x ()))
264  qSwap (begin, end);
265 }
266 
267 //////////////////////////////////////////////////////////////////////////////
268 
270  : q (parent), m_command (cmd), m_hasBlinkingCursor (true),
271  m_cursorType (BlockCursor), m_beginSelection (0, 0),
272  m_endSelection (0, 0), m_settingSelection (false),
273  m_process (NULL), m_inWheelEvent (false)
274 {
275  log (NULL);
276 
277  // Possibly detach from any existing console
278  log ("Detaching from existing console (if any)...\n");
279  FreeConsole ();
280  log ("Closing standard IO...\n");
281  closeStandardIO (0, STD_INPUT_HANDLE, "STDIN");
282  closeStandardIO (1, STD_OUTPUT_HANDLE, "STDOUT");
283  closeStandardIO (2, STD_ERROR_HANDLE, "STDERR");
284 
285 #ifdef HIDDEN_CONSOLE
286  HWINSTA hOrigSta, hNewSta;
287 
288  // Create new (hidden) console
289  hOrigSta = GetProcessWindowStation ();
290  hNewSta = CreateWindowStation (NULL, 0, GENERIC_ALL, NULL);
291  log ("Current Windows station: %p.\nNew Windows station: %p.\n", hOrigSta,
292  hNewSta);
293  if (! SetProcessWindowStation (hNewSta))
294  log ("Failed to switch to new Windows station.\n");
295 #endif
296  if (! AllocConsole ())
297  log ("Failed to create new console.\n");
298 #ifdef HIDDEN_CONSOLE
299  if (! SetProcessWindowStation (hOrigSta))
300  log ("Failed to restore original Windows station.\n");
301  if (! CloseWindowStation (hNewSta))
302  log ("Failed to close new Windows station.\n");
303 #endif
304 
305  log ("New (hidden) console created.\n");
306 
307  setupStandardIO (STD_INPUT_HANDLE, 0, "STDIN", "CONIN$");
308  setupStandardIO (STD_OUTPUT_HANDLE, 1, "STDOUT", "CONOUT$");
309  setupStandardIO (STD_ERROR_HANDLE, 2, "STDERR", "CONOUT$");
310 
311  log ("Standard input/output/error set up.\n");
312 
313  *stdin = *(fdopen (0, "rb"));
314  *stdout = *(fdopen (1, "wb"));
315  *stderr = *(fdopen (2, "wb"));
316 
317  log ("POSIX standard streams created.\n");
318 
319  setvbuf (stdin, NULL, _IONBF, 0);
320  setvbuf (stdout, NULL, _IONBF, 0);
321  setvbuf (stderr, NULL, _IONBF, 0);
322 
323  log ("POSIX standard stream buffers adjusted.\n");
324 
325  HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);
326 
327  log ("Console allocated: hStdOut: %p\n", hStdOut);
328 
329  m_stdOut = hStdOut;
330  m_consoleWindow = GetConsoleWindow ();
331 
332  // In case the console window hasn't been created hidden...
333 #ifdef HIDDEN_CONSOLE
334  ShowWindow (m_consoleWindow, SW_HIDE);
335 #endif
336 
337  CONSOLE_SCREEN_BUFFER_INFO sbi;
338 
339  GetConsoleScreenBufferInfo (hStdOut, &sbi);
340  m_bufferSize = QSize (sbi.dwSize.X, qMax (sbi.dwSize.Y, (SHORT)500));
341  m_consoleRect = QRect (sbi.srWindow.Left, sbi.srWindow.Top,
342  sbi.srWindow.Right - sbi.srWindow.Left + 1,
343  sbi.srWindow.Bottom - sbi.srWindow.Top + 1);
344  m_cursorPos = QPoint (sbi.dwCursorPosition.X, sbi.dwCursorPosition.Y);
345 
346  log ("Initial console parameters:\n");
347  log (" buffer size: %d x %d\n", m_bufferSize.width (),
348  m_bufferSize.height ());
349  log (" window: (%d, %d) -> (%d, %d) [%d x %d]\n",
350  m_consoleRect.left (), m_consoleRect.top (),
351  m_consoleRect.right (), m_consoleRect.bottom (),
352  m_consoleRect.width (), m_consoleRect.height ());
353 
354  wchar_t titleBuf[260];
355  GetConsoleTitleW (titleBuf, sizeof (titleBuf));
356  q->setWindowTitle (QString::fromWCharArray (titleBuf));
357 
358  m_font.setFamily ("Lucida Console");
359  m_font.setPointSize (9);
360  m_font.setStyleHint (QFont::TypeWriter);
361 
362  m_buffer = m_tmpBuffer = 0;
363 
364  m_consoleView = new QConsoleView (parent);
365  m_scrollBar = new QScrollBar (Qt::Vertical, parent);
366 
367  QHBoxLayout* l = new QHBoxLayout (parent);
368  l->setContentsMargins (0, 0, 0, 0);
369  l->setSpacing (0);
370  l->addWidget (m_consoleView, 1);
371  l->addWidget (m_scrollBar, 0);
372 
373  // Choose 15 (0xF) as index into the Windows console color map for the
374  // background and 0 (0x0) as the index for the foreground. This
375  // selection corresponds to the indices used in the foregroundColor,
376  // setForegroundColor, backgroundColor, and SetBackgroundColor
377  // functions.
378 
379  SetConsoleTextAttribute (m_stdOut, 0xF0);
380 
381  // Defaults.
382  setBackgroundColor (Qt::white);
383  setForegroundColor (Qt::black);
384  setSelectionColor (Qt::lightGray);
385  setCursorColor (false, Qt::darkGray);
386 
387  // FIXME -- should we set the palette?
388  QPalette palette (backgroundColor ());
389  m_consoleView->setPalette (palette);
390 
391  m_consoleView->setAutoFillBackground (true);
392 
393  m_consoleView->setFont (m_font);
394  parent->setFocusPolicy (Qt::StrongFocus);
395  parent->winId ();
396 
397  updateScrollBar ();
398 
399  m_consoleWatcher = new QTimer (parent);
400  m_consoleWatcher->setInterval (10);
401  m_consoleWatcher->setSingleShot (false);
402 
403  m_blinkCursorTimer = new QTimer (parent);
404  QObject::connect (m_blinkCursorTimer, SIGNAL (timeout()),
405  q, SLOT (blinkCursorEvent ()));
406 
407  QObject::connect (m_scrollBar, SIGNAL (valueChanged (int)),
408  q, SLOT (scrollValueChanged (int)));
409  QObject::connect (m_consoleWatcher, SIGNAL (timeout (void)),
410  q, SLOT (monitorConsole (void)));
411 
412  m_consoleWatcher->start ();
413 
414  if (m_command.isEmpty ())
415  m_consoleThread = 0;
416  else
417  {
419  QObject::connect (m_consoleThread, SIGNAL (finished (void)),
420  q, SIGNAL (terminated (void)));
421  m_consoleThread->start ();
422  }
423 }
424 
425 //////////////////////////////////////////////////////////////////////////////
426 
428 {
429  if (m_consoleThread && m_consoleThread->isRunning () && m_process)
430  {
431  TerminateProcess (m_process, (UINT)-1);
432  m_consoleThread->wait ();
433  }
434  if (m_buffer)
435  delete [] m_buffer;
436  if (m_tmpBuffer)
437  delete [] m_tmpBuffer;
438 }
439 
440 //////////////////////////////////////////////////////////////////////////////
441 
442 void QConsolePrivate::setupStandardIO (DWORD stdHandleId, int targetFd,
443  const char* name, const char* devName)
444 {
445  log ("Opening %s...\n", devName);
446 
447  int fd = open (devName, _O_RDWR | _O_BINARY);
448 
449  if (fd != -1)
450  {
451  if (fd != targetFd)
452  {
453  log ("Opened %s is not at target file descriptor %d, "
454  "duplicating...\n", name, targetFd);
455  if (dup2 (fd, targetFd) == -1)
456  log ("Failed to duplicate file descriptor: errno=%d.\n", errno);
457  if (close (fd) == -1)
458  log ("Failed to close original file descriptor: errno=%d.\n",
459  errno);
460  }
461  else
462  log ("%s opened and assigned to file descriptor %d.\n", devName, fd);
463  if (! SetStdHandle (stdHandleId, (HANDLE) _get_osfhandle (targetFd)))
464  log ("Failed to re-assign %s: error=%08x.\n", name, GetLastError ());
465  }
466  else
467  log ("Failed to open %s: errno=%d.\n", devName, errno);
468 }
469 
470 QPoint QConsolePrivate::posToCell (const QPoint& p)
471 {
472  return QPoint (m_consoleRect.left () + p.x () / m_charSize.width (),
473  m_consoleRect.top () + p.y () / m_charSize.height ());
474 }
475 
477 {
478  QString selection;
479 
480  QPoint begin = m_beginSelection;
481  QPoint end = m_endSelection;
482 
483  maybeSwapPoints (begin, end);
484 
485  if (begin != end)
486  {
487  CHAR_INFO* buf;
488  COORD bufSize, bufCoord;
489  SMALL_RECT bufRect;
490  int nr;
491 
492  nr = end.y () - begin.y () + 1;
493  buf = new CHAR_INFO[m_bufferSize.width () * nr];
494  bufSize.X = m_bufferSize.width ();
495  bufSize.Y = nr;
496  bufCoord.X = 0;
497  bufCoord.Y = 0;
498 
499  bufRect.Left = 0;
500  bufRect.Right = m_bufferSize.width ();
501  bufRect.Top = begin.y ();
502  bufRect.Bottom = end.y ();
503 
504  if (ReadConsoleOutput (m_stdOut, buf, bufSize, bufCoord, &bufRect))
505  {
506  int start_pos = begin.x ();
507  int end_pos = (nr - 1) * m_bufferSize.width () + end.x ();
508  int lastNonSpace = -1;
509 
510  for (int i = start_pos; i <= end_pos; i++)
511  {
512  if (i && (i % m_bufferSize.width ()) == 0)
513  {
514  if (lastNonSpace >= 0)
515  selection.truncate (lastNonSpace);
516  selection.append ('\n');
517  lastNonSpace = selection.length ();
518  }
519 
520  QChar c (buf[i].Char.UnicodeChar);
521 
522  selection.append (c);
523  if (! c.isSpace ())
524  lastNonSpace = selection.length ();
525  }
526 
527  if (lastNonSpace >= 0)
528  selection.truncate (lastNonSpace);
529  }
530  }
531 
532  return selection;
533 }
534 
536 {
537  QPoint begin = m_beginSelection;
538  QPoint end = m_endSelection;
539 
540  maybeSwapPoints (begin, end);
541 
542  begin.rx () = 0;
543  end.rx () = m_consoleRect.width ();
544 
545  m_consoleView->update ();
546 }
547 
549 {
550  m_beginSelection = m_endSelection = QPoint ();
551 
552  m_consoleView->update ();
553 }
554 
556 {
557  return m_colors[15];
558 }
559 
561 {
562  return m_colors[0];
563 }
564 
566 {
567  return m_selectionColor;
568 }
569 
570 QColor QConsolePrivate::cursorColor (void) const
571 {
572  return m_cursorColor.isValid () ? m_cursorColor : foregroundColor ();
573 }
574 
575 void QConsolePrivate::setBackgroundColor (const QColor& color)
576 {
577  m_colors[15] = color;
578 }
579 
580 void QConsolePrivate::setForegroundColor (const QColor& color)
581 {
582  m_colors[0] = color;
583 }
584 
585 void QConsolePrivate::setSelectionColor (const QColor& color)
586 {
587  m_selectionColor = color;
588 }
589 
590 void QConsolePrivate::setCursorColor (bool useForegroundColor,
591  const QColor& color)
592 {
593  m_cursorColor = useForegroundColor ? QColor () : color;
594 }
595 
596 void QConsolePrivate::drawTextBackground (QPainter& p, int cx1, int cy1,
597  int cx2, int cy2, int cw, int ch)
598 {
599  p.save ();
600 
601  int ascent = p.fontMetrics ().ascent ();
602  int stride = m_consoleRect.width ();
603  int y = ascent + cy1 * ch;;
604 
605  for (int j = cy1; j <= cy2; j++, y += ch)
606  {
607  int len = 0;
608  bool hasChar = false;
609  int x = cx1 * cw;
610  WORD attr = 0;
611 
612  for (int i = cx1; i <= cx2; i++)
613  {
614  CHAR_INFO* ci = &(m_buffer[stride*j+i]);
615 
616  if ((ci->Attributes & 0x00ff) != attr)
617  {
618  // Character attributes changed
619  if (len != 0)
620  {
621  // String buffer not empty -> draw it
622  if (hasChar || (attr & 0x00f0))
623  {
624  if (attr & 0x00f0)
625  p.fillRect (x, y-ascent, len * cw, ch, p.brush ());
626  }
627 
628  x += (len * cw);
629  len = 0;
630  hasChar = false;
631  }
632  // Update current brush and store current attributes
633  attr = (ci->Attributes & 0x00ff);
634  p.setBrush (m_colors[(attr >> 4) & 0x000f]);
635  }
636 
637  // Append current character to the string buffer
638  len++;
639  if (ci->Char.UnicodeChar != L' ')
640  hasChar = true;
641  }
642 
643  if (len != 0 && (hasChar || (attr & 0x00f0)))
644  {
645  // Line end reached, but string buffer not empty -> draw it
646  // No need to update s or x, they will be reset on the next
647  // for-loop iteration
648 
649  if (attr & 0x00f0)
650  p.fillRect (x, y-ascent, len * cw, ch, p.brush ());
651  }
652  }
653 
654  p.restore ();
655 }
656 
657 void QConsolePrivate::drawSelection (QPainter& p, int cx1, int cy1,
658  int cx2, int cy2, int cw, int ch)
659 {
660  p.save ();
661 
662  QPoint begin = m_beginSelection;
663  QPoint end = m_endSelection;
664 
665  bool haveSelection = (begin != end);
666 
667  if (haveSelection)
668  maybeSwapPoints (begin, end);
669 
670  int scrollOffset = m_consoleRect.top ();
671 
672  begin.ry () -= scrollOffset;
673  end.ry () -= scrollOffset;
674 
675  int ascent = p.fontMetrics ().ascent ();
676  int stride = m_consoleRect.width ();
677 
678  int y = ascent + cy1 * ch;;
679  for (int j = cy1; j <= cy2; j++, y += ch)
680  {
681  int charsThisLine = 0;
682  int len = 0;
683  bool hasChar = false;
684  WORD attr = 0;
685 
686  for (int i = cx1; i <= cx2; i++)
687  {
688  CHAR_INFO* ci = &(m_buffer[stride*j+i]);
689 
690  if ((ci->Attributes & 0x00ff) != attr)
691  {
692  // Character attributes changed
693  if (len != 0)
694  {
695  charsThisLine += len;
696  len = 0;
697  hasChar = false;
698  }
699 
700  // Store current attributes
701  attr = (ci->Attributes & 0x00ff);
702  }
703 
704  // Append current character to the string buffer
705  len++;
706  if (ci->Char.UnicodeChar != L' ')
707  hasChar = true;
708  }
709 
710  if (len != 0 && (hasChar || (attr & 0x00f0)))
711  charsThisLine += len;
712 
713  if (haveSelection && j >= begin.y () && j <= end.y ())
714  {
715  int selectionBegin = j == begin.y () ? begin.x (): 0;
716 
717  int len = ((j == end.y () && end.x () < charsThisLine)
718  ? end.x () - selectionBegin + 1
719  : stride - selectionBegin);
720 
721  p.fillRect (selectionBegin * cw, y-ascent, len * cw, ch,
722  selectionColor ());
723  }
724  }
725 
726  p.restore ();
727 }
728 
729 void QConsolePrivate::drawCursor (QPainter& p)
730 {
731  if (! m_cursorBlinking)
732  {
733  p.save ();
734 
735  QRect rect = cursorRect ();
736  QColor color = cursorColor ();
737 
738  p.setPen (color);
739 
741  {
742  if (q->hasFocus ())
743  p.fillRect (rect, color);
744  else
745  {
746  // draw the cursor outline, adjusting the area so that
747  // it is draw entirely inside 'rect'
748 
749  int penWidth = qMax (1, p.pen().width());
750 
751  p.drawRect (rect.adjusted (penWidth/2, penWidth/2,
752  - penWidth/2 - penWidth%2,
753  - penWidth/2 - penWidth%2));
754  }
755  }
757  {
758  p.drawLine (rect.left (), rect.bottom (),
759  rect.right (), rect.bottom ());
760  }
762  {
763  p.drawLine (rect.left (), rect.top (),
764  rect.left (), rect.bottom ());
765  }
766 
767  p.restore ();
768  }
769 }
770 
771 void QConsolePrivate::drawText (QPainter& p, int cx1, int cy1,
772  int cx2, int cy2, int cw, int ch)
773 {
774  p.save ();
775 
776  p.setFont (m_font);
777  p.setPen (foregroundColor ());
778 
779  QString s;
780  s.reserve (cx2 - cx1 + 1);
781 
782  int ascent = p.fontMetrics ().ascent ();
783  int stride = m_consoleRect.width ();
784 
785  int y = ascent + cy1 * ch;;
786  for (int j = cy1; j <= cy2; j++, y += ch)
787  {
788  // Reset string buffer and starting X coordinate
789  s.clear ();
790  bool hasChar = false;
791  int x = cx1 * cw;
792  WORD attr = 0;
793 
794  for (int i = cx1; i <= cx2; i++)
795  {
796  CHAR_INFO* ci = &(m_buffer[stride*j+i]);
797 
798  if ((ci->Attributes & 0x00ff) != attr)
799  {
800  // Character attributes changed
801  if (! s.isEmpty ())
802  {
803  // String buffer not empty -> draw it
804  if (hasChar || (attr & 0x00f0))
805  p.drawText (x, y, s);
806 
807  x += (s.length () * cw);
808  s.clear ();
809  hasChar = false;
810  }
811  // Update current pen and store current attributes
812  attr = (ci->Attributes & 0x00ff);
813  p.setPen (m_colors[attr & 0x000f]);
814  }
815 
816  // Append current character to the string buffer
817  s.append (ci->Char.UnicodeChar);
818  if (ci->Char.UnicodeChar != L' ')
819  hasChar = true;
820  }
821 
822  if (! s.isEmpty () && (hasChar || (attr & 0x00f0)))
823  {
824  // Line end reached, but string buffer not empty -> draw it
825  // No need to update s or x, they will be reset on the next
826  // for-loop iteration
827 
828  p.drawText (x, y, s);
829  }
830  }
831 
832  p.restore ();
833 }
834 
835 /////////////////////////////////////////////////////////////////////////////
836 
837 void QConsolePrivate::closeStandardIO (int fd, DWORD stdHandleId,
838  const char* name)
839 {
840  if (close (fd) == -1)
841  log ("Failed to close file descriptor %d: errno=%d.\n", fd, errno);
842  if (! CloseHandle (GetStdHandle (stdHandleId)))
843  log ("Failed to close Win32 %s: error=%08x.\n", name, GetLastError ());
844 }
845 
846 //////////////////////////////////////////////////////////////////////////////
847 
848 void QConsolePrivate::log (const char* fmt, ...)
849 {
850 #ifdef DEBUG_QCONSOLE
851  if (fmt)
852  {
853  va_list l;
854  FILE* flog = fopen (LOGFILENAME, "ab");
855 
856  va_start (l, fmt);
857  vfprintf (flog, fmt, l);
858  va_end (l);
859  fclose (flog);
860  }
861  else
862  {
863  // Special case to re-initialize the log file
864  FILE* flog = fopen (LOGFILENAME, "w");
865  fclose (flog);
866  }
867 #else
868  Q_UNUSED (fmt);
869 #endif
870 }
871 
872 //////////////////////////////////////////////////////////////////////////////
873 
875 {
876  QFontMetrics fm (m_font);
877  QSize winSize = m_consoleView->size ();
878 
879  m_charSize.rwidth () = fm.averageCharWidth ();
880  m_charSize.rheight () = fm.lineSpacing ();
881 
882  m_consoleRect.setWidth (winSize.width () / fm.averageCharWidth ());
883  m_consoleRect.setHeight (winSize.height () / fm.lineSpacing ());
884 
885  m_bufferSize.rwidth () = m_consoleRect.width ();
886  m_bufferSize.rheight () = qMax (m_bufferSize.height (),
887  m_consoleRect.height ());
888 
889  m_consoleRect.moveLeft (0);
890  if (m_consoleRect.bottom () >= m_bufferSize.height ())
891  m_consoleRect.moveTop (m_bufferSize.height () - m_consoleRect.height ());
892 
893  log ("Console resized:\n");
894  log (" widget size: %d x %d\n", winSize.width (), winSize.height ());
895  log (" buffer size: %d x %d\n", m_bufferSize.width (),
896  m_bufferSize.height ());
897  log (" window: (%d, %d) -> (%d, %d) [%d x %d]\n",
898  m_consoleRect.left (), m_consoleRect.top (),
899  m_consoleRect.right (), m_consoleRect.bottom (),
900  m_consoleRect.width (), m_consoleRect.height ());
901 
902  if (sync)
904 
905  updateScrollBar ();
906 }
907 
908 //////////////////////////////////////////////////////////////////////////////
909 
911 {
912  CONSOLE_SCREEN_BUFFER_INFO sbi;
913  HANDLE hStdOut = m_stdOut;
914 
915  GetConsoleScreenBufferInfo (hStdOut, &sbi);
916 
917  COORD bs;
918  SMALL_RECT sr;
919 
920  bs.X = sbi.dwSize.X;
921  bs.Y = m_bufferSize.height ();
922  sr.Left = sbi.srWindow.Left;
923  sr.Right = sbi.srWindow.Right;
924  sr.Top = m_consoleRect.top ();
925  sr.Bottom = m_consoleRect.bottom ();
926 
927  if (bs.Y > sbi.dwSize.Y)
928  {
929  SetConsoleScreenBufferSize (hStdOut, bs);
930  SetConsoleWindowInfo (hStdOut, TRUE, &sr);
931  }
932  else
933  {
934  SetConsoleWindowInfo (hStdOut, TRUE, &sr);
935  SetConsoleScreenBufferSize (hStdOut, bs);
936  }
937 
938  bs.X = m_bufferSize.width ();
939  sr.Left = m_consoleRect.left ();
940  sr.Right = m_consoleRect.right ();
941 
942  if (bs.X > sbi.dwSize.X)
943  {
944  SetConsoleScreenBufferSize (hStdOut, bs);
945  SetConsoleWindowInfo (hStdOut, TRUE, &sr);
946  }
947  else
948  {
949  SetConsoleWindowInfo (hStdOut, TRUE, &sr);
950  SetConsoleScreenBufferSize (hStdOut, bs);
951  }
952 
953  log ("Sync'ing console parameters:\n");
954  log (" buffer size: %d x %d\n", bs.X, bs.Y);
955  log (" window: (%d, %d) -> (%d, %d)\n",
956  sr.Left, sr.Top, sr.Right, sr.Bottom);
957 
958  if (m_buffer)
959  delete [] m_buffer;
960  if (m_tmpBuffer)
961  delete [] m_tmpBuffer;
962 
963  int bufSize = m_consoleRect.width () * m_consoleRect.height ();
964 
965  m_buffer = new CHAR_INFO[bufSize];
966  m_tmpBuffer = new CHAR_INFO[bufSize];
967 }
968 
969 //////////////////////////////////////////////////////////////////////////////
970 
972 {
973  COORD bs, bc;
974  SMALL_RECT r;
975 
976  bs.X = m_consoleRect.width ();
977  bs.Y = m_consoleRect.height ();
978  bc.X = 0;
979  bc.Y = 0;
980 
981  r.Left = m_consoleRect.left ();
982  r.Top = m_consoleRect.top ();
983  r.Right = m_consoleRect.right ();
984  r.Bottom = m_consoleRect.bottom ();
985 
986  if (! ReadConsoleOutput (m_stdOut, (buf ? buf : m_buffer), bs, bc, &r))
987  qCritical ("cannot read console output");
988 }
989 
990 //////////////////////////////////////////////////////////////////////////////
991 
993 {
994  m_scrollBar->setMinimum (0);
995  if (m_bufferSize.height () > m_consoleRect.height ())
996  m_scrollBar->setMaximum (m_bufferSize.height () - m_consoleRect.height ());
997  else
998  m_scrollBar->setMaximum (0);
999  m_scrollBar->setSingleStep (1);
1000  m_scrollBar->setPageStep (m_consoleRect.height ());
1001  m_scrollBar->setValue (m_consoleRect.top ());
1002 
1003  log ("Scrollbar parameters updated: %d/%d/%d/%d\n",
1004  m_scrollBar->minimum (), m_scrollBar->maximum (),
1005  m_scrollBar->singleStep (), m_scrollBar->pageStep ());
1006 }
1007 
1008 //////////////////////////////////////////////////////////////////////////////
1009 
1011 {
1012  if (value == m_consoleRect.top ())
1013  return;
1014 
1015  SMALL_RECT r;
1016  HANDLE hStdOut = m_stdOut;
1017 
1018  if (value + m_consoleRect.height () > m_bufferSize.height ())
1019  value = m_bufferSize.height () - m_consoleRect.height ();
1020 
1021  r.Left = m_consoleRect.left ();
1022  r.Top = value;
1023  r.Right = m_consoleRect.right ();
1024  r.Bottom = value + m_consoleRect.height () - 1;
1025 
1026  log ("Scrolling window: (%d, %d) -> (%d, %d) [%d x %d]\n",
1027  r.Left, r.Top, r.Right, r.Bottom,
1028  r.Right - r.Left + 1, r.Bottom - r.Top + 1);
1029 
1030  if (SetConsoleWindowInfo (hStdOut, TRUE, &r))
1031  {
1032  m_consoleRect.moveTop (value);
1033  updateConsoleView ();
1034  }
1035 }
1036 
1037 //////////////////////////////////////////////////////////////////////////////
1038 
1040 {
1041  if (grab)
1042  grabConsoleBuffer ();
1043  m_consoleView->update ();
1044  m_consoleWatcher->start ();
1045 }
1046 
1047 //////////////////////////////////////////////////////////////////////////////
1048 
1050 {
1051  CONSOLE_SCREEN_BUFFER_INFO sbi;
1052  HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);
1053 
1054  static wchar_t titleBuf[260];
1055 
1056  GetConsoleTitleW (titleBuf, sizeof (titleBuf));
1057  QString title = QString::fromWCharArray (titleBuf);
1058 
1059  if (title != m_title)
1060  {
1061  q->setWindowTitle (title);
1062  emit q->titleChanged (title);
1063  }
1064 
1065  if (GetConsoleScreenBufferInfo (hStdOut, &sbi))
1066  {
1067  if (m_bufferSize.width () != sbi.dwSize.X
1068  || m_bufferSize.height () != sbi.dwSize.Y)
1069  {
1070  // Buffer size changed
1071  m_bufferSize.rwidth () = sbi.dwSize.X;
1072  m_bufferSize.rheight () = sbi.dwSize.Y;
1073  updateScrollBar ();
1074  }
1075 
1076  if (m_cursorPos.x () != sbi.dwCursorPosition.X
1077  || m_cursorPos.y () != sbi.dwCursorPosition.Y)
1078  {
1079  // Cursor position changed
1080  m_consoleView->update
1081  ((m_cursorPos.x () - sbi.srWindow.Left) * m_charSize.width (),
1082  (m_cursorPos.y () - sbi.srWindow.Top) * m_charSize.height (),
1083  m_charSize.width (), m_charSize.height ());
1084  m_cursorPos.rx () = sbi.dwCursorPosition.X;
1085  m_cursorPos.ry () = sbi.dwCursorPosition.Y;
1086  m_consoleView->update
1087  ((m_cursorPos.x () - sbi.srWindow.Left) * m_charSize.width (),
1088  (m_cursorPos.y () - sbi.srWindow.Top) * m_charSize.height (),
1089  m_charSize.width (), m_charSize.height ());
1090  }
1091 
1092  if (m_consoleRect.left () != sbi.srWindow.Left
1093  || m_consoleRect.right () != sbi.srWindow.Right
1094  || m_consoleRect.top () != sbi.srWindow.Top
1095  || m_consoleRect.bottom () != sbi.srWindow.Bottom)
1096  {
1097  // Console window changed
1098  m_consoleRect = QRect (sbi.srWindow.Left, sbi.srWindow.Top,
1099  sbi.srWindow.Right - sbi.srWindow.Left + 1,
1100  sbi.srWindow.Bottom - sbi.srWindow.Top + 1);
1101  updateScrollBar ();
1102  updateConsoleView ();
1103  return;
1104  }
1105 
1106  if (m_tmpBuffer && m_buffer)
1107  {
1109  if (memcmp (m_tmpBuffer, m_buffer,
1110  sizeof (CHAR_INFO) * m_consoleRect.width () *
1111  m_consoleRect.height ()))
1112  {
1113  // FIXME: compute the area to update based on the
1114  // difference between the 2 buffers.
1115  qSwap (m_buffer, m_tmpBuffer);
1116  updateConsoleView (false);
1117  }
1118  }
1119  }
1120 }
1121 
1122 //////////////////////////////////////////////////////////////////////////////
1123 
1125 {
1126  QString cmd = m_command;
1127 
1128  if (cmd.isEmpty ())
1129  cmd = qgetenv ("COMSPEC").constData ();
1130 
1131  if (! cmd.isEmpty ())
1132  {
1133  STARTUPINFO si;
1134  PROCESS_INFORMATION pi;
1135 
1136  ZeroMemory (&si, sizeof (si));
1137  si.cb = sizeof (si);
1138  ZeroMemory (&pi, sizeof (pi));
1139 
1140  if (CreateProcessW (NULL,
1141  (LPWSTR)cmd.unicode (),
1142  NULL,
1143  NULL,
1144  TRUE,
1145  0,
1146  NULL,
1147  NULL,
1148  &si,
1149  &pi))
1150  {
1151  CloseHandle (pi.hThread);
1152  m_process = pi.hProcess;
1153  WaitForSingleObject (m_process, INFINITE);
1154  CloseHandle (m_process);
1155  m_process = NULL;
1156  }
1157  }
1158 }
1159 
1160 //////////////////////////////////////////////////////////////////////////////
1161 
1162 void QConsolePrivate::sendConsoleText (const QString& s)
1163 {
1164  // Send the string in chunks of 512 characters. Each character is
1165  // translated into an equivalent keypress event.
1166 
1167 #define TEXT_CHUNK_SIZE 512
1168 
1169  // clear any selection on inserting text
1170  clearSelection();
1171 
1172  int len = s.length ();
1173  INPUT_RECORD events[TEXT_CHUNK_SIZE];
1174  DWORD nEvents = 0, written;
1175  HANDLE hStdIn = GetStdHandle (STD_INPUT_HANDLE);
1176 
1177  ZeroMemory (events, sizeof (events));
1178 
1179  for (int i = 0; i < len; i++)
1180  {
1181  QChar c = s.at (i);
1182 
1183  if (c == L'\r' || c == L'\n')
1184  {
1185  if (c == L'\r' && i < (len - 1) && s.at (i+1) == L'\n')
1186  i++;
1187  if (nEvents)
1188  {
1189  WriteConsoleInput (hStdIn, events, nEvents, &written);
1190  nEvents = 0;
1191  ZeroMemory (events, sizeof (events));
1192  }
1193  PostMessage (m_consoleWindow, WM_KEYDOWN, VK_RETURN, 0x001C0001);
1194  PostMessage (m_consoleWindow, WM_KEYDOWN, VK_RETURN, 0xC01C0001);
1195  }
1196  else
1197  {
1198  events[nEvents].EventType = KEY_EVENT;
1199  events[nEvents].Event.KeyEvent.bKeyDown = TRUE;
1200  events[nEvents].Event.KeyEvent.wRepeatCount = 1;
1201  events[nEvents].Event.KeyEvent.wVirtualKeyCode =
1202  LOBYTE (VkKeyScan (c.unicode ()));
1203  events[nEvents].Event.KeyEvent.wVirtualScanCode = 0;
1204  events[nEvents].Event.KeyEvent.uChar.UnicodeChar = c.unicode ();
1205  events[nEvents].Event.KeyEvent.dwControlKeyState = 0;
1206  nEvents++;
1207  }
1208 
1209  if (nEvents == TEXT_CHUNK_SIZE
1210  || (nEvents > 0 && i == (len - 1)))
1211  {
1212  WriteConsoleInput (hStdIn, events, nEvents, &written);
1213  nEvents = 0;
1214  ZeroMemory (events, sizeof (events));
1215  }
1216  }
1217 }
1218 
1219 QRect
1221 {
1222  int cw = m_charSize.width ();
1223  int ch = m_charSize.height ();
1224 
1225  return QRect ((m_cursorPos.x () - m_consoleRect.x ()) * cw,
1226  (m_cursorPos.y () - m_consoleRect.y ()) * ch,
1227  cw, ch);
1228 }
1229 
1230 //////////////////////////////////////////////////////////////////////////////
1231 
1233  : QTerminal (parent), d (new QConsolePrivate (this))
1234 {
1235  installEventFilter (this);
1236 
1237  connect (this, SIGNAL (set_global_shortcuts_signal (bool)),
1238  parent, SLOT (set_global_shortcuts (bool)));
1239 }
1240 
1241 //////////////////////////////////////////////////////////////////////////////
1242 
1243 QWinTerminalImpl::QWinTerminalImpl (const QString& cmd, QWidget* parent)
1244  : QTerminal (parent), d (new QConsolePrivate (this, cmd))
1245 {
1246 }
1247 
1248 //////////////////////////////////////////////////////////////////////////////
1249 
1251 {
1252  delete d;
1253 }
1254 
1255 void QWinTerminalImpl::mouseMoveEvent (QMouseEvent *event)
1256 {
1257  if (d->m_settingSelection)
1258  {
1259  d->m_endSelection = d->posToCell (event->pos ());
1260 
1261  updateSelection ();
1262  }
1263 }
1264 
1265 void QWinTerminalImpl::mousePressEvent (QMouseEvent *event)
1266 {
1267  if (event->button () == Qt::LeftButton)
1268  {
1269  d->m_settingSelection = true;
1270 
1271  d->m_beginSelection = d->posToCell (event->pos ());
1272  }
1273 }
1274 
1275 void QWinTerminalImpl::mouseReleaseEvent (QMouseEvent *event)
1276 {
1277  if (event->button () == Qt::LeftButton)
1278  {
1279  d->m_endSelection = d->posToCell (event->pos ());
1280 
1281  updateSelection ();
1282 
1283  d->m_settingSelection = false;
1284  }
1285 }
1286 
1287 //////////////////////////////////////////////////////////////////////////////
1288 
1290 {
1291  d->updateConsoleSize (true);
1292  d->grabConsoleBuffer ();
1293 }
1294 
1295 //////////////////////////////////////////////////////////////////////////////
1296 
1298 {
1299  QPainter p (w);
1300 
1301  int cw = d->m_charSize.width ();
1302  int ch = d->m_charSize.height ();
1303 
1304  QRect updateRect = event->rect ();
1305 
1306  int cx1 = updateRect.left () / cw;
1307  int cy1 = updateRect.top () / ch;
1308  int cx2 = qMin (d->m_consoleRect.width () - 1, updateRect.right () / cw);
1309  int cy2 = qMin (d->m_consoleRect.height () - 1, updateRect.bottom () / ch);
1310 
1311  if (cx1 > d->m_consoleRect.width () - 1
1312  || cy1 > d->m_consoleRect.height () - 1)
1313  return;
1314 
1315  d->drawTextBackground (p, cx1, cy1, cx2, cy2, cw, ch);
1316  d->drawSelection (p, cx1, cy1, cx2, cy2, cw, ch);
1317  d->drawCursor (p);
1318  d->drawText (p, cx1, cy1, cx2, cy2, cw, ch);
1319 }
1320 
1322 {
1323  if (d->m_hasBlinkingCursor)
1325  else
1326  d->m_cursorBlinking = false;
1327 
1328  d->m_consoleView->update (d->cursorRect ());
1329 }
1330 
1332 {
1333  d->m_hasBlinkingCursor = blink;
1334 
1335  setBlinkingCursorState (blink);
1336 }
1337 
1339 {
1340  if (blink && ! d->m_blinkCursorTimer->isActive ())
1341  d->m_blinkCursorTimer->start (d->BLINK_DELAY);
1342 
1343  if (! blink && d->m_blinkCursorTimer->isActive ())
1344  {
1345  d->m_blinkCursorTimer->stop ();
1346 
1347  if (d->m_cursorBlinking)
1348  blinkCursorEvent ();
1349  }
1350 }
1351 
1352 //////////////////////////////////////////////////////////////////////////////
1353 
1354 void QWinTerminalImpl::wheelEvent (QWheelEvent* event)
1355 {
1356  if (! d->m_inWheelEvent)
1357  {
1358  // Forward to the scrollbar (avoid recursion)
1359  d->m_inWheelEvent = true;
1360  QApplication::sendEvent (d->m_scrollBar, event);
1361  d->m_inWheelEvent = false;
1362  }
1363 }
1364 
1365 //////////////////////////////////////////////////////////////////////////////
1366 
1368 {
1369  d->setScrollValue (value);
1370 }
1371 
1372 //////////////////////////////////////////////////////////////////////////////
1373 
1375 {
1376  d->monitorConsole ();
1377 }
1378 
1380 {
1381  d->updateSelection ();
1382 }
1383 
1384 //////////////////////////////////////////////////////////////////////////////
1385 
1386 void QWinTerminalImpl::focusInEvent (QFocusEvent* event)
1387 {
1388  emit set_global_shortcuts_signal (false); // disable some shortcuts
1389 
1390  setBlinkingCursorState (true);
1391 
1392  QWidget::focusInEvent (event);
1393 }
1394 
1395 void QWinTerminalImpl::focusOutEvent (QFocusEvent* event)
1396 {
1397  emit set_global_shortcuts_signal (true); // re-enable shortcuts
1398 
1399  // Force the cursor to be redrawn.
1400  d->m_cursorBlinking = true;
1401 
1402  setBlinkingCursorState (false);
1403 
1404  QWidget::focusOutEvent (event);
1405 }
1406 
1407 bool QWinTerminalImpl::eventFilter (QObject *obj, QEvent * event)
1408 {
1409  // if a keypress, filter out tab keys so that the next/prev tabbing is
1410  // disabled - but we still need to pass along to the console .
1411  if (event->type () == QEvent::KeyPress)
1412  {
1413  QKeyEvent* k = static_cast<QKeyEvent*>(event);
1414  if (k->key () == Qt::Key_Tab)
1415  {
1416  sendText ("\t");
1417  return true;
1418  }
1419  }
1420  return false;
1421 }
1422 
1423 void QWinTerminalImpl::keyPressEvent (QKeyEvent* event)
1424 {
1425  QString s = translateKey (event);
1426  if (!s.isEmpty ())
1427  sendText (s);
1428 
1429  if (d->m_hasBlinkingCursor)
1430  {
1431  d->m_blinkCursorTimer->start (d->BLINK_DELAY);
1432 
1433  if (d->m_cursorBlinking)
1434  blinkCursorEvent ();
1435  }
1436 
1437  QWidget::keyPressEvent (event);
1438 }
1439 
1440 //////////////////////////////////////////////////////////////////////////////
1441 
1443 {
1444  d->startCommand ();
1445 }
1446 
1447 //////////////////////////////////////////////////////////////////////////////
1448 
1449 void QWinTerminalImpl::sendText (const QString& s)
1450 {
1451  d->sendConsoleText (s);
1452 }
1453 
1455 {
1456  switch (type)
1457  {
1458  case UnderlineCursor:
1460  break;
1461 
1462  case BlockCursor:
1464  break;
1465 
1466  case IBeamCursor:
1468  break;
1469  }
1470 
1471  setBlinkingCursor (blinking);
1472 }
1473 
1474 void QWinTerminalImpl::setBackgroundColor (const QColor& color)
1475 {
1476  d->setBackgroundColor (color);
1477 }
1478 
1479 void QWinTerminalImpl::setForegroundColor (const QColor& color)
1480 {
1481  d->setForegroundColor (color);
1482 }
1483 
1484 void QWinTerminalImpl::setSelectionColor (const QColor& color)
1485 {
1486  d->setSelectionColor (color);
1487 }
1488 
1489 void QWinTerminalImpl::setCursorColor (bool useForegroundColor,
1490  const QColor& color)
1491 {
1492  d->setCursorColor (useForegroundColor, color);
1493 }
1494 
1495 //////////////////////////////////////////////////////////////////////////////
1496 
1498 {
1499  d->m_font = f;
1500  d->m_consoleView->setFont (f);
1501  d->updateConsoleSize (true);
1502 }
1503 
1504 //////////////////////////////////////////////////////////////////////////////
1505 
1506 void QWinTerminalImpl::setSize (int columns, int lines)
1507 {
1508  Q_UNUSED (columns);
1509  Q_UNUSED (lines);
1510 }
1511 
1512 //////////////////////////////////////////////////////////////////////////////
1513 
1515 {
1516  if(!hasFocus()) return;
1517 
1518  QClipboard *clipboard = QApplication::clipboard ();
1519 
1520  QString selection = d->getSelection ();
1521 
1522  if (selection.isEmpty ())
1523  terminal_interrupt ();
1524  else
1525  {
1526  clipboard->setText (selection);
1527 
1528  emit report_status_message (tr ("copied selection to clipboard"));
1529  }
1530 }
1531 
1532 //////////////////////////////////////////////////////////////////////////////
1533 
1535 {
1536  if(!hasFocus()) return;
1537 
1538  QString text = QApplication::clipboard()->text (QClipboard::Clipboard);
1539 
1540  if (! text.isEmpty ())
1541  sendText (text);
1542 }
1543 
1544 
1545 //////////////////////////////////////////////////////////////////////////////
1546 
1548 {
1549  QString selection = d->getSelection ();
1550  return selection;
1551 }