#include "../../shared/settings.h" #include "../net/serverconnection.h" #include "../vnc/vncwindow.h" #include "../vnc/vncserver.h" #include "../util/util.h" #include "../informationdialog/informationdialog.h" #include "../clientapp/clientapp.h" #include "../addons/addons.h" #include "toolbar.h" #include "ui_toolbar.h" #include #include #include /** * @brief * * Does exact teh same as Toolbar::Toolbar(QWidget *parent) but additionally * instantly tries to connect to a pvsmgr with the given sessionName. * @param sessionName The session to connect to. * @param parent If parent is 0, the new widget becomes a window. If parent is * another widget, this widget becomes a child window inside parent. The new * widget is deleted when its parent is deleted. */ Toolbar::Toolbar(const QByteArray sessionName, QWidget *parent) : QWidget(parent), _showTimer(this), _hideTimer(this), _hideCountdown(10), _blinkTimer(this), _beWatchedEye(":eye") { qDebug() << "sessionName - constructor"; init(); clientApp->connectWindow()->connectToSession(sessionName, ""); } /** * @brief Constructor of the Toolbar. * * Does exact the same as Toolbar::Toolbar(QWidget *parent) but additionally * instantly tries to connect to the pvsmgr in its room without sessionName. * @param autoConnect * @param parent If parent is 0, the new widget becomes a window. If parent is * another widget, this widget becomes a child window inside parent. The new * widget is deleted when its parent is deleted. */ Toolbar::Toolbar(const bool autoConnect, QWidget *parent) : QWidget(parent), _showTimer(this), _hideTimer(this), _hideCountdown(10), _blinkTimer(this), _beWatchedEye(":eye") { qDebug() << "auto - constructor!"; init(); if (autoConnect) { // Try getting manager ip. QString mgrIp = identifyMgrIP(); if (!mgrIp.isEmpty()) clientApp->connectWindow()->connectToSession("", mgrIp); } } /** * @brief Constructor of the Toolbar. * * Constructs a widget which is a child of parent. * Initialize the GUI and sets up the menu, sets window properties, create the * VNC- and connect-window, connects the necessary signals, sets the UI's * position and configuires the timer for the UI to be hidden. * @param parent If parent is 0, the new widget becomes a window. If parent is * another widget, this widget becomes a child window inside parent. The new * widget is deleted when its parent is deleted. */ Toolbar::Toolbar(QWidget *parent) : QWidget(parent), _showTimer(this), _hideTimer(this), _hideCountdown(10), _blinkTimer(this), _beWatchedEye(":eye") { init(); } void Toolbar::exit() { this->close(); this->deleteLater(); qApp->quit(); } void Toolbar::init() { _ui = new Ui::Toolbar; /* Initialize the GUI */ _ui->setupUi(this); /* Set window properties */ setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); setAttribute(Qt::WA_AlwaysShowToolTips); setAttribute(Qt::WA_QuitOnClose, false); setAttribute(Qt::WA_DeleteOnClose, false); /* Create the VNC Window */ _vnc = new VncWindow(nullptr); /* Create the connect window */ clientApp->connectWindow()->setAvailableRooms(myRooms()); // Connect the signals connect(clientApp->connectWindow(), SIGNAL(disconnect()), this, SLOT(onDoDisconnect())); connect(clientApp->connectWindow(), SIGNAL(connected(ServerConnection*)), this, SLOT(onConnected(ServerConnection*))); connect(_ui->btnAttention, SIGNAL(toggled(bool)), this, SLOT(onBtnAttention())); /* Setup menu */ initButtonsAndMenus(); /* hide attention button while disconnected */ _ui->btnAttention->setVisible(false); _ui->btnAttention->setMaximumWidth(30); /* Connect the signals from vnc server */ connect(VncServer::instance(), SIGNAL(started(int, QString&, QString&)), this, SLOT(onVncServerIsRunning(int))); /* React to screen geometry change */ connect(QGuiApplication::primaryScreen(), &QScreen::availableGeometryChanged, this, &Toolbar::setToolbarPosition); /* Detect task bars and set position */ const QRect availableScreen = QGuiApplication::primaryScreen()->availableGeometry(); setToolbarPosition(availableScreen); /* Setup show & hide timer */ _showTimer.setInterval(500); _showTimer.setSingleShot(true); connect(&_showTimer, SIGNAL(timeout()), this, SLOT(showBar())); _hideTimer.setInterval(50); _hideTimer.setSingleShot(false); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideBar())); setVisible(true); activateWindow(); _hideTimer.start(); // initially show PVS and hide later /* Setup blink timer */ _blinkTimer.setInterval(500); connect(&_blinkTimer, SIGNAL(timeout()), this, SLOT(cameraBlink())); } static QFrame* makeVerticalLine() { QFrame *f = new QFrame(); f->setFrameShape(QFrame::HLine); f->setFrameShadow(QFrame::Sunken); return f; } /** * This function should be called once from the main init() function which in * turn should only be called by the constructor. **/ void Toolbar::initButtonsAndMenus() { QList buttons; QList menus; auto settings = clientApp->getSettings(); AddonManager::loadFromPath(settings->value("addonConfigDir", "/opt/openslx/pvs2/addons").toString(), buttons, menus); // Buttons if (!buttons.isEmpty()) { _ui->buttonContainer->addWidget(makeVerticalLine()); for (auto i : buttons) { _ui->buttonContainer->addWidget(i); i->setVisible(false); } _ui->buttonContainer->addWidget(makeVerticalLine()); } // Menu _menu = new QMenu(this); _acnConnect = new QAction(tr("&Connect..."), this); _acnDisconnect = new QAction(tr("&Disconnect"), this); _acnDisconnect->setEnabled(false); _acnInformation = new QAction(tr("&Information..."), this); _acnAbout = new QAction(tr("&What's this?"), this); _acnQuit = new QAction(tr("&Quit"), this); _menu->addAction(_acnConnect); _menu->addAction(_acnDisconnect); _menu->addSeparator(); for (auto i : menus) { _menu->addAction(i); i->setVisible(false); } _menu->addAction(_acnInformation); _menu->addAction(_acnAbout); _menu->addSeparator(); _menu->addAction(_acnQuit); _ui->cmdMenu->setMenu(_menu); /* only add a "quit"-button when the configuration allows it. */ bool allow = settings->value("allowClientQuit").toBool(); _acnQuit->setVisible(allow); // Connect the signals connect(_menu, SIGNAL(aboutToHide()), this, SLOT(delayedHideBar())); connect(_acnConnect, SIGNAL(triggered()), clientApp->connectWindow(), SLOT(doShow())); connect(_acnDisconnect, SIGNAL(triggered()), clientApp->connectWindow(), SLOT(DoDisconnect())); connect(_acnInformation, SIGNAL(triggered()), this, SLOT(showInformationDialog())); connect(_acnAbout, SIGNAL(triggered()), this, SLOT(showAboutDialog())); connect(_acnQuit, SIGNAL(triggered()), this, SLOT(exit())); // Delay until bar is visible QTimer::singleShot(10, [=]() { AddonManager::initControls(); }); } /** * Destructor of the Toolbar. Destroys the widget. All this widget's children * are deleted first. */ Toolbar::~Toolbar() { VncServer::instance()->stop(); _vnc->deleteLater(); delete _ui; } /* * Override */ /** * This event is reimplemented to receive widget enter events. When the mouse * cursor enters the widget, the timer which, when timed out, hides * the Toolbar, gets stopped. * @param e The enter event (Mouse enters widget's boundaries.) */ void Toolbar::enterEvent(QEvent* e) { delayedShowBar(); QWidget::enterEvent(e); } void Toolbar::mousePressEvent(QMouseEvent* e) { _lastDragPos = e->globalPos(); } void Toolbar::mouseMoveEvent(QMouseEvent* e) { const QPoint currentPos = e->globalPos(); const QPoint offset = currentPos - _lastDragPos; _lastDragPos = currentPos; move(x() + offset.x(), y()); } /* * CFG to test this * http://git.openslx.org/tm-scripts.git/plain/server/modules/pvs2-freiburg/etc/xdg/openslx/pvs2client.ini * */ /* returns a sorted list of available rooms. * (Available means that this client is configured to be in that room) */ QList Toolbar::myRooms() { QList myRooms; auto conf = clientApp->getSettings(); if (!conf->contains("rooms")) { qDebug() << "Invalid config file!"; return myRooms; } QStringList roomNames = conf->value("rooms").toStringList(); /* go through all rooms and check if this client is a member of the room. */ auto localAddresses = QNetworkInterface::allAddresses(); for (auto roomName : roomNames) { conf->beginGroup(roomName); if (conf->contains("name")) { roomName = conf->value("name").toString(); } if (!conf->contains("mgrIP")) { qDebug() << "Room " << roomName << " has no mgrIP: Invalid config file!"; } QString mgrIP = conf->value("mgrIP").toString(); int priority = conf->value("priority").toInt(); if (mgrIP.length() != 0) { foreach (const QHostAddress & address, localAddresses) { int size = conf->beginReadArray("client"); for (int j = 0; j < size; ++j) { conf->setArrayIndex(j); QString ip = conf->value("ip").toString(); if (address != QHostAddress(QHostAddress::LocalHost) && ip == address.toString() ) { /* add this room to the list */ Room r(roomName, mgrIP, priority); myRooms << r; break; } } conf->endArray(); } } conf->endGroup(); } /* sort */ qStableSort(myRooms.begin(), myRooms.end(), qGreater()); return myRooms; } /** Identifies the responsible manager for this client by searching through the * configuration file. The manager whose room has the highest priority is chosen. */ QString Toolbar::identifyMgrIP() { QList rooms = myRooms(); if (!rooms.empty()) { return rooms.first().mgr; } else { return ""; } } /* * Slots */ /** * A slot for changing the camera icon. This slot should be called permanently * if the vnc server is recording the screen. */ void Toolbar::cameraBlink() { static bool showEye = false; if (!showEye) { _ui->icon_cam->setPixmap(_beWatchedEye); showEye = true; } else { _ui->icon_cam->setPixmap(QPixmap()); // set empty pixmap for blinking effect showEye = false; } } /** * A slot for the VncServerIsRunning signal. This slot will change the UI * according to the state fo the VncServer. * @param[in] port Indicates the state of the VncServer. */ void Toolbar::onVncServerIsRunning(int port) { if (port > 0) { _blinkTimer.start(); _ui->lblStatus->setStyleSheet("color:red"); _ui->lblStatus->setText(tr("Streaming")); showBar(); } else { _blinkTimer.stop(); _ui->icon_cam->setPixmap(QPixmap()); _ui->lblStatus->setStyleSheet("color:green"); _ui->lblStatus->setText(tr("Online")); delayedHideBar(); } } /** * A slot for the onDisconnected signal of the ConnectWindow. This slot will * change the UI according to the state fo the connection. */ void Toolbar::onDisconnected(ServerConnection* connection) { if (connection != nullptr) { disconnect(connection, SIGNAL(disconnected(ServerConnection*)), this, SLOT(onDisconnected(ServerConnection*))); } _ui->lblStatus->setStyleSheet("color:red"); _ui->lblStatus->setText(tr("Offline")); this->_acnConnect->setEnabled(true); this->_acnDisconnect->setEnabled(false); _ui->btnAttention->setVisible(false); onBtnAttention(); delayedHideBar(); AddonManager::disconnectEvent(); } /** * A slot for the onConnected signal of the ConnectWindow. This slot will * change the UI according to the state of the connection and connect the * relevant signals and slots. * @param connection Pointer to the ServerConnection */ void Toolbar::onConnected(ServerConnection* connection) { this->_acnConnect->setEnabled(false); this->_acnDisconnect->setEnabled(true); _ui->btnAttention->setChecked(false); _ui->lblStatus->setStyleSheet("color:green"); _ui->lblStatus->setText(tr("Online")); /* connected, show button */ _ui->btnAttention->setVisible(true); AddonManager::connectEvent(connection->isLocalConnection(), connection->getPeerAdress()); // connect(connection, SIGNAL(disconnected(ServerConnection*)), this, SLOT(onDisconnected(ServerConnection*))); connect(connection, SIGNAL(openVnc(const QString&, int, const QString&, bool, bool, const QString&, const int, const QByteArray&)), _vnc, SLOT(open(const QString&, int, const QString&, bool, bool, const QString&, const int, const QByteArray&))); connect(connection, SIGNAL(closeVnc()), _vnc, SLOT(close())); connect(connection, SIGNAL(attentionChanged(const bool)), this, SLOT(onServerAttentionChanged(const bool))); connect(_vnc, SIGNAL(running(const bool, const int)), connection, SLOT(onVncViewerStartStop(const bool, const int))); } /** * */ void Toolbar::onDoDisconnect() { if (clientApp->connection() != nullptr) clientApp->connection()->disconnectFromServer(); } void Toolbar::onServerAttentionChanged(const bool on) { _ui->btnAttention->setChecked(on); if (on) { showBar(); } else { delayedHideBar(); } } /** * This slot hides the toolbar. Places the toolbar hidden behind the edge of the * screen just showing 2 pixels. */ void Toolbar::hideBar() { // Don't hide window if any menu is open or VNC Server is running from this client. if (_ui->btnAttention->isChecked() || VncServer::instance()->isVncServerRunning()) { _hideTimer.stop(); return; } // These don't qualify for hiding, but don't stop timer if (_menu->isVisible() || this->underMouse()) return; // Countdown if (--_hideCountdown > 0) return; _hideTimer.stop(); move(x(), _yPosHidden); } void Toolbar::delayedHideBar() { _showTimer.stop(); if (!_hideTimer.isActive()) { _hideCountdown = 10; _hideTimer.start(); } } /** * This slot shows the toolbar. Used after a hideBar(). */ void Toolbar::showBar() { move(x(), _yPos); delayedHideBar(); } void Toolbar::delayedShowBar() { _hideTimer.stop(); if (!_showTimer.isActive()) { _showTimer.start(); } } /** * @brief Toolbar::showAboutDialog */ void Toolbar::showAboutDialog() { QMessageBox msgBox( QMessageBox::NoIcon, tr("About PVS Client"), tr("The PVS - client is part of a software system for managing the "\ "virtual data traffic within the computer pools, between the tutor's "\ "and student's PCs. It has been developed to simplify the information "\ "traffic in seminars and general eLearning."), QMessageBox::NoButton, this, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); msgBox.setIconPixmap(QIcon(":cam32.svg").pixmap(64, 64)); msgBox.exec(); } void Toolbar::showInformationDialog() { InformationDialog* d = new InformationDialog(); d->exec(); d->deleteLater(); auto b = new QPushButton("sdfadsfgadfgadfgadg"); this->layout()->addWidget(b); } void Toolbar::onBtnAttention() { const bool on = clientApp->connection() != nullptr && _ui->btnAttention->isChecked(); if (on != _ui->btnAttention->isChecked()) { _ui->btnAttention->setChecked(on); return; } if (clientApp->connection() != nullptr) { clientApp->connection()->sendAttention(on); } } void Toolbar::setToolbarPosition(const QRect &availableGeometry) { const QRect primaryScreen = QGuiApplication::primaryScreen()->geometry(); // set center position const int centerPos = primaryScreen.left() + (primaryScreen.width() - this->width()) / 2; // detect system task bars if (primaryScreen.height() > availableGeometry.height() && primaryScreen.y() < availableGeometry.y()) { // system task bar already in top position, prefer to show toolbar on bottom _yPos = primaryScreen.height() - this->height(); _yPosHidden = primaryScreen.height() - 2; } else { // no task bars at all or a bottom task bar, either way show our toolbar on top _yPos = primaryScreen.top(); _yPosHidden = _yPos - this->height() + 2; } move(centerPos, _yPos); delayedShowBar(); }