chore(CPP): Basic project configuration setup using Qt6

Considerations

- Use versioned files. Versioned Qt CMake APIs are disabled to force explicit calls and say that we don't support older `QT`s
- Don't use blobbing. Use `target_sources` and `qt_target_qml_sources`
- Distribute `CMake` definitions closer to the context: main folders with their own `CMakeLists.txt`
- Everything in libraries under `Status` namespace for cleaner code.
- Includes are exposed with Module folder externally and without prefix internally
- File/Folders name matches definitions they contain for uniformity that leads to cleaner code
- All source files (cpp, qml, js ...) have to be added to one of the CMakeLists.txt files to be tracked by CMake build system.
- Use BUILD_DEBUG, BUILD_RELEASE and BUILD_DEVELOPMENT variables from Helpers library
- Avoid Include directories. Not needed anymore CMake `target_*` APIs handles this through `INTERFACE`, `PUBLIC` and `PRIVATE` scope identifiers
- `StatusQ` is meant to be compiled as an external library, therefore StatusQ tests are kept inside its own directory
- Forced CMake version to `3.21` for the latest features and fixes. It is desired to be kept as recent as possible due to its backward compatibility. Following Qt's shipped version might be an option
- Depends on status-go changes to allow forcing of arm for apple silicon

Found limitations to CMake Qt API with Qt 6.3

- Having `0` as major version when using `qt_add_qml_module` doesn't work. Qml engine reports loading the `qmldir` but won't load the plugin library and no error is reported outside that exposed types are not found.
- `qt_target_qml_sources` doesn't work now, it generate a double copy error when deploying qml files in bin-directory. For now we stick with adding files using `qt_add_qml_module` central place
  - Need to add `OUTPUT_DIRECTORY` to `qt_add_qml_module` to use the workaround
- If `MACOSX_BUNDLE` target property is set breaks importing of QML files. Disabled until fixed or workaround found
- For an unknown reason application executable tries to include the `QML_ELEMENT` include files, therefore for now I include all the C++ qml elements in INTERFACE
This commit is contained in:
Stefan 2022-05-10 23:10:34 +03:00 committed by Stefan Dunca
parent 49b592daa3
commit 1e8c851283
67 changed files with 1408 additions and 206 deletions

View File

@ -1,148 +1,34 @@
cmake_minimum_required(VERSION 3.18)
# Provide general project configuration setup
#
cmake_minimum_required(VERSION 3.21)
set(STATUS_QT_VERSION 6.3)
set(STATUS_VERSION 0.6.0)
project(status-desktop
VERSION 0.1.0
VERSION ${STATUS_VERSION}
LANGUAGES CXX)
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there."
)
endif()
set(PROJECT_ORGANIZATION_DOMAIN "status.im")
set(PROJECT_ORGANIZATION_NAME "Status")
set(PROJECT_APPLICATION_NAME "Status Desktop")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
message(STATUS "Started CMake for ${PROJECT_NAME} v${PROJECT_VERSION}...\n")
include(ExternalProject)
# QtCreator supports the following variables for Android, which are identical to qmake Android variables.
# Check https://doc.qt.io/qt/deployment-android.html for more information.
# They need to be set before the find_package(...) calls below.
#if(ANDROID)
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
# if (ANDROID_ABI STREQUAL "armeabi-v7a")
# set(ANDROID_EXTRA_LIBS
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so)
# endif()
#endif()
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Widgets Concurrent REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Widgets Concurrent REQUIRED)
set(PROJECT_SOURCES
src-cpp/main.cpp
src-cpp/constants.cpp
src-cpp/logs.cpp
resources.rcc
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(status-desktop
${PROJECT_SOURCES}
)
else()
if(ANDROID)
add_library(status-desktop SHARED
${PROJECT_SOURCES}
)
else()
add_executable(status-desktop
${PROJECT_SOURCES}
)
endif()
endif()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/vendor/status-go bin/status-go)
# The following are dependencies that do not include CMakeList.txt files
# Create a PR in those projects so we can include them just like it was
# done with status-go
include(cmake/QRCodeGen.cmake)
include(cmake/Keycard.cmake)
# Submodiles
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/dotherside)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/app)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/app_service)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/backend)
target_include_directories(status-desktop PUBLIC include)
target_compile_definitions(status-desktop
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
target_link_libraries(status-desktop
PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::Widgets
DOtherSide
statusgo_shared # <- Link status-go with this target (remove `_shared` for static linking)
qrcodegen
keycard
app_service
app
)
add_custom_target(rcc ALL DEPENDS resources.rcc)
add_custom_command(
OUTPUT resources.rcc
COMMENT "Building resources.rcc"
COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc
COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc
COMMAND go run
${CMAKE_CURRENT_SOURCE_DIR}/ui/generate-rcc.go
-source=${CMAKE_CURRENT_SOURCE_DIR}/ui
-output=${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc
COMMAND rcc
--binary
$<$<CONFIG:Debug>:--no-compress>
${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc
${CMAKE_CURRENT_SOURCE_DIR}/resources/resources.qrc
-o ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc
VERBATIM
USES_TERMINAL
)
# run
add_custom_target(run
COMMENT "Execute desktop"
DEPENDS status-desktop
COMMAND ./status-desktop
VERBATIM
USES_TERMINAL
)
# run debug
add_custom_target(run-debug
COMMENT "Execute desktop with debugger on port 1234"
DEPENDS status-desktop
COMMAND ./status-desktop -qmljsdebugger=port:1234
VERBATIM
USES_TERMINAL
)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
include(CTest)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
add_subdirectory(test-cpp)
enable_testing()
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64" AND APPLE)
set(STATUSGO_FORCE_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR})
endif()
# status-desktop application
add_subdirectory(vendor)
add_subdirectory(libs)
add_subdirectory(app)
add_subdirectory(test)
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
add_subdirectory(ui/imports/assets)

View File

@ -5,69 +5,3 @@ Desktop client for the [Status Network](https://statusnetwork.com/) built with [
![https://github.com/status-im/nim-status-client/blob/master/screenshot.png](https://github.com/status-im/nim-status-client/blob/master/screenshot.png)
Dev Docs: [https://hackmd.io/@status-desktop/B1naRjxh_/https%3A%2F%2Fhackmd.io%2F%40status-desktop%2FB1eOaf-nd](https://hackmd.io/@status-desktop/B1naRjxh_/https%3A%2F%2Fhackmd.io%2F%40status-desktop%2FB1eOaf-nd)
# CPP App
### Setup `Linux`:
1. conancenter
Execute `conan remote list`. It should return this line among the results.
```
conancenter: https://center.conan.io [Verify SSL: True]
```
If it doesnt, consider upgrading conan with `pip install conan --upgrade` and then executing. `conan remote add -i 0 conancenter https://center.conan.io` . See [conan's documentation](https://docs.conan.io/en/latest/uploading_packages/remotes.html#conancenter) for more info.
2. conan libstdc++11
This applies to linux: the default conan profile does not work, since GCC uses the new C++ ABI since version 5.1 and conan, for compatibility purposes uses the old C++ ABI.
Execute this to update the profile:
```
conan profile update settings.compiler.libcxx=libstdc++11 default
```
3. Install dependencies:
```
cd build
conan install .. -s build_type=Release --build=missing
```
### Setup `OS X`:
1. Create `conan` profile `~/.conan/profiles/clang`:
```
[settings]
compiler=apple-clang
compiler.version=12.0
compiler.libcxx=libc++
arch=x86_64
os=Macos
build_type=Release
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++
```
2. Install dependecies:
```
cd build
conan install .. --profile=clang --build=missing
```
### Buid & test & run:
```
conan build ..
ctest -VV -C Release
./status-desktop
```
Instead of `conan build ..` CMake may be used:
```
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
cmake --build . --config Release
```

66
app/CMakeLists.txt Normal file
View File

@ -0,0 +1,66 @@
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
set(CMAKE_AUTORCC On)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick REQUIRED)
qt6_standard_project_setup()
qt6_add_executable(${PROJECT_NAME} "")
# TODO: Fix temporarly workaround until we make qt6_target_qml_sources work
# Adds qml files as /Status/Application/qml/.../<file name>
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Application
VERSION 1.0
QML_FILES
qml/main.qml
qml/Status/Application/StatusWindow.qml
qml/Status/Application/StatusContentView.qml
qml/Status/Application/MainShortcuts.qml
qml/Status/Application/MainView/MainView.qml
qml/Status/Application/System/StatusTrayIcon.qml
qml/Status/Application/Decorators/SplashScreen.qml
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application
)
# Compile time definitions required by the project
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_PROJECT_NAME=${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_PROJECT_ORGANIZATION_DOMAIN=${PROJECT_ORGANIZATION_DOMAIN})
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_PROJECT_ORGANIZATION_NAME=${PROJECT_ORGANIZATION_NAME})
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_PROJECT_APPLICATION_NAME=${PROJECT_APPLICATION_NAME})
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_BINARY_DIR=${CMAKE_BINARY_DIR})
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_SOURCE_DIR=${CMAKE_SOURCE_DIR})
add_subdirectory(qml)
add_subdirectory(src)
add_subdirectory(res)
include(${CMAKE_SOURCE_DIR}/cmake/platform_specific.cmake)
string(TOLOWER ${PROJECT_ORGANIZATION_NAME} URL_ORGANIZATION_NAME)
configure_app_os_specific(${PROJECT_NAME} ${URL_ORGANIZATION_NAME} ${PROJECT_ORGANIZATION_DOMAIN} ${PROJECT_VERSION_MAJOR} ${PROJECT_VERSION_MINOR} ${PROJECT_VERSION_PATCH})
target_compile_definitions(${PROJECT_NAME}
PRIVATE
$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
# TODO: Use Status:: namespace
#Core
Helpers
Onboarding
Assets
)
# QtCreator needs this
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
list(REMOVE_DUPLICATES QML_IMPORT_PATH)

59
app/README.md Normal file
View File

@ -0,0 +1,59 @@
# CPP App
## Setup dependencies
### 1. conancenter
Execute `conan remote list`. It should return this line among the results:
```bash
conancenter: https://center.conan.io [Verify SSL: True]
```
If it doesn't, consider upgrading conan with `pip install conan --upgrade` and then executing. `conan remote add -i 0 conancenter https://center.conan.io`. See [conan's documentation](https://docs.conan.io/en/latest/uploading_packages/remotes.html#conancenter) for more info.
### 2. conan libstdc++11
This applies to linux: the default conan profile does not work, since GCC uses the new C++ ABI since version 5.1 and conan, for compatibility purposes uses the old C++ ABI.
Execute this to update the profile:
```bash
conan profile update settings.compiler.libcxx=libstdc++11 default
```
### 2. Install dependencies
```bash
conan install . --profile=<Platform specific conan profile> -s build_type=Release --build=missing -if=build/conan
```
Platform specific conan profile
- Macos:
- Intel: `vendor/conan-configs/apple-arm64.ini`
- Apple silicon: `vendor/conan-configs/apple-x86_64.ini`
- Windows: TODO
- Linux: TODO
## Buid, test & run
Platform specific Qt prefix path
- Macos: `$HOME/Qt/6.3.0/macos`
- Windows: TODO
- Linux: TODO
### Build with conan
```bash
CMAKE_PREFIX_PATH=<Qt prefix path> conan build . -if=build/conan -bf=build
ctest -VV -C Release
./status-desktop
```
### Build with cmake
```bash
cmake -B build -S . -DCMAKE_PREFIX_PATH=<Qt prefix path> -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
cmake --build build --config Release
```

15
app/qml/CMakeLists.txt Normal file
View File

@ -0,0 +1,15 @@
# Note that the automatic plugin generation is only possible if the module does not do anything besides registering the types.
# If it needs to do something more advanced like registering an image provider in initializeEngine, you still need to manually write the plugin. qt6_add_qml_module has support for this with NO_GENERATE_PLUGIN_SOURCE.
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
add_subdirectory(Status/Application)
# Qt 6.3 fails calling qt6_target_qml_sources
#qt6_target_qml_sources(${PROJECT_NAME}
# QML_FILES
# main.qml
#)

View File

@ -0,0 +1,12 @@
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
# Qt 6.3 fails calling qt6_target_qml_sources
#qt6_target_qml_sources(${PROJECT_NAME}
# QML_FILES
# StatusWindow.qml
# StatusContentView.qml
# TODO
#)

View File

@ -0,0 +1,37 @@
import QtQuick
import Status.Assets
import Status.Core.Theme
Item {
id: root
signal animationFinished()
function show() {
splashLogo.playing = true
}
implicitWidth: splashLogo.implicitWidth
implicitHeight: splashLogo.implicitHeight
visible: (opacity > 0.0001)
// TODO: consider bringing POC attempt to use lottie animations
AnimatedImage {
id: splashLogo
anchors.centerIn: parent
scale: 0.5
source: Resources.gif("status_splash_" + Style.theme.name)
readonly property real frameToStartAnimation: frameCount/2
readonly property real animationRange: (frameCount - frameToStartAnimation)
onCurrentFrameChanged: {
if(currentFrame > frameToStartAnimation)
root.opacity = 1 - (currentFrame - frameToStartAnimation)/animationRange
if(currentFrame === (frameCount - 1))
root.animationFinished()
}
playing: false
}
}

View File

@ -0,0 +1,39 @@
import QtQuick
/*!
*/
Item {
required property Window window;
property alias enableHideWindow: hideWindowShortcut.enabled
Shortcut {
sequence: StandardKey.FullScreen
onActivated: {
if (visibility === Window.FullScreen)
window.showNormal()
else
window.showFullScreen()
}
}
Shortcut {
sequence: "Ctrl+M"
onActivated: {
if (visibility === Window.Minimized)
window.showNormal()
else
window.showMinimized()
}
}
Shortcut {
id: hideWindowShortcut
sequences: [StandardKey.Close]
onActivated: window.visible = false;
}
Shortcut {
sequence: StandardKey.Quit
onActivated: Qt.quit()
}
}

View File

@ -0,0 +1,35 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
/// Responsible for setup of user workflows after onboarding
Item {
id: root
/// Emited when everything is loaded and UX ready
signal ready()
Component.onCompleted: root.ready()
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
ColumnLayout {
id: mainLayout
anchors.fill: parent
RowLayout {}
Label {
Layout.alignment: Qt.AlignHCenter
text: "TODO MainView"
}
Button {
text: "Quit"
Layout.alignment: Qt.AlignHCenter
onClicked: Qt.quit()
}
RowLayout {}
}
}

View File

@ -0,0 +1,66 @@
import QtQml
import QtQuick
import QtQuick.Controls
import Status.Application
import Status.Onboarding
/// Has entry responsibility for the main workflows
Item {
id: root
implicitWidth: d.isViewLoaded ? d.loadedView.implicitWidth : 800
implicitHeight: d.isViewLoaded ? d.loadedView.implicitHeight : 600
QtObject {
id: d
readonly property bool isViewLoaded: contentLoader.status === Loader.Ready
readonly property Item loadedView: isViewLoaded ? contentLoader.item : null
}
Component {
id: onboardingViewComponent
OnboardingView {
onUserLoggedIn: {
splashScreenPopup.open()
contentLoader.sourceComponent = mainViewComponent
}
}
}
Component {
id: mainViewComponent
MainView {
onReady: splashScreenPopup.close()
}
}
Popup {
id: splashScreenPopup
onAboutToShow: splashScreenLoader.active = true
onClosed: splashScreenLoader.active = false
anchors.centerIn: Overlay.overlay
Loader {
id: splashScreenLoader
active: false
sourceComponent: SplashScreen {
id: splasScreen
onAnimationFinished: splashScreenPopup.close()
}
onStatusChanged: if(status === Loader.Ready) item.show()
}
background: Item {}
}
Loader {
id: contentLoader
anchors.fill: parent
sourceComponent: onboardingViewComponent
}
}

View File

@ -0,0 +1,59 @@
import QtQuick
import QtQuick.Layouts
import Qt.labs.settings
import Status.Application
/** Administrative scope
*/
Window {
id: root
minimumWidth: 900
minimumHeight: 600
Component.onCompleted: {
width: mainLayout.implicitWidth
height: mainLayout.implicitHeight
}
visible: true
title: qsTr(Qt.application.name)
flags: Qt.FramelessWindowHint
ColumnLayout {
id: mainLayout
anchors.fill: parent
// TODO: nav-bar?
// StatusAppNavBar {
// }
StatusContentView {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
ApplicationController {
id: appController
}
Settings {
property alias x: root.x
property alias y: root.y
property alias width: root.width
property alias height: root.height
// TODO: set this in non-deployment to the development "Status" folder
//fileName: `${appController.userSettings.directory}/appController.userSettings.fileName`
}
MainShortcuts {
window: root
enableHideWindow: true // TODO: Only if browser selected
}
}

View File

@ -0,0 +1,43 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import Status.Assets
SystemTrayIcon {
id: root
property bool production: true
signal showApplication()
visible: true
icon.source: {
if (production)
return Qt.platform.os === "osx" ? Resources.svg("status-logo-icon") : Resources.png("status-logo")
else
return Resources.svg("status-logo-dark")
}
icon.mask: false
menu: Menu {
MenuItem {
text: qsTr("Open Status")
onTriggered: root.showApplication()
}
MenuSeparator {
}
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit()
}
}
onActivated: function (reason) {
if (reason !== SystemTrayIcon.Context && Qt.platform.os !== "osx")
root.showApplication()
}
}

18
app/qml/main.qml Normal file
View File

@ -0,0 +1,18 @@
import QtQuick
import Status.Application
/**
QML entry point with minimal responsibilities
*/
StatusWindow {
id: root
StatusTrayIcon {
onShowApplication: {
root.show()
root.raise()
root.requestActivate()
}
}
}

15
app/res/CMakeLists.txt Normal file
View File

@ -0,0 +1,15 @@
# TODO workaround until Qt6 API is clarified
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/app.qrc
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
# TODO find out why it isn't working
#
#qt6_target_qml_sources(${PROJECT_NAME}
# RESOURCES
# qtquickcontrols2.conf
# PREFIX ""
#)

5
app/res/app.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>qtquickcontrols2.conf</file>
</qresource>
</RCC>

View File

@ -0,0 +1,2 @@
[Controls]
Style=Universal

View File

@ -0,0 +1,7 @@
#include "ApplicationController.h"
ApplicationController::ApplicationController(QObject *parent)
: QObject{parent}
{
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <QObject>
#include <QtQml/qqmlregistration.h>
/**
* @brief Responsible for providing general information and utility components
*/
class ApplicationController : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
explicit ApplicationController(QObject *parent = nullptr);
signals:
};

View File

@ -0,0 +1,13 @@
# Required by QML_ELEMENT
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.h
${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)

13
app/src/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
add_subdirectory(Application)

61
app/src/main.cpp Normal file
View File

@ -0,0 +1,61 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QLocale>
#include <QTranslator>
#include <Helpers/helpers.h>
#include <Helpers/logs.h>
#include <QDir>
#include <QDebug>
using namespace Status;
void setApplicationInformation(QGuiApplication& app);
int main(int argc, char *argv[])
{
//qInstallMessageHandler(Helpers::logFormatter);
QGuiApplication app(argc, argv);
setApplicationInformation(app);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages) {
const QString baseName = STRINGIFY(BUILD_PROJECT_NAME) + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName)) {
app.installTranslator(&translator);
break;
}
}
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/Status/Application/qml/main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
void setApplicationInformation(QGuiApplication& app) {
#if !defined BUILD_PROJECT_ORGANIZATION_NAME
static_assert(false, "Compile-time define missing: BUILD_PROJECT_ORGANIZATION_NAME")
#endif
app.setOrganizationName(STRINGIFY(BUILD_PROJECT_ORGANIZATION_NAME));
#if !defined BUILD_PROJECT_ORGANIZATION_DOMAIN
static_assert(false, "Compile-time define missing: BUILD_PROJECT_ORGANIZATION_DOMAIN")
#endif
app.setOrganizationDomain(STRINGIFY(BUILD_PROJECT_ORGANIZATION_DOMAIN));
#if !defined BUILD_PROJECT_APPLICATION_NAME
static_assert(false, "Compile-time define missing: BUILD_PROJECT_APPLICATION_NAME")
#endif
app.setApplicationName(STRINGIFY(BUILD_PROJECT_APPLICATION_NAME));
}

View File

@ -0,0 +1,5 @@
if(WIN32)
include(${CMAKE_CURRENT_LIST_DIR}/platform_specific/windows.cmake)
elseif(APPLE)
include(${CMAKE_CURRENT_LIST_DIR}/platform_specific/macos.cmake)
endif()

View File

@ -0,0 +1,11 @@
function(configure_app_os_specific TARGET_ARG URL_ORGANIZATION_NAME DOMAIN_ARG VERSION_MAJOR VERSION_MINOR VERSION_PATCH)
set_target_properties(${TARGET_ARG} PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER ${URL_ORGANIZATION_NAME}.${DOMAIN_ARG}
MACOSX_BUNDLE_BUNDLE_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MINOR}.${VERSION_PATCH}
# TODO: This breaks debugging QML imports
#MACOSX_BUNDLE TRUE
)
endfunction()

View File

@ -0,0 +1,7 @@
function(configure_app_os_specific)
set_target_properties(${PROJECT_NAME} PROPERTIES
WIN32_EXECUTABLE TRUE
)
endfunction()

View File

@ -0,0 +1,39 @@
# Global not theme dependent assets
# TODO: refactor it when switching to C++ code into Assets resource library linked or embed with the app
#
cmake_minimum_required(VERSION 3.21)
project(Assets
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Qml REQUIRED)
qt6_standard_project_setup()
set_source_files_properties(qml/Status/Assets/Resources.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Assets
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
qml/Status/Assets/Resources.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Assets/
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Qml
# TODO: refactor when moved to C++ code
UiAssets
)
# QtCreator needs this
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
list(REMOVE_DUPLICATES QML_IMPORT_PATH)

View File

@ -0,0 +1,17 @@
pragma Singleton
import QtQml
QtObject {
readonly property string assetPath: "qrc:/Status/UiAssets"
function svg(name) {
return assetPath + "/icons/" + name + ".svg";
}
function lottie(name) {
return assetPath + "/lottie/" + name + ".json";
}
function gif(name) {
return assetPath + "/gif/" + name + ".gif";
}
}

6
libs/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
add_subdirectory(Helpers)
# TODO: enable after adding content
#add_subdirectory(Core)
add_subdirectory(StatusQ)
add_subdirectory(Onboarding)
add_subdirectory(Assets)

32
libs/Core/CMakeLists.txt Normal file
View File

@ -0,0 +1,32 @@
# Base library. Expect most of the module libraries to depend on it
#
cmake_minimum_required(VERSION 3.21)
project(Core
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
qml/Status/Core/DevTest.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core
)
add_subdirectory(qml/Status/Core)
add_subdirectory(src)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
)

View File

@ -0,0 +1,4 @@
qt6_add_resources(${PROJECT_NAME} imageresources
PREFIX "/images"
FILES logo.png splashscreen.png
)

View File

@ -0,0 +1,14 @@
# Internally we use includes directly
# External clients have to explicitly use the module name
add_subdirectory(Core)
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Core
# TODO: Workaround to QML_ELEMENT Qt6
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/Core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@ -0,0 +1,5 @@
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)

View File

@ -0,0 +1,57 @@
# Light helpers library expected to be used by all other libraries
#
cmake_minimum_required(VERSION 3.21)
project(Helpers
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
add_library(${PROJECT_NAME} SHARED "")
# Setup configuration type (Debug/Release)
# Inspired by https://programmingrecluse.wordpress.com/2020/02/04/detect-debug-build-with-cmake/
if(CMAKE_CONFIGURATION_TYPES)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
elseif(NOT CMAKE_BUILD_TYPE)
message("Missing Build Type! Run cmake with:\n-DCMAKE_BUILD_TYPE=Debug|Release")
endif()
# Use by linker only
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
# Setup BUILD_DEBUG and BUILD_REALEASE if single configuration builds
if(CMAKE_BUILD_TYPE)
string(TOUPPER "${CMAKE_BUILD_TYPE}" _upper_build_type)
set(BUILD_${_upper_build_type} true)
endif()
set(BUILD_GENERATED_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/template/BuildConfiguration.h.in"
"${BUILD_GENERATED_DIRECTORY}/Helpers/BuildConfiguration.h"
@ONLY)
target_include_directories(${PROJECT_NAME}
PUBLIC
${BUILD_GENERATED_DIRECTORY}
PRIVATE
${BUILD_GENERATED_DIRECTORY}/Helpers
)
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/template/BuildConfiguration.h.in
)
add_subdirectory(src)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
)

View File

@ -0,0 +1,8 @@
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Helpers
)
add_subdirectory(Helpers)

View File

@ -0,0 +1,7 @@
target_sources(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/helpers.h
${CMAKE_CURRENT_SOURCE_DIR}/logs.h
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/logs.cpp
)

View File

@ -0,0 +1,19 @@
#include <Helpers/BuildConfiguration.h>
namespace Status::Helpers {
constexpr bool isDebugBuild()
{
#if defined BUILD_DEBUG
return true;
#else
return false;
#endif
}
}
#if !defined STRINGIFY
#define STRINGIFY(X) STRIFY(X)
#define STRIFY(X) #X
#endif

View File

@ -1,9 +1,23 @@
#include "helpers.h"
#include <QDateTime>
#include <QDebug>
#include <QString>
#include <iostream>
#include <Helpers/BuildConfiguration.h>
namespace Status::Helpers {
void logFormatter(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
// TODO: Refactor it into development-tools app
//if(isDebugBuild()) {
std::cout << msg.toLocal8Bit().data() << std::endl;
return;
//}
QByteArray localMsg = msg.toLocal8Bit();
const char* file = context.file ? context.file : "";
QByteArray function =
@ -22,5 +36,8 @@ void logFormatter(QtMsgType type, const QMessageLogContext& context, const QStri
case QtCriticalMsg: log = "\033[0;91mCRT \033[0m%s \033[1;91m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break;
case QtFatalMsg: log = "\033[0;31m!!! \033[0m%s \033[1m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break;
}
fprintf(stderr, log, timestamp.constData(), localMsg.constData(), file, context.line, function.constData());
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout,
log, timestamp.constData(), localMsg.constData(), file, context.line, function.constData());
}
} // namespace

View File

@ -4,4 +4,9 @@
#include <QDebug>
#include <QString>
namespace Status::Helpers {
/// Formats with colloring output if not a development build
void logFormatter(QtMsgType type, const QMessageLogContext& context, const QString& msg);
}

View File

@ -0,0 +1,19 @@
// Cross platform and environment build definition
#if defined(_MSC_VER)
// The microsoft compiler sets _DEBUG appropriately when targeting a debug build.
#if _DEBUG
#define BUILD_DEBUG 1
#else
#define BUILD_RELEASE 1
#endif
#elif defined(__apple_build_version__)
// Xcode might set DEBUG when targeting debug, maybe.
#if DEBUG
#define BUILD_DEBUG 1
#else
#define BUILD_RELEASE 1
#endif
#else
#cmakedefine BUILD_DEBUG 1
#cmakedefine BUILD_RELEASE 1
#endif

View File

@ -0,0 +1,32 @@
# Base library. Expect most of the module libraries to depend on it
#
cmake_minimum_required(VERSION 3.21)
project(Onboarding
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Onboarding
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
qml/Status/Onboarding/OnboardingView.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Onboarding/
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
)
# QtCreator needs this
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
list(REMOVE_DUPLICATES QML_IMPORT_PATH)

View File

@ -0,0 +1,30 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
signal userLoggedIn()
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
ColumnLayout {
id: mainLayout
anchors.fill: parent
RowLayout {}
Label {
Layout.alignment: Qt.AlignHCenter
text: "TODO OnboardingWorkflow"
}
Button {
text: "Done"
Layout.alignment: Qt.AlignHCenter
onClicked: root.userLoggedIn()
}
RowLayout {}
}
}

View File

@ -0,0 +1,31 @@
# Light helpers library expected to be used by all other libraries
#
cmake_minimum_required(VERSION 3.21)
project(StatusGoQt
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core REQUIRED)
qt6_standard_project_setup()
add_library(${PROJECT_NAME} SHARED "")
# Use by linker only
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
add_subdirectory(src)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Core
statusgo_shared
)
# Copy status-go lib close to the executable
# Temporary workaround; TODO: see a better alternative that doesn't depend on target order (dedicated dependencies dir?)
get_target_property(STATUSGO_LIBRARY_PATH statusgo_shared IMPORTED_LOCATION)
configure_file(${STATUSGO_LIBRARY_PATH} ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)

View File

@ -0,0 +1,14 @@
# Internally we use includes directly
# External clients have to explicitly use the module name
add_subdirectory(StatusGo)
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/StatusGo
# TODO: Workaround to QML_ELEMENT Qt6
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/StatusGo
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.21)
project(StatusQ
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status
)
add_subdirectory(qml/Status/Core/Theme)
add_subdirectory(tests)
# QtCreator needs this
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
list(REMOVE_DUPLICATES QML_IMPORT_PATH)

View File

@ -0,0 +1,28 @@
# Theme dependent components and assets
project(StatusQ_Core_Theme)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
set_source_files_properties(Style.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core.Theme
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusDarkPalette.qml
StatusDarkTheme.qml
StatusLightPalette.qml
StatusLightTheme.qml
StatusPalette.qml
StatusTheme.qml
Style.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core/Theme
)

View File

@ -0,0 +1,5 @@
import QtQuick
StatusPalette {
}

View File

@ -0,0 +1,6 @@
import QtQuick
StatusTheme {
readonly property string name: "dark"
readonly property StatusPalette palette: StatusDarkPalette {}
}

View File

@ -0,0 +1,5 @@
import QtQuick
StatusPalette {
}

View File

@ -0,0 +1,6 @@
import QtQuick
StatusTheme {
readonly property string name: "light"
readonly property StatusPalette palette: StatusLightPalette {}
}

View File

@ -0,0 +1,4 @@
import QtQuick
QtObject {
}

View File

@ -0,0 +1,6 @@
import QtQuick
Item {
required property string name
required property StatusPalette palette
}

View File

@ -0,0 +1,27 @@
pragma Singleton
import QtQuick
//import QtQuick.Controls.Universal
QtObject {
property StatusTheme theme: lightTheme
property StatusPalette palette: theme.palette
readonly property StatusTheme lightTheme: StatusLightTheme {}
readonly property StatusTheme darkTheme: StatusDarkTheme {}
property var changeTheme: function (palette, isCurrentSystemThemeDark) {
switch (theme) {
case Universal.Light:
theme = lightTheme;
break;
case Universal.Dark:
theme = darkTheme;
break;
case Universal.System:
current = isCurrentSystemThemeDark? darkTheme : lightTheme;
break;
default:
console.warning('Unknown theme. Valid themes are "light" and "dark"')
}
}
}

View File

@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.5)
project(TestStatusQ LANGUAGES CXX)
enable_testing()
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml QuickTest REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI StatusQ.TestHelpers
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/TestHelpers
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# no need to copy around qml test files for shadow builds - just set the respective define
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -input "${CMAKE_CURRENT_SOURCE_DIR}")
add_custom_target("Run_${PROJECT_NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
add_dependencies("Run_${PROJECT_NAME}" ${PROJECT_NAME})
# TODO: move this to a test helpers library
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(src)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::QuickTest
Qt6::Qml
Qt6::Quick
)

View File

@ -0,0 +1,26 @@
#include <QtQuickTest/quicktest.h>
#include <QQmlEngine>
#include "TestHelpers/MonitorQtOutput.h"
class TestSetup : public QObject
{
Q_OBJECT
public:
TestSetup() {}
public slots:
void qmlEngineAvailable(QQmlEngine *engine)
{
// TODO: Workaround until we make StatusQ a CMake library
engine->addImportPath("../src/");
engine->addImportPath("./qml/");
// TODO: Alternative to not yet supported QML_ELEMENT
qmlRegisterType<MonitorQtOutput>("StatusQ.TestHelpers", 0, 1, "MonitorQtOutput");
}
};
QUICK_TEST_MAIN_WITH_SETUP(TestControls, TestSetup)
#include "main.moc"

View File

@ -0,0 +1,23 @@
pragma Singleton
import QtQml
import QtTest
QtObject {
//> Simulate key and wait for side effects
function pressKeyAndWait(test, item, key) {
test.keyClick(key)
ensureRendered(test, item)
}
function ensureRendered(test, item) {
test.verify(test.waitForRendering(item, 1000))
}
function expectRendering(test, item) {
test.verify(test.isPolishScheduled(item))
test.verify(test.waitForRendering(item, 1000))
}
}

View File

@ -0,0 +1,24 @@
# Readme
## Developer instructions
CMake
```sh
cd ./tests/
cmake -B ./build/ -S .
cmake --build ./build/
ctest --test-dir ./build/
```
QtCreator
- Open the `./tests/CMakeLists.txt`
- Choose a QT kit to run the tests
- Set `%{sourceDir}/tests` as Working Directory for the TestStatusQ target
- In the *Test Results* panel choose Run All Tests or just run the *TestStatusQ* target
## TODO
- [ ] TestHelpers library
- [ ] Consolidate and integrate with https://github.com/status-im/desktop-ui-tests

View File

@ -0,0 +1,6 @@
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(TestHelpers)

View File

@ -0,0 +1,8 @@
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.h
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)

View File

@ -0,0 +1,68 @@
#include "MonitorQtOutput.h"
#include <stdio.h>
#include <stdlib.h>
std::weak_ptr<QString> MonitorQtOutput::m_qtMessageOutputForSharing;
std::mutex MonitorQtOutput::m_mutex;
QtMessageHandler MonitorQtOutput::m_previousHandler = nullptr;
MonitorQtOutput::MonitorQtOutput()
{
// Ensure only one instance registers a handler
// Warning: don't call QT's logger functions inside the critical section
std::unique_lock<std::mutex> localLock(m_mutex);
auto globalMsgOut = m_qtMessageOutputForSharing.lock();
auto prev = qInstallMessageHandler(qtMessageOutput);
if(prev != qtMessageOutput)
m_previousHandler = prev;
if(!globalMsgOut) {
m_thisMessageOutput = std::make_shared<QString>();
m_qtMessageOutputForSharing = m_thisMessageOutput;
}
else {
m_thisMessageOutput = globalMsgOut;
m_start = m_thisMessageOutput->length();
}
}
MonitorQtOutput::~MonitorQtOutput()
{
std::unique_lock<std::mutex> localLock(m_mutex);
if(m_thisMessageOutput.use_count() == 1) {
// Last instance, deregister the handler
qInstallMessageHandler(0);
m_thisMessageOutput.reset();
}
}
void
MonitorQtOutput::qtMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
std::unique_lock<std::mutex> localLock(m_mutex);
auto globalMsgOut = m_qtMessageOutputForSharing.lock();
assert(globalMsgOut != nullptr);
globalMsgOut->append(msg + '\n');
// Also reproduce the default output
m_previousHandler(type, context, msg);
}
QString
MonitorQtOutput::qtOuput()
{
std::unique_lock<std::mutex> localLock(m_mutex);
assert(m_thisMessageOutput->length() >= m_start);
return m_thisMessageOutput->right(m_thisMessageOutput->length() - m_start);
}
void
MonitorQtOutput::restartCapturing()
{
std::unique_lock<std::mutex> localLock(m_mutex);
// Ensure the messageHandler is installed. Foun to be reset at test initializaiton
auto prev = qInstallMessageHandler(qtMessageOutput);
if(prev != qtMessageOutput)
m_previousHandler = prev;
m_start = m_thisMessageOutput->length();
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <QQuickItem>
#include <QtGlobal>
#include <memory>
#include <mutex>
///
/// \brief Monitor output for tests and declaratively control message handler availability
///
/// The captured buffer is global and each instance has a reference to it and a start pointer
/// from its creation or last clear call
/// The first instance installs a QT message handler @see Qt::qInstallMessageHandler then
/// All other instances share the global buffer until the last instance goes out of scope and deregisters
/// from Qt's global message handler and destroyes the buffer
///
/// \todo Check that QML doesn't keep instance between test runs
///
class MonitorQtOutput : public QQuickItem
{
Q_OBJECT
public:
MonitorQtOutput();
~MonitorQtOutput();
/// Return captured output from the global buffer. That is from the instantiation or last `clear()` was called
Q_INVOKABLE QString qtOuput();
/// Reset buffer start after the last line. qtOutput won't return anything until new output is captured
Q_INVOKABLE void restartCapturing();
signals:
private:
static void qtMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
static QtMessageHandler m_previousHandler;
// Use it to keep track of qInstallMessageHandler call
static std::weak_ptr<QString> m_qtMessageOutputForSharing;
static std::mutex m_mutex;
std::shared_ptr<QString> m_thisMessageOutput;
int m_start = 0;
};

0
test/CMakeLists.txt Normal file
View File

View File

@ -0,0 +1,40 @@
# Temporary library not to duplicate resources
# TODO: refactor it when switching to C++ code into Assets resource library linked or embed with the app
#
cmake_minimum_required(VERSION 3.21)
project(UiAssets
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.UiAssets
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
RESOURCES
gif/status_splash_dark.gif
gif/status_splash_light.gif
icons/status-logo-icon.svg
icons/status-logo-dark.svg
png/traffic_lights/close.png
png/traffic_lights/close_pressed.png
png/traffic_lights/maximize.png
png/traffic_lights/maximize_pressed.png
png/traffic_lights/minimise.png
png/traffic_lights/minimise_pressed.png
png/status-logo.png
RESOURCE_PREFIX ""
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Qml
)

1
vendor/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/status-go)

11
vendor/conan-configs/apple-arm64.ini vendored Normal file
View File

@ -0,0 +1,11 @@
[settings]
compiler=apple-clang
compiler.version=13.1
compiler.libcxx=libc++
arch=armv8
os=Macos
build_type=Release
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++

11
vendor/conan-configs/apple-x86_64.ini vendored Normal file
View File

@ -0,0 +1,11 @@
[settings]
compiler=apple-clang
compiler.version=13.1
compiler.libcxx=libc++
arch=x86_64
os=Macos
build_type=Release
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 05073a96407ca40313663f7423195ce767c6d9c5
Subproject commit 23d745fe0a61501b847af9c54479d5fa763a51d0