summaryrefslogtreecommitdiff
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--noncore/apps/opie-console/MyPty.cpp299
-rw-r--r--noncore/apps/opie-console/MyPty.h95
-rw-r--r--noncore/apps/opie-console/default.cpp16
-rw-r--r--noncore/apps/opie-console/default.h4
-rw-r--r--noncore/apps/opie-console/emulation_handler.cpp89
-rw-r--r--noncore/apps/opie-console/emulation_handler.h8
-rw-r--r--noncore/apps/opie-console/io_layer.h4
-rw-r--r--noncore/apps/opie-console/opie-console.pro6
8 files changed, 507 insertions, 14 deletions
diff --git a/noncore/apps/opie-console/MyPty.cpp b/noncore/apps/opie-console/MyPty.cpp
new file mode 100644
index 0000000..10828b0
--- a/dev/null
+++ b/noncore/apps/opie-console/MyPty.cpp
@@ -0,0 +1,299 @@
+/* -------------------------------------------------------------------------- */
+/* */
+/* [MyPty.C] Pseudo Terminal Device */
+/* */
+/* -------------------------------------------------------------------------- */
+/* */
+/* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> */
+/* */
+/* This file is part of Konsole - an X terminal for KDE */
+/* -------------------------------------------------------------------------- */
+/* */
+/* Ported Konsole to Qt/Embedded */
+/* */
+/* Copyright (C) 2000 by John Ryland <jryland@trolltech.com> */
+/* */
+/* -------------------------------------------------------------------------- */
+
+/* If you're compiling konsole on non-Linux platforms and find
+ problems that you can track down to this file, please have
+ a look into ../README.ports, too.
+*/
+
+/*! \file
+*/
+
+/*! \class TEPty
+
+ \brief Ptys provide a pseudo terminal connection to a program.
+
+ Although closely related to pipes, these pseudo terminal connections have
+ some ability, that makes it nessesary to uses them. Most importent, they
+ know about changing screen sizes and UNIX job control.
+
+ Within the terminal emulation framework, this class represents the
+ host side of the terminal together with the connecting serial line.
+
+ One can create many instances of this class within a program.
+ As a side effect of using this class, a signal(2) handler is
+ installed on SIGCHLD.
+
+ \par FIXME
+
+ [NOTE: much of the technical stuff below will be replaced by forkpty.]
+
+ publish the SIGCHLD signal if not related to an instance.
+
+ clearify TEPty::done vs. TEPty::~TEPty semantics.
+ check if pty is restartable via run after done.
+
+ \par Pseudo terminals
+
+ Pseudo terminals are a unique feature of UNIX, and always come in form of
+ pairs of devices (/dev/ptyXX and /dev/ttyXX), which are connected to each
+ other by the operating system. One may think of them as two serial devices
+ linked by a null-modem cable. Being based on devices the number of
+ simultanous instances of this class is (globally) limited by the number of
+ those device pairs, which is 256.
+
+ Another technic are UNIX 98 PTY's. These are supported also, and prefered
+ over the (obsolete) predecessor.
+
+ There's a sinister ioctl(2), signal(2) and job control stuff
+ nessesary to make everything work as it should.
+*/
+
+
+#include <qapplication.h>
+#include <qsocketnotifier.h>
+#include <qstring.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_OPENPTY
+#include <pty.h>
+#endif
+
+#include "MyPty.h"
+
+
+#undef VERBOSE_DEBUG
+
+
+/* -------------------------------------------------------------------------- */
+
+/*!
+ Informs the client program about the
+ actual size of the window.
+*/
+
+void MyPty::setSize(int lines, int columns)
+{
+ struct winsize wsize;
+ wsize.ws_row = (unsigned short)lines;
+ wsize.ws_col = (unsigned short)columns;
+ if(m_fd < 0) return;
+ ioctl(m_fd,TIOCSWINSZ,(char *)&wsize);
+}
+
+
+void MyPty::donePty()
+{
+ // This is code from the Qt DumbTerminal example
+ int status = 0;
+
+ ::close(m_fd);
+
+ if (m_cpid) {
+ kill(m_cpid, SIGHUP);
+ waitpid(m_cpid, &status, 0);
+ }
+
+ emit done(status);
+}
+
+
+const char* MyPty::deviceName()
+{
+ return m_ttynam;
+}
+
+
+void MyPty::error()
+{
+ // This is code from the Qt DumbTerminal example
+ donePty();
+}
+
+void MyPty::start() {
+ char* cmd = "/bin/sh";
+ QStrList lis;
+ int r =run(cmd, lis, 0, 0);
+ r = r;
+}
+/*!
+ start the client program.
+*/
+int MyPty::run(const char* cmd, QStrList &, const char*, int)
+{
+ // This is code from the Qt DumbTerminal example
+ m_cpid = fork();
+
+ if ( !m_cpid ) {
+ // child - exec shell on tty
+ for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL);
+ int ttyfd = ::open(m_ttynam, O_RDWR);
+ dup2(ttyfd, STDIN_FILENO);
+ dup2(ttyfd, STDOUT_FILENO);
+ dup2(ttyfd, STDERR_FILENO);
+ // should be done with tty, so close it
+ ::close(ttyfd);
+ static struct termios ttmode;
+ if ( setsid() < 0 )
+ perror( "failed to set process group" );
+#if defined (TIOCSCTTY)
+ // grabbed from APUE by Stevens
+ ioctl(STDIN_FILENO, TIOCSCTTY, 0);
+#endif
+ tcgetattr( STDIN_FILENO, &ttmode );
+ ttmode.c_cc[VINTR] = 3;
+ ttmode.c_cc[VERASE] = 8;
+ tcsetattr( STDIN_FILENO, TCSANOW, &ttmode );
+ setenv("TERM","vt100",1);
+ setenv("COLORTERM","0",1);
+
+ if (getuid() == 0) {
+ char msg[] = "WARNING: You are running this shell as root!\n";
+ write(ttyfd, msg, sizeof(msg));
+ }
+ execl(cmd, cmd, 0);
+
+ donePty();
+ exit(-1);
+ }
+
+ // parent - continue as a widget
+ QSocketNotifier* sn_r = new QSocketNotifier(m_fd,QSocketNotifier::Read,this);
+ QSocketNotifier* sn_e = new QSocketNotifier(m_fd,QSocketNotifier::Exception,this);
+ connect(sn_r,SIGNAL(activated(int)),this,SLOT(readPty()));
+ connect(sn_e,SIGNAL(activated(int)),this,SLOT(error()));
+
+ return 0;
+}
+
+int MyPty::openPty()
+{
+ // This is code from the Qt DumbTerminal example
+ int ptyfd = -1;
+
+#ifdef HAVE_OPENPTY
+ int ttyfd;
+ if ( openpty(&ptyfd,&ttyfd,ttynam,0,0) )
+ ptyfd = -1;
+ else
+ close(ttyfd); // we open the ttynam ourselves.
+#else
+ for (const char* c0 = "pqrstuvwxyzabcde"; ptyfd < 0 && *c0 != 0; c0++) {
+ for (const char* c1 = "0123456789abcdef"; ptyfd < 0 && *c1 != 0; c1++) {
+ sprintf(m_ptynam,"/dev/pty%c%c",*c0,*c1);
+ sprintf(m_ttynam,"/dev/tty%c%c",*c0,*c1);
+ if ((ptyfd = ::open(m_ptynam,O_RDWR)) >= 0) {
+ if (geteuid() != 0 && !access(m_ttynam,R_OK|W_OK) == 0) {
+ ::close(ptyfd);
+ ptyfd = -1;
+ }
+ }
+ }
+ }
+#endif
+
+ if ( ptyfd < 0 ) {
+ qApp->exit(1);
+ return -1;
+ }
+
+ return ptyfd;
+}
+
+/*!
+ Create an instance.
+*/
+MyPty::MyPty(const Profile&) : m_cpid(0)
+{
+ m_fd = openPty();
+}
+
+/*!
+ Destructor.
+ Note that the related client program is not killed
+ (yet) when a instance is deleted.
+*/
+MyPty::~MyPty()
+{
+ donePty();
+}
+QString MyPty::identifier()const {
+ return QString::fromLatin1("term");
+}
+QString MyPty::name()const{
+ return identifier();
+}
+bool MyPty::open() {
+ start();
+ return true;
+}
+void MyPty::close() {
+ donePty();
+}
+void MyPty::reload( const Profile& ) {
+
+}
+/*! sends len bytes through the line */
+void MyPty::send(const QByteArray& ar)
+{
+
+#ifdef VERBOSE_DEBUG
+ // verbose debug
+ printf("sending bytes:\n");
+ for (uint i = 0; i < ar.count(); i++)
+ printf("%c", ar[i]);
+ printf("\n");
+#endif
+
+ ::write(m_fd, ar.data(), ar.count());
+}
+
+/*! indicates that a block of data is received */
+void MyPty::readPty()
+{
+ QByteArray buf(4096);
+
+ int len = ::read( m_fd, buf.data(), 4096 );
+
+ if (len == -1)
+ donePty();
+
+ if (len < 0)
+ return;
+
+ buf.resize(len);
+ emit received(buf);
+
+#ifdef VERBOSE_DEBUG
+ // verbose debug
+ printf("read bytes:\n");
+ for (uint i = 0; i < buf.count(); i++)
+ printf("%c", buf[i]);
+ printf("\n");
+#endif
+
+}
+
diff --git a/noncore/apps/opie-console/MyPty.h b/noncore/apps/opie-console/MyPty.h
new file mode 100644
index 0000000..9231a8a
--- a/dev/null
+++ b/noncore/apps/opie-console/MyPty.h
@@ -0,0 +1,95 @@
+/* -------------------------------------------------------------------------- */
+/* */
+/* [MyPty.h] Pseudo Terminal Device */
+/* */
+/* -------------------------------------------------------------------------- */
+/* */
+/* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> */
+/* */
+/* This file is part of Konsole - an X terminal for KDE */
+/* */
+/* -------------------------------------------------------------------------- */
+/* */
+/* Ported Konsole to Qt/Embedded */
+/* */
+/* Copyright (C) 2000 by John Ryland <jryland@trolltech.com> */
+/* */
+/* -------------------------------------------------------------------------- */
+
+/*! \file
+*/
+
+#ifndef MY_PTY_H
+#define MY_PTY_H
+
+#include <qobject.h>
+#include <qstrlist.h>
+
+#include "io_layer.h"
+
+class Profile;
+class MyPty : public IOLayer
+{
+ Q_OBJECT
+public:
+
+ MyPty(const Profile&);
+ ~MyPty();
+
+
+
+ QString identifier()const;
+ QString name()const;
+
+ public slots:
+ /*!
+ having a `run' separate from the constructor allows to make
+ the necessary connections to the signals and slots of the
+ instance before starting the execution of the client.
+ */
+ void start();
+ int run(const char* pgm, QStrList & args , const char* term, int addutmp);
+ bool open();
+ void close();
+ void reload( const Profile& );
+ void setSize(int lines, int columns);
+ void error();
+
+ signals:
+
+ /*!
+ emitted when the client program terminates.
+ \param status the wait(2) status code of the terminated client program.
+ */
+ void done(int status);
+
+ /*!
+ emitted when a new block of data comes in.
+ \param s - the data
+ \param len - the length of the block
+ */
+ void received(const QByteArray&);
+
+ public slots:
+
+ void send(const QByteArray& );
+
+private:
+ const char* deviceName();
+
+protected slots:
+ void readPty();
+ void donePty();
+
+private:
+ int openPty();
+
+private:
+
+ char m_ptynam[16]; // "/dev/ptyxx" | "/dev/ptmx"
+ char m_ttynam[16]; // "/dev/ttyxx" | "/dev/pts/########..."
+ int m_fd;
+ int m_cpid;
+};
+
+#endif
diff --git a/noncore/apps/opie-console/default.cpp b/noncore/apps/opie-console/default.cpp
index 5c1c05a..64c9542 100644
--- a/noncore/apps/opie-console/default.cpp
+++ b/noncore/apps/opie-console/default.cpp
@@ -6,13 +6,13 @@
#include "filereceive.h"
#include "serialconfigwidget.h"
#include "irdaconfigwidget.h"
#include "btconfigwidget.h"
#include "modemconfigwidget.h"
#include "terminalwidget.h"
-#include "vt102emulation.h"
+#include "MyPty.h"
#include "default.h"
extern "C" {
// FILE Transfer Stuff
FileTransferLayer* newSZTransfer(IOLayer* lay) {
@@ -46,12 +46,15 @@ extern "C" {
IOLayer* newIrDaLayer( const Profile& prof ) {
return new IOIrda( prof );
}
IOLayer* newModemLayer( const Profile& prof ) {
return new IOModem( prof );
}
+ IOLayer* newConsole( const Profile& prof ) {
+ return new MyPty(prof );
+ }
// Connection Widgets
ProfileDialogWidget* newSerialWidget( const QString& str, QWidget* wid ) {
return new SerialConfigWidget( str, wid );
}
ProfileDialogWidget* newIrDaWidget( const QString& str, QWidget* wid ) {
@@ -60,19 +63,22 @@ extern "C" {
ProfileDialogWidget* newModemWidget( const QString& str, QWidget* wid ) {
return new ModemConfigWidget(str, wid );
}
ProfileDialogWidget* newBTWidget( const QString& str, QWidget* wid ) {
return new BTConfigWidget(str, wid );
}
+ ProfileDialogWidget* newConsoleWid( const QString& str, QWidget* wid ) {
+ return 0l;
+ }
// Terminal Widget(s)
-/* ProfileDialogWidget* newTerminalWidget(const QString& na, QWidget* wid) {
+ ProfileDialogWidget* newTerminalWidget(const QString& na, QWidget* wid) {
return new TerminalWidget(na, wid,0 );
}
-*/
+
/* // VT Emulations
EmulationLayer* newVT102( WidgetLayer* wid ) {
return new Vt102Emulation( wid );
}
*/
};
@@ -87,19 +93,21 @@ Default::Default( MetaFactory* fact ) {
fact->addReceiveLayer( "SX", QObject::tr("X-Modem"), newSXReceive );
fact->addIOLayerFactory( "serial", QObject::tr("Serial"), newSerialLayer );
fact->addIOLayerFactory( "irda", QObject::tr("Infrared"), newIrDaLayer );
fact->addIOLayerFactory( "bt", QObject::tr("Bluetooth"), newBTLayer );
fact->addIOLayerFactory( "modem", QObject::tr("Modem"), newModemLayer );
+ fact->addIOLayerFactory( "console", QObject::tr("Console"), newConsole );
fact->addConnectionWidgetFactory( "serial", QObject::tr("Serial"), newSerialWidget );
fact->addConnectionWidgetFactory( "irda", QObject::tr("Infrared"), newIrDaWidget );
fact->addConnectionWidgetFactory( "modem", QObject::tr("Modem"), newModemWidget );
fact->addConnectionWidgetFactory( "bt", QObject::tr("Bluetooth"), newBTWidget );
+ fact->addConnectionWidgetFactory( "console", QObject::tr("Console"), newConsoleWid );
-// fact->addTerminalWidgetFactory( "default", QObject::tr("Default Terminal"), newTerminalWidget );
+ fact->addTerminalWidgetFactory( "default", QObject::tr("Default Terminal"), newTerminalWidget );
// fact->addEmulationLayer( "default", QObject::tr("Default Terminal"), newVT102 );
}
Default::~Default() {
}
diff --git a/noncore/apps/opie-console/default.h b/noncore/apps/opie-console/default.h
index 288f370..b8cda03 100644
--- a/noncore/apps/opie-console/default.h
+++ b/noncore/apps/opie-console/default.h
@@ -14,18 +14,20 @@ extern "C" {
ReceiveLayer* newSYReceive(IOLayer*);
ReceiveLayer* newSXReceive(IOLayer*);
IOLayer* newSerialLayer(const Profile&);
IOLayer* newBTLayer(const Profile& );
IOLayer* newIrDaLayer(const Profile& );
+ IOLayer* newConsole(const Profile& );
ProfileDialogWidget* newSerialWidget(const QString&, QWidget* );
ProfileDialogWidget* newIrDaWidget (const QString&, QWidget* );
ProfileDialogWidget* newBTWidget (const QString&, QWidget* );
+ ProfileDialogWidget* newConsoleWid (const QString&, QWidget* );
-// ProfileDialogWidget* newTerminalWidget(const QString&, QWidget* );
+ ProfileDialogWidget* newTerminalWidget(const QString&, QWidget* );
// EmulationLayer* newVT102( WidgetLayer* );
};
class MetaFactory;
struct Default {
diff --git a/noncore/apps/opie-console/emulation_handler.cpp b/noncore/apps/opie-console/emulation_handler.cpp
index 9e7f56c..48218e6 100644
--- a/noncore/apps/opie-console/emulation_handler.cpp
+++ b/noncore/apps/opie-console/emulation_handler.cpp
@@ -1,11 +1,12 @@
#include <qwidget.h>
#include "TEWidget.h"
#include "TEmuVt102.h"
+#include "profile.h"
#include "emulation_handler.h"
EmulationHandler::EmulationHandler( const Profile& prof, QWidget* parent,const char* name )
: QObject(0, name )
{
@@ -26,17 +27,17 @@ EmulationHandler::EmulationHandler( const Profile& prof, QWidget* parent,const c
}
EmulationHandler::~EmulationHandler() {
delete m_teEmu;
delete m_teWid;
}
-void EmulationHandler::load( const Profile& ) {
- QFont font = QFont("Fixed", 12, QFont::Normal );
- font.setFixedPitch(TRUE);
- m_teWid->setVTFont( font );
- m_teWid->setBackgroundColor(Qt::gray );
+void EmulationHandler::load( const Profile& prof) {
+ m_teWid->setVTFont( font( prof.readNumEntry("Font") ) );
+ int num = prof.readNumEntry("Color");
+ setColor( foreColor(num), backColor(num) );
+ m_teWid->setBackgroundColor(backColor(num) );
}
void EmulationHandler::recv( const QByteArray& ar) {
qWarning("received in EmulationHandler!");
m_teEmu->onRcvBlock(ar.data(), ar.count() );
}
void EmulationHandler::recvEmulation(const char* src, int len ) {
@@ -47,6 +48,84 @@ void EmulationHandler::recvEmulation(const char* src, int len ) {
emit send(ar);
}
QWidget* EmulationHandler::widget() {
return m_teWid;
}
+/*
+ * allocate a new table of colors
+ */
+void EmulationHandler::setColor( const QColor& fore, const QColor& back ) {
+ ColorEntry table[TABLE_COLORS];
+ const ColorEntry *defaultCt = m_teWid->getdefaultColorTable();
+
+ for (int i = 0; i < TABLE_COLORS; i++ ) {
+ if ( i == 0 || i == 10 ) {
+ table[i].color = fore;
+ }else if ( i == 1 || i == 11 ) {
+ table[i].color = back;
+ table[i].transparent = 0;
+ }else {
+ table[i].color = defaultCt[i].color;
+ }
+ }
+// m_teWid->setColorTable(table );
+ m_teWid->update();
+}
+QFont EmulationHandler::font( int id ) {
+ QString name;
+ int size = 0;
+ switch(id ) {
+ default: // fall through
+ case 0:
+ name = QString::fromLatin1("Micro");
+ size = 4;
+ break;
+ case 1:
+ name = QString::fromLatin1("Fixed");
+ size = 7;
+ break;
+ case 2:
+ name = QString::fromLatin1("Fixed");
+ size = 12;
+ break;
+ }
+ QFont font(name, size, QFont::Normal );
+ font.setFixedPitch(TRUE );
+ return font;
+}
+QColor EmulationHandler::foreColor(int col) {
+ QColor co;
+ /* we need to switch it */
+ switch( col ) {
+ default:
+ case Profile::White:
+ qWarning("Foreground black");
+ /* color is black */
+ co = Qt::black;
+ break;
+ case Profile::Black:
+ qWarning("Foreground white");
+ co = Qt::white;
+ break;
+ }
+
+ return co;
+}
+QColor EmulationHandler::backColor(int col ) {
+ QColor co;
+ /* we need to switch it */
+ switch( col ) {
+ default:
+ case Profile::White:
+ qWarning("Background white");
+ /* color is white */
+ co = Qt::white;
+ break;
+ case Profile::Black:
+ qWarning("Background black");
+ co = Qt::black;
+ break;
+ }
+
+ return co;
+}
diff --git a/noncore/apps/opie-console/emulation_handler.h b/noncore/apps/opie-console/emulation_handler.h
index 58b94bc..9af7680 100644
--- a/noncore/apps/opie-console/emulation_handler.h
+++ b/noncore/apps/opie-console/emulation_handler.h
@@ -1,10 +1,11 @@
#ifndef OPIE_EMULATION_HANDLER_H
#define OPIE_EMULATION_HANDLER_H
#include <qobject.h>
+#include <qcolor.h>
#include <qcstring.h>
/*
* Badly ibotty lacks the time to finish
* his widget in time..
* Never the less we've to have an EmulationWidget
@@ -23,12 +24,13 @@
* the pre QByteArray world!
*/
class Profile;
class QWidget;
class TEWidget;
class TEmulation;
+class QFont;
class EmulationHandler : public QObject {
Q_OBJECT
public:
/**
* simple c'tor the parent of the TEWdiget
* and a name
@@ -40,21 +42,27 @@ public:
* delete all components
*/
~EmulationHandler();
void load( const Profile& );
QWidget* widget();
+ void setColor( const QColor& fore, const QColor& back );
signals:
void send( const QByteArray& );
void changeSize(int rows, int cols );
+
public slots:
void recv( const QByteArray& );
private slots:
void recvEmulation( const char*, int len );
+private:
+ QFont font( int );
+ QColor foreColor(int );
+ QColor backColor(int );
private:
TEWidget* m_teWid;
TEmulation* m_teEmu;
};
diff --git a/noncore/apps/opie-console/io_layer.h b/noncore/apps/opie-console/io_layer.h
index 9c99222..5f2fa3c 100644
--- a/noncore/apps/opie-console/io_layer.h
+++ b/noncore/apps/opie-console/io_layer.h
@@ -67,20 +67,20 @@ public:
signals:
/**
* received input as QCString
*/
- virtual void received( const QByteArray& ) = 0;
+ virtual void received( const QByteArray& );
/**
* an error occured
* int for the error number
* and QString for a text
*/
- virtual void error( int, const QString& ) = 0;
+ virtual void error( int, const QString& );
public slots:
/**
* send a QCString to the device
*/
virtual void send( const QByteArray& ) = 0;
diff --git a/noncore/apps/opie-console/opie-console.pro b/noncore/apps/opie-console/opie-console.pro
index 7990a64..c2fb558 100644
--- a/noncore/apps/opie-console/opie-console.pro
+++ b/noncore/apps/opie-console/opie-console.pro
@@ -24,15 +24,16 @@ HEADERS = io_layer.h io_serial.h io_irda.h io_bt.h io_modem.h \
atconfigdialog.h dialdialog.h \
procctl.h \
function_keyboard.h \
receive_layer.h filereceive.h \
script.h \
dialer.h \
+ terminalwidget.h \
emulation_handler.h TECommon.h \
TEHistroy.h TEScreen.h TEWidget.h \
- TEmuVt102.h TEmulation.h
+ TEmuVt102.h TEmulation.h MyPty.h
SOURCES = io_layer.cpp io_serial.cpp io_irda.cpp io_bt.cpp io_modem.cpp \
file_layer.cpp filetransfer.cpp \
main.cpp \
metafactory.cpp \
session.cpp \
@@ -52,15 +53,16 @@ SOURCES = io_layer.cpp io_serial.cpp io_irda.cpp io_bt.cpp io_modem.cpp \
atconfigdialog.cpp dialdialog.cpp \
default.cpp procctl.cpp \
function_keyboard.cpp \
receive_layer.cpp filereceive.cpp \
script.cpp \
dialer.cpp \
+ terminalwidget.cpp \
emulation_handler.cpp TEHistory.cpp \
TEScreen.cpp TEWidget.cpp \
- TEmuVt102.cpp TEmulation.cpp
+ TEmuVt102.cpp TEmulation.cpp MyPty.cpp
INTERFACES = configurebase.ui editbase.ui
INCLUDEPATH += $(OPIEDIR)/include
DEPENDPATH += $(OPIEDIR)/include
LIBS += -lqpe -lopie