diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 1e6f8f6..d593566 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -79,6 +79,9 @@ qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) +# Clearpilot tools +qt_env.Program("/data/openpilot/system/clearpilot/tools/qt_shell", ["/data/openpilot/system/clearpilot/tools/qt_shell.cc"], LIBS=qt_libs) + # build main UI qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('extras'): diff --git a/system/clearpilot/configure/dependencies.sh b/system/clearpilot/configure/provision.sh similarity index 100% rename from system/clearpilot/configure/dependencies.sh rename to system/clearpilot/configure/provision.sh diff --git a/system/clearpilot/tools/qt_shell b/system/clearpilot/tools/qt_shell new file mode 100755 index 0000000..1b3f882 Binary files /dev/null and b/system/clearpilot/tools/qt_shell differ diff --git a/system/clearpilot/tools/qt_shell.cc b/system/clearpilot/tools/qt_shell.cc new file mode 100644 index 0000000..6c79f83 --- /dev/null +++ b/system/clearpilot/tools/qt_shell.cc @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + if (argc < 2) { + printf("Usage: %s ''\n", argv[0]); + return 1; + } + + QWidget window; + window.setWindowTitle("Shell Command Output Viewer"); + window.setStyleSheet("background-color: black;"); + window.showFullScreen(); + + auto windowHandle = window.windowHandle(); + if (!windowHandle) { + fprintf(stderr, "Error: Unable to obtain window handle.\n"); + return 1; + } + + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + auto *s = static_cast(native->nativeResourceForWindow("surface", windowHandle)); + if (!s) { + fprintf(stderr, "Error: Unable to obtain native Wayland surface.\n"); + return 1; + } + + wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); + wl_surface_commit(s); + + window.setFixedSize(2160, 1080); + + QVBoxLayout *layout = new QVBoxLayout(&window); + QTextEdit *outputDisplay = new QTextEdit; + outputDisplay->setFont(QFont("Consolas", 32)); + outputDisplay->setReadOnly(true); + outputDisplay->setFixedSize(2160, 1080); + outputDisplay->setStyleSheet("color: white; background-color: black;"); + outputDisplay->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Hide the vertical scrollbar + + layout->addWidget(outputDisplay); + + QProcess process; + QObject::connect(&process, &QProcess::readyReadStandardOutput, [&]() { + static QStringList lines; + QString output = process.readAllStandardOutput(); + lines += output.split("\n", QString::SkipEmptyParts); + while (lines.size() > 100) { + lines.removeFirst(); + } + outputDisplay->setPlainText(lines.join("\n")); + outputDisplay->verticalScrollBar()->setValue(outputDisplay->verticalScrollBar()->maximum()); + }); + + QObject::connect(&process, QOverload::of(&QProcess::finished), [&]() { + app.quit(); + }); + + QString command = argv[1]; + process.start(QString("bash -c \"%1\"").arg(command)); + + return app.exec(); +} \ No newline at end of file diff --git a/system/clearpilot/tools/shell.sh b/system/clearpilot/tools/shell.sh index bf50aa4..555345a 100644 --- a/system/clearpilot/tools/shell.sh +++ b/system/clearpilot/tools/shell.sh @@ -1 +1 @@ -sudo su comma -c "python3 shell.py \"echo hello; sleep 5\" +sudo su comma -c "python3 /data/openpilot/system/clearpilot/tools/shell.py \"echo hello; sleep 5\" diff --git a/system/clearpilot/tools_wip_4_28/QConsole.cc b/system/clearpilot/tools_wip_4_28/QConsole.cc new file mode 100644 index 0000000..cf441db --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/QConsole.cc @@ -0,0 +1,785 @@ +/** + Change log: + (C) 2005 by Houssem BDIOUI + (C) 2010 by YoungTaek Oh. (migrated to Qt4) + (C) 2014-2015 Igor Malinovskiy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +// #include "qconsole.h" +#include +#include +#include +#include +#include +#include +#include + +//#define USE_POPUP_COMPLETER +#define WRITE_ONLY QIODevice::WriteOnly + +QSize PopupListWidget::sizeHint() const +{ + QAbstractItemModel *model = this->model(); + QAbstractItemDelegate *delegate = this->itemDelegate(); + const QStyleOptionViewItem sovi; + int left, top, right, bottom = 0; + + QMargins margin = this->contentsMargins(); + + top = margin.top(); + bottom = margin.bottom(); + left = margin.left(); + right = margin.right(); + + const int vOffset = top + bottom; + const int hOffset = left + right; + + bool vScrollOn = false; + int height = 0; + int width = 0; + for (int i=0; icount(); ++i) { + QModelIndex index = model->index(i, 0); + QSize itemSizeHint = delegate->sizeHint(sovi, index); + if (itemSizeHint.width() > width) + width = itemSizeHint.width(); + + // height + const int nextHeight = height + itemSizeHint.height(); + if (nextHeight + vOffset < this->maximumHeight()) + height = nextHeight; + else { + // early termination + vScrollOn = true; + break; + } + } + + QSize sizeHint(width + hOffset, 0); + sizeHint.setHeight(height + vOffset); + if (vScrollOn) { + int scrollWidth = this->verticalScrollBar()->sizeHint().width(); + sizeHint.setWidth(sizeHint.width() + scrollWidth); + } + return sizeHint; +} + +void PopupListWidget::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Tab || + e->key() == Qt::Key_Return) + Q_EMIT itemActivated(currentItem()); + else + QListWidget::keyPressEvent(e); +} + +PopupCompleter::PopupCompleter(const QStringList& sl, QWidget *parent) + : QDialog(parent, Qt::Popup) +{ + setModal(true); + + listWidget_ = new PopupListWidget(); + listWidget_->setMaximumHeight(200); + qDebug() << "sizeHint(): " << listWidget_->sizeHint(); + Q_FOREACH(QString str, sl) { + QListWidgetItem *item = new QListWidgetItem; + item->setText(str); + listWidget_->addItem(item); + } + qDebug() << "sizeHint(): " << listWidget_->sizeHint(); + listWidget_->setFixedSize(listWidget_->sizeHint()); + + + QLayout *layout = new QVBoxLayout(); + layout->setSizeConstraint(QLayout::SetFixedSize); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(listWidget_); + + setLayout(layout); + + // connect signal + connect(listWidget_, SIGNAL(itemActivated(QListWidgetItem *)), + SLOT(onItemActivated(QListWidgetItem*))); +} + +PopupCompleter::~PopupCompleter() +{ +} + +void PopupCompleter::showEvent(QShowEvent *event) +{ + listWidget_->setFocus(); +} + +void PopupCompleter::onItemActivated(QListWidgetItem *event) +{ + selected_ = event->text(); + done(QDialog::Accepted); +} + +/** + * @brief execute PopupCompleter at appropriate position. + * + * @param parent Parent of this popup completer. usually QConsole. + * @return see QDialog::exec + * @see QDialog::exec + */ +int PopupCompleter::exec(QTextEdit *parent) +{ + QSize popupSizeHint = this->sizeHint(); + QRect cursorRect = parent->cursorRect(); + QPoint globalPt = parent->mapToGlobal(cursorRect.bottomRight()); + QDesktopWidget *dsk = QApplication::desktop(); + QRect screenGeom = dsk->screenGeometry(dsk->screenNumber(this)); + if (globalPt.y() + popupSizeHint.height() > screenGeom.height()) { + globalPt = parent->mapToGlobal(cursorRect.topRight()); + globalPt.setY(globalPt.y() - popupSizeHint.height()); + } + this->move(globalPt); + this->setFocus(); + return QDialog::exec(); +} + +/** + * returns a common word of the given list + * + * @param list String list + * + * @return common word in the given string. + */ +static +QString getCommonWord(QStringList& list) +{ + QChar ch; + QVector strarray = list.toVector(); + QString common; + int col = 0, min_len; + bool cont = true; + + // get minimum length + min_len = strarray.at(0).size(); + for (int i=1; iexec(this) == QDialog::Accepted) + replaceCurrentCommand(commandPrefix + popup->selected()); + delete popup; + + } + } +} + +// If return pressed, do the evaluation and append the result +void QConsole::handleReturnKeyPress() +{ + //Get the command to validate + QString command = getCurrentCommand(); + //execute the command and get back its text result and its return value + if (isCommandComplete(command)) + pExecCommand(command); + else + { + append(""); + moveCursor(QTextCursor::EndOfLine); + } +} + +bool QConsole::handleBackspaceKeyPress() +{ + QTextCursor cur = textCursor(); + const int col = cur.columnNumber(); + const int blk = cur.blockNumber(); + if (blk == promptParagraph && col == promptLength) + return true; + return false; +} + +void QConsole::handleUpKeyPress() +{ + if (history.count()) + { + QString command = getCurrentCommand(); + do + { + if (historyIndex) + historyIndex--; + else + { + break; + } + } while(history[historyIndex] == command); + replaceCurrentCommand(history[historyIndex]); + } +} + +void QConsole::handleDownKeyPress() +{ + if (history.count()) + { + QString command = getCurrentCommand(); + do + { + if (++historyIndex >= history.size()) + { + historyIndex = history.size() - 1; + break; + } + } while(history[historyIndex] == command); + replaceCurrentCommand(history[historyIndex]); + } +} + + +void QConsole::setHome(bool select) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::StartOfBlock, select ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor); + if(textCursor().blockNumber() == promptParagraph) + { + cursor.movePosition(QTextCursor::Right, select ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor, + promptLength); + } + setTextCursor(cursor); +} + +//Reimplemented key press event +void QConsole::keyPressEvent( QKeyEvent *e ) +{ + if (isLocked) + return; + + //If the user wants to copy or cut outside + //the editing area we perform a copy + if(textCursor().hasSelection()) + { + if(e->modifiers() == Qt::CTRL) + { + if( e->matches(QKeySequence::Cut) ) + { + e->accept(); + if(!isInEditionZone()) + { + copy(); + } + else + { + cut(); + } + return; + } + else if(e->matches(QKeySequence::Copy) ) + { + e->accept(); + copy(); + } + else + { + QTextEdit::keyPressEvent( e ); + return; + } + } + } + /* + // if the cursor out of editing zone put it back first + if(!isInEditionZone()) + { + QTextCursor editCursor = textCursor(); + editCursor.setPosition(oldEditPosition); + setTextCursor(editCursor); + } +*/ + // control is pressed + if ( (e->modifiers() & Qt::ControlModifier) && (e->key() == Qt::Key_C) ) + { + if ( isSelectionInEditionZone()) + { + //If Ctrl + C pressed, then undo the current commant + //append(""); + //displayPrompt(); + + //(Thierry Belair:)I humbly suggest that ctrl+C copies the text, as is expected, + //and indicated in the contextual menu + copy(); + return; + } + + } + else { + switch (e->key()) { + case Qt::Key_Tab: + if(isSelectionInEditionZone()) + { + handleTabKeyPress(); + } + return; + + case Qt::Key_Enter: + case Qt::Key_Return: + if (isSelectionInEditionZone()) + { + handleReturnKeyPress(); + } + // ignore return key + return; + + case Qt::Key_Backspace: + if (handleBackspaceKeyPress() || !isSelectionInEditionZone()) + return; + break; + + case Qt::Key_Home: + setHome(e->modifiers() & Qt::ShiftModifier); + case Qt::Key_Down: + if (isInEditionZone()) + { + handleDownKeyPress(); + } + return; + case Qt::Key_Up: + if (isInEditionZone()) + { + handleUpKeyPress(); + } + return; + + //Default behaviour + case Qt::Key_End: + case Qt::Key_Left: + case Qt::Key_Right: + break; + + default: + if (textCursor().hasSelection() ) { + if (!isSelectionInEditionZone()) + { + moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); + } + break; + } + else + { //no selection + //when typing normal characters, + //make sure the cursor is positionned in the + //edition zone + if ( !isInEditionZone() ) + { + moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); + } + } + } //end of switch + } //end of else : no control pressed + + QTextEdit::keyPressEvent( e ); +} + +//Get the current command +QString QConsole::getCurrentCommand() +{ + QTextCursor cursor = textCursor(); //Get the current command: we just remove the prompt + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + QString command = cursor.selectedText(); + cursor.clearSelection(); + return command; +} + +//Replace current command with a new one +void QConsole::replaceCurrentCommand(const QString &newCommand) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + cursor.insertText(newCommand); +} + +//default implementation: command always complete +bool QConsole::isCommandComplete(const QString &) +{ + return true; +} + +//Tests whether the cursor is in th edition zone or not (after the prompt +//or in the next lines (in case of multi-line mode) +bool QConsole::isInEditionZone() +{ + const int para = textCursor().blockNumber(); + const int index = textCursor().columnNumber(); + return (para > promptParagraph) || ( (para == promptParagraph) && (index >= promptLength) ); +} + + +//Tests whether position (in parameter) is in the edition zone or not (after the prompt +//or in the next lines (in case of multi-line mode) +bool QConsole::isInEditionZone(const int& pos) +{ + QTextCursor cur = textCursor(); + cur.setPosition(pos); + const int para = cur.blockNumber(); + const int index = cur.columnNumber(); + return (para > promptParagraph) || ( (para == promptParagraph) && (index >= promptLength) ); +} + + +//Tests whether the current selection is in th edition zone or not +bool QConsole::isSelectionInEditionZone() +{ + QTextCursor cursor(document()); + int range[2]; + + range[0] = textCursor().selectionStart(); + range[1] = textCursor().selectionEnd(); + for (int i = 0; i < 2; i++) + { + cursor.setPosition(range[i]); + int para = cursor.blockNumber(); + int index = cursor.columnNumber(); + if ((para <= promptParagraph) && ( (para != promptParagraph) || (index < promptLength) )) + { + return false; + } + } + return true; +} + + +//Basically, puts the command into the history list +//And emits a signal (should be called by reimplementations) +QString QConsole::addCommandToHistory(const QString &command) +{ + //Add the command to the recordedScript list + recordedScript.append(command); + //update the history and its index + QString modifiedCommand = command; + modifiedCommand.replace("\n", "\\n"); + history.append(modifiedCommand); + historyIndex = history.size(); + //emit the commandExecuted signal + Q_EMIT commandAddedToHistory(modifiedCommand); + return ""; +} + +//pExecCommand(QString) executes the command and displays back its result +void QConsole::pExecCommand(const QString &command) +{ + isLocked = true; + + addCommandToHistory(command); + + emit execCommand(command); +} + +void QConsole::printCommandExecutionResults(const QString &result, ResultType type) +{ + //According to the return value, display the result either in red or in blue + if (type == ResultType::Error) + setTextColor(errColor_); + else + setTextColor(outColor_); + + append(result); + + //Display the prompt again + if (type == ResultType::Complete || type == ResultType::Error) { + if (!result.endsWith("\n")) + append("\n"); + + isLocked = false; + displayPrompt(); + } + + moveCursor(QTextCursor::End); +} + + + +//Change paste behaviour +void QConsole::insertFromMimeData(const QMimeData *source) +{ + if (isSelectionInEditionZone()) + { + QTextEdit::insertFromMimeData(source); + } +} + +//Implement paste with middle mouse button +void QConsole::mousePressEvent(QMouseEvent* event) +{ + oldPosition = textCursor().position(); + if (event->button() == Qt::MidButton) + { + copy(); + QTextCursor cursor = cursorForPosition(event->pos()); + setTextCursor(cursor); + paste(); + return; + } + + QTextEdit::mousePressEvent(event); +} + + +//Redefinition of the dropEvent to have a copy paste +//instead of a cut paste when copying out of the +//editable zone +void QConsole::dropEvent ( QDropEvent * event) +{ + if(!isInEditionZone()) + { + //Execute un drop a drop at the old position + //if the drag started out of the editable zone + QTextCursor cur = textCursor(); + cur.setPosition(oldPosition); + setTextCursor(cur); + } + //Execute a normal drop + QTextEdit::dropEvent(event); +} + + + +void QConsole::dragMoveEvent( QDragMoveEvent * event) +{ + //Get a cursor for the actual mouse position + QTextCursor cur = textCursor(); + cur.setPosition(cursorForPosition(event->pos()).position()); + + if(!isInEditionZone(cursorForPosition(event->pos()).position())) + { + //Ignore the event if out of the editable zone + event->ignore(cursorRect(cur)); + } + else + { + //Accept the event if out of the editable zone + event->accept(cursorRect(cur)); + } +} + + +void QConsole::contextMenuEvent ( QContextMenuEvent * event) +{ + if (isLocked) + return; + + QMenu *menu = new QMenu(this); + + QAction *undo = new QAction(tr("Undo"), this); + undo->setShortcut(tr("Ctrl+Z")); + QAction *redo = new QAction(tr("Redo"), this); + redo->setShortcut(tr("Ctrl+Y")); + QAction *cut = new QAction(tr("Cut"), this); + cut->setShortcut(tr("Ctrl+X")); + QAction *copy = new QAction(tr("Copy"), this); + copy->setShortcut(tr("Ctrl+Ins")); + QAction *paste = new QAction(tr("Paste"), this); + paste->setShortcut(tr("Ctrl+V")); + QAction *del = new QAction(tr("Delete"), this); + del->setShortcut(tr("Del")); + QAction *selectAll = new QAction(tr("Select All"), this); + selectAll->setShortcut(tr("Ctrl+A")); + + menu->addAction(undo); + menu->addAction(redo); + menu->addSeparator(); + menu->addAction(cut); + menu->addAction(copy); + menu->addAction(paste); + menu->addAction(del); + menu->addSeparator(); + menu->addAction(selectAll); + + connect(undo, SIGNAL(triggered()), this, SLOT(undo())); + connect(redo, SIGNAL(triggered()), this, SLOT(redo())); + connect(cut, SIGNAL(triggered()), this, SLOT(cut())); + connect(copy, SIGNAL(triggered()), this, SLOT(copy())); + connect(paste, SIGNAL(triggered()), this, SLOT(paste())); + connect(del, SIGNAL(triggered()), this, SLOT(del())); + connect(selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); + + + menu->exec(event->globalPos()); + + delete menu; +} + +void QConsole::cut() +{ + //Cut only in the editing zone, + //perfom a copy otherwise + if(isSelectionInEditionZone()) + { + QTextEdit::cut(); + return; + } + + QTextEdit::copy(); +} + +/* +//Allows pasting with middle mouse button (x window) +//when clicking outside of the edition zone +void QConsole::paste() +{ + restoreOldPosition(); + QTextEdit::paste(); +} +*/ + +void QConsole::del() +{ + //Delete only in the editing zone + if(isInEditionZone()) + { + textCursor().movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + textCursor().deleteChar(); + } +} + +void QConsole::correctPathName(QString& pathName) +{ + if(pathName.contains(tr(":\\"))) + { + pathName.replace('\\', tr("/")); + } +} diff --git a/system/clearpilot/tools_wip_4_28/QConsole.h b/system/clearpilot/tools_wip_4_28/QConsole.h new file mode 100644 index 0000000..fedf736 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/QConsole.h @@ -0,0 +1,211 @@ +/** + Change log: + (C) 2005 by Houssem BDIOUI + (C) 2010 by YoungTaek Oh. (migrated to Qt4) + (C) 2014-2015 Igor Malinovskiy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + + +/** + * Subclasssing QListWidget + * + * @author YoungTaek Oh + */ +class PopupListWidget : public QListWidget +{ + Q_OBJECT + +public: + PopupListWidget(QWidget *parent = 0): QListWidget(parent) { + setUniformItemSizes(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + virtual ~PopupListWidget() { } + +protected: + virtual QSize sizeHint() const; + virtual void keyPressEvent(QKeyEvent *e); +}; + +/** + * Popup Completer class + * + * @author YoungTaek Oh + * @todo 1. beautifying + * 2. icons for classifying words (eg. functions, properties...) + * 3. bugs? + * @note still experimental + */ +class PopupCompleter : public QDialog +{ + Q_OBJECT + +public: + PopupCompleter(const QStringList&, QWidget *parent = 0); + virtual ~PopupCompleter(); + +public: + QString selected(void) { return selected_; } + int exec(QTextEdit*); + +protected: + virtual void showEvent(QShowEvent*); + +private Q_SLOTS: + void onItemActivated(QListWidgetItem*); + +public: + QListWidget *listWidget_; + QString selected_; +}; + +/** + * An abstract Qt console + * @author Houssem BDIOUI + */ +class QConsole : public QTextEdit +{ + Q_OBJECT +public: + //constructor + QConsole(QWidget *parent = NULL, const QString &welcomeText = ""); + //set the prompt of the console + void setPrompt(const QString &prompt, bool display = true); + + //clear & reset the console (useful sometimes) + void clear(); + void reset(const QString &welcomeText = ""); + + //cosmetic methods ! + + // @{ + /// get/set command color + QColor cmdColor() const { return cmdColor_; } + void setCmdColor(QColor c) {cmdColor_ = c;} + // @} + + // @{ + /// get/set error color + QColor errColor() const { return errColor_; } + void setErrColor(QColor c) {errColor_ = c;} + // @} + + // @{ + /// get/set output color + QColor outColor() const { return outColor_; } + void setOutColor(QColor c) {outColor_ = c;} + // @} + void setCompletionColor(QColor c) {completionColor = c;} + + // @{ + /// get set font + void setFont(const QFont& f); + QFont font() const { return currentFont(); } + // @} + + void correctPathName(QString& pathName); + + enum ResultType {Error, Partial, Complete}; + +private: + void dropEvent( QDropEvent * event); + void dragMoveEvent( QDragMoveEvent * event); + + void keyPressEvent(QKeyEvent * e); + void contextMenuEvent( QContextMenuEvent * event); + + //Return false if the command is incomplete (e.g. unmatched braces) + virtual bool isCommandComplete(const QString &command); + //Get the command to validate + QString getCurrentCommand(); + + //Replace current command with a new one + void replaceCurrentCommand(const QString &newCommand); + + //Test whether the cursor is in the edition zone + bool isInEditionZone(); + bool isInEditionZone(const int& pos); + + //Test whether the selection is in the edition zone + bool isSelectionInEditionZone(); + //Change paste behaviour + void insertFromMimeData(const QMimeData *); + +protected: + //colors + QColor cmdColor_, errColor_, outColor_, completionColor; + + int oldPosition; + // cached prompt length + int promptLength; + // The prompt string + QString prompt; + // The commands history + QStringList history; + //Contains the commands that has succeeded + QStringList recordedScript; + // Current history index (needed because afaik QStringList does not have such an index) + int historyIndex; + //Holds the paragraph number of the prompt (useful for multi-line command handling) + int promptParagraph; + +protected: + //Implement paste with middle mouse button + void mousePressEvent(QMouseEvent*); + + //execute a validated command (should be reimplemented and called at the end) + //the return value of the function is the string result + //res must hold back the return value of the command (0: passed; else: error) + virtual QString addCommandToHistory(const QString &command); + + //give suggestions to autocomplete a command (should be reimplemented) + //the return value of the function is the string list of all suggestions + //the returned prefix is useful to complete "sub-commands" + virtual QStringList suggestCommand(const QString &cmd, QString &prefix); + + +public slots: + //Contextual menu slots + void cut(); + //void paste(); + void del(); + //displays the prompt + void displayPrompt(); + + void printCommandExecutionResults(const QString &, ResultType t = ResultType::Complete); + +signals: + //Signal emitted after that a command is executed + void commandAddedToHistory(const QString &command); + void execCommand(const QString &command); + +private: + bool isLocked; + + void handleTabKeyPress(); + void handleReturnKeyPress(); + bool handleBackspaceKeyPress(); + void handleUpKeyPress(); + void handleDownKeyPress(); + void setHome(bool); + void pExecCommand(const QString &command); +}; + +#include "QConsole.cc" diff --git a/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/PKG-INFO b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/PKG-INFO new file mode 100644 index 0000000..8d80e20 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: RotationModule +Version: 1.0 +Summary: Module for rotating display via native interface +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/SOURCES.txt b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/SOURCES.txt new file mode 100644 index 0000000..ebac1c3 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/SOURCES.txt @@ -0,0 +1,6 @@ +rotation_module.cc +rotation_module_build.py +RotationModule.egg-info/PKG-INFO +RotationModule.egg-info/SOURCES.txt +RotationModule.egg-info/dependency_links.txt +RotationModule.egg-info/top_level.txt \ No newline at end of file diff --git a/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/dependency_links.txt b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/top_level.txt b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/top_level.txt new file mode 100644 index 0000000..ddccd9e --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/RotationModule.egg-info/top_level.txt @@ -0,0 +1 @@ +rotation diff --git a/system/clearpilot/tools_wip_4_28/decrypt b/system/clearpilot/tools_wip_4_28/decrypt new file mode 100644 index 0000000..a736b82 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/decrypt @@ -0,0 +1,17 @@ +#!/bin/bash + +# Check for the correct number of arguments +if [ "$#" -ne 2 ]; then + echo "Usage: $0 source destination" + exit 1 +fi + +# Set variables for source and destination +src="$1" +dest="$2" + +# Read DongleId for decryption key +dongle_id=/data/params/d/DongleId + +# Decrypt the file +cat "$src" | ccrypt -d -k "$dongle_id" > "$dest" diff --git a/system/clearpilot/tools_wip_4_28/encrypt b/system/clearpilot/tools_wip_4_28/encrypt new file mode 100644 index 0000000..9496892 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/encrypt @@ -0,0 +1,17 @@ +#!/bin/bash + +# Check for the correct number of arguments +if [ "$#" -ne 2 ]; then + echo "Usage: $0 source destination" + exit 1 +fi + +# Set variables for source and destination +src="$1" +dest="$2" + +# Read DongleId for encryption key +dongle_id=/data/params/d/DongleId + +# Encrypt the file +cat "$src" | ccrypt -e -k "$dongle_id" > "$dest" diff --git a/system/clearpilot/tools_wip_4_28/faketty.py b/system/clearpilot/tools_wip_4_28/faketty.py new file mode 100644 index 0000000..51a9a94 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/faketty.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +from sys import argv +import os +import signal + +# I've had problems with python's File objects at this low a level, so +# we're going to use integers to specify all files in this script. +stdin = 0 +stdout = 1 +stderr = 2 +# Include this if passing the command and arguments to fish to +# prevent fish from applying any expansions. +#import re +#def fish_escape(args): +# def escape_one(arg): +# return "'" + re.sub(r"('|\\)", r'\\\1', arg) + "'" +# escaped_args = map(escape_one, args) +# return ' '.join(escaped_args) + +if len(argv) < 2: + os.write(stderr, +b"""A tragically beautiful piece of hackery, made to fool programs like ls, +grep, rg, and fd into thinking they're actually connected to a terminal. +Its usage: + +pty command [arg1 arg2 ...] + +Examples: +pty ls --color -R | less -r +git log -p | pty rg | less -r +""") + exit(255) + +# We do not use forkpty here because it would block ^Cs from reaching the +# child process. And we don't need that. +ptm, pts = os.openpty() +pid = os.fork() +if pid == 0: + # The child runs this. + # To get the behaviour we want, we only need to replace the process's + # stdout with pts. Everything else should remain in place, so that things + # like `ps -eF | pty rg python | less -r` will still work as intended. + os.dup2(pts, stdout) + # This is not like a subprocess.call(). It replaces the entire child + # process with argv[1:], meaning execvp will not return! Web search + # "fork exec" for more. + os.execvp(argv[1], argv[1:]) + # Use this if calling fish. + #os.execvp('fish', ['fish', '-c', fish_escape(argv[1:])]) + + +# The parent runs this. + +# If the parent doesn't close the slave end, the script won't be able to +# exit. The next read on ptm after the child process terminates would hang +# forever because pts would technically still be open. +os.close(pts) + +# The whole process group gets SIGINT, including the child, so we don't need +# to react to it. We'll know when to leave, judging by what the child does. +signal.signal(signal.SIGINT, signal.SIG_IGN) + +while True: + try: + chunk = os.read(ptm, 4096) + except OSError: + break + try: + os.write(stdout, chunk) + except BrokenPipeError: + # This happens when the parent is piping output to another process in a + # pipeline, like in `pty ls --color -R | less -r`, and the receiving + # process is terminated before the child has exited. If the receiving + # process is less, this can happen very easily. It happens every time + # the user decides to quit less before it has displayed all output. So, + # we need to stop the child process now. + os.kill(pid, signal.SIGTERM) + break +wait_pid, status = os.waitpid(pid, 0) +exit(status >> 8) diff --git a/system/clearpilot/tools_wip_4_28/moc_test2.cc b/system/clearpilot/tools_wip_4_28/moc_test2.cc new file mode 100644 index 0000000..ddfdb87 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/moc_test2.cc @@ -0,0 +1,188 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'test2.h' +** +** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.8) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#include "test2.h" +#include +#include +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'test2.h' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 67 +#error "This file was generated using the moc from 5.12.8. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +QT_BEGIN_MOC_NAMESPACE +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +struct qt_meta_stringdata_TrackWidget_t { + QByteArrayData data[1]; + char stringdata0[12]; +}; +#define QT_MOC_LITERAL(idx, ofs, len) \ + Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(qt_meta_stringdata_TrackWidget_t, stringdata0) + ofs \ + - idx * sizeof(QByteArrayData)) \ + ) +static const qt_meta_stringdata_TrackWidget_t qt_meta_stringdata_TrackWidget = { + { +QT_MOC_LITERAL(0, 0, 11) // "TrackWidget" + + }, + "TrackWidget" +}; +#undef QT_MOC_LITERAL + +static const uint qt_meta_data_TrackWidget[] = { + + // content: + 8, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +void TrackWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + Q_UNUSED(_o); + Q_UNUSED(_id); + Q_UNUSED(_c); + Q_UNUSED(_a); +} + +QT_INIT_METAOBJECT const QMetaObject TrackWidget::staticMetaObject = { { + &QWidget::staticMetaObject, + qt_meta_stringdata_TrackWidget.data, + qt_meta_data_TrackWidget, + qt_static_metacall, + nullptr, + nullptr +} }; + + +const QMetaObject *TrackWidget::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; +} + +void *TrackWidget::qt_metacast(const char *_clname) +{ + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_TrackWidget.stringdata0)) + return static_cast(this); + return QWidget::qt_metacast(_clname); +} + +int TrackWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + return _id; +} +struct qt_meta_stringdata_Spinner_t { + QByteArrayData data[4]; + char stringdata0[18]; +}; +#define QT_MOC_LITERAL(idx, ofs, len) \ + Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(qt_meta_stringdata_Spinner_t, stringdata0) + ofs \ + - idx * sizeof(QByteArrayData)) \ + ) +static const qt_meta_stringdata_Spinner_t qt_meta_stringdata_Spinner = { + { +QT_MOC_LITERAL(0, 0, 7), // "Spinner" +QT_MOC_LITERAL(1, 8, 6), // "update" +QT_MOC_LITERAL(2, 15, 0), // "" +QT_MOC_LITERAL(3, 16, 1) // "n" + + }, + "Spinner\0update\0\0n" +}; +#undef QT_MOC_LITERAL + +static const uint qt_meta_data_Spinner[] = { + + // content: + 8, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: name, argc, parameters, tag, flags + 1, 1, 19, 2, 0x0a /* Public */, + + // slots: parameters + QMetaType::Void, QMetaType::Int, 3, + + 0 // eod +}; + +void Spinner::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->update((*reinterpret_cast< int(*)>(_a[1]))); break; + default: ; + } + } +} + +QT_INIT_METAOBJECT const QMetaObject Spinner::staticMetaObject = { { + &QWidget::staticMetaObject, + qt_meta_stringdata_Spinner.data, + qt_meta_data_Spinner, + qt_static_metacall, + nullptr, + nullptr +} }; + + +const QMetaObject *Spinner::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; +} + +void *Spinner::qt_metacast(const char *_clname) +{ + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_Spinner.stringdata0)) + return static_cast(this); + return QWidget::qt_metacast(_clname); +} + +int Spinner::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + if (_id < 1) + qt_static_metacall(this, _c, _id, _a); + _id -= 1; + } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { + if (_id < 1) + *reinterpret_cast(_a[0]) = -1; + _id -= 1; + } + return _id; +} +QT_WARNING_POP +QT_END_MOC_NAMESPACE diff --git a/system/clearpilot/tools_wip_4_28/provision.sh b/system/clearpilot/tools_wip_4_28/provision.sh new file mode 100644 index 0000000..826201c --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/provision.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Provision script for BrianBot +# These actions only occur on BrianBot's comma device. + +# 1. Check the string in /data/params/d/DongleId +dongle_id=$(cat /data/params/d/DongleId) +if [[ ! $dongle_id == 90bb71a* ]]; then + echo "Invalid dongle ID." + exit 1 +fi + +echo "BrianBot dongle ID detected." + +# 2. Check if ccrypt is installed, install if not +if ! command -v ccrypt >/dev/null 2>&1; then + echo "Installing ccrypt..." + sudo apt-get update + sudo apt-get install -y ccrypt +fi + +# 3. Decrypt SSH keys if they have not been decrypted yet +if [ ! -f /data/openpilot/system/clearpilot/dev/id_rsa.pub ]; then + echo "Decrypting SSH keys..." + ccrypt -d -k "$dongle_id" /data/openpilot/system/clearpilot/dev/id_rsa.pub.ccrypt + ccrypt -d -k "$dongle_id" /data/openpilot/system/clearpilot/dev/id_rsa.ccrypt + ccrypt -d -k "$dongle_id" /data/openpilot/system/clearpilot/dev/reverse_ssh.ccrypt +fi + +# 4. Ensure .ssh directory and keys exist +ssh_dir="/home/comma/.ssh" +if [[ ! -f "$ssh_dir/id_rsa" || ! -f "$ssh_dir/id_rsa.pub" ]]; then + echo "Setting up SSH directory and keys..." + mkdir -p "$ssh_dir" + cp /data/openpilot/system/clearpilot/dev/id_rsa /data/openpilot/system/clearpilot/dev/id_rsa.pub "$ssh_dir" + chmod 700 "$ssh_dir" + chmod 600 "$ssh_dir/id_rsa" "$ssh_dir/id_rsa.pub" +fi + +echo "Script execution complete." +exit 0 diff --git a/system/clearpilot/tools_wip_4_28/remount_ro.sh b/system/clearpilot/tools_wip_4_28/remount_ro.sh new file mode 100644 index 0000000..f4c166f --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/remount_ro.sh @@ -0,0 +1 @@ +sudo mount -o remount,ro / diff --git a/system/clearpilot/tools_wip_4_28/remount_rw.sh b/system/clearpilot/tools_wip_4_28/remount_rw.sh new file mode 100644 index 0000000..f819cb8 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/remount_rw.sh @@ -0,0 +1 @@ +sudo mount -o remount,rw / diff --git a/system/clearpilot/tools_wip_4_28/rotation_module.cpp b/system/clearpilot/tools_wip_4_28/rotation_module.cpp new file mode 100644 index 0000000..356eb66 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/rotation_module.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include "/usr/include/aarch64-linux-gnu/qt5/QtGui/5.12.8/QtGui/qpa/qplatformnativeinterface.h" +#include + +#include +#include + +static PyObject* rotate_display(PyObject* self, PyObject* args) { + PyObject* pyObj; + if (!PyArg_ParseTuple(args, "O", &pyObj)) + return NULL; + + QWindow* window = sipUnwrapInstance(pyObj, sipAPI_rotation); + if (!window) { + PyErr_SetString(PyExc_RuntimeError, "Invalid window object."); + return NULL; + } + + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + wl_surface *s = static_cast(native->nativeResourceForWindow("surface", window)); + if (!s) { + PyErr_SetString(PyExc_RuntimeError, "Failed to obtain native Wayland surface."); + return NULL; + } + + wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); + wl_surface_commit(s); + + Py_RETURN_NONE; +} + diff --git a/system/clearpilot/tools_wip_4_28/rotation_module.sip b/system/clearpilot/tools_wip_4_28/rotation_module.sip new file mode 100644 index 0000000..97f9c2a --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/rotation_module.sip @@ -0,0 +1,19 @@ +// Define the module +%Module rotation 0 + +// Import PyQt types +%Import QtCore/QtCoremod.sip +%Import QtGui/QtGuimod.sip + + +// Define your class with the necessary methods +class QWindow; + +%MethodCode + QWindow* window = reinterpret_cast(a0); + if (!window) { + sipError = sipBadCallableArg; + } +%End + +void rotate_display(QWindow *window); diff --git a/system/clearpilot/tools_wip_4_28/rotation_module_build.py b/system/clearpilot/tools_wip_4_28/rotation_module_build.py new file mode 100644 index 0000000..9010ddc --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/rotation_module_build.py @@ -0,0 +1,32 @@ +from setuptools import setup, Extension +from distutils.command.build_ext import build_ext +import os + +# Specify the C++ compiler +os.environ["CC"] = "clang++" +os.environ["CXX"] = "clang++" + +class BuildExt(build_ext): + def build_extensions(self): + c_opts = ['-std=c++1z', '-DQCOM2', '-mcpu=cortex-a57', '-Wno-deprecated-declarations', '-O2', '-Wunused', '-Werror', '-Wshadow'] + for e in self.extensions: + e.extra_compile_args = c_opts + build_ext.build_extensions(self) + +module = Extension('rotation', + sources=['rotation_module.cpp'], + libraries=['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'wayland-client'], + include_dirs=[ + '/usr/include/aarch64-linux-gnu/qt5', + '/usr/include/aarch64-linux-gnu/qt5/QtCore', + '/usr/include/aarch64-linux-gnu/qt5/QtGui', + '/usr/include/aarch64-linux-gnu/qt5/QtWidgets' + ], + library_dirs=['/usr/lib/aarch64-linux-gnu', '/lib/aarch64-linux-gnu'], + language='c++') + +setup(name='RotationModule', + version='1.0', + description='Module for rotating display via native interface', + ext_modules=[module], + cmdclass={'build_ext': BuildExt}) diff --git a/system/clearpilot/tools_wip_4_28/rotation_module_build.sh b/system/clearpilot/tools_wip_4_28/rotation_module_build.sh new file mode 100644 index 0000000..37e2380 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/rotation_module_build.sh @@ -0,0 +1,2 @@ +python3 rotation_module_build.py build +cp build/lib.linux-aarch64-cpython-311/rotation.cpython-311-aarch64-linux-gnu.so ./rotation.so diff --git a/system/clearpilot/tools_wip_4_28/scrun b/system/clearpilot/tools_wip_4_28/scrun new file mode 100644 index 0000000..074d598 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/scrun @@ -0,0 +1,37 @@ +#!/bin/bash + +# Usage: +# scrun instancename command + +# - If instancename doesnt exist, starts a screen with instancename and executes the given command. +# - Does not run if the instance is already running. +# - Runs in the same context as a shell (loads environment variables). +# - Logs output into /var/log/scrun/instance/DATE.log, with rotation + +# bash -l -c "$@" + +# Based on https://gist.github.com/camperdave/980040 +echo "defshell -bash" > ~/.screenrc +echo "startup_message off" >> ~/.screenrc +echo "vbell off" >> ~/.screenrc +echo "deflogin on" >> ~/.screenrc +echo "defscrollback 10000" >> ~/.screenrc +echo "defutf8 on" >> ~/.screenrc +echo "defflow off" >> ~/.screenrc +echo "msgwait 20" >> ~/.screenrc +echo "term screen-256color-bce" >> ~/.screenrc + +#SCREENNAME=scrun_$1_ +SCREENNAME=$1 + +screen -wipe 2>/dev/null >/dev/null + +if ! screen -list | grep -q $SCREENNAME; then + cesc="${@:2}" # Everything but first one +# cesc="${cesc@Q}" # Escape it + screen -dmS $SCREENNAME python3 /data/openpilot/system/clearpilot/tools/faketty.py bash -l -c "$cesc" + echo screen -dmS $SCREENNAME python3 /data/openpilot/system/clearpilot/tools/faketty.py bash -l -c "$cesc" +# screen -dmS $1 "$@" +else + echo $SCREENNAME is already running +fi diff --git a/system/clearpilot/tools_wip_4_28/shell.py b/system/clearpilot/tools_wip_4_28/shell.py new file mode 100644 index 0000000..351a1fc --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/shell.py @@ -0,0 +1,90 @@ +import os +import sys +import signal +import logging +import platform +from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QScrollBar, QGraphicsView, QGraphicsScene, QGraphicsProxyWidget +from PyQt5.QtCore import Qt, QCoreApplication +from PyQt5.QtGui import QFont, QScreen + +import termqt +from termqt import Terminal, TerminalPOSIXExecIO + +class ExitOnMessageHandler(logging.Handler): + def emit(self, record): + if "Spawned process has been killed" in record.getMessage(): + QApplication.quit() # Exit the application gracefully + +def setup_logger(): + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + formatter = logging.Formatter("[%(asctime)s] > [%(filename)s:%(lineno)d] %(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) + handler2 = ExitOnMessageHandler() + logger.addHandler(handler2) + return logger + +def create_terminal_app(): + os.environ["XDG_RUNTIME_DIR"] = "/var/tmp/weston" + os.environ["WAYLAND_DISPLAY"] = "wayland-0" + os.environ["QT_QPA_PLATFORM"] = "wayland" + os.environ["QT_WAYLAND_SHELL_INTEGRATION"] = "wl-shell" + + + QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + app = QApplication(sys.argv) + desktop = QApplication.desktop() + ag = desktop.availableGeometry(desktop.primaryScreen()) + print (ag.width()) + print (ag.height()) + + window = QWidget() + window.setWindowTitle("termqt on {}".format(platform.system())) + window.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + window.setGeometry(0,0, ag.width(), ag.height()) + window.setStyleSheet("background-color: black;") + window.showFullScreen() + + scene = QGraphicsScene() + view = QGraphicsView(scene, window) + print (window.width()) + print (window.height()) + view.setGeometry(0, 0, window.width(), window.height()) + view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + layout = QHBoxLayout() + terminal = Terminal(window.width(), window.height(), logger=setup_logger(), font_size=32) + + proxy_terminal = scene.addWidget(terminal) + view.setScene(scene) + view.rotate(90) # Rotate the view by 90 degrees clockwise + + window_layout = QHBoxLayout(window) + window_layout.addWidget(view) + window_layout.setContentsMargins(0,0,0,0) + window.setLayout(window_layout) + + return app, window, terminal + +def main(): + signal.signal(signal.SIGINT, signal.SIG_DFL) # Enable Ctrl+C + if len(sys.argv) < 2: + print("Usage: python start.py ''") + return + + command = "bash -c '{}'".format(sys.argv[1]) + app, window, terminal = create_terminal_app() + platform_name = platform.system() + terminal_io = TerminalPOSIXExecIO(terminal.col_len, terminal.row_len, command, os.environ, setup_logger()) + terminal_io.stdout_callback = terminal.stdout + terminal.stdin_callback = terminal_io.write + terminal.resize_callback = terminal_io.resize + terminal_io.spawn() + exit_code = app.exec_() + sys.exit(exit_code) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/system/clearpilot/tools_wip_4_28/shell.sh b/system/clearpilot/tools_wip_4_28/shell.sh new file mode 100644 index 0000000..bf50aa4 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/shell.sh @@ -0,0 +1 @@ +sudo su comma -c "python3 shell.py \"echo hello; sleep 5\" diff --git a/system/clearpilot/tools_wip_4_28/test.c b/system/clearpilot/tools_wip_4_28/test.c new file mode 100644 index 0000000..fca15ca --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +static void activate(GtkApplication* app, gpointer user_data) { + GtkWidget *window; + window = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(window), "Basic C GTK App"); + gtk_window_set_default_size(GTK_WINDOW(window), 200, 200); + gtk_widget_show_all(window); +} + +int main(int argc, char **argv) { + GtkApplication *app; + int status; + + // Signal handling + signal(SIGINT, SIG_DFL); + + app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +} diff --git a/system/clearpilot/tools_wip_4_28/test2 b/system/clearpilot/tools_wip_4_28/test2 new file mode 100755 index 0000000..0608f69 Binary files /dev/null and b/system/clearpilot/tools_wip_4_28/test2 differ diff --git a/system/clearpilot/tools_wip_4_28/test2.cc b/system/clearpilot/tools_wip_4_28/test2.cc new file mode 100644 index 0000000..c942a6a --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test2.cc @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include + +#include "/data/openpilot/system/hardware/hw.h" +#include "/data/openpilot/selfdrive/ui/qt/qt_window.h" +#include "/data/openpilot/selfdrive/ui/qt/util.h" + +static void handleSignal(int sig) { + QApplication::quit(); +} + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + // Set up Ctrl+C signal handler + signal(SIGINT, handleSignal); + + QWidget window; + window.setWindowTitle("Web Viewer"); + window.setStyleSheet("background-color: black;"); + window.showFullScreen(); // Show the window to ensure the handle is valid + + auto windowHandle = window.windowHandle(); + if (!windowHandle) { + fprintf(stderr, "Error: Unable to obtain window handle.\n"); + return 1; + } + + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + auto *s = static_cast(native->nativeResourceForWindow("surface", windowHandle)); + if (!s) { + fprintf(stderr, "Error: Unable to obtain native Wayland surface.\n"); + return 1; + } + + wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); + wl_surface_commit(s); + + QVBoxLayout *layout = new QVBoxLayout(&window); + QWebView *view = new QWebView; + view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Ensure it expands to fill layout + layout->addWidget(view); + + QString url = argv[1]; + view->load(QUrl(url)); + + // Resize window after setting up layout and loading the page + window.setFixedSize(2160, 1080); // Set fixed size after rotation + + window.show(); + + return app.exec(); +} diff --git a/system/clearpilot/tools_wip_4_28/test2.h b/system/clearpilot/tools_wip_4_28/test2.h new file mode 100644 index 0000000..a05b71b --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test2.h @@ -0,0 +1,10 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include diff --git a/system/clearpilot/tools_wip_4_28/test3.cc b/system/clearpilot/tools_wip_4_28/test3.cc new file mode 100644 index 0000000..c2d50f9 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test3.cc @@ -0,0 +1,120 @@ +#include "test2.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "/data/openpilot/system/hardware/hw.h" +#include "/data/openpilot/selfdrive/ui/qt/qt_window.h" +#include "/data/openpilot/selfdrive/ui/qt/util.h" + +TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent); + setFixedSize(spinner_size); + + // pre-compute all the track imgs. make this a gif instead? + QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); + QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); + + QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); + QPixmap pm(spinner_size); + QPainter p(&pm); + p.setRenderHint(QPainter::SmoothPixmapTransform); + for (int i = 0; i < track_imgs.size(); ++i) { + p.resetTransform(); + p.fillRect(0, 0, spinner_size.width(), spinner_size.height(), Qt::black); + p.drawPixmap(0, 0, comma_img); + p.setTransform(transform.rotate(360 / spinner_fps)); + p.drawPixmap(-width() / 2, -height() / 2, track_img); + track_imgs[i] = pm.copy(); + } + + m_anim.setDuration(1000); + m_anim.setStartValue(0); + m_anim.setEndValue(int(track_imgs.size() -1)); + m_anim.setLoopCount(-1); + m_anim.start(); + connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update())); +} + +void TrackWidget::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.drawPixmap(0, 0, track_imgs[m_anim.currentValue().toInt()]); +} + +// Spinner + +Spinner::Spinner(QWidget *parent) : QWidget(parent) { + QGridLayout *main_layout = new QGridLayout(this); + main_layout->setSpacing(0); + main_layout->setMargin(200); + + main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter); + + text = new QLabel(); + text->setWordWrap(true); + text->setVisible(false); + text->setAlignment(Qt::AlignCenter); + main_layout->addWidget(text, 1, 0, Qt::AlignHCenter); + + progress_bar = new QProgressBar(); + progress_bar->setRange(5, 100); + progress_bar->setTextVisible(false); + progress_bar->setVisible(false); + progress_bar->setFixedHeight(20); + main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter); + + setStyleSheet(R"( + Spinner { + background-color: black; + } + QLabel { + color: white; + font-size: 80px; + background-color: transparent; + } + QProgressBar { + background-color: #373737; + width: 1000px; + border solid white; + border-radius: 10px; + } + QProgressBar::chunk { + border-radius: 10px; + background-color: rgba(23, 134, 68, 255); + } + )"); + + notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read); + QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update); +} + +void Spinner::update(int n) { + std::string line; + std::getline(std::cin, line); + + if (line.length()) { + bool number = std::all_of(line.begin(), line.end(), ::isdigit); + text->setVisible(!number); + progress_bar->setVisible(number); + text->setText(QString::fromStdString(line)); + if (number) { + progress_bar->setValue(std::stoi(line)); + } + } +} + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication a(argc, argv); + Spinner spinner; + setMainWindow(&spinner); + return a.exec(); +} diff --git a/system/clearpilot/tools_wip_4_28/test3.h b/system/clearpilot/tools_wip_4_28/test3.h new file mode 100644 index 0000000..43d90a7 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test3.h @@ -0,0 +1,37 @@ +#include + +#include +#include +#include +#include +#include +#include + +constexpr int spinner_fps = 30; +constexpr QSize spinner_size = QSize(360, 360); + +class TrackWidget : public QWidget { + Q_OBJECT +public: + TrackWidget(QWidget *parent = nullptr); + +private: + void paintEvent(QPaintEvent *event) override; + std::array track_imgs; + QVariantAnimation m_anim; +}; + +class Spinner : public QWidget { + Q_OBJECT + +public: + explicit Spinner(QWidget *parent = 0); + +private: + QLabel *text; + QProgressBar *progress_bar; + QSocketNotifier *notifier; + +public slots: + void update(int n); +}; diff --git a/system/clearpilot/tools_wip_4_28/test4.cc b/system/clearpilot/tools_wip_4_28/test4.cc new file mode 100644 index 0000000..11ee15f --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/test4.cc @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + if (argc < 2) { + printf("Usage: %s ''\n", argv[0]); + return 1; + } + + QWidget window; + window.setWindowTitle("Shell Command Output Viewer"); + window.setStyleSheet("background-color: black;"); + + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", window.windowHandle())); + wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); + wl_surface_commit(s); + void *egl = native->nativeResourceForWindow("egldisplay", window.windowHandle()); + assert(egl != nullptr); + + window.showFullScreen(); + + QVBoxLayout *layout = new QVBoxLayout(&window); + QTextEdit *outputDisplay = new QTextEdit; + outputDisplay->setFont(QFont("Consolas", 32)); + outputDisplay->setReadOnly(true); + outputDisplay->setStyleSheet("color: white; background-color: black;"); + layout->addWidget(outputDisplay); + + QProcess process; + QObject::connect(&process, &QProcess::readyReadStandardOutput, [&]() { + static QStringList lines; + QString output = process.readAllStandardOutput(); + lines += output.split("\n", QString::SkipEmptyParts); + while (lines.size() > 100) { + lines.removeFirst(); + } + outputDisplay->setPlainText(lines.join("\n")); + outputDisplay->verticalScrollBar()->setValue(outputDisplay->verticalScrollBar()->maximum()); + }); + + QObject::connect(&process, QOverload::of(&QProcess::finished), [&]() { + app.quit(); + }); + + QString command = argv[1]; + process.start(QString("bash -c \"%1\"").arg(command)); + + return app.exec(); +} diff --git a/system/clearpilot/tools_wip_4_28/webview.py b/system/clearpilot/tools_wip_4_28/webview.py new file mode 100644 index 0000000..62e5337 --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/webview.py @@ -0,0 +1,66 @@ +import os +import sys +from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QGraphicsView, QGraphicsScene +from PyQt5.QtCore import Qt, QUrl, QTimer +from PyQt5.QtGui import QCursor, QPixmap, QTransform +from PyQt5.QtWebEngineWidgets import QWebEngineView +import rotation + +def create_webview_app(): + # Set environment for Wayland + os.environ["XDG_RUNTIME_DIR"] = "/var/tmp/weston" + os.environ["WAYLAND_DISPLAY"] = "wayland-0" + os.environ["QT_QPA_PLATFORM"] = "wayland" + os.environ["QT_WAYLAND_SHELL_INTEGRATION"] = "wl-shell" + + # Application setup + app = QApplication(sys.argv) + window = QWidget() + window.setWindowTitle("Qt WebView Example") + window.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + sg = QApplication.desktop().availableGeometry(window) + window.setGeometry(0, 0, sg.height(), sg.width()) + window.setStyleSheet("background-color: black;") + + scene = QGraphicsScene() + view = QGraphicsView(scene, window) + view.setGeometry(0, 0, sg.width(), sg.height()) + view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + webview = QWebEngineView() + webview.load(QUrl("https://cdpn.io/yananas/fullpage/rwvZvY")) + webview.setGeometry(0, 0, sg.width(), sg.height()) + scene.addWidget(webview) + view.setScene(scene) + + window_layout = QHBoxLayout(window) + window_layout.addWidget(view) + + window.setLayout(window_layout) + + window.showFullScreen() + + window_handle = window.windowHandle() + rotation.rotate_display(window_handle) + + # Delay rotation using QTimer +# QTimer.singleShot(1000, lambda: rotate_display(window)) + + return app, window + +def rotate_display(window): + window_handle = window.windowHandle() + if window_handle: + print("Rotating display.") + try: + print(int(window_handle.winId())) + rotation.rotate_display(int(window_handle.winId())) + except Exception as e: + print(f"Error rotating display: {e}") + else: + print("Window handle is not valid.") + +if __name__ == "__main__": + app, _ = create_webview_app() + sys.exit(app.exec_()) diff --git a/system/clearpilot/tools_wip_4_28/webview.sh b/system/clearpilot/tools_wip_4_28/webview.sh new file mode 100644 index 0000000..f6ed25b --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/webview.sh @@ -0,0 +1 @@ +sudo su comma -c "nice python3 webview.py" \ No newline at end of file diff --git a/system/clearpilot/tools_wip_4_28/webview2.py b/system/clearpilot/tools_wip_4_28/webview2.py new file mode 100644 index 0000000..5d4acbb --- /dev/null +++ b/system/clearpilot/tools_wip_4_28/webview2.py @@ -0,0 +1,75 @@ +import os +import sys +import signal +from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QGraphicsView, QGraphicsScene +from PyQt5.QtCore import Qt, QUrl, QPointF, QTimer, QObject, QEvent +from PyQt5.QtGui import QCursor, QPixmap, QTransform +from PyQt5.QtWebEngineWidgets import QWebEngineView + +import rotation + +webview = None + +def create_webview_app(): + global webview + + # Set environment for Wayland + os.environ["XDG_RUNTIME_DIR"] = "/var/tmp/weston" + os.environ["WAYLAND_DISPLAY"] = "wayland-0" + os.environ["QT_QPA_PLATFORM"] = "wayland" + os.environ["QT_WAYLAND_SHELL_INTEGRATION"] = "wl-shell" + + # Application setup + app = QApplication(sys.argv) + desktop = QApplication.desktop() + + sg = desktop.availableGeometry(desktop.primaryScreen()) + + window = QWidget() + window.setWindowTitle("Qt WebView Example") + window.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + window.setGeometry(0, 0, sg.height(), sg.width()) + window.setStyleSheet("background-color: black;") + window.showFullScreen() + + scene = QGraphicsScene() + view = QGraphicsView(scene, window) + view.setGeometry(0, 0, sg.width(), sg.height()) + view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + # Create WebView + webview = QWebEngineView() + webview.load(QUrl("https://cdpn.io/yananas/fullpage/rwvZvY")) + webview.setGeometry(0, 0, sg.width(), sg.height()) + + # Add WebView to the scene + proxy_webview = scene.addWidget(webview) + view.setScene(scene) + # view.rotate(90) # Rotate the view by 90 degrees + + # Layout setup + window_layout = QHBoxLayout(window) + window_layout.addWidget(view) + window_layout.setContentsMargins(0, 0, 0, 0) + window.setLayout(window_layout) + + window_handle = int(window.winId()) +# window_handle = window.windowHandle() + print ("Handle:") + print (window_handle) +# if window_handle: +# rotation.rotate_display(window_handle) +# else: +# print("Window handle is not valid.") + + return app, window + +def main(): + signal.signal(signal.SIGINT, signal.SIG_DFL) # Enable Ctrl+C to terminate the application + app, window = create_webview_app() + exit_code = app.exec_() + sys.exit(exit_code) + +if __name__ == "__main__": + main()