aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/desktop-utils/lumina-terminal
diff options
context:
space:
mode:
authorKen Moore <moorekou@gmail.com>2016-04-25 13:08:12 -0400
committerKen Moore <moorekou@gmail.com>2016-04-25 13:08:12 -0400
commited5ecf7ea7a482b4649e66ecb35fbc60af680684 (patch)
treeacc0fa17d228259e847f55c678db9fb0a9b50f0c /src-qt5/desktop-utils/lumina-terminal
parentMerge branch 'master' of github.com:pcbsd/lumina (diff)
downloadlumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.tar.gz
lumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.tar.bz2
lumina-ed5ecf7ea7a482b4649e66ecb35fbc60af680684.zip
Rearrange the Lumina source tree quite a bit:
Now the utilites are arranged by category (core, core-utils, desktop-utils), so all the -utils may be excluded by a package system (or turned into separate packages) as needed.
Diffstat (limited to 'src-qt5/desktop-utils/lumina-terminal')
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TermWindow.cpp301
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TermWindow.h70
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TerminalWidget.cpp504
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TerminalWidget.h68
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TrayIcon.cpp172
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TrayIcon.h59
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TtyProcess.cpp229
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/TtyProcess.h83
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/lumina-terminal.pro96
-rw-r--r--src-qt5/desktop-utils/lumina-terminal/main.cpp47
10 files changed, 1629 insertions, 0 deletions
diff --git a/src-qt5/desktop-utils/lumina-terminal/TermWindow.cpp b/src-qt5/desktop-utils/lumina-terminal/TermWindow.cpp
new file mode 100644
index 00000000..82f71e6b
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TermWindow.cpp
@@ -0,0 +1,301 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "TermWindow.h"
+//#include "ui_TermWindow.h"
+
+#include <QDesktopWidget>
+#include <QDebug>
+#include <QTimer>
+#include <QApplication>
+#include <QVBoxLayout>
+#include "TerminalWidget.h"
+
+// ===============
+// PUBLIC
+// ===============
+TermWindow::TermWindow(QSettings *set) : QWidget(0, Qt::Window | Qt::BypassWindowManagerHint){//, ui(new Ui::TermWindow){
+ CLOSING = false; //internal flag
+ settings = set;
+ //Create the Window
+ this->setLayout(new QVBoxLayout());
+ this->setCursor(Qt::SplitVCursor);
+ tabWidget = new QTabWidget(this);
+ tabWidget->clear(); //just in case
+ tabWidget->setCursor(Qt::ArrowCursor);
+ tabWidget->setTabBarAutoHide(true);
+ tabWidget->setTabsClosable(true);
+ tabWidget->setMovable(true);
+ tabWidget->setUsesScrollButtons(true);
+ this->layout()->addWidget(tabWidget);
+ //Setup the animation
+ ANIM = new QPropertyAnimation(this, "geometry", this);
+ ANIM->setDuration(300); //1/3 second animation
+ connect(ANIM, SIGNAL(finished()), this, SLOT(AnimFinished()) );
+ //Create the keyboard shortcuts
+ //hideS = new QShortcut(QKeySequence(Qt::Key_Escape),this);
+ closeS = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q),this);
+ newTabS = new QShortcut(QKeySequence::AddTab,this);
+ closeTabS = new QShortcut(QKeySequence::Close,this);
+ prevTabS = new QShortcut(QKeySequence::PreviousChild,this);
+ nextTabS = new QShortcut(QKeySequence::NextChild,this);
+ //Print out all the keyboard shortcuts onto the screen
+ qDebug() << "New Tab Shortcut:" << QKeySequence::keyBindings(QKeySequence::AddTab);
+ qDebug() << "Close Tab Shortcut:" << QKeySequence::keyBindings(QKeySequence::Close);
+ qDebug() << "Next Tab Shortcut:" << QKeySequence::keyBindings(QKeySequence::NextChild);
+ qDebug() << "Previous Tab Shortcut:" << QKeySequence::keyBindings(QKeySequence::PreviousChild);
+ //Connect the signals/slots
+ connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(Close_Tab(int)) );
+ connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(focusOnWidget()) );
+ connect(closeTabS, SIGNAL(activated()), this, SLOT(Close_Tab()) );
+ connect(newTabS, SIGNAL(activated()), this, SLOT(New_Tab()) );
+ //connect(hideS, SIGNAL(activated()), this, SLOT(HideWindow()) );
+ connect(closeS, SIGNAL(activated()), this, SLOT(CloseWindow()) );
+ connect(prevTabS, SIGNAL(activated()), this, SLOT(Prev_Tab()) );
+ connect(nextTabS, SIGNAL(activated()), this, SLOT(Next_Tab()) );
+
+ //Now set the defaults
+ screennum = 0; //default value
+ setTopOfScreen(true); //default value
+ if(settings->contains("lastSize")){
+ //qDebug() << "Re-use last size:" << settings->value("lastSize").toSize();
+ this->resize( settings->value("lastSize").toSize() );
+ CalculateGeom();
+ //qDebug() << "After size:" << this->size();
+ }
+
+ //this->resize(this->width(),300);
+ //this->setMinimumSize(20, 300);
+
+}
+
+
+TermWindow::~TermWindow(){
+
+}
+
+void TermWindow::cleanup(){
+ //called right before the window is closed
+ //Make sure to close any open tabs/processes
+ CLOSING = true;
+ for(int i=0; i<tabWidget->count(); i++){
+ static_cast<TerminalWidget*>(tabWidget->widget(i))->aboutToClose();
+ }
+}
+
+void TermWindow::OpenDirs(QStringList dirs){
+ for(int i=0; i<dirs.length(); i++){
+ //Open a new tab for each directory
+ TerminalWidget *page = new TerminalWidget(tabWidget, dirs[i]);
+ QString ID = GenerateTabID();
+ page->setWhatsThis(ID);
+ tabWidget->addTab(page, ID);
+ tabWidget->setCurrentWidget(page);
+ page->setFocus();
+ qDebug() << "New Tab:" << ID << dirs[i];
+ connect(page, SIGNAL(ProcessClosed(QString)), this, SLOT(Close_Tab(QString)) );
+ }
+}
+
+void TermWindow::setCurrentScreen(int num){
+ screennum = num;
+ QTimer::singleShot(0,this, SLOT(ReShowWindow()));
+}
+
+void TermWindow::setTopOfScreen(bool ontop){
+ onTop = ontop;
+ this->layout()->setContentsMargins(0, (onTop ? 0 : 3), 0, (onTop ? 3 : 0));
+ tabWidget->setTabPosition(onTop ? QTabWidget::South : QTabWidget::North);
+ QTimer::singleShot(0,this, SLOT(ReShowWindow()));
+}
+
+// =======================
+// PUBLIC SLOTS
+// =======================
+void TermWindow::ShowWindow(){
+ if(animRunning>=0){ return; } //something running
+ animRunning = 1;
+ this->hide();
+ QApplication::processEvents();
+ CalculateGeom();
+ //Now setup the animation
+ ANIM->setEndValue(this->geometry());
+ if(onTop){ //use top edge
+ ANIM->setStartValue( QRect(this->x(), this->y(), this->width(), 0) ); //same location - no height
+ }else{
+ ANIM->setStartValue( QRect(this->x(), this->geometry().bottom(), this->width(), 0) ); //same location - no height
+ }
+ this->show();
+ //qDebug() << "Start Animation" << ANIM->startValue() << ANIM->endValue();
+ ANIM->start();
+}
+
+void TermWindow::HideWindow(){
+ if(animRunning>=0){ return; } //something running
+ //Now setup the animation
+ //Note: Do *not* use the private settings/variables because it may be changing right now - use the current geometry *ONLY*
+ animRunning = 0;
+ ANIM->setStartValue(this->geometry());
+ QDesktopWidget *desk = QApplication::desktop();
+ int screen = desk->screenNumber(this); //which screen it is currently on
+ if(desk->availableGeometry(screen).top() == this->geometry().top()){ //use top edge
+ ANIM->setEndValue( QRect(this->x(), this->y(), this->width(), 0) ); //same location - no height
+ }else{
+ ANIM->setEndValue( QRect(this->x(), this->y()+this->height(), this->width(), 0) ); //same location - no height
+ }
+ this->show();
+ ANIM->start();
+}
+
+void TermWindow::CloseWindow(){
+ if(animRunning>=0){ return; } //something running
+ //Now setup the animation
+ animRunning = 2;
+ ANIM->setStartValue(this->geometry());
+ if(onTop){ //use top edge
+ ANIM->setEndValue( QRect(this->x(), this->y(), this->width(), 0) ); //same location - no height
+ }else{
+ ANIM->setEndValue( QRect(this->x(), this->geometry().bottom(), this->width(), 0) ); //same location - no height
+ }
+ this->show();
+ ANIM->start();
+}
+
+void TermWindow::ReShowWindow(){
+ if(this->isVisible()){
+ HideWindow(); //start with same animation as hide
+ animRunning = 3; //flag as a re-show (hide, then show);
+ }else{
+ //Already hidden, just show it
+ ShowWindow();
+ }
+}
+// =======================
+// PRIVATE
+// =======================
+void TermWindow::CalculateGeom(){
+ //qDebug() << "Calculating Geom:" << this->size();
+ QDesktopWidget *desk = QApplication::desktop();
+ if(desk->screenCount() <= screennum){ screennum = desk->primaryScreen(); } //invalid screen detected
+ //Now align the window with the proper screen edge
+ QRect workarea = desk->availableGeometry(screennum); //this respects the WORKAREA property
+ if(onTop){
+ this->setGeometry( workarea.x(), workarea.y(), workarea.width(), this->height()); //maintain current hight of window
+
+ }else{
+ this->setGeometry( workarea.x(), workarea.y() + workarea.height() - this->height(), workarea.width(), this->height()); //maintain current hight of window
+ }
+ this->setFixedWidth(this->width()); //Make sure the window is not re-sizeable in the width dimension
+ this->setMinimumHeight(0);
+}
+
+QString TermWindow::GenerateTabID(){
+ //generate a unique ID for this new tab
+ int num = 1;
+ for(int i=0; i<tabWidget->count(); i++){
+ if(tabWidget->widget(i)->whatsThis().toInt() >= num){ num = tabWidget->widget(i)->whatsThis().toInt()+1; }
+ }
+ return QString::number(num);
+}
+
+// =======================
+// PRIVATE SLOTS
+// =======================
+
+//Tab Interactions
+void TermWindow::New_Tab(){
+ OpenDirs(QStringList() << QDir::homePath());
+}
+
+void TermWindow::Close_Tab(int tab){
+ //qDebug() << "Close Tab:" << tab;
+ if(tab<0){ tab = tabWidget->currentIndex(); }
+ static_cast<TerminalWidget*>(tabWidget->widget(tab))->aboutToClose();
+ tabWidget->widget(tab)->deleteLater(); //delete the page within the tag
+ tabWidget->removeTab(tab); // remove the tab itself
+ //Let the tray know when the last terminal is closed
+ if(tabWidget->count() < 1){
+ emit TerminalFinished();
+ }
+}
+
+void TermWindow::Close_Tab(QString ID){
+ //Close a tab based on it's ID instead of it's tab number
+ for(int i=0; i<tabWidget->count(); i++){
+ if(tabWidget->widget(i)->whatsThis()==ID){
+ Close_Tab(i);
+ return; //all done
+ }
+ }
+}
+
+void TermWindow::Next_Tab(){
+ qDebug() << "Next Tab";
+ int next = tabWidget->currentIndex()+1;
+ if(next>=tabWidget->count()){ next = 0; }
+ tabWidget->setCurrentIndex(next);
+}
+
+void TermWindow::Prev_Tab(){
+ qDebug() << "Previous Tab";
+ int next = tabWidget->currentIndex()-1;
+ if(next<0){ next = tabWidget->count()-1; }
+ tabWidget->setCurrentIndex(next);
+}
+
+void TermWindow::focusOnWidget(){
+ if(tabWidget->currentWidget()!=0){
+ tabWidget->currentWidget()->setFocus();
+ }
+}
+
+//Animation finishing
+void TermWindow::AnimFinished(){
+ if(animRunning <0){ return; } //nothing running
+ if(animRunning==0){
+ //Hide Event
+ this->hide(); //need to hide the whole thing now
+ this->setGeometry( ANIM->startValue().toRect() ); //reset back to initial size after hidden
+ emit TerminalHidden();
+ }else if(animRunning==1){
+ //Show Event
+ this->activateWindow();
+ tabWidget->currentWidget()->setFocus();
+ emit TerminalVisible();
+ }else if(animRunning==2){
+ //Close Event
+ this->hide(); //need to hide the whole thing now
+ emit TerminalClosed();
+ }else if(animRunning>2){
+ //Re-Show event
+ this->hide();
+ this->setGeometry( ANIM->startValue().toRect() ); //reset back to initial size after hidden
+ //Now re-show it
+ QTimer::singleShot(0,this, SLOT(ShowWindow()));
+ }
+ animRunning = -1; //done
+}
+
+// ===================
+// PROTECTED
+// ===================
+void TermWindow::mouseMoveEvent(QMouseEvent *ev){
+ //Note: With mouse tracking turned off, this event only happens when the user is holding down the mouse button
+ if(onTop){
+ //Move the bottom edge to the current point
+ if( (ev->globalPos().y() - this->y()) < 50){ return; } //quick check that the window is not smaller than 20 pixels
+ QRect geom = this->geometry();
+ geom.setBottom(ev->globalPos().y());
+ this->setGeometry(geom);
+ }else{
+ //Move the top edge to the current point
+ if( (this->y() + this->height() -ev->globalPos().y()) < 50){ return; } //quick check that the window is not smaller than 20 pixels
+ QRect geom = this->geometry();
+ geom.setTop(ev->globalPos().y());
+ this->setGeometry(geom);
+ }
+ settings->setValue("lastSize",this->geometry().size());
+} \ No newline at end of file
diff --git a/src-qt5/desktop-utils/lumina-terminal/TermWindow.h b/src-qt5/desktop-utils/lumina-terminal/TermWindow.h
new file mode 100644
index 00000000..d68c5457
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TermWindow.h
@@ -0,0 +1,70 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_UTILITIES_TERMINAL_MAIN_WINDOW_H
+#define _LUMINA_DESKTOP_UTILITIES_TERMINAL_MAIN_WINDOW_H
+
+#include <QWidget>
+#include <QPropertyAnimation>
+#include <QTabWidget>
+#include <QDir>
+#include <QShortcut>
+#include <QMouseEvent>
+#include <QSettings>
+
+class TermWindow : public QWidget{
+ Q_OBJECT
+public:
+ TermWindow(QSettings *set);
+ ~TermWindow();
+
+ void cleanup(); //called right before the window is closed
+ void OpenDirs(QStringList);
+
+ void setCurrentScreen(int num = 0);
+ void setTopOfScreen(bool ontop);
+
+public slots:
+ void ShowWindow();
+ void HideWindow();
+ void CloseWindow();
+ void ReShowWindow();
+
+private:
+ QTabWidget *tabWidget;
+ QSettings *settings;
+ QShortcut *hideS, *closeS, *newTabS, *closeTabS, *prevTabS, *nextTabS;
+ int screennum;
+ bool onTop, CLOSING;
+ QPropertyAnimation *ANIM;
+ int animRunning; //internal flag for what animation is currently running
+
+ //Calculate the window geometry necessary based on screen/location
+ void CalculateGeom();
+ QString GenerateTabID();
+
+private slots:
+ //Tab Interactions
+ void New_Tab();
+ void Close_Tab(int tab = -1);
+ void Close_Tab(QString ID); //alternate form of the close routine - based on tab ID
+ void Next_Tab();
+ void Prev_Tab();
+ void focusOnWidget();
+ //Animation finishing
+ void AnimFinished();
+
+protected:
+ void mouseMoveEvent(QMouseEvent*);
+
+signals:
+ void TerminalHidden();
+ void TerminalVisible();
+ void TerminalClosed();
+ void TerminalFinished();
+};
+
+#endif
diff --git a/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.cpp b/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.cpp
new file mode 100644
index 00000000..a90d9846
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.cpp
@@ -0,0 +1,504 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "TerminalWidget.h"
+
+#include <QProcessEnvironment>
+#include <QDebug>
+#include <QApplication>
+#include <QScrollBar>
+#include <QTextBlock>
+
+#include <LuminaXDG.h>
+
+//Special control code ending symbols (aside from letters)
+
+TerminalWidget::TerminalWidget(QWidget *parent, QString dir) : QTextEdit(parent){
+ //Setup the text widget
+ this->setLineWrapMode(QTextEdit::WidgetWidth);
+ this->setAcceptRichText(false);
+ this->setOverwriteMode(true);
+ this->setFocusPolicy(Qt::StrongFocus);
+ this->setWordWrapMode(QTextOption::NoWrap);
+ this->setContextMenuPolicy(Qt::CustomContextMenu);
+ DEFFMT = this->textCursor().charFormat(); //save the default structure for later
+ CFMT = this->textCursor().charFormat(); //current format
+ selCursor = this->textCursor(); //used for keeping track of selections
+ lastCursor = this->textCursor();
+ startrow = endrow = -1;
+ altkeypad = false;
+ QFontDatabase FDB;
+ QStringList fonts = FDB.families(QFontDatabase::Latin);
+ for(int i=0; i<fonts.length(); i++){
+ if(FDB.isFixedPitch(fonts[i])){ this->setFont(QFont(fonts[i])); qDebug() << "Using Font:" << fonts[i]; break; }
+ }
+ //Create/open the TTY port
+ PROC = new TTYProcess(this);
+ qDebug() << "Open new TTY";
+ //int fd;
+ bool ok = PROC->startTTY( QProcessEnvironment::systemEnvironment().value("SHELL","/bin/sh"), QStringList(), dir);
+ qDebug() << " - opened:" << ok;
+ this->setEnabled(PROC->isOpen());
+ contextMenu = new QMenu(this);
+ copyA = contextMenu->addAction(LXDG::findIcon("edit-copy"), tr("Copy Selection"), this, SLOT(copySelection()) );
+ pasteA = contextMenu->addAction(LXDG::findIcon("edit-paste"), tr("Paste"), this, SLOT(pasteSelection()) );
+ //Connect the signals/slots
+ connect(PROC, SIGNAL(readyRead()), this, SLOT(UpdateText()) );
+ connect(PROC, SIGNAL(processClosed()), this, SLOT(ShellClosed()) );
+
+}
+
+TerminalWidget::~TerminalWidget(){
+ aboutToClose();
+}
+
+void TerminalWidget::aboutToClose(){
+ if(PROC->isOpen()){ PROC->closeTTY(); } //TTY PORT
+}
+
+// ==================
+// PRIVATE
+// ==================
+void TerminalWidget::InsertText(QString txt){
+ if(txt.isEmpty()){ return; }
+ //qDebug() << "Insert Text:" << txt << "Cursor Pos:" << this->textCursor().position() << "Column:" << this->textCursor().columnNumber();
+ QTextCursor cur = this->textCursor();
+ cur.setCharFormat(CFMT);
+ cur.insertText( txt, CFMT);
+ this->setTextCursor(cur);
+}
+
+void TerminalWidget::applyData(QByteArray data){
+ //Make sure the current cursor is the right cursor
+ if(this->textCursor()==selCursor){ this->setTextCursor(lastCursor); }
+ //Iterate through the data and apply it when possible
+ QByteArray chars;
+ //qDebug() << "Data:" << data;
+ for(int i=0; i<data.size(); i++){
+ if( data.at(i)=='\b' ){
+ //Flush current text buffer to widget
+ //Simple cursor backward 1 (NOT backspace in this context!! - this widget should be in "insert" mode instead)
+ InsertText(chars); chars.clear();
+ this->moveCursor(QTextCursor::Left, QTextCursor::MoveAnchor);
+ //}else if( data.at(i)=='\t' ){
+ //chars.append(" ");
+ }else if( data.at(i)=='\x1B' ){
+ //Flush current text buffer to widget
+ if(!chars.isEmpty()){ InsertText(chars); chars.clear(); }
+ //ANSI Control Code start
+ //Look for the end of the code
+ int end = -1;
+ for(int j=1; j<(data.size()-i) && end<0; j++){
+ if(QChar(data.at(i+j)).isLetter() || (QChar(data.at(i+j)).isSymbol() && data.at(i+j)!=';') ){ end = j; }
+ else if(data.at(i+j)=='\x1B'){ end = j-1; } //start of the next control code
+ }
+ if(end<0){ return; } //skip everything else - no end to code found
+ applyANSI(data.mid(i+1, end));
+ //qDebug() << "Code:" << data.mid(i+1, end) << "Next Char:" << data[i+end+2];
+ i+=end; //move the final loop along - already handled these bytes
+
+ }else if( data.at(i) != '\r' ){
+ //Special Check: if inserting text within a line, clear the rest of this line first
+ if(i==0 && this->textCursor().position() < this->document()->characterCount()-1){
+ applyANSI("[K");
+ }
+ chars.append(data.at(i));
+ //Plaintext character - just add it here
+ //qDebug() << "Insert Text:" << data.at(i) << CFMT.foreground().color() << CFMT.background().color();
+ //qDebug() << " " << this->currentCharFormat().foreground().color() << this->currentCharFormat().background().color();
+ //this->textCursor().insertText( QChar(data.at(i)), CFMT );
+ }
+ } //end loop over data
+ if(!chars.isEmpty()){ InsertText(chars); }
+}
+
+void TerminalWidget::applyANSI(QByteArray code){
+ //Note: the first byte is often the "[" character
+ qDebug() << "Handle ANSI:" << code;
+ if(code.length()==1){
+ //KEYPAD MODES
+ if(code.at(0)=='='){ altkeypad = true; }
+ else if(code.at(0)=='>'){ altkeypad = false; }
+ else{
+ qDebug() << "Unhandled ANSI Code:" << code;
+ }
+ }else if(code.startsWith("[")){
+ // VT100 ESCAPE CODES
+ //CURSOR MOVEMENT
+ if( code.endsWith("A") ){ //Move Up
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("B")){ //Move Down
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("C")){ //Move Forward
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("D")){ //Move Back
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("E")){ //Move Next/down Lines (go toward end)
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::NextRow, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("F")){ //Move Previous/up Lines (go to beginning)
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::PreviousRow, QTextCursor::MoveAnchor, num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("G")){ //Move to specific column
+ int num = 1;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ QTextCursor cur = this->textCursor();
+ cur.setPosition(num);
+ this->setTextCursor(cur);
+ }else if(code.endsWith("H") || code.endsWith("f") ){ //Move to specific position (row/column)
+ int mid = code.indexOf(";");
+ if(mid>1){
+ int numR, numC; numR = numC = 1;
+ if(mid >=2){ numR = code.mid(1,mid-1).toInt(); }
+ if(mid < code.size()-1){ numC = code.mid(mid+1,code.size()-mid-2).toInt(); }
+
+ if(startrow>=0 && endrow>=0){
+ if(numR == startrow){ numR = 0;}
+ else if(numR==endrow){ numR = this->document()->lineCount()-1; }
+ }
+ qDebug() << "Set Text Position (absolute):" << "Code:" << code << "Row:" << numR << "Col:" << numC;
+ //qDebug() << " - Current Pos:" << this->textCursor().position() << "Line Count:" << this->document()->lineCount();
+ //if(!this->textCursor().movePosition(QTextCursor::Start, QTextCursor::MoveAnchor,1) ){ qDebug() << "Could not go to start"; }
+ QTextCursor cur(this->textCursor());
+ cur.setPosition(QTextCursor::Start, QTextCursor::MoveAnchor); //go to start of document
+ //qDebug() << " - Pos After Start Move:" << cur.position();
+ if( !cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, numR) ){ qDebug() << "Could not go to row:" << numR; }
+ //qDebug() << " - Pos After Down Move:" << cur.position();
+ if( !cur.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, numC) ){ qDebug() << "Could not go to col:" << numC; }
+ /*this->textCursor().setPosition( this->document()->findBlockByLineNumber(numR).position() );
+ qDebug() << " - Pos After Row Move:" << this->textCursor().position();
+ if( !this->textCursor().movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, numC) ){ qDebug() << "Could not go to col:" << numC; }*/
+ //qDebug() << " - Ending Pos:" << cur.position();
+ this->setTextCursor(cur);
+ }else{
+ //Go to home position
+ this->moveCursor(QTextCursor::Start);
+ }
+
+ // CURSOR MANAGEMENT
+ }else if(code.endsWith("r")){ //Tag top/bottom lines as perticular numbers
+ int mid = code.indexOf(";");
+ qDebug() << "New Row Codes:" << code << "midpoint:" << mid;
+ if(mid>1){
+ if(mid >=2){ startrow = code.mid(1,mid-1).toInt(); }
+ if(mid < code.size()-1){ endrow = code.mid(mid+1,code.size()-mid-2).toInt(); }
+ }
+ qDebug() << "New Row Codes:" << startrow << endrow;
+ // DISPLAY CLEAR CODES
+ }else if(code.endsWith("J")){ //ED - Erase Display
+ int num = 0;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ //qDebug() << "Erase Display:" << num;
+ if(num==1){
+ //Clear from cursor to beginning of screen
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor, 1);
+ cur.removeSelectedText();
+ this->setTextCursor(cur);
+ }else if(num==2){
+ //Clear the whole screen
+ qDebug() << "Clear Screen:" << this->document()->lineCount();
+ this->clear();
+ }else{
+ //Clear from cursor to end of screen
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1);
+ cur.removeSelectedText();
+ this->setTextCursor(cur);
+ }
+ }else if(code.endsWith("K")){ //EL - Erase in Line
+ int num = 0;
+ if(code.size()>2){ num = code.mid(1, code.size()-2).toInt(); } //everything in the middle
+ //qDebug() << "Erase Number" << num;
+ //Now determine what should be cleared based on code
+ if(num==1){
+ //Clear from current cursor to beginning of line
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor, 1);
+ cur.removeSelectedText();
+ this->setTextCursor(cur);
+ }else if(num==2){
+ //Clear the entire line
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor, 1);
+ cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor, 1);
+ cur.removeSelectedText();
+ this->setTextCursor(cur);
+ }else{
+ //Clear from current cursor to end of line
+ QTextCursor cur = this->textCursor();
+ cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor, 1);
+ cur.removeSelectedText();
+ this->setTextCursor(cur);
+ }
+
+ //SCROLL MOVEMENT CODES
+ //}else if(code.endsWith("S")){ // SU - Scroll Up
+ //qDebug() << "Scroll Up:" << code;
+ //}else if(code.endsWith("T")){ // SD - Scroll Down
+ //qDebug() << "Scroll Down:" << code;
+
+ // GRAPHICS RENDERING
+ }else if(code.endsWith("m")){
+ //Format: "[<number>;<number>m" (no limit to sections separated by ";")
+ int start = 1;
+ int end = code.indexOf(";");
+ while(end>start){
+ applyANSIColor(code.mid(start, end-start).toInt());
+ //Now update the iterators and try again
+ start = end;
+ end = code.indexOf(";",start+1); //go to the next one
+ }
+ //Need the last section as well
+ end = code.size()-1;
+ if(end>start){ applyANSIColor(code.mid(start, end-start).toInt());}
+ else{ applyANSIColor(0); }
+
+
+ // GRAPHICS MODES
+ //}else if(code.endsWith("h")){
+
+ //}else if(code.endsWith("l")){
+
+ }else{
+ qDebug() << "Unhandled Control Code:" << code;
+ }
+
+ } //End VT100 control codes
+ else{
+ qDebug() << "Unhandled Control Code:" << code;
+ }
+}
+
+void TerminalWidget::applyANSIColor(int code){
+ //qDebug() << "Apply Color code:" << code;
+ if(code <=0){ CFMT = DEFFMT; } //Reset back to default
+ else if(code==1){ CFMT.setFontWeight(75); } //BOLD font
+ else if(code==2){ CFMT.setFontWeight(25); } //Faint font (smaller than normal by a bit)
+ else if(code==3){ CFMT.setFontWeight(75); } //Italic font
+ else if(code==4){ CFMT.setFontUnderline(true); } //Underline
+ //5-6: Blink text (unsupported)
+ //7: Reverse foreground/background (unsupported)
+ //8: Conceal (unsupported)
+ else if(code==9){ CFMT.setFontStrikeOut(true); } //Crossed out
+ //10-19: Change font family (unsupported)
+ //20: Fraktur Font (unsupported)
+ //21: Bold:off or Underline:Double (unsupported)
+ else if(code==22){ CFMT.setFontWeight(50); } //Normal weight
+ //23: Reset font (unsupported)
+ else if(code==24){ CFMT.setFontUnderline(false); } //disable underline
+ //25: Disable blinking (unsupported)
+ //26: Reserved
+ //27: Reset reversal (7) (unsupported)
+ //28: Reveal (cancel 8) (unsupported)
+ else if(code==29){ CFMT.setFontStrikeOut(false); } //Not Crossed out
+ else if(code>=30 && code<=39){
+ //Set the font color
+ QColor color;
+ if(code==30){color=QColor(Qt::black); }
+ else if(code==31){ color=QColor(Qt::red); }
+ else if(code==32){ color=QColor(Qt::green); }
+ else if(code==33){ color=QColor(Qt::yellow); }
+ else if(code==34){ color=QColor(Qt::blue); }
+ else if(code==35){ color=QColor(Qt::magenta); }
+ else if(code==36){ color=QColor(Qt::cyan); }
+ else if(code==37){ color=QColor(Qt::white); }
+ //48: Special extended color setting (unsupported)
+ else if(code==39){ color= DEFFMT.foreground().color(); } //reset to default color
+QBrush brush = CFMT.background();
+ color.setAlpha(255); //fully opaque
+ brush.setColor(color);
+ CFMT.setForeground( brush );
+ this->setTextColor(color); //just in case the format is not used
+ }
+ else if(code>=40 && code<=49){
+ //Set the font color
+ QColor color;
+ if(code==40){color=QColor(Qt::black); }
+ else if(code==41){ color=QColor(Qt::red); }
+ else if(code==42){ color=QColor(Qt::green); }
+ else if(code==43){ color=QColor(Qt::yellow); }
+ else if(code==44){ color=QColor(Qt::blue); }
+ else if(code==45){ color=QColor(Qt::magenta); }
+ else if(code==46){ color=QColor(Qt::cyan); }
+ else if(code==47){ color=QColor(Qt::white); }
+ //48: Special extended color setting (unsupported)
+ else if(code==49){ color= DEFFMT.background().color(); } //reset to default color
+ QBrush brush = CFMT.background();
+ color.setAlpha(255); //fully opaque
+ brush.setColor(color);
+ CFMT.setBackground( brush );
+ }
+ //50: Reserved
+ //51: Framed
+ //52: Encircled
+ else if(code==53){ CFMT.setFontOverline(true); } //enable overline
+ //54: Not framed/circled (51/52)
+ else if(code==55){ CFMT.setFontOverline(false); } //disable overline
+ //56-59: Reserved
+ //60+: Not generally supported (special code for particular terminals such as aixterm)
+}
+
+//Outgoing Data parsing
+void TerminalWidget::sendKeyPress(int key){
+ QByteArray ba;
+ //if(this->textCursor()==selCursor){ this->setTextCursor(lastCursor); }
+ //int fromEnd = this->document()->characterCount() - this->textCursor().position();
+ //Check for special keys
+ switch(key){
+ case Qt::Key_Delete:
+ ba.append("\x7F");
+ break;
+ case Qt::Key_Backspace:
+ ba.append("\x08");
+ break;
+ case Qt::Key_Left:
+ if(altkeypad){ ba.append("^[D"); }
+ else{ ba.append("\x1b[D"); }
+ break;
+ case Qt::Key_Right:
+ if(altkeypad){ ba.append("^[C"); }
+ else{ ba.append("\x1b[C"); }
+ break;
+ case Qt::Key_Up:
+ if(altkeypad){ ba.append("^[A"); }
+ else{ ba.append("\x1b[A"); }
+ break;
+ case Qt::Key_Down:
+ if(altkeypad){ ba.append("^[B"); }
+ else{ ba.append("\x1b[B"); }
+ break;
+ case Qt::Key_Home:
+ ba.append("\x1b[H");
+ break;
+ case Qt::Key_End:
+ ba.append("\x1b[F");
+ break;
+ }
+ qDebug() << "Forward Input:" << ba;
+ if(!ba.isEmpty()){ PROC->writeTTY(ba); }
+}
+
+// ==================
+// PRIVATE SLOTS
+// ==================
+void TerminalWidget::UpdateText(){
+ //read the data from the process
+ //qDebug() << "UpdateText";
+ if(!PROC->isOpen()){ return; }
+ applyData(PROC->readTTY());
+ //adjust the scrollbar as needed
+ this->ensureCursorVisible();
+ //this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum());
+}
+
+void TerminalWidget::ShellClosed(){
+ emit ProcessClosed(this->whatsThis());
+}
+
+void TerminalWidget::copySelection(){
+ QApplication::clipboard()->setText( selCursor.selectedText() );
+}
+
+void TerminalWidget::pasteSelection(){
+ QString text = QApplication::clipboard()->text();
+ if(!text.isEmpty()){
+ QByteArray ba; ba.append(text); //avoid any byte conversions
+ PROC->writeTTY(ba);
+ }
+}
+
+// ==================
+// PROTECTED
+// ==================
+void TerminalWidget::keyPressEvent(QKeyEvent *ev){
+
+ if(ev->text().isEmpty() || ev->text()=="\b" ){
+ sendKeyPress(ev->key());
+ //PROC->writeTTY( QByteArray::fromHex(ev->nativeVirtualKey()) );
+ }else{
+ if( (ev->key()==Qt::Key_Enter || ev->key()==Qt::Key_Return) && !this->textCursor().atEnd() ){
+ sendKeyPress(Qt::Key_End); //just in case the cursor is not at the end (TTY will split lines and such - ugly)
+ }
+ QByteArray ba; ba.append(ev->text()); //avoid any byte conversions
+ //qDebug() << "Forward Input:" << ba;
+ PROC->writeTTY(ba);
+ }
+
+ ev->ignore();
+}
+
+void TerminalWidget::mousePressEvent(QMouseEvent *ev){
+ this->setFocus();
+ if(ev->button()==Qt::RightButton){
+ QTextEdit::mousePressEvent(ev);
+ }else if(ev->button()==Qt::MiddleButton){
+ pasteSelection();
+ }else if(ev->button()==Qt::LeftButton){
+ if(this->textCursor()!=selCursor){ lastCursor = this->textCursor(); }
+ selCursor = this->cursorForPosition(ev->pos());
+ }
+ Q_UNUSED(ev);
+}
+
+void TerminalWidget::mouseMoveEvent(QMouseEvent *ev){
+ if(ev->button()==Qt::LeftButton){
+ selCursor.setPosition(this->cursorForPosition(ev->pos()).position(), QTextCursor::KeepAnchor);
+ if(selCursor.hasSelection()){ this->setTextCursor(selCursor); }
+ }else{
+ QTextEdit::mouseMoveEvent(ev);
+ }
+}
+
+void TerminalWidget::mouseReleaseEvent(QMouseEvent *ev){
+ if(ev->button()==Qt::LeftButton){
+ selCursor.setPosition(this->cursorForPosition(ev->pos()).position(), QTextCursor::KeepAnchor);
+ if(selCursor.hasSelection()){ this->setTextCursor(selCursor); }
+ else{ this->setTextCursor(lastCursor); }
+ }else if(ev->button()==Qt::RightButton){
+ copyA->setEnabled( selCursor.hasSelection() );
+ pasteA->setEnabled( !QApplication::clipboard()->text().isEmpty() );
+ contextMenu->popup( this->mapToGlobal(ev->pos()) );
+ }
+ Q_UNUSED(ev);
+}
+
+void TerminalWidget::mouseDoubleClickEvent(QMouseEvent *ev){
+ Q_UNUSED(ev);
+}
+
+void TerminalWidget::resizeEvent(QResizeEvent *ev){
+ if(!PROC->isOpen()){ return; }
+ QSize pix = ev->size(); //pixels
+ QSize chars;
+ chars.setWidth( pix.width()/this->fontMetrics().width("W") );
+ chars.setHeight( pix.height()/this->fontMetrics().lineSpacing() );
+
+ PROC->setTerminalSize(chars,pix);
+ QTextEdit::resizeEvent(ev);
+}
diff --git a/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.h b/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.h
new file mode 100644
index 00000000..32fd55ad
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TerminalWidget.h
@@ -0,0 +1,68 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_UTILITIES_TERMINAL_PROCESS_WIDGET_H
+#define _LUMINA_DESKTOP_UTILITIES_TERMINAL_PROCESS_WIDGET_H
+
+#include <QTextEdit>
+#include <QKeyEvent>
+#include <QResizeEvent>
+#include <QSocketNotifier>
+#include <QTimer>
+#include <QMenu>
+#include <QClipboard>
+
+#include "TtyProcess.h"
+
+class TerminalWidget : public QTextEdit{
+ Q_OBJECT
+public:
+ TerminalWidget(QWidget *parent =0, QString dir="");
+ ~TerminalWidget();
+
+ void aboutToClose();
+
+private:
+ TTYProcess *PROC;
+ QTextCharFormat DEFFMT, CFMT; //default/current text format
+ QTextCursor selCursor, lastCursor;
+ QMenu *contextMenu;
+ QAction *copyA, *pasteA;
+ int selectionStart;
+
+ //Incoming Data parsing
+ void InsertText(QString);
+ void applyData(QByteArray data); //overall data parsing
+ void applyANSI(QByteArray code); //individual code application
+ void applyANSIColor(int code); //Add the designated color code to the CFMT structure
+
+ //Outgoing Data parsing
+ void sendKeyPress(int key);
+
+ //Special incoming data flags
+ int startrow, endrow; //indexes for the first/last row ("\x1b[A;Br" CC)
+ bool altkeypad;
+private slots:
+ void UpdateText();
+ void ShellClosed();
+
+ void copySelection();
+ void pasteSelection();
+
+signals:
+ void ProcessClosed(QString);
+
+protected:
+ void keyPressEvent(QKeyEvent *ev);
+ void mousePressEvent(QMouseEvent *ev);
+ void mouseMoveEvent(QMouseEvent *ev);
+ void mouseReleaseEvent(QMouseEvent *ev);
+ void mouseDoubleClickEvent(QMouseEvent *ev);
+ //void contextMenuEvent(QContextMenuEvent *ev);
+ void resizeEvent(QResizeEvent *ev);
+};
+
+#endif
diff --git a/src-qt5/desktop-utils/lumina-terminal/TrayIcon.cpp b/src-qt5/desktop-utils/lumina-terminal/TrayIcon.cpp
new file mode 100644
index 00000000..ea970df9
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TrayIcon.cpp
@@ -0,0 +1,172 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "TrayIcon.h"
+
+#include <QDir>
+#include <QDesktopWidget>
+
+#include <LuminaUtils.h>
+
+TrayIcon::TrayIcon() : QSystemTrayIcon(){
+ //Create the child widgets here
+ settings = new QSettings("lumina-desktop","lumina-terminal");
+ this->setContextMenu(new QMenu());
+ ScreenMenu = new QMenu();
+ connect(ScreenMenu, SIGNAL(triggered(QAction*)), this, SLOT(ChangeScreen(QAction*)) );
+ TERM = new TermWindow(settings);
+ //Load the current settings
+ TERM->setTopOfScreen(settings->value("TopOfScreen",true).toBool());
+ TERM->setCurrentScreen(settings->value("OnScreen",0).toInt());
+ connect(TERM, SIGNAL(TerminalHidden()), this, SLOT(TermHidden()));
+ connect(TERM, SIGNAL(TerminalVisible()), this, SLOT(TermVisible()));
+ connect(TERM, SIGNAL(TerminalClosed()), this, SLOT(startCleanup()));
+ connect(TERM, SIGNAL(TerminalFinished()), this, SLOT(stopApplication()));
+ connect(this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(TrayActivated(QSystemTrayIcon::ActivationReason)) );
+}
+
+TrayIcon::~TrayIcon(){
+ delete TERM;
+ delete ScreenMenu;
+}
+
+// =============
+// PUBLIC
+// =============
+void TrayIcon::parseInputs(QStringList inputs){
+ //Note that this is only run on the primary process - otherwise inputs are sent to the slotSingleInstance() below
+ termVisible = !inputs.contains("-toggle"); //will automatically show the terminal on first run, even if "-toggle" is set
+
+ setupContextMenu();
+ updateIcons();
+ inputs = adjustInputs(inputs); //will adjust termVisible as necessary
+ if(inputs.isEmpty()){ inputs << QDir::homePath(); } //always start up with one terminal minimum
+ TERM->OpenDirs(inputs);
+ if(termVisible){ QTimer::singleShot(0, TERM, SLOT(ShowWindow())); }
+}
+
+// =================
+// PUBLIC SLOTS
+// =================
+void TrayIcon::slotSingleInstance(QStringList inputs){
+ //Note that this is only run for a secondary process forwarding its inputs
+ //qDebug() << "Single Instance Event:" << inputs << termVisible;
+ bool visible = termVisible;
+ inputs = adjustInputs(inputs); //will adjust termVisible as necessary
+ if(!inputs.isEmpty()){ TERM->OpenDirs(inputs); }
+ //Only adjust the window if there was a change in the visibility status
+ //qDebug() << "Set Visible:" << termVisible;
+ if(!visible && termVisible){ QTimer::singleShot(0, TERM, SLOT(ShowWindow())); }
+ else if(visible && !termVisible){ QTimer::singleShot(0, TERM, SLOT(HideWindow())); }
+}
+
+void TrayIcon::updateIcons(){
+ this->setIcon(LXDG::findIcon("utilities-terminal",""));
+}
+
+// ================
+// PRIVATE
+// ================
+QStringList TrayIcon::adjustInputs(QStringList inputs){
+ bool hasHide = false;
+ //Look for the special CLI flags just for the tray icon and trim them out
+ for(int i=0; i<inputs.length(); i++){
+ if(inputs[i]=="-toggle"){ hasHide = termVisible; inputs.removeAt(i); i--; } //toggle the visibility
+ else if(inputs[i]=="-show"){ hasHide = false; inputs.removeAt(i); i--; } //change the visibility
+ else if(inputs[i]=="-hide"){ hasHide = true; inputs.removeAt(i); i--; } //change the visibility
+ else{
+ //Must be a directory - convert to an absolute path and check for existance
+ inputs[i] = LUtils::PathToAbsolute(inputs[i]);
+ QFileInfo info(inputs[i]);
+ if(!info.exists()){
+ qDebug() << "Directory does not exist: " << inputs[i];
+ inputs.removeAt(i);
+ i--;
+ }else if(!info.isDir()){
+ //Must be some kind of file, open the parent directory
+ inputs[i] = inputs[i].section("/",0,-2);
+ }
+ }
+ }
+ termVisible = !hasHide;
+ return inputs;
+}
+
+// ================
+// PRIVATE SLOTS
+// ================
+void TrayIcon::startCleanup(){
+ TERM->cleanup();
+}
+
+void TrayIcon::stopApplication(){
+ QApplication::exit(0);
+}
+
+void TrayIcon::ChangeTopBottom(bool ontop){
+ TERM->setTopOfScreen(ontop);
+ settings->setValue("TopOfScreen",ontop); //save for later
+}
+
+void TrayIcon::ChangeScreen(QAction *act){
+ int screen = act->whatsThis().toInt();
+ TERM->setCurrentScreen(screen);
+ settings->setValue("OnScreen",screen);
+ updateScreenMenu();
+}
+
+void TrayIcon::setupContextMenu(){
+ this->contextMenu()->clear();
+ this->contextMenu()->addAction(LXDG::findIcon("edit-select",""), tr("Trigger Terminal"), this, SLOT(ToggleVisibility()) );
+ this->contextMenu()->addSeparator();
+ QAction * act = this->contextMenu()->addAction(tr("Top of Screen"), this, SLOT(ChangeTopBottom(bool)) );
+ act->setCheckable(true);
+ act->setChecked(settings->value("TopOfScreen",true).toBool() );
+ this->contextMenu()->addMenu(ScreenMenu);
+ this->contextMenu()->addSeparator();
+ this->contextMenu()->addAction(LXDG::findIcon("application-exit",""), tr("Close Terminal"), this, SLOT(stopApplication()) );
+ updateScreenMenu();
+}
+
+void TrayIcon::updateScreenMenu(){
+ ScreenMenu->clear();
+ QDesktopWidget *desk = QApplication::desktop();
+ int cscreen = settings->value("OnScreen",0).toInt();
+ if(cscreen>=desk->screenCount()){ cscreen = desk->primaryScreen(); }
+ ScreenMenu->setTitle(tr("Move To Monitor"));
+ for(int i=0; i<desk->screenCount(); i++){
+ if(i!=cscreen){
+ QAction *act = new QAction( QString(tr("Monitor %1")).arg(QString::number(i+1)),ScreenMenu);
+ act->setWhatsThis(QString::number(i));
+ ScreenMenu->addAction(act);
+ }
+ }
+ ScreenMenu->setVisible(!ScreenMenu->isEmpty());
+}
+
+void TrayIcon::TrayActivated(QSystemTrayIcon::ActivationReason reason){
+ switch(reason){
+ case QSystemTrayIcon::Context:
+ this->contextMenu()->popup(this->geometry().center());
+ break;
+ default:
+ ToggleVisibility();
+ }
+}
+
+//Slots for the window visibility
+void TrayIcon::ToggleVisibility(){
+ if(termVisible){ QTimer::singleShot(0, TERM, SLOT(HideWindow())); }
+ else{ QTimer::singleShot(0, TERM, SLOT(ShowWindow())); }
+}
+
+void TrayIcon::TermHidden(){
+ termVisible = false;
+}
+
+void TrayIcon::TermVisible(){
+ termVisible = true;
+} \ No newline at end of file
diff --git a/src-qt5/desktop-utils/lumina-terminal/TrayIcon.h b/src-qt5/desktop-utils/lumina-terminal/TrayIcon.h
new file mode 100644
index 00000000..961aaa90
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TrayIcon.h
@@ -0,0 +1,59 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_UTILITIES_TERMINAL_TRAY_ICON_H
+#define _LUMINA_DESKTOP_UTILITIES_TERMINAL_TRAY_ICON_H
+// QT Includes
+#include <QApplication>
+#include <QSystemTrayIcon>
+#include <QMenu>
+#include <QTimer>
+#include <QSettings>
+
+#include <LuminaXDG.h>
+
+#include "TermWindow.h"
+
+class TrayIcon : public QSystemTrayIcon {
+ Q_OBJECT
+
+public:
+ TrayIcon();
+ ~TrayIcon();
+
+ //First run
+ void parseInputs(QStringList); //Note that this is only run on the primary process - otherwise it gets sent to the singleInstance slot below
+
+public slots:
+ void slotSingleInstance(QStringList inputs = QStringList());
+ void updateIcons();
+
+private:
+ bool termVisible;
+ TermWindow *TERM;
+ QMenu *ScreenMenu;
+ QStringList adjustInputs(QStringList);
+ QSettings *settings;
+private slots:
+ //Action Buttons
+ void startCleanup();
+ void stopApplication();
+ void ChangeTopBottom(bool ontop);
+ void ChangeScreen(QAction*);
+
+ //Tray Updates
+ void setupContextMenu();
+ void updateScreenMenu();
+ void TrayActivated(QSystemTrayIcon::ActivationReason);
+
+ //Slots for the window visibility
+ void ToggleVisibility();
+ void TermHidden();
+ void TermVisible();
+
+};
+
+#endif
diff --git a/src-qt5/desktop-utils/lumina-terminal/TtyProcess.cpp b/src-qt5/desktop-utils/lumina-terminal/TtyProcess.cpp
new file mode 100644
index 00000000..c5844255
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TtyProcess.cpp
@@ -0,0 +1,229 @@
+#include "TtyProcess.h"
+
+#include <QDir>
+#include <QProcessEnvironment>
+
+TTYProcess::TTYProcess(QObject *parent) : QObject(parent){
+ childProc = 0;
+ sn = 0;
+ ttyfd = 0;
+}
+
+TTYProcess::~TTYProcess(){
+ closeTTY(); //make sure everything is closed properly
+}
+
+// === PUBLIC ===
+bool TTYProcess::startTTY(QString prog, QStringList args, QString workdir){
+ if(workdir=="~"){ workdir = QDir::homePath(); }
+ QDir::setCurrent(workdir);
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ setenv("TERM","vt100",1); //VT100 emulation support
+ unsetenv("TERMCAP");
+ /*setenv("TERMCAP","mvterm|vv100|mvterm emulator with ANSI colors:\
+ :pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[100m:tc=vt102:",1); //see /etc/termcap as well*/
+ QStringList filter = env.keys().filter("XTERM");
+ for(int i=0; i<filter.length(); i++){ unsetenv(filter[i].toLocal8Bit().data()); }
+ //if(env.contains("TERM")){ unsetenv("TERM"); }
+ //else if(env.contains
+ //Turn the program/arguments into C-compatible arrays
+ char cprog[prog.length()]; strcpy(cprog, prog.toLocal8Bit().data());
+ char *cargs[args.length()+2];
+ QByteArray nullarray;
+ for(int i=0; i<args.length()+2; i++){
+ // First arg needs to be the program
+ if ( i == 0 ) {
+ cargs[i] = new char[ prog.toLocal8Bit().size()+1];
+ strcpy( cargs[i], prog.toLocal8Bit().data() );
+ } else if(i<args.length()){
+ cargs[i] = new char[ args[i].toLocal8Bit().size()+1];
+ strcpy( cargs[i], args[i].toLocal8Bit().data() );
+ }else{
+ cargs[i] = NULL;
+ }
+ }
+ qDebug() << "PTY Start:" << prog;
+ //Launch the process attached to a new PTY
+ int FD = 0;
+ pid_t tmp = LaunchProcess(FD, cprog, cargs);
+ qDebug() << " - PID:" << tmp;
+ qDebug() << " - FD:" << FD;
+ if(tmp<0){ return false; } //error
+ else{
+ childProc = tmp;
+ //Load the file for close notifications
+ //TO-DO
+ //Watch the socket for activity
+ sn= new QSocketNotifier(FD, QSocketNotifier::Read);
+ sn->setEnabled(true);
+ connect(sn, SIGNAL(activated(int)), this, SLOT(checkStatus(int)) );
+ ttyfd = FD;
+ qDebug() << " - PTY:" << ptsname(FD);
+ return true;
+ }
+}
+
+void TTYProcess::closeTTY(){
+ int junk;
+ if(0==waitpid(childProc, &junk, WNOHANG)){
+ kill(childProc, SIGKILL);
+ }
+ if(ttyfd!=0 && sn!=0){
+ sn->setEnabled(false);
+ ::close(ttyfd);
+ ttyfd = 0;
+ emit processClosed();
+ }
+}
+
+void TTYProcess::writeTTY(QByteArray output){
+ //qDebug() << "Write:" << output;
+ ::write(ttyfd, output.data(), output.size());
+}
+
+QByteArray TTYProcess::readTTY(){
+ QByteArray BA;
+ //qDebug() << "Read TTY";
+ if(sn==0){ return BA; } //not setup yet
+ char buffer[64];
+ ssize_t rtot = read(sn->socket(),&buffer,64);
+ //buffer[rtot]='\0';
+ BA = QByteArray(buffer, rtot);
+ //qDebug() << " - Got Data:" << BA;
+ if(!fragBA.isEmpty()){
+ //Have a leftover fragment, include this too
+ BA = BA.prepend(fragBA);
+ fragBA.clear();
+ }
+ bool bad = true;
+ BA = CleanANSI(BA, bad);
+ if(bad){
+ //incomplete fragent - read some more first
+ fragBA = BA;
+ return readTTY();
+ }else{
+ //qDebug() << "Read Data:" << BA;
+ return BA;
+ }
+}
+
+void TTYProcess::setTerminalSize(QSize chars, QSize pixels){
+ if(ttyfd==0){ return; }
+
+ struct winsize c_sz;
+ c_sz.ws_row = chars.height();
+ c_sz.ws_col = chars.width();
+ c_sz.ws_xpixel = pixels.width();
+ c_sz.ws_ypixel = pixels.height();
+ if( ioctl(ttyfd, TIOCSWINSZ, &ws) ){
+ qDebug() << "Error settings terminal size";
+ }else{
+ //qDebug() <<"Set Terminal Size:" << pixels << chars;
+ }
+}
+
+bool TTYProcess::isOpen(){
+ return (ttyfd!=0);
+}
+
+QByteArray TTYProcess::CleanANSI(QByteArray raw, bool &incomplete){
+ incomplete = true;
+ //qDebug() << "Clean ANSI Data:" << raw;
+ //IN_LINE TERMINAL COLOR CODES (ANSI Escape Codes) "\x1B[<colorcode>m"
+ // - Just remove them for now
+
+ //Special XTERM encoding (legacy support)
+ int index = raw.indexOf("\x1B]");
+ while(index>=0){
+ //The end character of this sequence is the Bell command ("\x07")
+ int end = raw.indexOf("\x07");
+ if(end<0){ return raw; } //incomplete raw array
+ raw = raw.remove(index, end-index+1);
+ index = raw.indexOf("\x1B]");
+ }
+
+ // GENERIC ANSI CODES ((Make sure the output is not cut off in the middle of a code)
+ index = raw.indexOf("\x1B");
+ while(index>=0){
+ //CURSOR MOVEMENT
+ int end = 0;
+ for(int i=1; i<raw.size() && end==0; i++){
+ if(raw.size() < index+i){ return raw; }//cut off - go back for more data
+ //qDebug() << "Check Char:" << raw.at(index+i);
+ if( QChar(raw.at(index+i)).isLetter() ){
+ end = i; //found the end of the control code
+ }
+ }
+ index = raw.indexOf("\x1B",index+1); //now find the next one
+ }
+
+ // SYSTEM BELL
+ index = raw.indexOf("\x07");
+ while(index>=0){
+ //qDebug() << "Remove Bell:" << index;
+ raw = raw.remove(index,1);
+ index = raw.indexOf("\x07");
+ }
+
+ incomplete = false;
+ return raw;
+}
+
+// === PRIVATE ===
+pid_t TTYProcess::LaunchProcess(int& fd, char *prog, char **child_args){
+ //Returns: -1 for errors, positive value (file descriptor) for the master side of the TTY to watch
+
+ //First open/setup a new pseudo-terminal file/device on the system (master side)
+ fd = posix_openpt(O_RDWR | O_NOCTTY); //open read/write
+ if(fd<0){ return -1; } //could not create pseudo-terminal
+ int rc = grantpt(fd); //set permissions
+ if(rc!=0){ return -1; }
+ rc = unlockpt(fd); //unlock file (ready for use)
+ if(rc!=0){ return -1; }
+ //Now fork, return the Master device and setup the child
+ pid_t PID = fork();
+ if(PID==0){
+ //SLAVE/child
+ int fds = ::open(ptsname(fd), O_RDWR | O_NOCTTY); //open slave side read/write
+ ::close(fd); //close the master side from the slave thread
+
+ //Adjust the slave side mode to RAW
+ struct termios TSET;
+ rc = tcgetattr(fds, &TSET); //read the current settings
+ cfmakesane(&TSET); //set the RAW mode on the settings ( cfmakeraw(&TSET); )
+ tcsetattr(fds, TCSANOW, &TSET); //apply the changed settings
+
+ //Change the controlling terminal in child thread to the slave PTY
+ ::close(0); //close current terminal standard input
+ ::close(1); //close current terminal standard output
+ ::close(2); //close current terminal standard error
+ dup(fds); // Set slave PTY as standard input (0);
+ dup(fds); // Set slave PTY as standard output (1);
+ dup(fds); // Set slave PTY as standard error (2);
+
+ setsid(); //Make current process new session leader (so we can set controlling terminal)
+ ioctl(0,TIOCSCTTY, 1); //Set the controlling terminal to the slave PTY
+
+ //Execute the designated program
+ rc = execvp(prog, child_args);
+ ::close(fds); //no need to keep original file descriptor open any more
+ exit(rc);
+ }
+ //MASTER thread (or error)
+ return PID;
+}
+
+// === PRIVATE SLOTS ===
+void TTYProcess::checkStatus(int sock){
+ //This is run when the socket gets activated
+ if(sock!=ttyfd){
+
+ }
+ //Make sure the child PID is still active
+ int junk;
+ if(0!=waitpid(childProc, &junk, WNOHANG)){
+ this->closeTTY(); //clean up everything else
+ }else{
+ emit readyRead();
+ }
+}
diff --git a/src-qt5/desktop-utils/lumina-terminal/TtyProcess.h b/src-qt5/desktop-utils/lumina-terminal/TtyProcess.h
new file mode 100644
index 00000000..9b3873b0
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/TtyProcess.h
@@ -0,0 +1,83 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This is basically a replacement for QProcess, where all process/terminal outputs
+// are redirected and not just the standard input/output channels. This allows it
+// to be used for terminal-like apps (shells) which directly modify the terminal output
+// rather than stick to input/output channels for communication.
+//===========================================
+// IMPLEMENTATION NOTE
+//======================
+// The process requires/uses ANSI control codes (\x1B[<something>) for special operations
+// such as moving the cursor, erasing characters, etc..
+// It is recommended that you pair this class with the graphical "TerminalWidget.h" class
+// or some other ANSI-compatible display widget.
+//===========================================
+#ifndef _LUMINA_DESKTOP_UTILITIES_TERMINAL_TTY_PROCESS_WIDGET_H
+#define _LUMINA_DESKTOP_UTILITIES_TERMINAL_TTY_PROCESS_WIDGET_H
+
+#include <QDebug>
+#include <QSocketNotifier>
+#include <QKeyEvent>
+
+//Standard C library functions for PTY access/setup
+#include <stdlib.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+class TTYProcess : public QObject{
+ Q_OBJECT
+public:
+ TTYProcess(QObject *parent = 0);
+ ~TTYProcess();
+
+ bool startTTY(QString prog, QStringList args = QStringList(), QString workdir = "~");
+ void closeTTY();
+
+ //Primary read/write functions
+ void writeTTY(QByteArray output);
+ QByteArray readTTY();
+
+ //Setup the terminal size (characters and pixels)
+ void setTerminalSize(QSize chars, QSize pixels);
+
+ //Status update checks
+ bool isOpen();
+
+ //Functions for handling ANSI escape codes (typically not used by hand)
+ QByteArray CleanANSI(QByteArray, bool &incomplete);
+
+private:
+ pid_t childProc;
+ int ttyfd;
+ QSocketNotifier *sn;
+ QByteArray fragBA; //fragment ByteArray
+
+ //====================================
+ // C Library function for setting up the PTY
+ // Inputs:
+ // int &fd: (output) file descriptor for the master PTY (positive integer if valid)
+ // char *prog: program to run
+ // char **args: program arguments
+ // Returns:
+ // -1 for errors, child process PID (positive integer) if successful
+ //====================================
+ static pid_t LaunchProcess(int& fd, char *prog, char **child_args);
+
+private slots:
+ void checkStatus(int);
+
+signals:
+ void readyRead();
+ void processClosed();
+};
+
+#endif
diff --git a/src-qt5/desktop-utils/lumina-terminal/lumina-terminal.pro b/src-qt5/desktop-utils/lumina-terminal/lumina-terminal.pro
new file mode 100644
index 00000000..104ff33f
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/lumina-terminal.pro
@@ -0,0 +1,96 @@
+include("$${PWD}/../../OS-detect.pri")
+
+QT += core gui widgets network
+
+TARGET = lumina-terminal
+target.path = $${L_BINDIR}
+
+HEADERS += TrayIcon.h \
+ TermWindow.h \
+ TerminalWidget.h \
+ TtyProcess.h
+
+SOURCES += main.cpp \
+ TrayIcon.cpp \
+ TermWindow.cpp \
+ TerminalWidget.cpp \
+ TtyProcess.cpp
+
+
+LIBS += -lLuminaUtils
+
+
+DEPENDPATH += ../../libLumina
+
+TRANSLATIONS = i18n/lumina-terminal_af.ts \
+ i18n/lumina-terminal_ar.ts \
+ i18n/lumina-terminal_az.ts \
+ i18n/lumina-terminal_bg.ts \
+ i18n/lumina-terminal_bn.ts \
+ i18n/lumina-terminal_bs.ts \
+ i18n/lumina-terminal_ca.ts \
+ i18n/lumina-terminal_cs.ts \
+ i18n/lumina-terminal_cy.ts \
+ i18n/lumina-terminal_da.ts \
+ i18n/lumina-terminal_de.ts \
+ i18n/lumina-terminal_el.ts \
+ i18n/lumina-terminal_en_GB.ts \
+ i18n/lumina-terminal_en_ZA.ts \
+ i18n/lumina-terminal_es.ts \
+ i18n/lumina-terminal_et.ts \
+ i18n/lumina-terminal_eu.ts \
+ i18n/lumina-terminal_fa.ts \
+ i18n/lumina-terminal_fi.ts \
+ i18n/lumina-terminal_fr.ts \
+ i18n/lumina-terminal_fr_CA.ts \
+ i18n/lumina-terminal_gl.ts \
+ i18n/lumina-terminal_he.ts \
+ i18n/lumina-terminal_hi.ts \
+ i18n/lumina-terminal_hr.ts \
+ i18n/lumina-terminal_hu.ts \
+ i18n/lumina-terminal_id.ts \
+ i18n/lumina-terminal_is.ts \
+ i18n/lumina-terminal_it.ts \
+ i18n/lumina-terminal_ja.ts \
+ i18n/lumina-terminal_ka.ts \
+ i18n/lumina-terminal_ko.ts \
+ i18n/lumina-terminal_lt.ts \
+ i18n/lumina-terminal_lv.ts \
+ i18n/lumina-terminal_mk.ts \
+ i18n/lumina-terminal_mn.ts \
+ i18n/lumina-terminal_ms.ts \
+ i18n/lumina-terminal_mt.ts \
+ i18n/lumina-terminal_nb.ts \
+ i18n/lumina-terminal_nl.ts \
+ i18n/lumina-terminal_pa.ts \
+ i18n/lumina-terminal_pl.ts \
+ i18n/lumina-terminal_pt.ts \
+ i18n/lumina-terminal_pt_BR.ts \
+ i18n/lumina-terminal_ro.ts \
+ i18n/lumina-terminal_ru.ts \
+ i18n/lumina-terminal_sk.ts \
+ i18n/lumina-terminal_sl.ts \
+ i18n/lumina-terminal_sr.ts \
+ i18n/lumina-terminal_sv.ts \
+ i18n/lumina-terminal_sw.ts \
+ i18n/lumina-terminal_ta.ts \
+ i18n/lumina-terminal_tg.ts \
+ i18n/lumina-terminal_th.ts \
+ i18n/lumina-terminal_tr.ts \
+ i18n/lumina-terminal_uk.ts \
+ i18n/lumina-terminal_uz.ts \
+ i18n/lumina-terminal_vi.ts \
+ i18n/lumina-terminal_zh_CN.ts \
+ i18n/lumina-terminal_zh_HK.ts \
+ i18n/lumina-terminal_zh_TW.ts \
+ i18n/lumina-terminal_zu.ts
+
+dotrans.path=$${L_SHAREDIR}/Lumina-DE/i18n/
+dotrans.extra=cd i18n && $${LRELEASE} -nounfinished *.ts && cp *.qm $(INSTALL_ROOT)$${L_SHAREDIR}/Lumina-DE/i18n/
+
+INSTALLS += target dotrans
+
+NO_I18N{
+ INSTALLS -= dotrans
+}
+
diff --git a/src-qt5/desktop-utils/lumina-terminal/main.cpp b/src-qt5/desktop-utils/lumina-terminal/main.cpp
new file mode 100644
index 00000000..896f7765
--- /dev/null
+++ b/src-qt5/desktop-utils/lumina-terminal/main.cpp
@@ -0,0 +1,47 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include <QSystemTrayIcon>
+#include <QDebug>
+
+#include <LuminaSingleApplication.h>
+#include <LuminaThemes.h>
+
+#include <unistd.h>
+
+#include "TrayIcon.h"
+int main(int argc, char *argv[]) {
+ LTHEME::LoadCustomEnvSettings();
+ LSingleApplication a(argc, argv, "lumina-terminal");
+ if( !a.isPrimaryProcess() ){ return 0; } //poked the current process instead
+
+ //First make sure a system tray is available
+ /*qDebug() << "Checking for system tray";
+ bool ready = false;
+ for(int i=0; i<60 && !ready; i++){
+ ready = QSystemTrayIcon::isSystemTrayAvailable();
+ if(!ready){
+ //Pause for 5 seconds
+ sleep(5); //don't worry about stopping event handling - nothing running yet
+ }
+ }
+ if(!ready){
+ qDebug() << "Could not find any available system tray after 5 minutes: exiting....";
+ return 1;
+ }*/
+
+ //Now go ahead and setup the app
+ LuminaThemeEngine theme(&a);
+ QApplication::setQuitOnLastWindowClosed(false);
+
+ //Now start the tray icon
+ TrayIcon tray;
+ QObject::connect(&a, SIGNAL(InputsAvailable(QStringList)), &tray, SLOT(slotSingleInstance(QStringList)) );
+ QObject::connect(&theme, SIGNAL(updateIcons()), &tray, SLOT(updateIcons()) );
+ tray.parseInputs(a.inputlist);
+ tray.show();
+ return a.exec();
+}
bgstack15