diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2434e00..5b90ed8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,22 +16,22 @@ jobs: fail-fast: false matrix: config: - - { name: "Windows gcc", os: windows-latest, cc: "gcc", cxx: "g++" } - - { name: "Ubuntu gcc", os: ubuntu-latest, cc: "gcc", cxx: "g++" } - - { name: "MacOS clang", os: macos-latest, cc: "clang", cxx: "clang++" } + - { name: "Windows gcc", os: windows-latest, cc: "gcc", cxx: "g++", qt-version: "", qt-arch: "", cmake-extra: "" } + - { name: "Ubuntu gcc", os: ubuntu-latest, cc: "gcc", cxx: "g++", qt-version: "6.5.3", qt-arch: "gcc_64", cmake-extra: "" } + - { name: "MacOS 26 clang", os: macos-26, cc: "clang", cxx: "clang++", qt-version: "6.9.2", qt-arch: "clang_64", cmake-extra: "-DCMAKE_OSX_ARCHITECTURES=arm64" } build-configuration: [ Debug, Release ] steps: - name: Checkout repository uses: actions/checkout@v6 with: - submodules: true + submodules: recursive - name: Install Ninja (Linux) if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build + sudo apt-get install -y ninja-build libgl1-mesa-dev - name: Install Ninja (macOS) if: runner.os == 'macOS' @@ -41,22 +41,63 @@ jobs: fi - - name: Install Ninja (Windows) + - name: Install Qt (Linux/macOS) + if: runner.os != 'Windows' + uses: jurplel/install-qt-action@v4 + with: + version: ${{ matrix.config.qt-version }} + target: 'desktop' + arch: ${{ matrix.config.qt-arch }} + + - name: Verify Qt tool dependencies (Linux) + if: runner.os == 'Linux' + run: | + ldd "$QT_ROOT_DIR/libexec/qmlimportscanner" + ! ldd "$QT_ROOT_DIR/libexec/qmlimportscanner" | grep "not found" + + - name: Install Qt (Windows) if: runner.os == 'Windows' - shell: pwsh - run: choco install ninja -y + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: >- + mingw-w64-x86_64-gcc + mingw-w64-x86_64-cmake + mingw-w64-x86_64-ninja + mingw-w64-x86_64-qt6-base + mingw-w64-x86_64-qt6-declarative + + - name: Configure CMake (Linux/macOS) + if: runner.os != 'Windows' + run: | + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} -DCMAKE_C_COMPILER=${{ matrix.config.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} -DCMAKE_PREFIX_PATH="${{ env.QT_ROOT_DIR }}" ${{ matrix.config.cmake-extra }} - - name: Configure CMake + - name: Configure CMake (Windows) + if: runner.os == 'Windows' + shell: msys2 {0} run: | - cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} -DCMAKE_C_COMPILER=${{ matrix.config.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_PREFIX_PATH=/mingw64 + + - name: Build Project (Linux/macOS) + if: runner.os != 'Windows' + run: cmake --build build --config ${{ matrix.build-configuration }} - - name: Build Project + - name: Build Project (Windows) + if: runner.os == 'Windows' + shell: msys2 {0} run: cmake --build build --config ${{ matrix.build-configuration }} - - name: Run Catch2 Unit Tests + - name: Run Catch2 Unit Tests (Linux/macOS) + if: runner.os != 'Windows' # Runs the unit tests you specified run: ctest --test-dir build -C ${{ matrix.build-configuration }} --output-on-failure + - name: Run Catch2 Unit Tests (Windows) + if: runner.os == 'Windows' + shell: msys2 {0} + run: ctest --test-dir build -C ${{ matrix.build-configuration }} --output-on-failure + # We only package and upload 'Release' builds to save storage and time - name: Compress Release Build Directory if: matrix.build-configuration == 'Release' @@ -107,4 +148,4 @@ jobs: gh release create "$TAG_NAME" ./release-artifacts/*.zip \ --title "Development Build $TAG_NAME" \ --prerelease \ - --generate-notes \ No newline at end of file + --generate-notes diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b32830f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/RSA"] + path = lib/RSA + url = https://github.com/ParallelEngineering/RSA.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e69de29..42f67cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.28.3) +project(Messenger) + +# Enforce C++20 and disable compiler-specific extensions +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_subdirectory(lib) +add_subdirectory(src) + +#target_link_libraries(Messenger PRIVATE RSA) \ No newline at end of file diff --git a/README.md b/README.md index 9e07021..03314ab 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,60 @@ # Messenger -A client to server chat system based on RSA + +Messenger is a cross-platform client-server messenger application based on the RSA algorithm. The project is still in an early stage and currently provides the foundation for the client and server. + +## Architecture + +The repository contains two applications: `Messenger-Client` and `Messenger-Server`. The client is built with Qt/QML; the server is a terminal program. + +Both applications use a singleton through `getInstance()`. The project is built with CMake and C++20, with the RSA library included as a submodule. + +## Dependencies + +```mermaid +flowchart TD + Client["Messenger-Client"] + Server["Messenger-Server"] + RSA["RSA"] + QtGUI["Qt QML"] + QtNetwork["Qt Network"] + + Client --> QtGUI + Client --> QtNetwork + Server --> QtNetwork + Client --> RSA + Server --> RSA + + click QtGUI "https://doc.qt.io/qt-6/qtquick-index.html" "Qt Quick/QML documentation" + click QtNetwork "https://doc.qt.io/qt-6/qtnetwork-index.html" "Qt Network documentation" + click RSA "https://github.com/ParallelEngineering/RSA" +``` + +The application depends on the RSA library for encryption, Qt Quick/QML for the graphical client, and Qt Network for networking functionality. + +## Setup + +> [!NOTE] +> Qt must be installed locally for the client so CMake can resolve `find_package(Qt6 COMPONENTS Quick Qml REQUIRED)`. The official installation guide is available in the [Qt documentation](https://doc.qt.io/qt-6/get-and-install-qt.html). + +Clone the repository including its submodules: + +```bash +git clone --recurse-submodules https://github.com/ParallelEngineering/Messenger.git +``` + +If the repository has already been cloned without submodules, they can be initialized recursively with remote updates: + +```bash +git submodule update --init --recursive --remote +``` + +## Build + +The project can be configured and built with CMake: + +```bash +cmake -S . -B cmake-build-debug +cmake --build cmake-build-debug +``` + +This creates the `Messenger-Client` and `Messenger-Server` targets. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e69de29..23c1a9b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -0,0 +1,4 @@ +if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/RSA/CMakeLists.txt") + message(FATAL_ERROR "RSA submodule not initialized. Run: git submodule update --init --recursive") +endif() +add_subdirectory(RSA) \ No newline at end of file diff --git a/lib/RSA b/lib/RSA new file mode 160000 index 0000000..480dfb0 --- /dev/null +++ b/lib/RSA @@ -0,0 +1 @@ +Subproject commit 480dfb017249f79769978e23fe9bd5167e5a6b4d diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e69de29..38199f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(shared) +add_subdirectory(client) +add_subdirectory(server) diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index e69de29..8775839 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -0,0 +1,28 @@ +find_package(Qt6 COMPONENTS Quick Qml Network REQUIRED) + +qt_standard_project_setup(REQUIRES 6.5) + +qt_add_executable(Messenger-Client + WIN32 + MACOSX_BUNDLE + client.cpp +) + +set_target_properties(Messenger-Client PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME Messenger + MACOSX_BUNDLE_GUI_IDENTIFIER de.ParallelEngineering.Messenger +) + +qt_add_qml_module(Messenger-Client + URI Messenger.Client + VERSION 1.0 + QML_FILES + Main.qml +) + +target_link_libraries(Messenger-Client + PRIVATE + Qt6::Quick + Qt6::Qml + Qt6::Network +) diff --git a/src/client/Main.qml b/src/client/Main.qml new file mode 100644 index 0000000..f71ebff --- /dev/null +++ b/src/client/Main.qml @@ -0,0 +1,10 @@ +import QtQuick + +Window { + width: 960 + height: 640 + visible: true + title: qsTr("Messenger Client") + + color: "#f7f8fa" +} diff --git a/src/client/client.cpp b/src/client/client.cpp new file mode 100644 index 0000000..c9ff8d3 --- /dev/null +++ b/src/client/client.cpp @@ -0,0 +1,36 @@ +#include "client.h" + +#include +#include + +client& client::getInstance() { + static client instance; + return instance; +} + +client::client() = default; + +client::~client() = default; + +int client::run(QGuiApplication& app) { + engine_ = std::make_unique(); + engine_->loadFromModule("Messenger.Client", "Main"); + + if (engine_->rootObjects().isEmpty()) { + engine_.reset(); + return -1; + } + + const auto exitCode = app.exec(); + engine_.reset(); + + return exitCode; +} + +int main(int argc, char* argv[]) { + QGuiApplication app(argc, argv); + + auto& clientInstance = client::getInstance(); + + return clientInstance.run(app); +} diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..6eec7b3 --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,27 @@ +#ifndef MESSENGER_CLIENT_H +#define MESSENGER_CLIENT_H + +#include + +class QGuiApplication; +class QQmlApplicationEngine; + +class client { + public: + static client& getInstance(); + ~client(); + + int run(QGuiApplication& app); + + client(const client&) = delete; + client& operator=(const client&) = delete; + client(client&&) = delete; + client& operator=(client&&) = delete; + + private: + client(); + + std::unique_ptr engine_; +}; + +#endif // MESSENGER_CLIENT_H diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index e69de29..ba9761d 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(Qt6 REQUIRED COMPONENTS Network) + +add_executable(Messenger-Server server.cpp) + +target_link_libraries(Messenger-Server PRIVATE Qt6::Network) diff --git a/src/server/server.cpp b/src/server/server.cpp new file mode 100644 index 0000000..7a1b330 --- /dev/null +++ b/src/server/server.cpp @@ -0,0 +1,19 @@ +#include "server.h" + +#include + +server& server::getInstance() { + static server instance; + return instance; +} + +server::server() { std::cout << "Starting Messenger Server ...\n"; } + +server::~server() { std::cout << "Stopping Messenger Server ...\n"; } + +int main() { + auto& serverInstance = server::getInstance(); + (void)serverInstance; + + return 0; +} diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..2825dfe --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,18 @@ +#ifndef MESSENGER_SERVER_H +#define MESSENGER_SERVER_H + +class server { + public: + static server& getInstance(); + ~server(); + + server(const server&) = delete; + server& operator=(const server&) = delete; + server(server&&) = delete; + server& operator=(server&&) = delete; + + private: + server(); +}; + +#endif // MESSENGER_SERVER_H