Skip to content

Commit

Permalink
Initial add
Browse files Browse the repository at this point in the history
  • Loading branch information
SneWs committed Jul 6, 2024
1 parent 015b28d commit 9908dff
Show file tree
Hide file tree
Showing 14 changed files with 714 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps

build/
57 changes: 57 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.5)

project(tail-tray VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
main.cpp
MainWindow.cpp
MainWindow.h
MainWindow.ui
resources.qrc
models.h
TrayMenuManager.cpp
TrayMenuManager.h
)

qt_add_executable(tail-tray
MANUAL_FINALIZATION
${PROJECT_SOURCES}
TailRunner.h TailRunner.cpp
)

target_link_libraries(tail-tray PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.tail-tray)
endif()
set_target_properties(tail-tray PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS tail-tray
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(tail-tray)
endif()
65 changes: 65 additions & 0 deletions MainWindow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "MainWindow.h"
#include "./ui_MainWindow.h"

MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, pTrayManager(nullptr)
, eCurrentState(TailState::NoAccount)
, pCurrentExecution(nullptr)
, pTailStatus(nullptr)
, pStatusCheckTimer(nullptr)
{
ui->setupUi(this);

pStatusCheckTimer = new QTimer(this);
connect(pStatusCheckTimer, &QTimer::timeout, this, [this]() {
pCurrentExecution->checkStatus();
});
pStatusCheckTimer->setSingleShot(false);
pStatusCheckTimer->start(1000 * 30); // 30sec interval

pCurrentExecution = new TailRunner(this);
connect(pCurrentExecution, &TailRunner::statusUpdated, this, &MainWindow::onTailStatusChanged);

pTrayManager = new TrayMenuManager(pCurrentExecution, this);

changeToState(TailState::NotLoggedIn);
pCurrentExecution->checkStatus();
}

MainWindow::~MainWindow()
{
pStatusCheckTimer->stop();
delete pStatusCheckTimer;

delete pCurrentExecution;
delete pTailStatus;
delete pTrayManager;
delete ui;
}

TailState MainWindow::changeToState(TailState newState)
{
auto retVal = eCurrentState;
eCurrentState = newState;

pTrayManager->stateChangedTo(newState, pTailStatus);

return retVal;
}

void MainWindow::onTailStatusChanged(TailStatus* pNewStatus)
{
delete pTailStatus;

pTailStatus = pNewStatus;
if (pTailStatus->user->id > 0)
{
// Logged in
if (pTailStatus->health.count() < 1)
changeToState(TailState::Connected);
else
changeToState(TailState::NotConnected);
}
}
43 changes: 43 additions & 0 deletions MainWindow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QTimer>

#include "TailRunner.h"
#include "TrayMenuManager.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();

private:
Ui::MainWindow* ui;

TrayMenuManager* pTrayManager;

TailState eCurrentState;
TailRunner* pCurrentExecution;
TailStatus* pTailStatus;
QTimer* pStatusCheckTimer;


private:
// Switch to the new state and return the prev (old) state back to caller
TailState changeToState(TailState newState);

void onTailStatusChanged(TailStatus* pNewStatus);
};
#endif // MAINWINDOW_H
37 changes: 37 additions & 0 deletions MainWindow.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
<normaloff>:/icons/tray-off.png</normaloff>:/icons/tray-off.png</iconset>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>
112 changes: 112 additions & 0 deletions TailRunner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "TailRunner.h"

#include <QProcess>
#include <QDebug>
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonObject>

TailRunner::TailRunner(QObject* parent)
: QObject(parent)
, pProcess(nullptr)
, eCommand(Command::Status) {
}

TailRunner::~TailRunner()
{
delete pProcess;
}

void TailRunner::checkStatus()
{
eCommand = Command::Status;
runCommand("status", QStringList(), true);
}

void TailRunner::start()
{
// tailscale up --operator marcus --accept-routes --exit-node pelican
eCommand = Command::Connect;
QStringList args;
args << "--operator" << "marcus";
args << "--accept-routes";
args << "--exit-node" << "pelican";

runCommand("up", args, true);
}

void TailRunner::stop()
{
eCommand = Command::Disconnect;
runCommand("down", QStringList());
}

void TailRunner::runCommand(QString cmd, QStringList args, bool jsonResult)
{
if (pProcess != nullptr)
{
if (pProcess->state() == QProcess::Running) {
assert(!"Process already running!");
return;
}

delete pProcess;
}

pProcess = new QProcess(this);
connect(pProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError err) {
qDebug() << "Failed to run process: " << err;
});

connect(pProcess, &QProcess::finished, this, [this](int exitCode, QProcess::ExitStatus status) {
qDebug() << "Process exited with " << exitCode << " - " << status;

// After we've invoked a command not status command we check for new status update
if (eCommand != Command::Status) {
checkStatus();
}
});

connect(pProcess, &QProcess::readyReadStandardOutput,
this, &TailRunner::onProcessCanReadStdOut);

if (jsonResult)
args << "--json";

args.insert(0, cmd);
pProcess->start("tailscale", args);
}

void TailRunner::onProcessCanReadStdOut() {
assert(pProcess != nullptr);

auto data = pProcess->readAllStandardOutput();

// Parse the status object

switch (eCommand) {
case Command::Status: {
QJsonParseError* parseError = nullptr;
QJsonDocument doc = QJsonDocument::fromJson(data, parseError);

if (parseError != nullptr)
{
qDebug() << parseError->errorString();
return;
}
QJsonObject obj = doc.object();
parseStatusResponse(obj);
break;
}
case Command::Connect: {
break;
}
case Command::Disconnect: {
break;
}
}
}

void TailRunner::parseStatusResponse(const QJsonObject& obj) {
emit statusUpdated(TailStatus::parse(obj));
}
44 changes: 44 additions & 0 deletions TailRunner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef TAILRUNNER_H
#define TAILRUNNER_H

#include <QObject>
#include <QString>
#include <QList>
#include <QProcess>
#include <QStringList>

#include "models.h"

class TailRunner : public QObject
{
Q_OBJECT
public:
explicit TailRunner(QObject* parent = nullptr);
virtual ~TailRunner();

void checkStatus();

void start();
void stop();

private:
QProcess* pProcess;
enum class Command {
Connect,
Disconnect,
Status
};

Command eCommand;

signals:
void statusUpdated(TailStatus* newStatus);

private:
void runCommand(QString cmd, QStringList args, bool jsonResult = false);
void onProcessCanReadStdOut();

void parseStatusResponse(const QJsonObject& obj);
};

#endif // TAILRUNNER_H
Loading

0 comments on commit 9908dff

Please sign in to comment.