📄 qgsgrassshell.cpp
字号:
/*************************************************************************** qgsgrassshell.cpp -------------------------------------- Date : Sun Sep 16 12:06:10 AKDT 2007 Copyright : (C) 2007 by Gary E. Sherman Email : sherman at mrcc dot com *************************************************************************** * * * 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/#include <iostream>#include <vector>#include <qstring.h>#include <qapplication.h>#include <qpushbutton.h>#include <qwidget.h>#include <q3textedit.h>#include <q3process.h>#include <qmessagebox.h>#include <q3cstring.h>#include <qfile.h>#include <qdatastream.h>#include <qstringlist.h>#include <qsocketnotifier.h>#include <q3socket.h>#include <q3socketdevice.h>#include <qevent.h>#include <q3textbrowser.h>#include <qregexp.h>#include <qcursor.h>#include <qlayout.h>#include <qclipboard.h>#include <qfontmetrics.h>#include <q3progressbar.h>#include "qgsapplication.h"#include "qgsgrassshell.h"//Added by qt3to4:#include <QGridLayout>#include <QKeyEvent>#include <QResizeEvent>#include <QMouseEvent>extern "C" {#include <stdio.h>#include <stdlib.h>#ifndef _MSC_VER#include <unistd.h>#else#include <io.h>#endif#ifndef WIN32#ifdef Q_OS_MACX#include <util.h>#else#ifdef __NetBSD__#include <util.h>#else#ifdef __FreeBSD__#include <termios.h>#include <libutil.h>#else#include <pty.h>#endif#endif#endif#include <sys/types.h>#include <sys/stat.h>#include <sys/ioctl.h>#include <fcntl.h>#include <signal.h>#include <sys/wait.h>#endif //!WIN32}QgsGrassShell::QgsGrassShell ( QgsGrassTools *tools, QTabWidget * parent, const char * name ):QDialog(parent), QgsGrassShellBase(), mTools(tools){ mValid = false; mSkipLines = 2; mTabWidget = parent;#ifdef WIN32 QMessageBox::warning( 0, "Warning", "GRASS Shell is not supported on Windows." ); return;#else setupUi(this); QGridLayout *layout = new QGridLayout( mTextFrame, 1, 1 ); mText = new QgsGrassShellText( this, mTextFrame); layout->addWidget ( mText, 0 , 0 ); mText->show(); connect(mCloseButton, SIGNAL(clicked()), this, SLOT(closeShell())); mFont = QFont ( "Courier", 10 ); mAppDir = mTools->appDir();#ifndef Q_WS_MAC // Qt4.3.2/Mac Q3TextEdit readOnly property causes keys to be processed as keyboard actions mText->setReadOnly(TRUE);#endif //mText->setFocusPolicy ( QWidget::NoFocus ); // To get key press directly#ifndef HAVE_OPENPTY mText->append ( "GRASS shell is not supported" ); return;#endif // TODO set cursor IbeamCursor // This does not work - the cursor is used for scrollbars -> disabled //mText->setCursor ( QCursor(Qt::IbeamCursor) ); mParagraph = -1; // first will be 0 mIndex = -1; mNewLine = true; for ( int i = 0; i < ModeCount; i++ ) { resetMode(i); } int uid; seteuid( uid=getuid() ); /* Run unprivileged */ // Get and open pseudo terminal // Note: 0 (stdin), 1 (stdout) or 2 (stderr) int fdSlave; // slave file descriptor seteuid(0); int ret = openpty ( &mFdMaster, &fdSlave, NULL, NULL, NULL ); if ( ret != 0 ) { QMessageBox::warning( 0, "Warning", "Cannot open pseudo terminal" ); return; } fchown( fdSlave, uid, (gid_t)-1); fchmod( fdSlave, S_IRUSR | S_IWUSR); seteuid(uid);#ifdef QGISDEBUG std::cerr << "mFdMaster = " << mFdMaster << std::endl; std::cerr << "fdSlave = " << fdSlave << std::endl;#endif fcntl( mFdMaster, F_SETFL, O_NDELAY); //fcntl( fdSlave, F_SETFL, O_NDELAY); // enable? QString slaveName = ttyname(fdSlave);#ifdef QGISDEBUG std::cerr << "master ttyname = " << ttyname(mFdMaster) << std::endl; std::cerr << "slave ttyname = " << ttyname(fdSlave) << std::endl;#endif //close ( fdSlave ); // -> crash // Fork slave and open shell int pid = fork();#ifdef QGISDEBUG std::cerr << "pid = " << pid << std::endl;#endif if ( pid == -1 ) { QMessageBox::warning( 0, "Warning", "Cannot fork shell" ); return; } // Child - slave if ( pid == 0 ) {#ifdef QGISDEBUG std::cerr << "child ->" << std::endl;#endif // TODO close all opened file descriptors - close(0)???#ifndef Q_OS_DARWIN // Makes child process unusable on Mac close ( mFdMaster );#endif //close ( fdSlave ); // -> freeze setsid(); seteuid(0); int fd = open ( (char*) slaveName.ascii(), O_RDWR); if ( fd < 0 ) { QMessageBox::warning( 0, "Warning", "Cannot open slave file " "in child process" ); return; } fchown(fd, uid, (gid_t)-1); fchmod(fd, S_IRUSR | S_IWUSR); setuid(uid); dup2 (fd, 0); /* stdin */ dup2 (fd, 1); /* stdout */ dup2 (fd, 2); /* stderr */ // TODO: test if shell is available QString shell = ( getenv("SHELL") ); if ( shell.isEmpty() ) { shell = "/bin/bash"; } const char *norc = ""; QFileInfo si(shell); if ( si.fileName() == "bash" || si.fileName() == "sh" ) { norc = "--norc"; } else if ( si.fileName() == "tcsh" || si.fileName() == "csh" ) { norc = "-f"; } // Warning: execle + --norc will not inherit not given variables // -> overwrite here const char *env = "GRASS_MESSAGE_FORMAT=gui"; char *envstr = new char[strlen(env)+1]; strcpy ( envstr, env ); putenv( envstr ); putenv ( (char *) "GISRC_MODE_MEMORY" ); // unset env = "PS1=GRASS > "; envstr = new char[strlen(env)+1]; strcpy ( envstr, env ); putenv( envstr ); env = "TERM=vt100"; envstr = new char[strlen(env)+1]; strcpy ( envstr, env ); putenv( envstr ); //char *envar[] = { "PS1=GRASS > ", "TERM=vt100", "GISRC_MODE_MEMORY=", // "GRASS_MESSAGE_FORMAT=gui", (char *)0 }; //execle ( (char*)shell.ascii(), (char *)si.fileName().ascii(), // norc, (char *) 0, envar); execl ( (char*)shell.ascii(), (char *)si.fileName().ascii(), norc, (char *) 0); // Failed (QMessageBox here does not work) fprintf ( stderr, "GRASS_INFO_ERROR(1,1): Cannot start shell %s\n", (char*)shell.ascii() ); exit(1); } mPid = pid; // Create socket notifier mOutNotifier = new QSocketNotifier ( mFdMaster, QSocketNotifier::Read, this); QObject::connect ( mOutNotifier, SIGNAL(activated(int)), this, SLOT(readStdout(int))); // Set tab stops ??? mTabStop.resize(200); for ( int i = 0 ; i * 8 < (int)mTabStop.size(); i++ ) { mTabStop[i*8] = true; } // Set trap to write history on SIGUSR1 //QString trap = "trap 'history -w' SIGUSR1\015\012"; QString trap = "trap 'history -w' SIGUSR1\015"; write( mFdMaster, trap.ascii(), trap.length()); mText->clear(); resizeTerminal(); mValid = true;#endif // !WIN32}QgsGrassShell::~QgsGrassShell(){#ifdef QGISDEBUG std::cerr << "QgsGrassShell::~QgsGrassShell()" << std::endl;#endif#ifndef WIN32 // This was old trick to write history /* write( mFdMaster, "exit\015\012", 6); while ( 1 ) { readStdout(0); int status; if ( waitpid ( mPid, &status, WNOHANG ) > 0 ) break; struct timespec t, r; t.tv_sec = 0; t.tv_nsec = 10000000; // 0.01 s nanosleep ( &t, &r ); } */ // Write history if ( kill(mPid,SIGUSR1) == -1 ) { std::cerr << "cannot write history (signal SIGUSR1 to pid = " << mPid << ")" << std::endl; } std::cerr << "kill shell pid = " << mPid << std::endl; if ( kill(mPid,SIGTERM ) == -1 ) { std::cerr << "cannot kill shell pid = " << mPid << std::endl; }#endif}void QgsGrassShell::keyPressEvent( QKeyEvent * e ){#ifdef QGISDEBUG std::cerr << "QgsGrassShell::keyPressEvent()" << std::endl;#endif char s[10]; int length = 0; int ret = 0; if ( !mValid ) return; mProgressBar->setProgress ( 0, 100 ); char c = (char) e->ascii();#ifdef QGISDEBUG std::cerr << "c = " << (int)c << " key = " << e->key() << " text = " << e->text().local8Bit().data() << std::endl;#endif s[0] = c; length = 1; // Set key down if ( e->key() == Qt::Key_Control ) mKeyDown[DownControl] = true; else if ( e->key() == Qt::Key_Shift ) mKeyDown[DownShift] = true; else if ( e->key() == Qt::Key_Alt ) mKeyDown[DownAlt] = true; else if ( e->key() == Qt::Key_Meta ) mKeyDown[DownMeta] = true; if ( c == 0 ) { switch ( e->key() ) { case Qt::Key_Up : strcpy ( s, "\033[A" ); length = 3; break; case Qt::Key_Down : strcpy ( s, "\033[B" ); length = 3; break; case Qt::Key_Right : strcpy ( s, "\033[C" ); length = 3; break; case Qt::Key_Left : strcpy ( s, "\033[D" ); length = 3; break; } } ret = write( mFdMaster, s, length);#ifdef QGISDEBUG std::cerr << "write ret = " << ret << std::endl;#endif}void QgsGrassShell::keyReleaseEvent( QKeyEvent * e ){#ifdef QGISDEBUG // std::cerr << "QgsGrassShell::keyReleaseEvent()" << std::endl;#endif // Reset key down if ( e->key() == Qt::Key_Control ) mKeyDown[DownControl] = false; else if ( e->key() == Qt::Key_Shift ) mKeyDown[DownShift] = false; else if ( e->key() == Qt::Key_Alt ) mKeyDown[DownAlt] = false; else if ( e->key() == Qt::Key_Meta ) mKeyDown[DownMeta] = false;}void QgsGrassShell::readStdout( int socket ){#ifdef QGISDEBUG //std::cerr << "QgsGrassShell::readStdout()" << std::endl;#endif char buf[4097]; int len; while ( (len = read( mFdMaster, buf, 4096)) > 0 ) { // Terminate string buf[len] = '\0'; mStdoutBuffer.append ( buf ); } printStdout();}void QgsGrassShell::printStdout(){ // Debug#ifdef QGISDEBUG std::cerr << "****** buffer ******" << std::endl; std::cerr << "-->"; for ( int i = 0; i < (int)mStdoutBuffer.length(); i++ ) { int c = mStdoutBuffer[i]; QString s = ""; if ( c > '\037' && c != '\177' ) // control characters { s = (char) c; std::cerr << s.local8Bit().data(); } else { std::cerr << "(c=" << QString::number(c,8).local8Bit().data() << ")"; } } std::cerr << "<--" << std::endl;#endif eraseCursor(); // To make it faster we want to print maximum lenght blocks from buffer while ( mStdoutBuffer.length() > 0 ) {#ifdef QGISDEBUG std::cerr << "------ cycle ------" << std::endl;#endif // Search control character int control = -1; for ( int i = 0; i < (int)mStdoutBuffer.length(); i++ ) { int c = mStdoutBuffer[i]; if ( c < '\037' || c == '\177' ) { control = i; break; } }#ifdef QGISDEBUG std::cerr << "control = " << control << std::endl;#endif // Process control character if found at index 0 if ( control == 0 ) { int c = mStdoutBuffer[0];#ifdef QGISDEBUG std::cerr << "c = " << QString::number(c,8).local8Bit().data() << std::endl;#endif // control sequence if ( c == '\033' ) { //std::cerr << "control sequence" << std::endl; bool found = false; // It is sequence, so it should be at least one more character // wait for more data if ( mStdoutBuffer.length() < 2 ) break; if ( mStdoutBuffer[1] == ']' && mStdoutBuffer.length() < 3 ) break; // ESC ] Ps ; Pt BEL (xterm title hack) QRegExp rx ( "\\](\\d+);([^\\a]+)\\a" ); if ( rx.search ( mStdoutBuffer, 1 ) == 1 ) { int mlen = rx.matchedLength();#ifdef QGISDEBUG std::cerr << "ESC(set title): " << rx.cap(2).local8Bit().data() << std::endl;#endif mStdoutBuffer.remove ( 0, mlen+1 ); found = true; } if ( !found ) { // ESC [ Pn ; Pn FINAL // or ESC [ = Pn ; Pn FINAL // or ESC [ = Pn ; Pn FINAL // TODO: QRegExp captures only last of repeated patterns
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -