QProcess and Process Control

Table of contents:

QProcess is a very convenient (and cross-platform) class for starting and controlling other processes. It is derived from QObject and takes full advantage of signals and slots to make it easier to "hook up" with other Qt classes.

We discuss now a simple example that starts a process and views its continually running output. Example 12.1 shows the definition of a simple class derived from QProcess.[1]

[1] tail -f runs forever showing whatever is appended to a file, and is useful for showing the contents of a log file of a running process.

Example 12.1. src/logtail/logtail.h

[ . . . . ]
#include 
#include 
class LogTail : public QProcess {
 Q_OBJECT
 public:
 LogTail(QString fn = QString());
 ~LogTail();
 public slots:
 void logOutput();
};
[ . . . . ]

A QProcess can spawn another process using the start() function. The new process is a child process in that it will terminate when the parent process does. Example 12.2 shows the implementation of the constructor and destructor of the LogTail class.[2]

[2] It is also possible to use startDetached() to start a process that will continue to live after the calling process exits.

Example 12.2. src/logtail/logtail.cpp

[ . . . . ]

LogTail::LogTail(QString fn) {
 if (fn == QString()) {
 fn = "/var/log/apache/access.log";
 }
 connect (this, SIGNAL(readyReadStandardOutput()),
 this, SLOT(logOutput())); <-- 1
 QStringList argv;
 argv << "-f" << fn;
 // We want to exec "tail -f filename"
 start("tail", argv); <-- 2
}

LogTail::~LogTail() {
 terminate(); <-- 3
}
 

(1)When there is input ready, we will know about it.

(2)Returns immediately, and now there is a forked process running independently but "attached" to this process. When the calling process exits, the forked process will terminate.

(3)Attempts to terminate this process.

The child process can be treated as a sequential I/O device with two predefined output channels that represent two separate streams of data: stdout and stderr. The parent process can select an output channel with setReadChannel() (default is stdout). The signal readyReadStandardOutput() is emitted when data is available on the selected channel of the child process. The parent process can then read its output by calling read(), readLine(), or getChar(). If the child process has standard input enabled, the parent can use write() to send data to it.

Example 12.3 shows the implementation of the slot logOutput(), which is connected to the signal readyReadStandardOutput() and uses readAllStandardOutput() so that it pays attention only to stdout.

Example 12.3. src/logtail/logtail.cpp

[ . . . . ]

// tail sends its output to stdout.
void LogTail::logOutput() { <-- 1
 QByteArray bytes = readAllStandardOutput();
 QStringList lines = QString(bytes).split("
");
 foreach (QString line, lines) {
 qDebug() << line;
 }
}
 

(1)event drivenpassive interface

The use of signals eliminates the need for a read loop. When there is no more input to be read, the slot will no longer be called. Signals and slots make concurrent code much simpler to read, because they hide the event-handling and dispatching code. Some client code is shown in Example 12.4.

Example 12.4. src/logtail/logtail.cpp

[ . . . . ]

#include 
#include 
#include 

int main (int argc, char* argv[]) {
 QApplication app(argc, argv);
 ArgumentList al;
 LogWindow lw("debug"); <-- 1
 lw.setWindowTitle("logtail demo");
 QString filename;
 if (al.size() > 1) filename = al[1];
 LogTail tail(filename); <-- 2
 lw.show();
 return app.exec();
}
 

(1)Create a scrolling edit window watching debug messages.

(2)Create object, but start process too.

This application appends lines to the LogWindow whenever they appear in the specified log file. The default log file is named access.log for the apache Web server on a typical *nix local host.

12.1.1. Processes and Environment

Environment variables are name/value string pairs that can be stored quite easily in a map or a hash table. Every running process has an environment, or a collection of environment variables. Most programming languages support a way of getting and setting these variables.

The most common environment variables are

  1. PATH, a list of directories to search for executables
  2. HOME, the location of your home directory
  3. HOSTNAME (*nix) or COMPUTERNAME (win32), which usually gives you the name of your machine
  4. USER (*nix) or USERNAME (Win32), which usually gives you the currently logged-in user

Another environment variable you will encounter in the context of the exercises is CPPLIBS, which denotes the location of our own C++ libraries.

Environment variables and their values are arbitrarily set by the parent process, so they can be any pair of strings. Programs that depend on specific variables are generally not portable, but depend somehow on their parent process. Variables provide a convenient cross-language mechanism for communicating information between processes.

Operating system shells allow the user to set environment variables for that process and its future children. Here are some examples.

  1. Microsoft Windows desktop: start -> settings -> system -> advanced -> environment variables

  2. Microsoft command prompt: set VARIABLE=value and echo %VARIABLE%
  3. bash command line: export VARIABLE=value and echo $VARIABLE

Many programming languages support getting and setting environment variables too, as listed here.

  1. C/C++: getenv() and putenv() from <cstdlib> (see Appendix B)[3]

    [3] Some platforms also offer setenv(), which is more convenient but less portable.

  2. Python: os.getenv() and os.putenv()
  3. Perl: %ENV hashtable
  4. Java 1.5: ProcessBuilder.environment()
  5. Qt 4: QProcess::environment()

Changing the environment does not affect other processes that are already running. Environments are inherited from the parent process at process creation time.

Any program that you run is quite possibly a few levels deep in the process tree. This is because the typical desktop operating system environment consists of many processes running together. In Figure 12.1 indentation levels indicate the parent-child relationships. Processes that are at the same indentation level are siblings (i.e., children of the same parent).

Figure 12.1. Linux process hierarchy

Whenever program A runs program B, A is the parent process of B. B inherits (a copy of) A's environment when B is created. Changes to B's environment from inside B will only affect B and B's future children, and will always be invisible to A.

In Example 12.5, we verify that the value given to setenv() is propagated to its child process.

Example 12.5. src/environment/setenv.cpp

#include 
#include 
#include 
#include 
class Fork : public QProcess {
 public:
 Fork(QStringList argv = QStringList() ) {
 execute("environment", argv); <-- 1
 }
 ~Fork() {
 waitForFinished();
 }
};

int main(int argc, char* argv[]) {
 using namespace qstd;
 ArgumentList al(argc, argv);

 al.removeFirst();
 bool fork=al.getSwitch("-f");

 QStringList extraVars;
 if (al.count() > 0) {
 setenv("PENGUIN", al.first().toAscii(), true);
 }
 cout << " HOME=" << getenv("HOME") << endl;
 cout << " PWD=" << getenv("PWD") << endl;
 cout << " PENGUIN=" << getenv ("PENGUIN") << endl;

 if (fork) {
 Fork f;
 }
}
 

(1)Runs this same app as a child.

When this program is run, our output looks like this:

/home/lazarus/src/environment> export PENGUIN=tux
/home/lazarus/src/environment> ./environment -f
 HOME=/home/lazarus
 PWD=/home/lazarus/src/environment
 PENGUIN=tux
 HOME=/home/lazarus
 PWD=/home/lazarus/src/environment
 PENGUIN=tux
/home/lazarus/src/environment> ./environment -f opus
 HOME=/home/lazarus
 PWD=/home/lazarus/src/environment
 PENGUIN=opus
 HOME=/home/lazarus
 PWD=/home/lazarus/src/environment
 PENGUIN=opus

 

12.1.2. Qonsole: Writing an Xterm in Qt

A command line shell reads commands from the user and prints the output. In this example, we have a LogWindow (see Section 7.4) providing a view of the output of another running process, in this case, bash. The QProcess is a model, representing a running process. Figure 12.2 shows a screenshot of Qonsole, our first attempt at a GUI for a command shell.

Figure 12.2. Qonsole1

Because it connects signals to slots and handles user interactions, Qonsole is considered a controller. Because it derives from QMainWindow, it also contains some view code. The UML diagram in Figure 12.3 shows the relationships between the classes in this application.

Figure 12.3. Qonsole UML: Model and view

In Example 12.6 we see how the constructor establishes the structure of Qonsole and the important connections between its components.

Example 12.6. src/qonsole/qonsole1/qonsole.cpp

[ . . . . ]

Qonsole::Qonsole() {
 m_Logw = new LogWindow("debug");
 m_Logw->setReadOnly(true);
 setCentralWidget(m_Logw);
 m_InputArea = new QLineEdit();
 QDockWidget* qdw = new QDockWidget("Type commands here");
 qdw->setWidget(m_InputArea);
 addDockWidget(Qt::BottomDockWidgetArea, qdw);
 connect (m_InputArea, SIGNAL(returnPressed()),
 this, SLOT(execute()));
 m_Bash = new QProcess();
 m_Bash->setReadChannelMode(QProcess::MergedChannels); <-- 1
 connect (m_Bash, SIGNAL(readyReadStandardOutput()),
 this, SLOT(showOutput()));
 m_Bash->start("bash", QStringList() << "-i"); <-- 2
}
 

(1)Merge stdout and stderr.

(2)Run bash in interactive mode.

Whenever bash outputs anything, Qonsole sends it to the LogWindow. Whenever the user presses the return key, Qonsole grabs any text that is in the QLineEdit and sends it to bash, which interprets it as a command, as we see in Example 12.7.

Example 12.7. src/qonsole/qonsole1/qonsole.cpp

[ . . . . ]

void Qonsole::showOutput() { <-- 1
 QByteArray bytes = m_Bash->readAllStandardOutput();
 QStringList lines = QString(bytes).split("
");
 foreach (QString line, lines) {
 m_Logw->append(line);
 }
}

void Qonsole::execute() {
 QString cmdStr = m_InputArea->text() + "
";
 m_InputArea->setText("");
 m_Logw->append(cmdStr);
 QByteArray bytes = cmdStr.toUtf8(); <-- 2
 m_Bash->write(bytes); <-- 3
}
 

(1)A slot that gets called whenever input is ready

(2)8-bit Unicode Transformation Format

(3)Send the data into the stdin stream of the bash child process

Example 12.8 shows the client code that launches this application.

Example 12.8. src/qonsole/qonsole1/qonsole.cpp

[ . . . . ]

#include 

int main(int argc, char* argv[]) {
 QApplication app(argc, argv);
 Qonsole qon;
 qon.show();
 return app.exec();
}

12.1.3. Qonsole with Keyboard Events

In the preceding example, Qonsole had a separate widget for user input. For a more authentic xterm experience, the user should be able to type commands in the command output window. To accomplish this Qonsole needs to capture keyboard events. The first step is to override the QObject base class eventFilter() method, as we see in Example 12.9, the revised class definition.

Example 12.9. src/qonsole/keyevents/qonsole.h

[ . . . . ]
class Qonsole : public QMainWindow {
 Q_OBJECT
 public:
 Qonsole();
 public slots:
 void execute();
 void showOutput();
 bool eventFilter(QObject *o, QEvent *e) ;
 protected:
 void updateCursor();
 private:
 QString m_UserInput;
 LogWindow* m_Logw;
 QProcess* m_Bash;
};
[ . . . . ]

As we discussed in Section 9.3, an event is an object derived from QEvent. Within the context of an application, such a QEvent is associated with a QObject that is its intended receiver. The receiving object has a handler to process the event. An eventFilter examines a QEvent and determines whether or not to permit it to be processed by the intended receiver. We have provided our revised Qonsole application with an eventFilter() function that will be used to filter keyboard events from m_Logw, an extended QTextEdit. QOnsole, an extended QMainWindow, is the intended recipient of those events. The implementation of this function is shown in Example 12.10.

Example 12.10. src/qonsole/keyevents/qonsole.cpp

[ . . . . ]

bool Qonsole::eventFilter(QObject *o, QEvent *e) {
 if (e->type() == QEvent::KeyPress) {
 QKeyEvent *k = static_cast (e);
 int key = k->key();
 QString str = k->text();
 m_UserInput.append(str);
 updateCursor();
 if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) ) {
 execute();
 return true; <-- 1
 }
 else {
 m_Logw->insertPlainText(str);
 return true;
 }
 }
 return QMainWindow::eventFilter(o,e); <-- 2
}
 

(1)We processed the event. This prevents other widgets from seeing it.

(2)Let the base class eventFilter have a shot at it.

Each time a key is pressed by the user the character generated by that key is appended to the mUserInput string and the position of the cursor in the LogWindow is adjusted by the member function updateCursor(). When the Enter key is pressed, the member function execute() is called so that the command string can be sent to the shell and then reset. Example 12.11 shows the implementation of these two functions.

Example 12.11. src/qonsole/keyevents/qonsole.cpp

[ . . . . ]

bool Qonsole::eventFilter(QObject *o, QEvent *e) {
 if (e->type() == QEvent::KeyPress) {
 QKeyEvent *k = static_cast (e);
 int key = k->key();
 QString str = k->text();
 m_UserInput.append(str);
 updateCursor();

 if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) ) {
 execute();
 return true;
 }
 else {
 m_Logw->insertPlainText(str);
 return true; <-- 1
 }
 }
 return QMainWindow::eventFilter(o, e); <-- 2
}
 

(1)We processed the event. This prevents other widgets from seeing it.

(2)Let the base class eventFilter have a shot at it.

All that remains to be done is to call the base class function installEventFilter() on mLogw, the widget whose events we want to capture. This is done in the constructor, as we see in Example 12.12.

Here is the controller code that sets up Qonsole.

Example 12.12. src/qonsole/keyevents/qonsole.cpp

[ . . . . ]

Qonsole::Qonsole() {
 m_Logw = new LogWindow("debug");
 setCentralWidget(m_Logw);
 m_Logw->installEventFilter(this);
 m_Logw->setLineWrapMode(QTextEdit::WidgetWidth);
 m_Bash = new QProcess();
 m_Bash->setReadChannelMode(QProcess::MergedChannels);
 connect (m_Bash, SIGNAL(readyReadStandardOutput()),
 this, SLOT(showOutput()));
 m_Bash->start("bash", QStringList("-i"), QIODevice::ReadWrite);
}

Exercises: QProcess and Process Control

1.

Modify Qonsole to support the backspace key.

2.

Modify Qonsole to support multiple simultaneous terminals in separate tabs.

3.

The command htpasswd[4] can be run from the shell, and it provides a command-line interface for encrypting and storing passwords. It stores a list of names and passwords separated by colons in a text file. The passwords are encrypted with a user-selectable protocol.

[4] This command is available on most Linux systems, since it is included with Apache httpd server, one of the most widely used Web servers. It is also available on other platforms at no change.

If htpasswd has been installed on your *nix system you can learn about its command-line options by viewing its manual page.

Write a Qt wrapper, called Users, around the htpasswd command. It should have the interface shown in the following figure.

The constructor should load all users and encrypted passwords into a QMap. addUser will require using QProcess to run htpasswd. checkPassword will use the in-memory map and the htpasswd -nb command to verify that the password is correct.

Write a graphical front-end to this applicationa login screen, as in the figure that follows.

Use message boxes to describe precisely any errors that occur during login. For example if the user enters the correct username but an incorrect password, the error message should be different from the one for an incorrect username. In a real-world security application, you would not have different error messages (no reason to help would-be hackers gaining access to our systems, right?) but we want a different message to help in testing.

If the user clicks Register, show another dialog (or modify the existing one) so that it asks for the password twice, to verify that the user entered it correctly.

The authors encountered some difficulties using the crypt()(-d) and MD5 (-m) encryption schemes for this problem, so we recommend using SHA (-s) encryption.



Threads and QThread

Part I: Introduction to C++ and Qt 4

C++ Introduction

Classes

Introduction to Qt

Lists

Functions

Inheritance and Polymorphism

Part II: Higher-Level Programming

Libraries

Introduction to Design Patterns

QObject

Generics and Containers

Qt GUI Widgets

Concurrency

Validation and Regular Expressions

Parsing XML

Meta Objects, Properties, and Reflective Programming

More Design Patterns

Models and Views

Qt SQL Classes

Part III: C++ Language Reference

Types and Expressions

Scope and Storage Class

Statements and Control Structures

Memory Access

Chapter Summary

Inheritance in Detail

Miscellaneous Topics

Part IV: Programming Assignments

MP3 Jukebox Assignments

Part V: Appendices

MP3 Jukebox Assignments

Bibliography

MP3 Jukebox Assignments



An Introduction to Design Patterns in C++ with Qt 4
An Introduction to Design Patterns in C++ with Qt 4
ISBN: 0131879057
EAN: 2147483647
Year: 2004
Pages: 268

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net