├── .gitignore ├── src ├── GUI │ ├── Resources │ │ ├── qos │ │ │ ├── qos-0.hpp │ │ │ ├── qos-1.hpp │ │ │ ├── qos-2.hpp │ │ │ ├── qos-0.png │ │ │ ├── qos-1.png │ │ │ ├── qos-2.png │ │ │ ├── qos-2.cpp │ │ │ ├── qos-0.cpp │ │ │ └── qos-1.cpp │ │ ├── send │ │ │ ├── send-18x14.hpp │ │ │ ├── send-18x14.png │ │ │ ├── send-376x376.png │ │ │ └── send-18x14.cpp │ │ ├── pin │ │ │ ├── pinned-18x18.hpp │ │ │ ├── not-pinned-18x18.hpp │ │ │ ├── pin-217x217.png │ │ │ ├── pinned-18x18.png │ │ │ ├── not-pinned-18x18.png │ │ │ ├── pinned-18x18.cpp │ │ │ └── not-pinned-18x18.cpp │ │ ├── history │ │ │ ├── history-18x14.hpp │ │ │ ├── history-18x14.png │ │ │ ├── history-128x128.png │ │ │ └── history-18x14.cpp │ │ ├── preview │ │ │ ├── preview-18x14.hpp │ │ │ ├── preview-18x14.png │ │ │ ├── preview-128x128.png │ │ │ └── preview-18x14.cpp │ │ ├── messages │ │ │ ├── messages-18x14.hpp │ │ │ ├── messages-18x14.png │ │ │ ├── messages-128x128.png │ │ │ └── messages-18x14.cpp │ │ └── subscription │ │ │ ├── subscription-18x14.hpp │ │ │ ├── subscription-18x14.png │ │ │ ├── subscription-128x128.png │ │ │ └── subscription-18x14.cpp │ ├── Events │ │ ├── TopicCtrl.cpp │ │ ├── Edit.cpp │ │ ├── Profile.cpp │ │ ├── Recording.cpp │ │ ├── Subscription.cpp │ │ ├── Layout.cpp │ │ ├── Connection.cpp │ │ ├── TopicCtrl.hpp │ │ ├── Edit.hpp │ │ ├── Profile.hpp │ │ ├── Connection.hpp │ │ ├── Recording.hpp │ │ ├── Layout.hpp │ │ └── Subscription.hpp │ ├── Types │ │ ├── ClientOptions.hpp │ │ ├── ClientOptions.cpp │ │ ├── Subscription.hpp │ │ └── Subscription.cpp │ ├── Notifiers │ │ ├── Layouts.hpp │ │ └── Layouts.cpp │ ├── ArtProvider.hpp │ ├── Models │ │ ├── ProfilesWrapper.cpp │ │ ├── ProfilesWrapper.hpp │ │ ├── Messages.hpp │ │ ├── KnownTopics.hpp │ │ ├── Layouts.hpp │ │ ├── Subscriptions.hpp │ │ ├── History.hpp │ │ ├── Messages.cpp │ │ ├── Profiles.hpp │ │ ├── FsTree.hpp │ │ └── KnownTopics.cpp │ ├── Widgets │ │ ├── Layouts.hpp │ │ ├── TopicCtrl.hpp │ │ └── Edit.hpp │ ├── App.hpp │ ├── Tabs │ │ ├── Homepage.hpp │ │ └── Settings.hpp │ └── ArtProvider.cpp ├── Common │ ├── Version.hpp │ ├── Info.hpp │ ├── Env.hpp │ ├── Version.in.cpp │ ├── Env.Linux.cpp │ ├── Url.hpp │ ├── Info.in.cpp │ ├── Console.hpp │ ├── String.hpp │ ├── Env.Windows.cpp │ ├── Helpers.hpp │ ├── Log.hpp │ ├── Extract.hpp │ ├── String.cpp │ ├── XdgBaseDir.hpp │ ├── Extract.cpp │ ├── XdgBaseDir.Windows.cpp │ ├── Log.cpp │ ├── XdgBaseDir.cpp │ ├── XdgBaseDir.Linux.cpp │ ├── Console.cpp │ ├── Filesystem.hpp │ ├── Url.cpp │ └── Helpers.cpp ├── MQTT │ ├── QualityOfService.hpp │ ├── Message.hpp │ ├── Message.cpp │ ├── Subscription.hpp │ ├── Subscription.cpp │ ├── BrokerOptions.hpp │ └── Client.hpp ├── Arguments.hpp ├── main.cpp ├── transmitron.in.rc ├── Arguments.cpp └── CMakeLists.txt ├── resources ├── images │ ├── nsis │ │ ├── header.bmp │ │ └── welcomefinish.bmp │ ├── transmitron.ico │ └── CMakeLists.txt ├── debian │ ├── postinst.in.sh │ ├── transmitron.in.desktop │ ├── mimetypes-transmitron.xml │ └── CMakeLists.txt ├── icons │ ├── add.svg │ ├── CMakeLists.txt │ ├── arrow_forward.svg │ ├── clear_all.svg │ ├── send.svg │ ├── home.svg │ ├── music_note.svg │ ├── draft.svg │ ├── folder.svg │ ├── delete.svg │ ├── edit.svg │ ├── description.svg │ ├── content_copy.svg │ ├── note_add.svg │ ├── create_new_folder.svg │ ├── search.svg │ ├── inventory_2.svg │ ├── volume_up.svg │ ├── save.svg │ ├── notifications.svg │ ├── archive.svg │ ├── history.svg │ ├── notification_add.svg │ ├── task_alt.svg │ ├── notifications_off.svg │ ├── volume_off.svg │ ├── cancel.svg │ ├── hide_source.svg │ ├── person.svg │ ├── save_as.svg │ ├── person_add.svg │ ├── pending.svg │ ├── settings.svg │ └── palette.svg ├── cmake-installer.sh ├── update-alternatives-clang.sh └── windows │ └── FileAssociation.nsh ├── .github └── FUNDING.yml ├── docker ├── windows-x86-64 │ ├── compiler │ │ ├── default.ini │ │ ├── build.sh │ │ ├── windows.ini │ │ └── Dockerfile │ ├── debug │ │ ├── build.sh │ │ ├── macro-make │ │ └── Dockerfile │ └── release │ │ ├── build.sh │ │ ├── macro-make │ │ └── Dockerfile └── linux-x86-64 │ ├── compiler │ ├── default.ini │ ├── build.sh │ └── Dockerfile │ ├── debug │ ├── build.sh │ ├── macro-make │ └── Dockerfile │ └── release │ ├── build.sh │ ├── macro-make │ └── Dockerfile ├── cmake ├── install.cmake ├── clang-tidy.cmake └── git-version.cmake ├── conan └── conanfile.py ├── docs └── build.md ├── CMakeLists.txt ├── .drone.yml ├── .clang-tidy ├── README.md ├── .clang-format └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | /compile_commands.json 3 | /build* 4 | /tags 5 | /.cache 6 | /.container 7 | /.hidden* 8 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-0.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cQos0(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-1.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cQos1(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cQos2(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/qos/qos-0.png -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/qos/qos-1.png -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/qos/qos-2.png -------------------------------------------------------------------------------- /resources/images/nsis/header.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/resources/images/nsis/header.bmp -------------------------------------------------------------------------------- /resources/images/transmitron.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/resources/images/transmitron.ico -------------------------------------------------------------------------------- /src/GUI/Resources/send/send-18x14.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cSend18x14(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/pin/pinned-18x18.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cPinned18x18(); 5 | 6 | -------------------------------------------------------------------------------- /resources/images/nsis/welcomefinish.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/resources/images/nsis/welcomefinish.bmp -------------------------------------------------------------------------------- /src/GUI/Resources/history/history-18x14.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cHistory18x14(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/pin/not-pinned-18x18.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cNotPinned18x18(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/pin/pin-217x217.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/pin/pin-217x217.png -------------------------------------------------------------------------------- /src/GUI/Resources/pin/pinned-18x18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/pin/pinned-18x18.png -------------------------------------------------------------------------------- /src/GUI/Resources/preview/preview-18x14.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cPreview18x14(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/send/send-18x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/send/send-18x14.png -------------------------------------------------------------------------------- /src/GUI/Resources/send/send-376x376.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/send/send-376x376.png -------------------------------------------------------------------------------- /src/GUI/Resources/messages/messages-18x14.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cMessages18x14(); 5 | 6 | -------------------------------------------------------------------------------- /src/GUI/Resources/history/history-18x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/history/history-18x14.png -------------------------------------------------------------------------------- /src/GUI/Resources/pin/not-pinned-18x18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/pin/not-pinned-18x18.png -------------------------------------------------------------------------------- /src/GUI/Resources/preview/preview-18x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/preview/preview-18x14.png -------------------------------------------------------------------------------- /src/GUI/Resources/history/history-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/history/history-128x128.png -------------------------------------------------------------------------------- /src/GUI/Resources/messages/messages-18x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/messages/messages-18x14.png -------------------------------------------------------------------------------- /src/GUI/Resources/preview/preview-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/preview/preview-128x128.png -------------------------------------------------------------------------------- /src/GUI/Resources/subscription/subscription-18x14.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class wxBitmap; 4 | const wxBitmap *bin2cSubscription18x14(); 5 | 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rapatas 2 | custom: ["https://rapatas.com/monero.html", "https://www.paypal.com/donate/?hosted_button_id=UN5339YKVHB4G"] 3 | -------------------------------------------------------------------------------- /src/GUI/Resources/messages/messages-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/messages/messages-128x128.png -------------------------------------------------------------------------------- /src/GUI/Resources/subscription/subscription-18x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/subscription/subscription-18x14.png -------------------------------------------------------------------------------- /src/GUI/Resources/subscription/subscription-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapatas/transmitron/HEAD/src/GUI/Resources/subscription/subscription-128x128.png -------------------------------------------------------------------------------- /resources/debian/postinst.in.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | update-mime-database @CMAKE_INSTALL_PREFIX@/share/mime 4 | gtk-update-icon-cache @CMAKE_INSTALL_PREFIX@/share/icons/hicolor 5 | -------------------------------------------------------------------------------- /resources/icons/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB svgs ${CMAKE_CURRENT_LIST_DIR}/*.svg) 3 | 4 | install( 5 | FILES ${svgs} 6 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME} 7 | ) 8 | -------------------------------------------------------------------------------- /src/Common/Version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Rapatas::Transmitron::Common::Info { 4 | 5 | const char *getProjectVersion(); 6 | 7 | } // namespace Rapatas::Transmitron::Common::Info 8 | -------------------------------------------------------------------------------- /docker/windows-x86-64/compiler/default.ini: -------------------------------------------------------------------------------- 1 | [settings] 2 | arch=x86_64 3 | build_type=Debug 4 | compiler=gcc 5 | compiler.cppstd=gnu20 6 | compiler.libcxx=libstdc++11 7 | compiler.version=14 8 | os=Linux 9 | -------------------------------------------------------------------------------- /resources/icons/arrow_forward.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/clear_all.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Common/Info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Rapatas::Transmitron::Common::Info { 4 | 5 | const char *getProjectName(); 6 | const char *getProjectDescription(); 7 | 8 | } // namespace Rapatas::Transmitron::Common::Info 9 | -------------------------------------------------------------------------------- /src/Common/Env.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::Common::Env { 6 | 7 | std::string get(const std::string &name); 8 | 9 | } // namespace Rapatas::Transmitron::Common::Env 10 | -------------------------------------------------------------------------------- /src/Common/Version.in.cpp: -------------------------------------------------------------------------------- 1 | #include "Common/Version.hpp" 2 | 3 | constexpr const char *ProjectVersion = "@GIT_DESCRIBE@"; 4 | 5 | const char *Rapatas::Transmitron::Common::Info::getProjectVersion() { 6 | return ProjectVersion; 7 | } 8 | -------------------------------------------------------------------------------- /resources/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/music_note.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/GUI/Events/TopicCtrl.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/TopicCtrl.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::TOPICCTRL_RETURN, Events::TopicCtrl); 7 | // NOLINTEND(cert-err58-cpp) 8 | -------------------------------------------------------------------------------- /resources/icons/draft.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/MQTT/QualityOfService.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::MQTT { 6 | 7 | enum class QoS : uint8_t { 8 | AtLeastOnce = 0, 9 | AtMostOnce = 1, 10 | ExactlyOnce = 2 11 | }; 12 | 13 | } // namespace Rapatas::Transmitron::MQTT 14 | -------------------------------------------------------------------------------- /src/GUI/Events/Edit.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Edit.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::EDIT_PUBLISH, Events::Edit); 7 | wxDEFINE_EVENT(Events::EDIT_SAVE_MESSAGE, Events::Edit); 8 | // NOLINTEND(cert-err58-cpp) 9 | -------------------------------------------------------------------------------- /resources/icons/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/GUI/Events/Profile.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Profile.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::PROFILE_CREATE, Events::Profile); 7 | wxDEFINE_EVENT(Events::PROFILE_EDIT, Events::Profile); 8 | // NOLINTEND(cert-err58-cpp) 9 | -------------------------------------------------------------------------------- /src/GUI/Events/Recording.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Recording.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::RECORDING_SAVE, Events::Recording); 7 | wxDEFINE_EVENT(Events::RECORDING_OPEN, Events::Recording); 8 | // NOLINTEND(cert-err58-cpp) 9 | -------------------------------------------------------------------------------- /resources/icons/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/debian/transmitron.in.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | # Version=0.0.0 3 | Categories=Development;Internet; 4 | Comment=@PROGRAM_DESCRIPTION@ 5 | GenericName=Transmitron MQTT Client 6 | Exec=@TRANSMITRON_BIN_NAME@ 7 | Icon=@TRANSMITRON_BIN_NAME@ 8 | Name=@TRANSMITRON_NAME@ 9 | Terminal=false 10 | Type=Application 11 | -------------------------------------------------------------------------------- /resources/icons/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmake/install.cmake: -------------------------------------------------------------------------------- 1 | include(GNUInstallDirs) 2 | 3 | install( 4 | TARGETS 5 | ${PROJECT_NAME} 6 | RUNTIME 7 | DESTINATION bin 8 | ) 9 | 10 | add_subdirectory(${CMAKE_SOURCE_DIR}/resources/debian) 11 | add_subdirectory(${CMAKE_SOURCE_DIR}/resources/images) 12 | add_subdirectory(${CMAKE_SOURCE_DIR}/resources/icons) 13 | -------------------------------------------------------------------------------- /resources/icons/description.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/linux-x86-64/compiler/default.ini: -------------------------------------------------------------------------------- 1 | [settings] 2 | arch=x86_64 3 | build_type=Debug 4 | compiler=clang 5 | compiler.cppstd=gnu20 6 | compiler.libcxx=libstdc++11 7 | compiler.version=18 8 | os=Linux 9 | 10 | [buildenv] 11 | CC=/usr/bin/clang 12 | CXX=/usr/bin/clang++ 13 | 14 | [conf] 15 | tools.system.package_manager:mode=install 16 | -------------------------------------------------------------------------------- /resources/icons/content_copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/note_add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/linux-x86-64/debug/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../conan/conanfile.py" "$temp_dir/" 10 | 11 | docker build \ 12 | -t rapatas-transmitron-linux-x86-64-debug \ 13 | "$temp_dir" 14 | -------------------------------------------------------------------------------- /resources/debian/mimetypes-transmitron.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Transmitron history recording 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Common/Env.Linux.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | 3 | #include "Env.hpp" 4 | 5 | using namespace Rapatas::Transmitron::Common; 6 | 7 | std::string Env::get(const std::string &name) { 8 | // NOLINTNEXTLINE 9 | char *result = ::getenv(name.c_str()); 10 | if (result == nullptr) { return {}; } 11 | return result; 12 | } 13 | 14 | #endif // _WIN32 15 | -------------------------------------------------------------------------------- /docker/linux-x86-64/release/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../conan/conanfile.py" "$temp_dir/" 10 | 11 | docker build \ 12 | -t rapatas-transmitron-linux-x86-64-release \ 13 | "$temp_dir" 14 | -------------------------------------------------------------------------------- /docker/windows-x86-64/debug/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../conan/conanfile.py" "$temp_dir/" 10 | 11 | docker build \ 12 | -t rapatas-transmitron-windows-x86-64-debug \ 13 | "$temp_dir" 14 | -------------------------------------------------------------------------------- /resources/icons/create_new_folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/windows-x86-64/compiler/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../resources/cmake-installer.sh" "$temp_dir/" 10 | 11 | docker build \ 12 | -t rapatas-transmitron-windows-x86-64 \ 13 | "$temp_dir" 14 | -------------------------------------------------------------------------------- /docker/windows-x86-64/release/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../conan/conanfile.py" "$temp_dir/" 10 | 11 | docker build \ 12 | -t rapatas-transmitron-windows-x86-64-release \ 13 | "$temp_dir" 14 | -------------------------------------------------------------------------------- /src/Common/Url.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::Common::Url { 6 | 7 | std::string encode(const std::string &data); 8 | std::string decode(const std::string &data); 9 | 10 | inline bool encodable(char value); 11 | inline bool isHexChar(char value); 12 | 13 | } // namespace Rapatas::Transmitron::Common::Url 14 | -------------------------------------------------------------------------------- /resources/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/inventory_2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/volume_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/notifications.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Common/Info.in.cpp: -------------------------------------------------------------------------------- 1 | #include "Common/Info.hpp" 2 | 3 | constexpr const char *ProjectName = "@PROJECT_NAME@"; 4 | constexpr const char *ProjectDescription = "@PROJECT_DESCRIPTION@"; 5 | 6 | using namespace Rapatas::Transmitron; 7 | 8 | const char *Common::Info::getProjectName() { return ProjectName; } 9 | 10 | const char *Common::Info::getProjectDescription() { return ProjectDescription; } 11 | -------------------------------------------------------------------------------- /src/Common/Console.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | 6 | namespace Rapatas::Transmitron::Common::Console { 7 | 8 | bool redirect(); 9 | bool release(); 10 | void adjustBuffer(int16_t minLength); 11 | bool create(int16_t minLength); 12 | bool attachToParent(int16_t minLength); 13 | 14 | } // namespace Rapatas::Transmitron::Common::Console 15 | 16 | #endif // _WIN32 17 | -------------------------------------------------------------------------------- /resources/icons/archive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Common/String.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Rapatas::Transmitron::Common::String { 7 | 8 | std::vector split(const std::string &data, char delim); 9 | 10 | std::string replace( 11 | const std::string &str, 12 | const std::string &what, 13 | const std::string &with 14 | ); 15 | 16 | } // namespace Rapatas::Transmitron::Common::String 17 | -------------------------------------------------------------------------------- /src/GUI/Events/Subscription.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Subscription.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::SUBSCRIPTION_SUBSCRIBED, Events::Subscription); 7 | wxDEFINE_EVENT(Events::SUBSCRIPTION_UNSUBSCRIBED, Events::Subscription); 8 | wxDEFINE_EVENT(Events::SUBSCRIPTION_RECEIVED, Events::Subscription); 9 | // NOLINTEND(cert-err58-cpp) 10 | -------------------------------------------------------------------------------- /src/Arguments.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron { 6 | 7 | struct Arguments { 8 | bool exit = false; 9 | 10 | int argc{}; 11 | char **argv = nullptr; 12 | 13 | std::string profileName; 14 | std::string recordingFile; 15 | bool verbose = false; 16 | 17 | static Arguments handleArgs(int argc, char **argv); 18 | }; 19 | 20 | } // namespace Rapatas::Transmitron 21 | -------------------------------------------------------------------------------- /resources/icons/history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/linux-x86-64/compiler/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | script_dir=$(dirname "$0") 4 | temp_dir=$(mktemp -d) 5 | 6 | echo "Using temp dir $temp_dir" 7 | 8 | cp -r "$script_dir"/* "$temp_dir" 9 | cp "$script_dir/../../../resources/cmake-installer.sh" "$temp_dir/" 10 | cp "$script_dir/../../../resources/update-alternatives-clang.sh" "$temp_dir/" 11 | 12 | docker build \ 13 | -t rapatas-transmitron-linux-x86-64 \ 14 | "$temp_dir" 15 | -------------------------------------------------------------------------------- /cmake/clang-tidy.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(BUILD_WITH_TIDY OFF CACHE BOOL "") 3 | 4 | if (BUILD_WITH_TIDY) 5 | find_program(CLANG_TIDY_COMMAND NAMES "clang-tidy" REQUIRED) 6 | set(CMAKE_CXX_CLANG_TIDY "") 7 | list(APPEND CMAKE_CXX_CLANG_TIDY "clang-tidy") 8 | list(APPEND CMAKE_CXX_CLANG_TIDY "--config-file=${CMAKE_SOURCE_DIR}/.clang-tidy") 9 | list(APPEND CMAKE_CXX_CLANG_TIDY "-header-filter=${CMAKE_SOURCE_DIR}/src/.*.hpp") 10 | endif() 11 | 12 | -------------------------------------------------------------------------------- /docker/linux-x86-64/debug/macro-make: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | build_dir="build-$(cat /etc/compilername)" 4 | 5 | mkdir -p "/workspace/$build_dir" 6 | cd "/workspace/$build_dir" 7 | 8 | conan install \ 9 | --build=missing \ 10 | --profile:build=default \ 11 | --profile:host=default \ 12 | -of . \ 13 | ../conan 14 | 15 | cmake \ 16 | -DCMAKE_BUILD_TYPE=Debug \ 17 | -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \ 18 | .. 19 | 20 | make -j $(nproc) 21 | -------------------------------------------------------------------------------- /docker/linux-x86-64/release/macro-make: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | build_dir="build-$(cat /etc/compilername)" 4 | 5 | mkdir -p "/workspace/$build_dir" 6 | cd "/workspace/$build_dir" 7 | 8 | conan install \ 9 | --build=missing \ 10 | --profile:build=default \ 11 | --profile:host=default \ 12 | -of . \ 13 | ../conan 14 | 15 | cmake \ 16 | -DCMAKE_BUILD_TYPE=Release \ 17 | -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \ 18 | .. 19 | 20 | make -j $(nproc) 21 | -------------------------------------------------------------------------------- /resources/icons/notification_add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/task_alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/GUI/Events/Layout.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Layout.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::LAYOUT_SELECTED, Events::Layout); 7 | wxDEFINE_EVENT(Events::LAYOUT_ADDED, Events::Layout); 8 | wxDEFINE_EVENT(Events::LAYOUT_REMOVED, Events::Layout); 9 | wxDEFINE_EVENT(Events::LAYOUT_CHANGED, Events::Layout); 10 | wxDEFINE_EVENT(Events::LAYOUT_RESIZED, Events::Layout); 11 | // NOLINTEND(cert-err58-cpp) 12 | -------------------------------------------------------------------------------- /resources/icons/notifications_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/volume_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/cancel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/windows-x86-64/debug/macro-make: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | build_dir="build-$(cat /etc/compilername)" 4 | 5 | mkdir -p "/workspace/$build_dir" 6 | cd "/workspace/$build_dir" 7 | 8 | conan install \ 9 | --build=missing \ 10 | --profile:build=default \ 11 | --profile:host=windows \ 12 | -of . \ 13 | ../conan 14 | 15 | cmake \ 16 | -DCMAKE_BUILD_TYPE=Debug \ 17 | -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \ 18 | -DCMAKE_PREFIX_PATH=/usr/x86_64-w64-mingw32 \ 19 | .. 20 | 21 | make -j $(nproc) 22 | -------------------------------------------------------------------------------- /docker/windows-x86-64/release/macro-make: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | build_dir="build-$(cat /etc/compilername)" 4 | 5 | mkdir -p "/workspace/$build_dir" 6 | cd "/workspace/$build_dir" 7 | 8 | conan install \ 9 | --build=missing \ 10 | --profile:build=default \ 11 | --profile:host=windows \ 12 | -of . \ 13 | ../conan 14 | 15 | cmake \ 16 | -DCMAKE_BUILD_TYPE=Release \ 17 | -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \ 18 | -DCMAKE_PREFIX_PATH=/usr/x86_64-w64-mingw32 \ 19 | .. 20 | 21 | make -j $(nproc) 22 | -------------------------------------------------------------------------------- /src/GUI/Events/Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/Events/Connection.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI; 4 | 5 | // NOLINTBEGIN(cert-err58-cpp) 6 | wxDEFINE_EVENT(Events::CONNECTION_REQUESTED, Events::Connection); 7 | wxDEFINE_EVENT(Events::CONNECTION_CONNECTED, Events::Connection); 8 | wxDEFINE_EVENT(Events::CONNECTION_DISCONNECTED, Events::Connection); 9 | wxDEFINE_EVENT(Events::CONNECTION_FAILURE, Events::Connection); 10 | wxDEFINE_EVENT(Events::CONNECTION_LOST, Events::Connection); 11 | // NOLINTEND(cert-err58-cpp) 12 | -------------------------------------------------------------------------------- /resources/icons/hide_source.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/person.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icons/save_as.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Common/Env.Windows.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include "Env.hpp" 4 | 5 | #include 6 | #include 7 | 8 | using namespace Rapatas::Transmitron::Common; 9 | 10 | std::string Env::get(const std::string &name) { 11 | LPCSTR lpName = name.c_str(); 12 | const DWORD nSize = 4096; 13 | 14 | std::string buffer; 15 | buffer.resize(nSize); 16 | 17 | const auto size = GetEnvironmentVariableA(lpName, buffer.data(), nSize); 18 | if (size == 0) { return {}; } 19 | 20 | return {buffer.begin(), buffer.begin() + size}; 21 | } 22 | 23 | #endif // _WIN32 24 | -------------------------------------------------------------------------------- /resources/icons/person_add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/MQTT/Message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "QualityOfService.hpp" 9 | 10 | namespace Rapatas::Transmitron::MQTT { 11 | 12 | struct Message { 13 | std::string topic; 14 | std::string payload; 15 | MQTT::QoS qos = MQTT::QoS::AtLeastOnce; 16 | bool retained = false; 17 | std::chrono::system_clock::time_point timestamp; 18 | 19 | static Message fromJson(const nlohmann::json &data); 20 | [[nodiscard]] nlohmann::json toJson() const; 21 | }; 22 | 23 | } // namespace Rapatas::Transmitron::MQTT 24 | -------------------------------------------------------------------------------- /src/GUI/Events/TopicCtrl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::GUI::Events { 6 | 7 | class TopicCtrl; 8 | wxDECLARE_EVENT(TOPICCTRL_RETURN, TopicCtrl); 9 | 10 | // NOLINTNEXTLINE 11 | class TopicCtrl : public wxCommandEvent 12 | { 13 | public: 14 | 15 | explicit TopicCtrl(wxEventType commandType, int id = 0) : 16 | wxCommandEvent(commandType, id) // 17 | {} 18 | 19 | TopicCtrl(const TopicCtrl &event) = default; 20 | 21 | [[nodiscard]] wxEvent *Clone() const override { return new TopicCtrl(*this); } 22 | }; 23 | 24 | } // namespace Rapatas::Transmitron::GUI::Events 25 | -------------------------------------------------------------------------------- /resources/cmake-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | [ $(id -u) -ne 0 ] && SUDO=sudo 6 | 7 | version=3.28.1 8 | 9 | $SUDO apt-get install -y openssl libssl-dev build-essential wget 10 | 11 | cd /tmp/ 12 | 13 | wget https://github.com/Kitware/CMake/releases/download/v$version/cmake-$version.tar.gz 14 | tar -xzf cmake-$version.tar.gz 15 | 16 | cd cmake-$version 17 | # Enable openssl 18 | sed -i 's/cmake_options="-DCMAKE_BOOTSTRAP=1"/cmake_options="-DCMAKE_BOOTSTRAP=1 -DCMAKE_USE_OPENSSL=ON"/' bootstrap 19 | 20 | mkdir build && cd build 21 | ../bootstrap --parallel=$(nproc) 22 | make -j $(nproc) 23 | $SUDO make install 24 | -------------------------------------------------------------------------------- /src/GUI/Events/Edit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::GUI::Events { 6 | 7 | class Edit; 8 | wxDECLARE_EVENT(EDIT_PUBLISH, Edit); 9 | wxDECLARE_EVENT(EDIT_SAVE_MESSAGE, Edit); 10 | 11 | // NOLINTNEXTLINE 12 | class Edit : public wxCommandEvent 13 | { 14 | public: 15 | 16 | explicit Edit(wxEventType commandType, int id = 0) : 17 | wxCommandEvent(commandType, id) // 18 | {} 19 | 20 | Edit(const Edit &event) = default; 21 | 22 | [[nodiscard]] wxEvent *Clone() const override { return new Edit(*this); } 23 | }; 24 | 25 | } // namespace Rapatas::Transmitron::GUI::Events 26 | -------------------------------------------------------------------------------- /docker/windows-x86-64/compiler/windows.ini: -------------------------------------------------------------------------------- 1 | [settings] 2 | os=Windows 3 | arch=x86_64 4 | compiler=gcc 5 | compiler.version=10 6 | compiler.libcxx=libstdc++11 7 | build_type=Debug 8 | 9 | [buildenv] 10 | LDFLAGS=-static -static-libstdc++ 11 | 12 | AR=x86_64-w64-mingw32-ar 13 | AS=x86_64-w64-mingw32-as-posix 14 | CC=x86_64-w64-mingw32-gcc-posix 15 | CHOST=x86_64-w64-mingw32 16 | CXX=x86_64-w64-mingw32-g++-posix 17 | RANLIB=x86_64-w64-mingw32-ranlib 18 | RC=x86_64-w64-mingw32-windres 19 | STRIP=x86_64-w64-mingw32-strip-posix 20 | 21 | [conf] 22 | tools.build:compiler_executables = { "cpp": "x86_64-w64-mingw32-g++-posix", "c": "x86_64-w64-mingw32-gcc-posix" } 23 | 24 | -------------------------------------------------------------------------------- /resources/images/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | install( 2 | FILES 3 | ${CMAKE_SOURCE_DIR}/resources/images/transmitron.ico 4 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME} 5 | ) 6 | 7 | file( 8 | COPY ${CMAKE_SOURCE_DIR}/resources/images/transmitron.ico 9 | DESTINATION ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME} 10 | ) 11 | 12 | install( 13 | FILES 14 | transmitron.svg 15 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps 16 | ) 17 | 18 | install( 19 | FILES 20 | transmitron.svg 21 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes 22 | RENAME text-transmitron.svg 23 | ) 24 | -------------------------------------------------------------------------------- /src/GUI/Types/ClientOptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "GUI/Models/Layouts.hpp" 8 | 9 | namespace Rapatas::Transmitron::GUI::Types { 10 | 11 | class ClientOptions 12 | { 13 | public: 14 | 15 | explicit ClientOptions() = default; 16 | explicit ClientOptions(std::string layout); 17 | 18 | static ClientOptions fromJson(const nlohmann::json &data); 19 | [[nodiscard]] nlohmann::json toJson() const; 20 | 21 | [[nodiscard]] std::string getLayout() const; 22 | 23 | private: 24 | 25 | std::string mLayout{Models::Layouts::DefaultName}; 26 | }; 27 | 28 | } // namespace Rapatas::Transmitron::GUI::Types 29 | -------------------------------------------------------------------------------- /resources/icons/pending.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmake/git-version.cmake: -------------------------------------------------------------------------------- 1 | find_package(Git) 2 | 3 | if(GIT_EXECUTABLE) 4 | execute_process( 5 | COMMAND ${GIT_EXECUTABLE} describe --always --tags --dirty 6 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 7 | OUTPUT_VARIABLE GIT_DESCRIBE_VERSION 8 | RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE 9 | OUTPUT_STRIP_TRAILING_WHITESPACE 10 | ) 11 | # If no error took place, save the version 12 | if(NOT GIT_DESCRIBE_ERROR_CODE) 13 | set(GIT_DESCRIBE "${GIT_DESCRIBE_VERSION}") 14 | endif() 15 | endif() 16 | 17 | if(NOT DEFINED GIT_DESCRIBE) 18 | set(GIT_DESCRIBE 0.0.0-0-unknown) 19 | message(WARNING "Failed to determine GIT_DESCRIBE from Git tags. Using default version \"${GIT_DESCRIBE}\".") 20 | endif() 21 | -------------------------------------------------------------------------------- /src/Common/Helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Rapatas::Transmitron::Common::Helpers { 8 | 9 | wxColor colorFromNumber(size_t number); 10 | 11 | std::string timeToFilename( 12 | const std::chrono::system_clock::time_point ×tamp 13 | ); 14 | 15 | std::string timeToString( // 16 | const std::chrono::system_clock::time_point ×tamp 17 | ); 18 | 19 | std::string durationToString(const std::chrono::milliseconds &dur); 20 | 21 | std::chrono::system_clock::time_point stringToTime(const std::string &line); 22 | 23 | std::string hexDump(const std::vector &bytes, size_t columns); 24 | 25 | } // namespace Rapatas::Transmitron::Common::Helpers 26 | -------------------------------------------------------------------------------- /resources/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Common/Log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Rapatas::Transmitron::Common { 9 | 10 | class Log 11 | { 12 | public: 13 | 14 | virtual ~Log(); 15 | Log(const Log &other) = delete; 16 | Log(Log &&other) = delete; 17 | Log &operator=(const Log &other) = delete; 18 | Log &operator=(Log &&other) = delete; 19 | 20 | void initialize(bool verbose); 21 | 22 | static Log &instance(); 23 | static std::shared_ptr create(const std::string &name); 24 | 25 | private: 26 | 27 | Log() = default; 28 | 29 | std::vector mSinks; 30 | 31 | std::shared_ptr createPrivate(const std::string &name); 32 | }; 33 | 34 | } // namespace Rapatas::Transmitron::Common 35 | -------------------------------------------------------------------------------- /src/GUI/Types/ClientOptions.cpp: -------------------------------------------------------------------------------- 1 | #include "ClientOptions.hpp" 2 | 3 | #include "Common/Extract.hpp" 4 | #include "GUI/Models/Layouts.hpp" 5 | 6 | using namespace Rapatas::Transmitron; 7 | using namespace GUI::Types; 8 | 9 | ClientOptions::ClientOptions(std::string layout) : 10 | mLayout(std::move(layout)) // 11 | {} 12 | 13 | ClientOptions ClientOptions::fromJson(const nlohmann::json &data) { 14 | using namespace Common; 15 | 16 | const auto layoutOpt = extract(data, "layout"); 17 | const auto layout = layoutOpt.value_or( 18 | std::string(Models::Layouts::DefaultName) 19 | ); 20 | 21 | return ClientOptions{layout}; 22 | } 23 | 24 | nlohmann::json ClientOptions::toJson() const { return {{"layout", mLayout}}; } 25 | 26 | std::string ClientOptions::getLayout() const { return mLayout; } 27 | -------------------------------------------------------------------------------- /src/GUI/Notifiers/Layouts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Rapatas::Transmitron::GUI::Notifiers { 6 | 7 | class Layouts : // 8 | public wxEvtHandler, 9 | public wxDataViewModelNotifier 10 | { 11 | public: 12 | private: 13 | 14 | // wxDataViewModelNotifier interface. 15 | bool Cleared() override; 16 | bool ItemChanged(const wxDataViewItem &item) override; 17 | void Resort() override; 18 | bool ValueChanged(const wxDataViewItem &item, unsigned int col) override; 19 | bool ItemAdded( 20 | const wxDataViewItem &parent, 21 | const wxDataViewItem &item // 22 | ) override; 23 | bool ItemDeleted( 24 | const wxDataViewItem &parent, 25 | const wxDataViewItem &item // 26 | ) override; 27 | }; 28 | 29 | } // namespace Rapatas::Transmitron::GUI::Notifiers 30 | -------------------------------------------------------------------------------- /resources/icons/palette.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/MQTT/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "Message.hpp" 2 | 3 | #include "Common/Extract.hpp" 4 | 5 | using namespace Rapatas::Transmitron; 6 | using namespace MQTT; 7 | 8 | Message Message::fromJson(const nlohmann::json &data) { 9 | Message result; 10 | 11 | auto iqos = Common::extract(data, "qos").value_or(0); 12 | if (iqos > 2) { iqos = 0; } 13 | 14 | result.topic = Common::extract(data, "topic").value_or(""); 15 | result.payload = Common::extract(data, "payload").value_or(""); 16 | result.qos = static_cast(iqos); 17 | result.retained = Common::extract(data, "retained").value_or(false); 18 | 19 | return result; 20 | } 21 | 22 | nlohmann::json Message::toJson() const { 23 | return { 24 | {"topic", topic}, 25 | {"payload", payload}, 26 | {"qos", qos}, 27 | {"retained", retained}, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/Common/Extract.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Rapatas::Transmitron::Common { 8 | 9 | template 10 | std::optional extract(const nlohmann::json &data, const std::string &key); 11 | 12 | template <> 13 | std::optional extract( 14 | const nlohmann::json &data, 15 | const std::string &key 16 | ); 17 | 18 | template <> 19 | std::optional extract( 20 | const nlohmann::json &data, 21 | const std::string &key 22 | ); 23 | 24 | template <> 25 | std::optional extract( 26 | const nlohmann::json &data, 27 | const std::string &key 28 | ); 29 | 30 | template <> 31 | std::optional extract( 32 | const nlohmann::json &data, 33 | const std::string &key 34 | ); 35 | 36 | } // namespace Rapatas::Transmitron::Common 37 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Arguments.hpp" 5 | #include "GUI/App.hpp" 6 | 7 | using namespace Rapatas::Transmitron; 8 | 9 | int main(int argc, char **argv) { 10 | try { 11 | const auto args = Arguments::handleArgs(argc, argv); 12 | if (args.exit) { return 0; } 13 | 14 | auto *app = new GUI::App(args.verbose); 15 | wxApp::SetInstance(app); 16 | wxEntryStart(argc, argv); 17 | app->CallOnInit(); 18 | 19 | if (!args.profileName.empty()) { 20 | app->openProfile(args.profileName); 21 | } else if (!args.recordingFile.empty()) { 22 | app->openRecording(args.recordingFile); 23 | } 24 | 25 | app->OnRun(); 26 | app->OnExit(); 27 | wxEntryCleanup(); 28 | } catch (const std::exception &error) { 29 | fmt::print("{}\n", error.what()); 30 | return EXIT_FAILURE; 31 | } 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /docker/windows-x86-64/compiler/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | USER root 4 | WORKDIR /workspace 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | RUN apt-get update 8 | 9 | COPY ./cmake-installer.sh /workspace 10 | RUN /workspace/cmake-installer.sh 11 | 12 | RUN true \ 13 | && apt-get update \ 14 | && apt-get install -y \ 15 | mingw-w64 \ 16 | git \ 17 | nsis 18 | 19 | ENV PATH="${PATH}:/root/.local/bin" 20 | RUN true \ 21 | && apt-get install -y python3 pipx \ 22 | && pipx ensurepath \ 23 | && pipx install conan 24 | 25 | RUN true \ 26 | && apt-get update \ 27 | && apt-get install -y \ 28 | curl \ 29 | wget \ 30 | vim \ 31 | tree \ 32 | unzip 33 | 34 | RUN mkdir -p /root/.conan2/profiles 35 | COPY ./default.ini /root/.conan2/profiles/default 36 | COPY ./windows.ini /root/.conan2/profiles/windows 37 | 38 | RUN echo "rapatas-transmitron-windows-x86-64" > /etc/compilername 39 | -------------------------------------------------------------------------------- /src/GUI/Events/Profile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Rapatas::Transmitron::GUI::Events { 7 | 8 | class Profile; 9 | wxDECLARE_EVENT(PROFILE_CREATE, Profile); 10 | wxDECLARE_EVENT(PROFILE_EDIT, Profile); 11 | 12 | // NOLINTNEXTLINE 13 | class Profile : public wxCommandEvent 14 | { 15 | public: 16 | 17 | explicit Profile(wxEventType commandType, int id = 0) : 18 | wxCommandEvent(commandType, id) // 19 | {} 20 | 21 | Profile(const Profile &event) : 22 | wxCommandEvent(event) { 23 | this->setProfile(event.getProfile()); 24 | } 25 | 26 | [[nodiscard]] wxEvent *Clone() const override { return new Profile(*this); } 27 | 28 | [[nodiscard]] wxDataViewItem getProfile() const { return mProfile; } 29 | 30 | void setProfile(wxDataViewItem profile) { mProfile = profile; } 31 | 32 | private: 33 | 34 | wxDataViewItem mProfile; 35 | }; 36 | 37 | } // namespace Rapatas::Transmitron::GUI::Events 38 | -------------------------------------------------------------------------------- /resources/debian/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (UNIX AND NOT APPLE) 2 | 3 | set(DEB_POSTINST_PRE_CONF "${CMAKE_CURRENT_SOURCE_DIR}/postinst.in.sh") 4 | set(DEB_POSTINST_POST_CONF "${CMAKE_BINARY_DIR}/postinst") 5 | set(DEB_POSTINST_POST_CONF ${DEB_POSTINST_POST_CONF} PARENT_SCOPE) 6 | configure_file(${DEB_POSTINST_PRE_CONF} ${DEB_POSTINST_POST_CONF} @ONLY) 7 | 8 | install(CODE " 9 | include(CPack) 10 | configure_file(${DEB_POSTINST_PRE_CONF} ${DEB_POSTINST_POST_CONF} @ONLY) 11 | ") 12 | 13 | install( 14 | FILES 15 | mimetypes-transmitron.xml 16 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages/ 17 | ) 18 | 19 | set(DESKTOP_PRE_CONF "${CMAKE_CURRENT_SOURCE_DIR}/transmitron.in.desktop") 20 | set(DESKTOP_POST_CONF "${CMAKE_BINARY_DIR}/transmitron.desktop") 21 | configure_file(${DESKTOP_PRE_CONF} ${DESKTOP_POST_CONF} @ONLY) 22 | 23 | install( 24 | FILES 25 | ${DESKTOP_POST_CONF} 26 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications 27 | ) 28 | 29 | endif () 30 | -------------------------------------------------------------------------------- /src/transmitron.in.rc: -------------------------------------------------------------------------------- 1 | APPLICATION_ICON ICON "../resources/images/transmitron.ico" 2 | 3 | 1 VERSIONINFO 4 | FILEVERSION @TRANSMITRON_VERSION_MAJOR@,@TRANSMITRON_VERSION_MINOR@,@TRANSMITRON_VERSION_PATCH@,0 5 | PRODUCTVERSION @TRANSMITRON_VERSION_MAJOR@,@TRANSMITRON_VERSION_MINOR@,@TRANSMITRON_VERSION_PATCH@,0 6 | FILEOS 0x40004 7 | FILETYPE 1 8 | BEGIN 9 | BLOCK "StringFileInfo" 10 | BEGIN 11 | BLOCK "040904b0" 12 | BEGIN 13 | VALUE "Comments", "Published under the GNU GPL-3.0" 14 | VALUE "FileDescription", "@PROGRAM_DESCRIPTION@" 15 | VALUE "FileVersion", "@TRANSMITRON_VERSION@" 16 | VALUE "InternalName", "@PROJECT_NAME@" 17 | VALUE "OriginalFilename", "@PROJECT_NAME@.exe" 18 | VALUE "ProductName", "@PROJECT_NAME@" 19 | VALUE "ProductVersion", "@TRANSMITRON_VERSION@" 20 | END 21 | END 22 | BLOCK "VarFileInfo" 23 | BEGIN 24 | VALUE "Translation", 0x409, 1200 25 | END 26 | END 27 | 28 | #include "wx/msw/wx.rc" 29 | -------------------------------------------------------------------------------- /docker/linux-x86-64/release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rapatas-transmitron-linux-x86-64 2 | 3 | RUN true \ 4 | && mkdir wxWidgets \ 5 | && cd wxWidgets \ 6 | && wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.zip \ 7 | && unzip *.zip \ 8 | && mkdir buildgtk \ 9 | && cd buildgtk \ 10 | && ../configure \ 11 | --with-gtk \ 12 | --disable-mediactrl \ 13 | --disable-webview \ 14 | --without-libtiff \ 15 | --disable-shared \ 16 | --enable-stl \ 17 | && make -j $(nproc) \ 18 | && make install \ 19 | && cd /workspace \ 20 | && rm -rf * 21 | 22 | COPY ./conanfile.py /workspace 23 | RUN true \ 24 | && sed -i '/build_type/s/=.*$/=Release/' /root/.conan2/profiles/default \ 25 | && conan install . --build=missing \ 26 | && rm -rf * 27 | 28 | RUN true \ 29 | && apt-get install -y git \ 30 | && git config --global --add safe.directory /workspace 31 | 32 | RUN echo "rapatas-transmitron-linux-x86-64-release" > /etc/compilername 33 | COPY ./macro-make /usr/local/bin 34 | -------------------------------------------------------------------------------- /docker/linux-x86-64/debug/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rapatas-transmitron-linux-x86-64 2 | 3 | RUN true \ 4 | && mkdir wxWidgets \ 5 | && cd wxWidgets \ 6 | && wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.zip \ 7 | && unzip *.zip \ 8 | && mkdir buildgtk \ 9 | && cd buildgtk \ 10 | && ../configure \ 11 | --with-gtk \ 12 | --disable-mediactrl \ 13 | --disable-webview \ 14 | --without-libtiff \ 15 | --disable-shared \ 16 | --enable-stl \ 17 | --enable-debug \ 18 | && make -j $(nproc) \ 19 | && make install \ 20 | && cd /workspace \ 21 | && rm -rf * 22 | 23 | COPY ./conanfile.py /workspace 24 | RUN true \ 25 | && sed -i '/build_type/s/=.*$/=Debug/' /root/.conan2/profiles/default \ 26 | && conan install . --build=missing \ 27 | && rm -rf * 28 | 29 | RUN true \ 30 | && apt-get install -y git \ 31 | && git config --global --add safe.directory /workspace 32 | 33 | RUN echo "rapatas-transmitron-linux-x86-64-debug" > /etc/compilername 34 | COPY ./macro-make /usr/local/bin 35 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-2.cpp: -------------------------------------------------------------------------------- 1 | #include "qos-2.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cQos2() 7 | { 8 | constexpr size_t Length = 285; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\12\0\0\0\24\10\6\0\0\0\264\356\254V\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\1\36\7\16;-<\373\242\0\0\0\252IDAT(\317\225\3231\12\2A\14\5\320\267\203`%\336\301^\20\354\354\4\355<\203g\20<\217g\261\23-\204\355<\202\7\260TX\233\250\213\350\356\354\20743\311\344\377\374L\341\203>\226\230\241\302\36'\334j9\306qX}\3051\356\336/\375Jz\305\5CX5$U\270c\233\202S\23z\230\244\250jE\12uMx\340\14\203P\367\217\3435\4\277\307s\11\342u\21W\214\240\250\265\30`]\233[\211]\24H:\242\265u'1\213\14g6\11\363\14g\246\351Ky\2433\207\14g\312Nk\226\265\270E\356Wx\2`\313Z\27\260\311\224A\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/GUI/Resources/messages/messages-18x14.cpp: -------------------------------------------------------------------------------- 1 | #include "messages-18x14.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cMessages18x14() 7 | { 8 | constexpr size_t Length = 266; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\16\10\6\0\0\0\"\332L\267\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\3\357\0\0\3\357\1\347?\364\24\0\0\0\7tIME\7\345\2\15\7)\26\267*\227\23\0\0\0\227IDAT(\317\315\320\261\11\302P\20\207\361_$8\200\301\306\306\21\34\300i\\\307%\34#\240\215\205V\331\302V\254\15\242h\363\204\370H\36\2116~\360o\356\270\217\273\343\223\12\317\236\251\232\203Y$*0\326\217\33.m\215\3j\\{\246\0163 o\210\326\230\206\265\373\220\341\34\213\226X\30\316,\234w|\27v\3\236\34g\13\243 \272\373\236GS\24\263\301\274#\233\266\201\274C\264\307)\321[\305\305\256\215\212\304)\223\324\235\345\17\317.S\33\15\346\377D/\313\27E$\23%1h\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /docker/windows-x86-64/release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rapatas-transmitron-windows-x86-64 2 | 3 | RUN true \ 4 | && mkdir wxWidgets \ 5 | && cd wxWidgets \ 6 | && wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.zip \ 7 | && unzip *.zip \ 8 | && mkdir buildwin \ 9 | && cd buildwin \ 10 | && ../configure \ 11 | --prefix=/usr/x86_64-w64-mingw32 \ 12 | --host=x86_64-w64-mingw32 \ 13 | --build=x86_64-linux \ 14 | --disable-mediactrl \ 15 | --disable-webview \ 16 | --without-libtiff \ 17 | --disable-shared \ 18 | --enable-stl \ 19 | && make -j $(nproc) \ 20 | && make install \ 21 | && cd /workspace \ 22 | && rm -rf * 23 | 24 | COPY ./conanfile.py /workspace 25 | RUN true \ 26 | && sed -i '/build_type/s/=.*$/=Release/' /root/.conan2/profiles/windows \ 27 | && conan install /workspace --build=missing --profile:build=default --profile:host=windows 28 | 29 | RUN true \ 30 | && apt-get install -y git \ 31 | && git config --global --add safe.directory /workspace 32 | 33 | RUN echo "rapatas-transmitron-windows-x86-64-release" > /etc/compilername 34 | COPY ./macro-make /usr/local/bin 35 | -------------------------------------------------------------------------------- /src/Common/String.cpp: -------------------------------------------------------------------------------- 1 | #include "String.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace Rapatas::Transmitron::Common; 7 | 8 | std::vector String::split(const std::string &data, char delim) { 9 | const size_t segments = 0U // 10 | + (data.empty() ? 1U : 0U) 11 | + static_cast(std::count_if( 12 | std::begin(data), 13 | std::end(data), 14 | [&](char value) { return value == delim; } 15 | )); 16 | 17 | std::vector result; 18 | result.reserve(segments); 19 | 20 | std::stringstream sstream(data); 21 | std::string segment; 22 | while (std::getline(sstream, segment, delim)) { result.push_back(segment); } 23 | 24 | return result; 25 | } 26 | 27 | std::string String::replace( 28 | const std::string &str, 29 | const std::string &what, 30 | const std::string &with 31 | ) { 32 | if (what.empty()) { return str; } 33 | auto result = str; 34 | size_t start = 0; 35 | while ((start = result.find(what, start)) != std::string::npos) { 36 | result.replace(start, what.length(), with); 37 | start += with.length(); 38 | } 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /src/GUI/Resources/subscription/subscription-18x14.cpp: -------------------------------------------------------------------------------- 1 | #include "subscription-18x14.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cSubscription18x14() 7 | { 8 | constexpr size_t Length = 305; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\16\10\6\0\0\0\"\332L\267\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\3\261\0\0\3\261\1\365\203\355I\0\0\0\7tIME\7\345\2\15\10,\5E\277e\265\0\0\0\276IDAT(\317\235\3221N\2A\24\306\361\37\13\215\24$\320\31\16\240\241\304{\320jae\247'\240\341*\326z\36\270\2\1Kc\203\311\352R\370\226LH\\v\366K\276\314\233\314\373\376o2\31\3764\303!\352\25\252\13^E\357!\262'\275\3439\352\252\245\341\5o)\350\7Wx\310\0\335c\30YE\200\12\374\342F{\335\6\244HA#|'\3736*\"3JA_\261N2@\3434{~\203y\6\350\256\351\360#\343\261\367\377A\26\31\220\332\213s\310\0e\7P\31\331\223\326\35 \2657\22\332#\246\272i\13\275\206\206k\274\326?\27} 4 | 5 | #include 6 | #include 7 | 8 | #include "Common/Filesystem.hpp" 9 | 10 | namespace Rapatas::Transmitron::GUI { 11 | 12 | enum class Icon : uint8_t { 13 | Add, 14 | Archive, 15 | Cancel, 16 | Clear, 17 | Connect, 18 | Connected, 19 | Connecting, 20 | Copy, 21 | Delete, 22 | Disconnected, 23 | Edit, 24 | File, 25 | FileFull, 26 | Folder, 27 | History, 28 | Home, 29 | Mute, 30 | NewColor, 31 | NewDir, 32 | NewFile, 33 | NewProfile, 34 | Profile, 35 | Publish, 36 | Save, 37 | SaveAs, 38 | Search, 39 | Settings, 40 | Solo, 41 | Subscribe, 42 | Subscriptions, 43 | Unmute, 44 | Unsubscribe, 45 | }; 46 | 47 | class ArtProvider 48 | { 49 | public: 50 | 51 | ArtProvider(); 52 | 53 | void initialize(const Common::fs::path &base, wxSize size, bool dark); 54 | 55 | [[nodiscard]] const wxBitmap &bitmap(Icon icon) const; 56 | 57 | private: 58 | 59 | std::shared_ptr mLogger; 60 | std::map mIcons; 61 | wxBitmap mPlaceholder; 62 | }; 63 | 64 | } // namespace Rapatas::Transmitron::GUI 65 | -------------------------------------------------------------------------------- /docker/windows-x86-64/debug/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rapatas-transmitron-windows-x86-64 2 | 3 | COPY ./conanfile.py /workspace 4 | RUN true \ 5 | && sed -i '/build_type/s/=.*$/=Debug/' /root/.conan2/profiles/windows \ 6 | && conan install /workspace --build=missing --profile:build=default --profile:host=windows 7 | 8 | RUN true \ 9 | && mkdir wxWidgets \ 10 | && cd wxWidgets \ 11 | && wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.zip \ 12 | && unzip *.zip \ 13 | && mkdir buildwin \ 14 | && cd buildwin \ 15 | && ../configure \ 16 | --prefix=/usr/x86_64-w64-mingw32 \ 17 | --host=x86_64-w64-mingw32 \ 18 | --build=x86_64-linux \ 19 | --disable-mediactrl \ 20 | --disable-webview \ 21 | --without-libtiff \ 22 | --disable-shared \ 23 | --enable-stl \ 24 | --enable-debug \ 25 | && make -j $(nproc) \ 26 | && make install \ 27 | && cd /workspace \ 28 | && rm -rf * 29 | 30 | RUN true \ 31 | && apt-get install -y git \ 32 | && git config --global --add safe.directory /workspace 33 | 34 | RUN echo "rapatas-transmitron-windows-x86-64-debug" > /etc/compilername 35 | COPY ./macro-make /usr/local/bin 36 | -------------------------------------------------------------------------------- /src/GUI/Events/Connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Rapatas::Transmitron::GUI::Events { 7 | 8 | class Connection; 9 | wxDECLARE_EVENT(CONNECTION_REQUESTED, Connection); 10 | wxDECLARE_EVENT(CONNECTION_CONNECTED, Connection); 11 | wxDECLARE_EVENT(CONNECTION_DISCONNECTED, Connection); 12 | wxDECLARE_EVENT(CONNECTION_FAILURE, Connection); 13 | wxDECLARE_EVENT(CONNECTION_LOST, Connection); 14 | 15 | // NOLINTNEXTLINE 16 | class Connection : public wxCommandEvent 17 | { 18 | public: 19 | 20 | explicit Connection(wxEventType commandType, int id = 0) : 21 | wxCommandEvent(commandType, id) {} 22 | 23 | Connection(const Connection &event) : 24 | wxCommandEvent(event) // 25 | { 26 | this->setProfile(event.getProfile()); 27 | } 28 | 29 | [[nodiscard]] wxEvent *Clone() const override { 30 | return new Connection(*this); 31 | } 32 | 33 | [[nodiscard]] wxDataViewItem getProfile() const { return mProfile; } 34 | 35 | void setProfile(wxDataViewItem profile) { mProfile = profile; } 36 | 37 | private: 38 | 39 | wxDataViewItem mProfile; 40 | }; 41 | 42 | } // namespace Rapatas::Transmitron::GUI::Events 43 | -------------------------------------------------------------------------------- /conan/conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.tools.cmake import CMakeDeps, CMake, CMakeToolchain, cmake_layout 3 | 4 | class TransmitronConan(ConanFile): 5 | 6 | name = "Transmitron" 7 | version = "0.0.4" 8 | settings = [ 9 | "os", 10 | "compiler", 11 | "build_type", 12 | "arch" 13 | ] 14 | url = "https://github.com/Rapatas/transmitron" 15 | license = "GPL3" 16 | description = "MQTT client for desktop" 17 | 18 | options = { 19 | "shared": [True, False] 20 | } 21 | 22 | default_options = { 23 | "shared": False, 24 | "openssl/*:no_zlib": True, 25 | } 26 | 27 | def requirements(self): 28 | self.requires("paho-mqtt-cpp/1.4.0") 29 | self.requires("nlohmann_json/3.11.3") 30 | self.requires("tinyxml2/10.0.0") 31 | self.requires("fmt/11.0.2", force=True) 32 | self.requires("spdlog/1.14.1") 33 | self.requires("cli11/2.4.2") 34 | self.requires("date/3.0.3") 35 | 36 | def generate(self): 37 | cmake = CMakeDeps(self) 38 | cmake.generate() 39 | cmake = CMakeToolchain(self) 40 | cmake.generate() 41 | -------------------------------------------------------------------------------- /src/GUI/Events/Recording.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Rapatas::Transmitron::GUI::Events { 7 | 8 | class Recording; 9 | wxDECLARE_EVENT(RECORDING_SAVE, Recording); 10 | wxDECLARE_EVENT(RECORDING_OPEN, Recording); 11 | 12 | // NOLINTNEXTLINE 13 | class Recording : public wxCommandEvent 14 | { 15 | public: 16 | 17 | explicit Recording(wxEventType commandType, int id = 0) : 18 | wxCommandEvent(commandType, id) // 19 | {} 20 | 21 | Recording(const Recording &event) : 22 | wxCommandEvent(event) { 23 | this->setContents(event.getContents()); 24 | this->setName(event.getName()); 25 | } 26 | 27 | [[nodiscard]] wxEvent *Clone() const override { return new Recording(*this); } 28 | 29 | [[nodiscard]] std::string getContents() const { return mContents; } 30 | 31 | void setContents(const std::string &contents) { mContents = contents; } 32 | 33 | [[nodiscard]] wxString getName() const { return mName; } 34 | 35 | void setName(const wxString &name) { mName = name; } 36 | 37 | private: 38 | 39 | std::string mContents; 40 | wxString mName; 41 | }; 42 | 43 | } // namespace Rapatas::Transmitron::GUI::Events 44 | -------------------------------------------------------------------------------- /src/GUI/Resources/preview/preview-18x14.cpp: -------------------------------------------------------------------------------- 1 | #include "preview-18x14.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cPreview18x14() 7 | { 8 | constexpr size_t Length = 319; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\16\10\6\0\0\0\"\332L\267\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\3\261\0\0\3\261\1\365\203\355I\0\0\0\7tIME\7\345\2\15\7\"1\361\324\373\263\0\0\0\314IDAT(\317\245\322\261JCA\20\5\320\23_ \6-L\227.`a\221\302\306*\275\205\340\27$\26\371\267@\364\7\202\376\202\255\205\245h\33,\254\224\210\304g\363B\226Gfy\301\13\303\262s\231;\263;\227-\306\370B\31\304\2C\34\332\201\203\344\234\241+F\211\1\236rB-\2545\303\31\236#\241&(\253\206\252'\276\244\344\206(\360\211NFh\211>\316\223\2327|\354+\4\337xL\356?\270J\277\245\300*\263\261(VU\255v\320y\216\273\200\33\343\246\236\214\204Nq\235\341\374gkYD\23\275\342>\340N0j*t\211^\300]\3543\321\3n\3n\202i=\31\371\350\275\346\334\337\312\210\307;|u\204\365\37t{2\340-di\364\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/GUI/Resources/send/send-18x14.cpp: -------------------------------------------------------------------------------- 1 | #include "send-18x14.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cSend18x14() 7 | { 8 | constexpr size_t Length = 321; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\16\10\4\0\0\0\210\323\204<\0\0\0\2bKGD\0\377\207\217\314\277\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\2\15\7/\31q\317-\4\0\0\0\322IDAT(\317u\321\261.\4Q\24\6\340oY;\211Y\211\214\331b%\204R\357\0114\364\22\225\25\321\350\24\274\202\27\21\255B\241Sx\0\275l\224\250HV\245\220\25G1\273v2;{\222\333}\271\347\277\377e_\10a\340\301\236\31\223\373\34\261\20\372v$\323\250\345\272\204Bxt\254[e=\303\12\13\317.\245e\264\341e\12\205\360\345Ls\302n\375\324\262\360dW\253\0016mY\220H\245\332\243\223Z\325\265f\321Mq\335\267\206\266L.\227\313\254\310t4\315\231G\24\353\356f,\373\365\352\244 \353\336jI\337\205\345q\354\303\232\330\357\216d\223\227%\2252\207>\234W\253\354\30\2242\334;-w3\236\203\177re\333R\335\367\376\1\242~x\273v$?S\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/Common/XdgBaseDir.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Common/Filesystem.hpp" 6 | 7 | namespace Rapatas::Transmitron::Common { 8 | 9 | class XdgBaseDir 10 | { 11 | public: 12 | 13 | static Common::fs::path configHome(); 14 | static Common::fs::path cacheHome(); 15 | static Common::fs::path dataHome(); 16 | static Common::fs::path stateHome(); 17 | static std::vector dataDirs(); 18 | static std::vector configDirs(); 19 | 20 | private: 21 | 22 | static XdgBaseDir &instance(); 23 | XdgBaseDir(); 24 | 25 | Common::fs::path mHome; 26 | Common::fs::path mConfigHome; 27 | Common::fs::path mCacheHome; 28 | Common::fs::path mDataHome; 29 | Common::fs::path mStateHome; 30 | std::vector mDataDirs; 31 | std::vector mConfigDirs; 32 | 33 | static std::string readHome(); 34 | std::string readConfigHome(); 35 | std::string readDataHome(); 36 | std::string readCacheHome(); 37 | std::string readStateHome(); 38 | 39 | [[nodiscard]] std::vector readDataDirs() const; 40 | [[nodiscard]] std::vector readConfigDirs() const; 41 | }; 42 | 43 | } // namespace Rapatas::Transmitron::Common 44 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-0.cpp: -------------------------------------------------------------------------------- 1 | #include "qos-0.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cQos0() 7 | { 8 | constexpr size_t Length = 333; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\12\0\0\0\24\10\6\0\0\0\264\356\254V\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\1\36\7\16\30\217[\212\320\0\0\0\332IDAT(\317\225\323\275JCA\20\305\361\337]\13\13\11\276\203\220\322\17\0041\305\255\204t>\203\357 \276\216\317b%($\220\3044\201\0246\26\351\5\33\321\330\234\304\240\27\275\231jw\347\277g\346,\263\225\357\330E\0375\226\270\303#^7\30\207\30\343-\311\207\300\203\344\326J\343\34\326\350`\17= 4 | #include 5 | 6 | namespace Rapatas::Transmitron::GUI::Events { 7 | 8 | class Layout; 9 | wxDECLARE_EVENT(LAYOUT_SELECTED, Layout); 10 | wxDECLARE_EVENT(LAYOUT_ADDED, Layout); 11 | wxDECLARE_EVENT(LAYOUT_REMOVED, Layout); 12 | wxDECLARE_EVENT(LAYOUT_CHANGED, Layout); 13 | wxDECLARE_EVENT(LAYOUT_RESIZED, Layout); 14 | 15 | // NOLINTNEXTLINE 16 | class Layout : public wxCommandEvent 17 | { 18 | public: 19 | 20 | explicit Layout(wxEventType commandType, int id = 0) : 21 | wxCommandEvent(commandType, id) // 22 | {} 23 | 24 | Layout(const Layout &event) : 25 | wxCommandEvent(event) {} 26 | 27 | [[nodiscard]] wxEvent *Clone() const override { return new Layout(*this); } 28 | 29 | void setPerspective(std::string perspective) { 30 | mPerspective = std::move(perspective); 31 | } 32 | 33 | [[nodiscard]] std::string getPerspective() const { return mPerspective; } 34 | 35 | [[nodiscard]] wxDataViewItem getItem() const { return mItem; } 36 | 37 | void setItem(wxDataViewItem item) { mItem = item; } 38 | 39 | private: 40 | 41 | wxDataViewItem mItem; 42 | std::string mPerspective; 43 | }; 44 | 45 | } // namespace Rapatas::Transmitron::GUI::Events 46 | -------------------------------------------------------------------------------- /src/GUI/Notifiers/Layouts.cpp: -------------------------------------------------------------------------------- 1 | #include "Layouts.hpp" 2 | 3 | #include "GUI/Events/Layout.hpp" 4 | 5 | using namespace Rapatas::Transmitron; 6 | using namespace GUI::Notifiers; 7 | using namespace GUI; 8 | 9 | // wxDataViewModelNotifier interface { 10 | 11 | bool Layouts::Cleared() { return true; } 12 | 13 | bool Layouts::ItemChanged(const wxDataViewItem &item) { 14 | auto *event = new Events::Layout(Events::LAYOUT_CHANGED); 15 | event->setItem(item); 16 | wxQueueEvent(this, event); 17 | return true; 18 | } 19 | 20 | void Layouts::Resort() {} 21 | 22 | bool Layouts::ValueChanged( 23 | const wxDataViewItem & /* item */, 24 | unsigned int /* col */ 25 | ) { 26 | return true; 27 | } 28 | 29 | bool Layouts::ItemAdded( 30 | const wxDataViewItem & /* parent */, 31 | const wxDataViewItem &item 32 | ) { 33 | auto *event = new Events::Layout(Events::LAYOUT_ADDED); 34 | event->setItem(item); 35 | wxQueueEvent(this, event); 36 | return true; 37 | } 38 | 39 | bool Layouts::ItemDeleted( 40 | const wxDataViewItem & /* parent */, 41 | const wxDataViewItem & /* item */ 42 | ) { 43 | auto *event = new Events::Layout(Events::LAYOUT_REMOVED); 44 | wxQueueEvent(this, event); 45 | return true; 46 | } 47 | 48 | // wxDataViewModelNotifier interface } 49 | -------------------------------------------------------------------------------- /docs/build.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | The recommended build method is to use the provided docker images for [linux](../docker/linux-x86-64) and [windows](../docker/windows-x86-64). 4 | The debug and release variants will bake the dependencies in the docker image. 5 | 6 | ## Linux 7 | 8 | ```bash 9 | git clone https://github.com/Rapatas/transmitron.git 10 | cd transmitron 11 | ./docker/linux-x86-64/compiler/build-image.sh 12 | ./docker/linux-x86-64/release/build-image.sh 13 | docker run --rm -it -v $PWD:/workspace rapatas-transmitron-linux-x86-64-release bash 14 | ``` 15 | 16 | Then, in the container: 17 | 18 | ```bash 19 | macro-make 20 | cd build-rapatas-transmitron-linux-x86-64-release 21 | cpack 22 | ``` 23 | 24 | ## Windows 25 | 26 | **Transmitron is cross-compiled from Linux to windows. You will need a Linux 27 | host to run this image.** 28 | 29 | ```bash 30 | git clone https://github.com/Rapatas/transmitron.git 31 | cd transmitron 32 | ./docker/windows-x86-64/compiler/build-image.sh 33 | ./docker/windows-x86-64/release/build-image.sh 34 | docker run --rm -it -v $PWD:/workspace rapatas-transmitron-windows-x86-64-release bash 35 | ``` 36 | 37 | Then, in the container: 38 | 39 | ```bash 40 | macro-make 41 | cd build-rapatas-transmitron-windows-x86-64-release 42 | cpack 43 | ``` 44 | -------------------------------------------------------------------------------- /src/GUI/Resources/pin/pinned-18x18.cpp: -------------------------------------------------------------------------------- 1 | #include "pinned-18x18.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cPinned18x18() 7 | { 8 | constexpr size_t Length = 337; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\22\10\6\0\0\0V\316\216W\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\1\21\11\4\22\307\234\266\30\0\0\0\336IDAT8\313\245\322?K\202A\34\7\360\217\231\344\330\346\324\322\356\32\15\201\21MbB.\21\265\6\275\207\206\336\220\270\204.-\22\251\203\4\265\365\32\202hh\315ly\202\207\303\347\361\36\237\37\334pw\334\347\276\367\247*\276jx\3047\336\225\2506\226X\240[\6\272K\240\377\326\333\24z\11\240\5\316\212\0;\230\6\310F\311\236s\220%~\321\255\4\213\366p\200}|\340\26\207\21\233\315\323\235\36\276\326\354\236no\270\307)\266\323\320C\1d\222\27\257\212a\4\362\204z\314\345\16r\220Y\321?\223\365\3347Y\13\266V\214u\320\304x\305\\#6\311e\362/Z\250`\24$:\217A:\370\301u0\336O\220O\354\306&9\3168\376\30\27\353\220\223$\311\225\222\365\212\243\262\310\37'\201`\310U\216(\351\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/Arguments.cpp: -------------------------------------------------------------------------------- 1 | #include "Arguments.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "Common/Info.hpp" 7 | #include "Common/Version.hpp" 8 | 9 | using namespace Rapatas::Transmitron; 10 | 11 | Arguments Arguments::handleArgs(int argc, char **argv) { 12 | const auto projectInfo = fmt::format( 13 | "{} {}", 14 | Common::Info::getProjectName(), 15 | Common::Info::getProjectVersion() 16 | ); 17 | 18 | CLI::App args; 19 | 20 | Arguments result; 21 | result.argc = argc; 22 | result.argv = argv; 23 | 24 | // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) 25 | auto *versionOpt = args.set_version_flag("--version", projectInfo); 26 | (void)versionOpt; 27 | 28 | auto *verboseOpt = args.add_flag("--verbose", "Print logs"); 29 | 30 | args.add_option("--profile", result.profileName, "Profile to launch"); 31 | 32 | auto *recordingFileOpt = args.add_option( 33 | "recording,--recording", 34 | result.recordingFile, 35 | "History recording file to load" 36 | ); 37 | recordingFileOpt->option_text(".TMRC"); 38 | 39 | try { 40 | args.parse(argc, argv); 41 | } catch (const CLI::ParseError &event) { 42 | args.exit(event); 43 | result.exit = true; 44 | } 45 | 46 | result.verbose = !verboseOpt->empty(); 47 | 48 | return result; 49 | } 50 | -------------------------------------------------------------------------------- /src/GUI/Resources/qos/qos-1.cpp: -------------------------------------------------------------------------------- 1 | #include "qos-1.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cQos1() 7 | { 8 | constexpr size_t Length = 362; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\12\0\0\0\24\10\6\0\0\0\264\356\254V\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\1\36\7\16,\256\357~e\0\0\0\367IDAT(\317\205\323=J\3A\30\306\361\337.\201\24\22\274\203\2202\10\212\20d\33\301t\236\3013(^\307\263h\247\221\4\2146\11X\330\10I\37\260\211\2726\357\304\260\216\361\201awg\376\363\274\37;S\370Q\33\3T\250q\203!\226\33\214\36&x\217\305\373\200G\261\266v\232\304d\205\16v\320\307+f\330\205\263p\252\374V7\234\257\312\0\236\361\224\1_\"\332Q\31;j|\311\3533\275\14\2\354g\240\375\0/E\362\243H\274\333\200\336\260\210\202\327\355\231\205\363#\306\341\264\300\36\24\33\16\35\234\343 \276\307\270\306\12Z\231\274V\361\254s\225\3650\15(ua\205y\12\235B\0167\200\346\230\247bN\267@\311\371\242\304\211\355j\341\260lT\376\247J\334\375\303|\304\377\326\306\303\226\34\247\351\230\245\366\344\340a:\270E\346*\34\7t\33\233\227\360\15\333\240T\353M\343\351\5\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/GUI/Events/Subscription.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "MQTT/Message.hpp" 6 | #include "MQTT/Subscription.hpp" 7 | 8 | namespace Rapatas::Transmitron::GUI::Events { 9 | 10 | class Subscription; 11 | wxDECLARE_EVENT(SUBSCRIPTION_SUBSCRIBED, Subscription); 12 | wxDECLARE_EVENT(SUBSCRIPTION_UNSUBSCRIBED, Subscription); 13 | wxDECLARE_EVENT(SUBSCRIPTION_RECEIVED, Subscription); 14 | 15 | // NOLINTNEXTLINE 16 | class Subscription : public wxCommandEvent 17 | { 18 | public: 19 | 20 | explicit Subscription(wxEventType commandType, int id = 0) : 21 | wxCommandEvent(commandType, id) // 22 | {} 23 | 24 | Subscription(const Subscription &event) : 25 | wxCommandEvent(event) { 26 | this->setId(event.getSubscriptionId()); 27 | this->setMessage(event.getMessage()); 28 | } 29 | 30 | [[nodiscard]] wxEvent *Clone() const override { 31 | return new Subscription(*this); 32 | } 33 | 34 | [[nodiscard]] MQTT::Subscription::Id getSubscriptionId() const { 35 | return mId; 36 | } 37 | 38 | [[nodiscard]] MQTT::Message getMessage() const { return mMsg; } 39 | 40 | void setMessage(MQTT::Message msg) { mMsg = std::move(msg); } 41 | 42 | void setId(MQTT::Subscription::Id id) { mId = id; } 43 | 44 | private: 45 | 46 | MQTT::Message mMsg; 47 | MQTT::Subscription::Id mId = 0; 48 | }; 49 | 50 | } // namespace Rapatas::Transmitron::GUI::Events 51 | -------------------------------------------------------------------------------- /src/GUI/Types/Subscription.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "MQTT/QualityOfService.hpp" 9 | #include "MQTT/Subscription.hpp" 10 | 11 | namespace Rapatas::Transmitron::GUI::Types { 12 | 13 | class Subscription : 14 | public wxEvtHandler, // 15 | public MQTT::Subscription::Observer 16 | { 17 | public: 18 | 19 | explicit Subscription(const std::shared_ptr &sub); 20 | Subscription(MQTT::Subscription::Id id, std::string filter, MQTT::QoS qos); 21 | 22 | // MQTT::Subscription::Observer interface. 23 | void onSubscribed() override; 24 | void onUnsubscribed() override; 25 | void onMessage(const MQTT::Message &message) override; 26 | 27 | void setMuted(bool muted); 28 | void setColor(const wxColor &color); 29 | void unsubscribe(); 30 | 31 | [[nodiscard]] std::string getFilter() const; 32 | [[nodiscard]] wxColor getColor() const; 33 | [[nodiscard]] MQTT::QoS getQos() const; 34 | [[nodiscard]] bool getMuted() const; 35 | [[nodiscard]] size_t getId() const; 36 | 37 | private: 38 | 39 | std::shared_ptr mSub; 40 | bool mMuted; 41 | MQTT::Subscription::Id mId; 42 | std::string mFilter; 43 | MQTT::QoS mQos; 44 | wxColor mColor; 45 | 46 | static wxColor colorFromString(const std::string &data); 47 | }; 48 | 49 | } // namespace Rapatas::Transmitron::GUI::Types 50 | -------------------------------------------------------------------------------- /src/GUI/Resources/pin/not-pinned-18x18.cpp: -------------------------------------------------------------------------------- 1 | #include "not-pinned-18x18.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cNotPinned18x18() 7 | { 8 | constexpr size_t Length = 400; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\22\10\6\0\0\0V\316\216W\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\1\21\11\4$\10&#\201\0\0\1\35IDAT8\313\245\322O+Dq\24\306\361\317\230\301\354\330\261\261\264\263\306B\221d\241\31\312\202\374y\5^\206\215\225w\300;P\32I\222\5\26\344OB\310N\241(+f+l\216\232n\227\2311\317\346\376\3569\347\367\355\271\317=Y\265\253\31\273x\307\235\0064\206W|a<\331l\252\3\324\2135\24P\302\344\177\35]c%\316\305pV\254\7\320\212\23\234\"\213L\324\247\2V\263\263c\34\4$\251\321\237\3142\211F\27\372\321\215\27,\304`_<\277b.\217\245x\237\305C%d\22e\334b\31\373\330Kq\222\307e\314-b\4\271\312\201M\274\241%q1\223\200\354\340\350\257,\262\330\3023\332R\372y\\\3410\316UU\302#:\22\365\215\370{\251J[\310\11<\341,\341,\207\325z@\5\364\340\0367\350\214\372y\212\313_5\203O\14F\310\333\221Y6\366\251\246\345+\340\3\363\211\372zdVF{\255N\206~\371\374=LW\203\14\207\2239\15\352\2\3\215B\276\1\214\10<\226\272\32\311#\0\0\0\0IEND\256B`\202", Length); 10 | static const wxBitmap *bitmap = new wxBitmap(wxImage(mistream, wxBITMAP_TYPE_ANY), -1); 11 | return bitmap; 12 | } 13 | -------------------------------------------------------------------------------- /src/Common/Extract.cpp: -------------------------------------------------------------------------------- 1 | #include "Extract.hpp" 2 | 3 | using namespace Rapatas::Transmitron; 4 | 5 | template <> 6 | std::optional Common::extract( 7 | const nlohmann::json &data, 8 | const std::string &key 9 | ) { 10 | auto it = data.find(key); 11 | if (it == std::end(data) 12 | || it->type() != nlohmann::json::value_t::number_unsigned) { 13 | return std::nullopt; 14 | } 15 | return it->get(); 16 | } 17 | 18 | template <> 19 | std::optional Common::extract( 20 | const nlohmann::json &data, 21 | const std::string &key 22 | ) { 23 | auto it = data.find(key); 24 | if (it == std::end(data) 25 | || it->type() != nlohmann::json::value_t::number_unsigned) { 26 | return std::nullopt; 27 | } 28 | return it->get(); 29 | } 30 | 31 | template <> 32 | std::optional Common::extract( 33 | const nlohmann::json &data, 34 | const std::string &key 35 | ) { 36 | auto it = data.find(key); 37 | if (it == std::end(data) || it->type() != nlohmann::json::value_t::string) { 38 | return std::nullopt; 39 | } 40 | return it->get(); 41 | } 42 | 43 | template <> 44 | std::optional Common::extract( 45 | const nlohmann::json &data, 46 | const std::string &key 47 | ) { 48 | auto it = data.find(key); 49 | if (it == std::end(data) || it->type() != nlohmann::json::value_t::boolean) { 50 | return std::nullopt; 51 | } 52 | return it->get(); 53 | } 54 | -------------------------------------------------------------------------------- /docker/linux-x86-64/compiler/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | USER root 4 | WORKDIR /workspace 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | RUN apt-get update 8 | 9 | COPY ./cmake-installer.sh /workspace 10 | RUN /workspace/cmake-installer.sh 11 | 12 | RUN true \ 13 | && apt-get install -y lsb-release wget software-properties-common gnupg \ 14 | && wget https://apt.llvm.org/llvm.sh \ 15 | && chmod +x llvm.sh \ 16 | && ./llvm.sh 18 \ 17 | && apt-get install -y \ 18 | clang-tidy-18 \ 19 | clang-format-18 \ 20 | && update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang-18 180 --force \ 21 | && update-alternatives --install /usr/bin/cc cc /usr/bin/clang-18 180 --force 22 | 23 | COPY ./update-alternatives-clang.sh /workspace/update-alternatives-clang.sh 24 | RUN /workspace/update-alternatives-clang.sh 18 18 25 | 26 | RUN true \ 27 | && apt-get install -y python3.8 python3-pip \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 308 --force \ 29 | && pip3 install markupsafe==2.0.0 \ 30 | && pip3 install conan 31 | 32 | RUN true \ 33 | && apt-get update \ 34 | && apt-get install -y \ 35 | curl \ 36 | wget \ 37 | vim \ 38 | tree \ 39 | unzip \ 40 | libgtk-3-dev \ 41 | libgl1-mesa-dev \ 42 | libegl1-mesa-dev \ 43 | libpng-dev \ 44 | libjpeg-dev 45 | 46 | RUN mkdir -p /root/.conan2/profiles 47 | COPY ./default.ini /root/.conan2/profiles/default 48 | 49 | RUN echo "rapatas-transmitron-linux-x86-64" > /etc/compilername 50 | -------------------------------------------------------------------------------- /src/GUI/Models/ProfilesWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "ProfilesWrapper.hpp" 2 | 3 | using namespace Rapatas::Transmitron::GUI::Models; 4 | using Item = wxDataViewItem; 5 | 6 | ProfilesWrapper::ProfilesWrapper(const wxObjectDataPtr &profiles) : 7 | mProfiles(profiles) // 8 | {} 9 | 10 | unsigned ProfilesWrapper::GetColumnCount() const { 11 | return mProfiles->GetColumnCount(); 12 | } 13 | 14 | wxString ProfilesWrapper::GetColumnType(unsigned int col) const { 15 | return mProfiles->GetColumnType(col); 16 | } 17 | 18 | void ProfilesWrapper::GetValue( 19 | wxVariant &variant, 20 | const Item &item, 21 | unsigned int col // 22 | ) const { 23 | mProfiles->GetValue(variant, item, col); 24 | } 25 | 26 | bool ProfilesWrapper::SetValue( 27 | const wxVariant &value, 28 | const Item &item, 29 | unsigned int col // 30 | ) { 31 | (void)value; 32 | (void)item; 33 | (void)col; 34 | (void)this; 35 | return false; 36 | } 37 | 38 | bool ProfilesWrapper::IsEnabled( 39 | const Item &item, 40 | unsigned int col // 41 | ) const { 42 | (void)item; 43 | (void)col; 44 | (void)this; 45 | return true; 46 | } 47 | 48 | Item ProfilesWrapper::GetParent(const Item &item) const { 49 | return mProfiles->GetParent(item); 50 | } 51 | 52 | bool ProfilesWrapper::IsContainer(const Item &item) const { 53 | return mProfiles->IsContainer(item); 54 | } 55 | 56 | unsigned int ProfilesWrapper::GetChildren( 57 | const Item &parent, 58 | wxDataViewItemArray &array // 59 | ) const { 60 | return mProfiles->GetChildren(parent, array); 61 | } 62 | -------------------------------------------------------------------------------- /src/Common/XdgBaseDir.Windows.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include "XdgBaseDir.hpp" 4 | 5 | #include 6 | 7 | #include "Env.hpp" 8 | #include "String.hpp" 9 | 10 | using namespace Rapatas::Transmitron::Common; 11 | 12 | std::string XdgBaseDir::readHome() { return Env::get("USERPROFILE"); } 13 | 14 | std::string XdgBaseDir::readConfigHome() { return readDataHome(); } 15 | 16 | std::string XdgBaseDir::readDataHome() { 17 | auto var = Env::get("LOCALAPPDATA"); 18 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 19 | if (!var.empty()) { return var; } 20 | if (mHome.empty()) { return {}; } 21 | return fmt::format("{}/AppData/Local", mHome.string()); 22 | } 23 | 24 | std::string XdgBaseDir::readCacheHome() { 25 | auto var = Env::get("TEMP"); 26 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 27 | if (!var.empty()) { return var; } 28 | if (mHome.empty()) { return {}; } 29 | return fmt::format("{}/AppData/Local/Temp", mHome.string()); 30 | } 31 | 32 | std::string XdgBaseDir::readStateHome() { return readCacheHome(); } 33 | 34 | std::vector XdgBaseDir::readDataDirs() const { 35 | auto var = Env::get("APPDATA"); 36 | if (var.empty()) { var = fmt::format("{}/AppData/Roaming", mHome.string()); } 37 | auto dirs = String::split(var, ';'); 38 | for (auto &dir : dirs) { 39 | if (dir.back() == '/') { dir.pop_back(); } 40 | } 41 | return dirs; 42 | } 43 | 44 | std::vector XdgBaseDir::readConfigDirs() const { 45 | return readDataDirs(); 46 | } 47 | 48 | #endif // _WIN32 49 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(transmitron) 4 | 5 | set(PROGRAM_DESCRIPTION "Transmitron MQTT desktop client") 6 | 7 | set(TRANSMITRON_VERSION_MAJOR 1) 8 | set(TRANSMITRON_VERSION_MINOR 0) 9 | set(TRANSMITRON_VERSION_PATCH 1) 10 | set(TRANSMITRON_VERSION_POSTFIX "") 11 | 12 | string(CONCAT TRANSMITRON_VERSION 13 | ${TRANSMITRON_VERSION_MAJOR} 14 | "." 15 | ${TRANSMITRON_VERSION_MINOR} 16 | "." 17 | ${TRANSMITRON_VERSION_PATCH} 18 | ${TRANSMITRON_VERSION_POSTFIX} 19 | ) 20 | set(PROJECT_VERSION ${TRANSMITRON_VERSION}) 21 | 22 | set(TRANSMITRON_NAME "Transmitron") 23 | set(TRANSMITRON_BIN_NAME "transmitron") 24 | 25 | if (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64") 26 | set(TRANSMITRON_ARCH "amd64") 27 | elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86") 28 | set(TRANSMITRON_ARCH "i686") 29 | else() 30 | set(TRANSMITRON_ARCH ${CMAKE_SYSTEM_PROCESSOR}) 31 | endif() 32 | 33 | find_package(Threads REQUIRED) 34 | find_package(nlohmann_json REQUIRED) 35 | find_package(PahoMqttCpp REQUIRED) 36 | find_package(tinyxml2 REQUIRED) 37 | find_package(fmt REQUIRED) 38 | find_package(CLI11 REQUIRED) 39 | find_package(spdlog REQUIRED) 40 | find_package(date REQUIRED) 41 | find_package(wxWidgets REQUIRED 42 | COMPONENTS 43 | aui 44 | core 45 | propgrid 46 | richtext 47 | stc 48 | ) 49 | 50 | include(${CMAKE_SOURCE_DIR}/cmake/clang-tidy.cmake) 51 | include(${CMAKE_SOURCE_DIR}/cmake/git-version.cmake) 52 | 53 | add_subdirectory(src) 54 | 55 | include(${CMAKE_SOURCE_DIR}/cmake/install.cmake) 56 | include(${CMAKE_SOURCE_DIR}/cmake/cpack.cmake) 57 | -------------------------------------------------------------------------------- /src/GUI/Models/ProfilesWrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "GUI/Models/Profiles.hpp" 8 | 9 | namespace Rapatas::Transmitron::GUI::Models { 10 | 11 | class ProfilesWrapper : public wxDataViewModel 12 | { 13 | public: 14 | 15 | using Id = size_t; 16 | using Item = wxDataViewItem; 17 | 18 | explicit ProfilesWrapper(const wxObjectDataPtr &profiles); 19 | 20 | [[nodiscard]] unsigned GetColumnCount() const override; 21 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 22 | void GetValue( 23 | wxVariant &variant, 24 | const Item &item, 25 | unsigned int col // 26 | ) const override; 27 | bool SetValue( 28 | const wxVariant &value, 29 | const Item &item, 30 | unsigned int col // 31 | ) override; 32 | [[nodiscard]] bool IsEnabled( 33 | const Item &item, 34 | unsigned int col // 35 | ) const override; 36 | [[nodiscard]] Item GetParent(const Item &item) const override; 37 | [[nodiscard]] bool IsContainer(const Item &item) const override; 38 | unsigned int GetChildren( 39 | const Item &parent, 40 | wxDataViewItemArray &array // 41 | ) const override; 42 | 43 | private: 44 | 45 | struct Node { 46 | enum class Type : uint8_t { 47 | Folder, 48 | Payload, 49 | }; 50 | Id parent = 0; 51 | std::string name; 52 | Type type = Type::Folder; 53 | std::list children; 54 | }; 55 | 56 | const wxObjectDataPtr &mProfiles; 57 | std::map mNodes; 58 | }; 59 | 60 | } // namespace Rapatas::Transmitron::GUI::Models 61 | -------------------------------------------------------------------------------- /src/GUI/Models/Messages.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "Common/Filesystem.hpp" 10 | #include "GUI/ArtProvider.hpp" 11 | #include "GUI/Models/FsTree.hpp" 12 | #include "MQTT/Message.hpp" 13 | 14 | namespace Rapatas::Transmitron::GUI::Models { 15 | 16 | class Messages : public FsTree 17 | { 18 | public: 19 | 20 | enum Column : uint8_t { 21 | Name, 22 | Max 23 | }; 24 | 25 | explicit Messages(const ArtProvider &artProvider); 26 | 27 | wxDataViewItem createMessage( 28 | wxDataViewItem parent, 29 | const MQTT::Message &message 30 | ); 31 | wxDataViewItem replace(wxDataViewItem item, const MQTT::Message &message); 32 | 33 | bool load(const std::string &messagesDir); 34 | [[nodiscard]] MQTT::Message getMessage(wxDataViewItem item) const; 35 | [[nodiscard]] std::set getKnownTopics() const; 36 | 37 | private: 38 | 39 | struct Message : 40 | public FsTree::Leaf, 41 | public MQTT::Message // 42 | { 43 | explicit Message() = default; 44 | explicit Message(const MQTT::Message &msg); 45 | }; 46 | 47 | std::shared_ptr mLogger; 48 | std::string mMessagesDir; 49 | const ArtProvider &mArtProvider; 50 | 51 | [[nodiscard]] bool isLeaf(const Common::fs::directory_entry &entry 52 | ) const override; 53 | std::unique_ptr leafLoad(Id id, const Common::fs::path &path) override; 54 | void leafValue( 55 | Id id, 56 | wxDataViewIconText &value, 57 | unsigned int col // 58 | ) const override; 59 | bool leafSave(Id id) override; 60 | }; 61 | 62 | } // namespace Rapatas::Transmitron::GUI::Models 63 | -------------------------------------------------------------------------------- /src/GUI/Widgets/Layouts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "GUI/ArtProvider.hpp" 8 | #include "GUI/Events/Layout.hpp" 9 | #include "GUI/Models/Layouts.hpp" 10 | 11 | namespace Rapatas::Transmitron::GUI::Widgets { 12 | 13 | class Layouts : public wxPanel 14 | { 15 | public: 16 | 17 | explicit Layouts( 18 | wxWindow *parent, 19 | wxWindowID id, 20 | const wxObjectDataPtr &layoutsModel, 21 | wxAuiManager *auiMan, 22 | const ArtProvider &artProvider, 23 | int optionsHeight 24 | ); 25 | 26 | bool setSelectedLayout(const std::string &layoutName); 27 | 28 | private: 29 | 30 | int mOptionsHeight; 31 | 32 | std::shared_ptr mLogger; 33 | wxObjectDataPtr mLayoutsModel; 34 | wxDataViewItem mCurrentSelection; 35 | wxAuiManager *mAuiMan; 36 | wxFont mFont; 37 | const ArtProvider &mArtProvider; 38 | 39 | wxBoxSizer *mSizer = nullptr; 40 | wxButton *mSave = nullptr; 41 | wxComboBox *mLayoutsEdit = nullptr; 42 | wxComboBox *mLayoutsLocked = nullptr; 43 | 44 | void onLayoutSaveClicked(wxCommandEvent &event); 45 | void onLayoutEditEnter(wxCommandEvent &event); 46 | void onLayoutEditSelected(wxCommandEvent &event); 47 | void onLayoutEditLostFocus(wxFocusEvent &event); 48 | void onLayoutLockedSelected(wxCommandEvent &event); 49 | void onLayoutSelected(const std::string &value); 50 | void onLayoutAdded(Events::Layout &event); 51 | void onLayoutRemoved(Events::Layout &event); 52 | void onLayoutChanged(Events::Layout &event); 53 | 54 | [[nodiscard]] wxArrayString getNames() const; 55 | void resize(); 56 | }; 57 | 58 | } // namespace Rapatas::Transmitron::GUI::Widgets 59 | -------------------------------------------------------------------------------- /src/MQTT/Subscription.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "Client.hpp" 10 | 11 | namespace Rapatas::Transmitron::MQTT { 12 | 13 | struct Message; 14 | 15 | class Subscription : public std::enable_shared_from_this 16 | { 17 | public: 18 | 19 | using Id = size_t; 20 | 21 | enum class State { 22 | ToSubscribe, 23 | PendingSubscription, 24 | Subscribed, 25 | PendingUnsubscription, 26 | Unsubscribed 27 | }; 28 | 29 | struct Observer { 30 | Observer() = default; 31 | Observer(const Observer &other) = default; 32 | Observer(Observer &&other) = default; 33 | Observer &operator=(const Observer &other) = default; 34 | Observer &operator=(Observer &&other) = default; 35 | virtual ~Observer() = default; 36 | virtual void onSubscribed() = 0; 37 | virtual void onUnsubscribed() = 0; 38 | virtual void onMessage(const Message &message) = 0; 39 | }; 40 | 41 | explicit Subscription( 42 | Id id, 43 | std::string filter, 44 | QoS qos, 45 | std::shared_ptr client 46 | ); 47 | 48 | size_t attachObserver(Observer *observer); 49 | 50 | void unsubscribe(); 51 | 52 | void onMessage(const mqtt::const_message_ptr &msg); 53 | void onUnsubscribed(); 54 | void onSubscribed(); 55 | 56 | std::string getFilter() const; 57 | State getState() const; 58 | QoS getQos() const; 59 | Id getId() const; 60 | 61 | private: 62 | 63 | Id mId; 64 | std::string mFilter; 65 | QoS mQos; 66 | State mState = State::Unsubscribed; 67 | std::shared_ptr mClient; 68 | std::map mObservers; 69 | std::shared_ptr mLogger; 70 | 71 | friend class Client; // Can set the state. 72 | void setState(State newState); 73 | }; 74 | 75 | } // namespace Rapatas::Transmitron::MQTT 76 | -------------------------------------------------------------------------------- /src/Common/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | 10 | #include "Common/Console.hpp" 11 | #endif // _WIN32 12 | 13 | #ifndef NDEBUG 14 | const bool DEBUG = true; 15 | #else 16 | const bool DEBUG = false; 17 | #endif 18 | 19 | using namespace Rapatas::Transmitron::Common; 20 | 21 | using Level = spdlog::level::level_enum; 22 | 23 | Log &Log::instance() { 24 | static Log log; 25 | return log; 26 | } 27 | 28 | Log::~Log() { 29 | #ifdef _WIN32 30 | Console::release(); 31 | #else 32 | (void)this; 33 | #endif // _WIN32 34 | } 35 | 36 | void Log::initialize(bool verbose) { 37 | #ifdef _WIN32 38 | if (verbose) { Console::attachToParent(1024); } 39 | #endif // _WIN32 40 | 41 | const Level level = !verbose // 42 | ? Level::off 43 | : DEBUG // 44 | ? Level::trace 45 | : Level::info; 46 | (void)level; 47 | 48 | auto sink = std::make_shared(); 49 | sink->set_level(Level::trace); 50 | mSinks.push_back(sink); 51 | 52 | #ifdef _WIN32 53 | auto event = std::make_shared( 54 | "Transmitron" 55 | ); 56 | event->set_level(Level::warn); 57 | mSinks.push_back(event); 58 | #endif // _WIN32 59 | 60 | const std::string pattern{"[%Y-%m-%d %H:%M:%S.%e] %^[%l]%$ [%n] %v"}; 61 | spdlog::set_pattern(pattern); 62 | } 63 | 64 | std::shared_ptr Log::create(const std::string &name) { 65 | return instance().createPrivate(name); 66 | } 67 | 68 | std::shared_ptr Log::createPrivate(const std::string &name) { 69 | auto logger = spdlog::get(name); 70 | if (!logger) { 71 | logger = std::make_shared( 72 | name, 73 | mSinks.begin(), 74 | mSinks.end() 75 | ); 76 | spdlog::initialize_logger(logger); 77 | spdlog::cfg::load_env_levels(); 78 | } 79 | return logger; 80 | } 81 | -------------------------------------------------------------------------------- /src/GUI/App.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "GUI/ArtProvider.hpp" 8 | #include "GUI/Events/Profile.hpp" 9 | #include "GUI/Events/Recording.hpp" 10 | #include "GUI/Models/Layouts.hpp" 11 | #include "GUI/Models/Profiles.hpp" 12 | #include "GUI/Tabs/Settings.hpp" 13 | 14 | namespace Rapatas::Transmitron::GUI { 15 | 16 | class App : public wxApp 17 | { 18 | public: 19 | 20 | explicit App(bool verbose); 21 | 22 | bool openProfile(const std::string &profileName); 23 | void openRecording(const std::string &filename); 24 | 25 | bool OnInit() override; 26 | int FilterEvent(wxEvent &event) override; 27 | 28 | private: 29 | 30 | std::shared_ptr mLogger; 31 | size_t mCount = 0; 32 | wxFrame *mFrame = nullptr; 33 | wxAuiNotebook *mNote = nullptr; 34 | ArtProvider mArtProvider; 35 | bool mDarkMode = false; 36 | int mOptionsHeight = 0; 37 | int mIconHeight = 0; 38 | Tabs::Settings *mSettingsTab = nullptr; 39 | 40 | wxObjectDataPtr mProfilesModel; 41 | wxObjectDataPtr mLayoutsModel; 42 | 43 | wxFontInfo LabelFontInfo; 44 | 45 | void onPageClosing(wxBookCtrlEvent &event); 46 | void onPageSelected(wxBookCtrlEvent &event); 47 | void onKeyDownControlW(); 48 | void onKeyDownControlT(); 49 | void onRecordingSave(Events::Recording &event); 50 | void onRecordingOpen(Events::Recording &event); 51 | void onProfileCreate(Events::Profile &event); 52 | void onProfileEdit(Events::Profile &event); 53 | 54 | void createHomepageTab(size_t index); 55 | void createSettingsTab(); 56 | void setupIcon(); 57 | 58 | Common::fs::path createConfigDir(); 59 | Common::fs::path createCacheDir(); 60 | Common::fs::path getExecutablePath(); 61 | Common::fs::path getInstallPrefix(); 62 | 63 | void openProfile(wxDataViewItem item); 64 | 65 | void calculateOptions(); 66 | }; 67 | 68 | } // namespace Rapatas::Transmitron::GUI 69 | 70 | DECLARE_APP(Rapatas::Transmitron::GUI::App) 71 | -------------------------------------------------------------------------------- /src/GUI/Models/KnownTopics.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "Common/Filesystem.hpp" 10 | 11 | namespace Rapatas::Transmitron::GUI::Models { 12 | 13 | class KnownTopics : public wxDataViewVirtualListModel 14 | { 15 | public: 16 | 17 | enum class Column : uint8_t { 18 | Topic, 19 | Max 20 | }; 21 | 22 | explicit KnownTopics(); 23 | ~KnownTopics() override; 24 | 25 | KnownTopics(const KnownTopics &other) = delete; 26 | KnownTopics(KnownTopics &&other) = delete; 27 | KnownTopics &operator=(const KnownTopics &other) = delete; 28 | KnownTopics &operator=(KnownTopics &&other) = delete; 29 | 30 | bool load(const Common::fs::path &filepath); 31 | bool save(const Common::fs::path &filepath); 32 | 33 | void clear(); 34 | void setFilter(std::string filter); 35 | void append(std::string topic); 36 | void append(std::set topics); 37 | 38 | [[nodiscard]] const std::string &getTopic(const wxDataViewItem &item) const; 39 | [[nodiscard]] const std::string &getFilter() const; 40 | 41 | // wxDataViewVirtualListModel interface. 42 | [[nodiscard]] unsigned GetColumnCount() const override; 43 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 44 | [[nodiscard]] unsigned GetCount() const override; 45 | void GetValueByRow( 46 | wxVariant &variant, 47 | unsigned int row, 48 | unsigned int col // 49 | ) const override; 50 | bool GetAttrByRow( 51 | unsigned int row, 52 | unsigned int col, 53 | wxDataViewItemAttr &attr 54 | ) const override; 55 | bool SetValueByRow( 56 | const wxVariant &variant, 57 | unsigned int row, 58 | unsigned int col 59 | ) override; 60 | 61 | private: 62 | 63 | using Topics = std::set; 64 | 65 | std::shared_ptr mLogger; 66 | Common::fs::path mFilepath; 67 | Topics mTopics; 68 | std::vector mRemap; 69 | std::string mFilter; 70 | 71 | void remap(); 72 | void save(); 73 | }; 74 | 75 | } // namespace Rapatas::Transmitron::GUI::Models 76 | -------------------------------------------------------------------------------- /src/Common/XdgBaseDir.cpp: -------------------------------------------------------------------------------- 1 | #include "XdgBaseDir.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "Log.hpp" 8 | 9 | using namespace Rapatas::Transmitron; 10 | using namespace Rapatas::Transmitron::Common; 11 | 12 | XdgBaseDir::XdgBaseDir() { 13 | auto logger = Log::create("Common::XDG"); 14 | 15 | mHome = readHome(); 16 | mConfigHome = readConfigHome(); 17 | mCacheHome = readCacheHome(); 18 | mDataHome = readDataHome(); 19 | mStateHome = readStateHome(); 20 | 21 | mHome.make_preferred(); 22 | mConfigHome.make_preferred(); 23 | mCacheHome.make_preferred(); 24 | mDataHome.make_preferred(); 25 | mStateHome.make_preferred(); 26 | 27 | logger->debug("HOME: {}", mHome.string()); 28 | logger->debug("XDG_CONFIG_HOME: {}", mConfigHome.string()); 29 | logger->debug("XDG_DATA_HOME: {}", mDataHome.string()); 30 | logger->debug("XDG_CACHE_HOME: {}", mCacheHome.string()); 31 | logger->debug("XDG_STATE_HOME: {}", mStateHome.string()); 32 | 33 | logger->debug("XDG_DATA_DIRS:"); 34 | for (const auto &dir : readDataDirs()) { 35 | Common::fs::path path{dir}; 36 | path.make_preferred(); 37 | 38 | logger->debug(" - {}", path.string()); 39 | mDataDirs.emplace_back(path); 40 | } 41 | 42 | logger->debug("XDG_CONFIG_DIRS:"); 43 | for (const auto &dir : readConfigDirs()) { 44 | Common::fs::path path{dir}; 45 | path.make_preferred(); 46 | logger->debug(" - {}", path.string()); 47 | mConfigDirs.emplace_back(path); 48 | } 49 | } 50 | 51 | XdgBaseDir &XdgBaseDir::instance() { 52 | static XdgBaseDir result; 53 | return result; 54 | } 55 | 56 | Common::fs::path XdgBaseDir::configHome() { return instance().mConfigHome; } 57 | 58 | Common::fs::path XdgBaseDir::cacheHome() { return instance().mCacheHome; } 59 | 60 | Common::fs::path XdgBaseDir::dataHome() { return instance().mDataHome; } 61 | 62 | Common::fs::path XdgBaseDir::stateHome() { return instance().mStateHome; } 63 | 64 | std::vector XdgBaseDir::dataDirs() { 65 | return instance().mDataDirs; 66 | } 67 | 68 | std::vector XdgBaseDir::configDirs() { 69 | return instance().mConfigDirs; 70 | } 71 | -------------------------------------------------------------------------------- /src/GUI/Widgets/TopicCtrl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "GUI/Models/KnownTopics.hpp" 9 | 10 | namespace Rapatas::Transmitron::GUI::Widgets { 11 | 12 | class TopicCtrl : public wxTextCtrl 13 | { 14 | public: 15 | 16 | explicit TopicCtrl(wxWindow *parent, wxWindowID id); 17 | 18 | enum class ContextIDs : uint8_t { 19 | Copy 20 | }; 21 | 22 | void setReadOnly(bool readonly); 23 | void addKnownTopics( 24 | const wxObjectDataPtr &knownTopicsModel 25 | ); 26 | 27 | private: 28 | 29 | struct NotAllowedDropTarget : public wxDropTarget { 30 | // NOLINTNEXTLINE(readability-identifier-length) 31 | wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult defResult) override; 32 | }; 33 | 34 | wxFont mFont; 35 | bool mFakeSelection = false; 36 | bool mFirstClick = true; 37 | bool mReadOnly = false; 38 | long mCursonPosition = 0; 39 | wxPopupWindow *mAutoComplete = nullptr; 40 | wxDataViewCtrl *mAutoCompleteList = nullptr; 41 | wxDataViewColumn *mAutoCompleteTopic = nullptr; 42 | std::shared_ptr mLogger; 43 | wxObjectDataPtr mKnownTopicsModel; 44 | bool mPopupShow = false; 45 | 46 | void onContextSelected(wxCommandEvent &event); 47 | 48 | void onContext(wxContextMenuEvent &event); 49 | void onDoubleClicked(wxMouseEvent &event); 50 | void onKeyDown(wxKeyEvent &event); 51 | void onKeyUp(wxKeyEvent &event); 52 | void onChar(wxKeyEvent &event); 53 | void onLeftDown(wxMouseEvent &event); 54 | void onLeftUp(wxMouseEvent &event); 55 | void onLostFocus(wxFocusEvent &event); 56 | void onRight(wxMouseEvent &event); 57 | void onRightClicked(wxMouseEvent &event); 58 | void onValueChanged(wxCommandEvent &event); 59 | void onCompletionDoubleClicked(wxDataViewEvent &event); 60 | void onCompletionLeftUp(wxMouseEvent &event); 61 | 62 | void popupHide(); 63 | void popupShow(); 64 | void popupRefresh(); 65 | void autoCompleteUp(); 66 | void autoCompleteDown(); 67 | void autoCompleteSelect(); 68 | }; 69 | 70 | } // namespace Rapatas::Transmitron::GUI::Widgets 71 | -------------------------------------------------------------------------------- /src/GUI/Types/Subscription.cpp: -------------------------------------------------------------------------------- 1 | #include "Subscription.hpp" 2 | 3 | #include "Common/Helpers.hpp" 4 | #include "GUI/Events/Subscription.hpp" 5 | 6 | using namespace Rapatas::Transmitron; 7 | using namespace GUI::Types; 8 | using namespace GUI; 9 | 10 | Subscription::Subscription(const std::shared_ptr &sub) : 11 | mSub(sub), 12 | mMuted(false), 13 | mId(mSub->getId()), 14 | mFilter(mSub->getFilter()), 15 | mQos(mSub->getQos()), 16 | mColor(colorFromString(mFilter)) // 17 | { 18 | sub->attachObserver(this); 19 | } 20 | 21 | Subscription::Subscription( 22 | MQTT::Subscription::Id id, 23 | std::string filter, 24 | MQTT::QoS qos 25 | ) : 26 | mSub(nullptr), 27 | mMuted(false), 28 | mId(id), 29 | mFilter(std::move(filter)), 30 | mQos(qos), 31 | mColor(colorFromString(mFilter)) // 32 | {} 33 | 34 | void Subscription::onSubscribed() { 35 | auto *event = new Events::Subscription(Events::SUBSCRIPTION_SUBSCRIBED); 36 | event->setId(mId); 37 | wxQueueEvent(this, event); 38 | } 39 | 40 | void Subscription::onUnsubscribed() { 41 | auto *event = new Events::Subscription(Events::SUBSCRIPTION_UNSUBSCRIBED); 42 | event->setId(mId); 43 | wxQueueEvent(this, event); 44 | } 45 | 46 | void Subscription::onMessage(const MQTT::Message &message) { 47 | auto *event = new Events::Subscription(Events::SUBSCRIPTION_RECEIVED); 48 | event->setMessage(message); 49 | event->setId(mId); 50 | wxQueueEvent(this, event); 51 | } 52 | 53 | size_t Subscription::getId() const { return mId; } 54 | 55 | void Subscription::setMuted(bool muted) { mMuted = muted; } 56 | 57 | void Subscription::setColor(const wxColor &color) { mColor = color; } 58 | 59 | void Subscription::unsubscribe() { 60 | if (mSub == nullptr) { return; } 61 | mSub->unsubscribe(); 62 | } 63 | 64 | std::string Subscription::getFilter() const { return mFilter; } 65 | 66 | wxColor Subscription::getColor() const { return mColor; } 67 | 68 | MQTT::QoS Subscription::getQos() const { return mQos; } 69 | 70 | bool Subscription::getMuted() const { return mMuted; } 71 | 72 | wxColor Subscription::colorFromString(const std::string &data) { 73 | const size_t hash = std::hash{}(data); 74 | return Common::Helpers::colorFromNumber(hash); 75 | } 76 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | platform: 4 | os: linux 5 | arch: amd64 6 | 7 | clone: 8 | disable: true 9 | 10 | steps: 11 | 12 | - name: Clone 13 | image: alpine/git 14 | commands: 15 | - git clone https://github.com/rapatas/transmitron.git . 16 | - git checkout ${DRONE_COMMIT} 17 | 18 | - name: Build Linux x86_64 19 | image: rapatas-transmitron-linux-x86-64-release 20 | pull: if-not-exists 21 | user: 0 22 | commands: 23 | - mkdir build-rapatas-transmitron-linux-x86-64-release 24 | - cd build-rapatas-transmitron-linux-x86-64-release 25 | - conan install --build=missing --profile:build=default --profile:host=default -of ./ ../conan 26 | - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake .. 27 | - make -j 6 28 | - make package 29 | 30 | - name: Build Windows x86_64 31 | image: rapatas-transmitron-windows-x86-64-release 32 | pull: if-not-exists 33 | user: 0 34 | commands: 35 | - mkdir build-rapatas-transmitron-windows-x86-64-release 36 | - cd build-rapatas-transmitron-windows-x86-64-release 37 | - conan install --build=missing --profile:build=default --profile:host=windows -of ./ ../conan 38 | - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_PREFIX_PATH=/usr/x86_64-w64-mingw32 .. 39 | - make -j 6 40 | - make package 41 | 42 | - name: Release Notes 43 | image: ubuntu:22.04 44 | pull: if-not-exists 45 | commands: 46 | - echo "$(awk -v section=[${DRONE_TAG}] '$2==section{ f=1; next }; /^## /{ f=0; next }; f==1{ print $0 };' ./CHANGELOG.md)" > release_notes_${DRONE_TAG}.md 47 | - cat release_notes_${DRONE_TAG}.md 48 | when: 49 | event: tag 50 | 51 | - name: Release 52 | image: plugins/github-release 53 | settings: 54 | title: "transmitron ${DRONE_TAG}" 55 | note: ./release_notes_${DRONE_TAG}.md 56 | api_key: 57 | from_secret: drone.io-release 58 | files: 59 | - build-*/transmitron_*_*.deb 60 | - build-*/transmitron_*_*.exe 61 | checksum: 62 | - md5 63 | - sha1 64 | - sha256 65 | - sha512 66 | - adler32 67 | - crc32 68 | when: 69 | event: tag 70 | -------------------------------------------------------------------------------- /src/GUI/Resources/history/history-18x14.cpp: -------------------------------------------------------------------------------- 1 | #include "history-18x14.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | const wxBitmap *bin2cHistory18x14() 7 | { 8 | constexpr size_t Length = 720; 9 | static wxMemoryInputStream mistream("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\0\22\0\0\0\16\10\6\0\0\0\"\332L\267\0\0\0\6bKGD\0\377\0\377\0\377\240\275\247\223\0\0\0\11pHYs\0\0\13\23\0\0\13\23\1\0\232\234\30\0\0\0\7tIME\7\345\2\15\6%'K\203\262\22\0\0\2]IDAT(\317\205\221MH\224Q\24\206\237s\325A\322,\303\230\271\367Sg \3046\341\246]\204 \10R\213\250V\366\3-\202\240\332'H5\20\342*\202\210~\10\241\240 \250UDDR\221\264\255\26A\233\17t\204\271\367\233!\322\310\37r\320\357\264h&\304E\275\273\363s\237s\316{%\212\242N\357\375\342\340\340 q\34\17\250\352C`\0\230\3\336\210\310\7U]\4\262\300\0200\10X`FD\316\204\20\346r\271\34\342\234{i\2149\225\246\351.\340\225\252\346\201{\231L\346\362\374\374\374\"[\24EQ>M\323\353\300q\340\2131f\330{_\25k\255\212\3104\360MUG\201\253I\222\\\3p\316e\0\7,\205\20\276\347r9*\225\12\0\326\332\373\300Y\21y\334\321\321q\332\0\250\352\260\252\236\0^7 u9\340f}:\225J\205(\212\0hoo?\7|R\325\223KKK\373\315\246G\33\"\262\0215:\377\250\5\350\6v6\22\336{\234s\304q\234\212\310d}\221\363\233AM\300!U}\324\335\335\275\257\236S`\3\250\1d\263Yz{{\11!\0\220\311d\236\3\253\252:\324,\"\23\233`\6\330H\323\264kSl\200\376b\261H\261X\244\356\35\"B\251T\252Yk=\260G\370\207\242(\352L\323t\34\30\5\336\211\310\215\20\302\307z\15\357=\326\332\370\277\240\372\357\344\200\303\300E`\207\210\334\5n\207\20\226\13\205B\313\332\332\332\17`\241\3319g\2001U=h\214\231\364\336\3174V\367\336\223$I\5\230\262\326\316\0\27\200_\215!\265Z\355\10\260MD\236\31\21Q\340+0\222\246\351\30\360\327\314\206\37\0I\222\304\306\230+\300\203\20\302\362\354\354,\252:\16 \"w\32\315\333\235s\323\326Z\265\326Nl\205\344\363y\262\331\354\326\223\247\254\265\352\234{\232\317\347E\0\372\372\372X]]\335\253\252oU\265\13\230jjj\272T.\227\177n\365\254\247\247\307\256\257\257\337\2\216\2\263\306\230\3\336\373\252\264\265\265\261\262\262B\241P\220Z\2556\0 4 | #include 5 | 6 | #include "Common/Log.hpp" 7 | #include "Message.hpp" 8 | 9 | using namespace Rapatas::Transmitron; 10 | using namespace MQTT; 11 | 12 | Subscription::Subscription( 13 | Id id, 14 | std::string filter, 15 | QoS qos, 16 | std::shared_ptr client 17 | ) : 18 | mId(id), 19 | mFilter(std::move(filter)), 20 | mQos(qos), 21 | mClient(std::move(client)), 22 | mLogger(Common::Log::create("MQTT::Subscription")) // 23 | {} 24 | 25 | size_t Subscription::attachObserver(Observer *observer) { 26 | static size_t id = 0; 27 | 28 | if (mState == State::Subscribed) { onSubscribed(); } 29 | 30 | return mObservers.insert(std::make_pair(id++, observer)).first->first; 31 | } 32 | 33 | void Subscription::unsubscribe() { 34 | switch (mState) { 35 | case State::ToSubscribe: 36 | case State::PendingSubscription: 37 | case State::Subscribed: { 38 | mClient->unsubscribe(mId); 39 | } break; 40 | default: { 41 | } 42 | } 43 | } 44 | 45 | void Subscription::onSubscribed() { 46 | for (const auto &[id, observer] : mObservers) { observer->onSubscribed(); } 47 | } 48 | 49 | void Subscription::onUnsubscribed() { 50 | for (const auto &[id, observer] : mObservers) { observer->onUnsubscribed(); } 51 | } 52 | 53 | void Subscription::onMessage(const mqtt::const_message_ptr &msg) { 54 | for (const auto &[id, observer] : mObservers) { 55 | const std::string payload{ 56 | std::begin(msg->get_payload()), 57 | std::end(msg->get_payload()) 58 | }; 59 | 60 | QoS qos = QoS::AtLeastOnce; 61 | switch (msg->get_qos()) { 62 | case 0: qos = MQTT::QoS::AtLeastOnce; break; 63 | case 1: qos = MQTT::QoS::AtMostOnce; break; 64 | case 2: qos = MQTT::QoS::ExactlyOnce; break; 65 | } 66 | 67 | const auto timestamp = std::chrono::system_clock::now(); 68 | 69 | const Message message{ 70 | msg->get_topic(), 71 | payload, 72 | qos, 73 | msg->is_retained(), 74 | timestamp, 75 | }; 76 | 77 | observer->onMessage(message); 78 | } 79 | } 80 | 81 | std::string Subscription::getFilter() const { return mFilter; } 82 | 83 | QoS Subscription::getQos() const { return mQos; } 84 | 85 | Subscription::State Subscription::getState() const { return mState; } 86 | 87 | Subscription::Id Subscription::getId() const { return mId; } 88 | 89 | void Subscription::setState(State newState) { mState = newState; } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Transmitron Logo 6 |
7 | Transmitron Name 11 |

12 |

Open source MQTT Client for desktop. Flexible, native, and cross-platform.

13 |

14 | Release version 19 | Build status at drone.rapatas.com 24 | License: GPL-3 29 |

30 |
31 |

32 | 33 |

34 | 35 | 36 | ## Features 37 | 38 | - **Profiles.** Store connections to brokers. 39 | - **Multiple Connections.** Connect to multiple `Profiles` at the same time using tabs. 40 | - **Archive.** Store messages in a nested folder structure, ready to publish. 41 | - **Record History.** Record received messages and play them back later. 42 | - **Text Folding.** For messages with nested data. 43 | - **Syntax highlight, detection & formatting.** Supports JSON, XML & binary. 44 | - **Auto complete topics.** Subscribed and published topics are cached. 45 | - **Flexible.** Resize, drag, detach or hide each sidebar separately. 46 | - **Layouts.** Store sidebar locations and sizes. 47 | - **XDG BaseDir.** Respects the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) specification. 48 | - **Native UI.** Using `wxWidgets` to integrate seamlessly with your desktop theme. 49 | - **Mute / Solo.** Hide or isolate messages for each subscription. 50 | - **History Filter.** Limit history using search terms. 51 | - **Cross-Platform.** Built for Windows and Linux. 52 | 53 | ## Documentation 54 | 55 | - [Install](https://github.com/Rapatas/transmitron/releases/latest) 56 | - [Build](./docs/build.md) 57 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | UseTab: Never 3 | IndentWidth: 2 4 | ContinuationIndentWidth: 2 5 | ConstructorInitializerIndentWidth: 2 6 | ColumnLimit: 80 7 | BreakBeforeBraces: Custom 8 | PackConstructorInitializers: Never 9 | IndentCaseLabels: true 10 | CompactNamespaces: false 11 | BreakConstructorInitializers: AfterColon 12 | BreakInheritanceList: AfterColon 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | BinPackParameters: false 15 | BinPackArguments: false 16 | AllowAllArgumentsOnNextLine: false 17 | AlignAfterOpenBracket: BlockIndent 18 | LambdaBodyIndentation: Signature 19 | AllowShortBlocksOnASingleLine: Always 20 | AllowShortCaseLabelsOnASingleLine: true 21 | AllowShortLambdasOnASingleLine: All 22 | AllowShortLoopsOnASingleLine: true 23 | AllowShortIfStatementsOnASingleLine: true 24 | AllowShortEnumsOnASingleLine: false 25 | AlignConsecutiveAssignments: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | AlignConsecutiveMacros: 31 | Enabled: true 32 | AcrossEmptyLines: false 33 | AcrossComments: false 34 | AlignOperands: DontAlign 35 | AlignTrailingComments: 36 | Kind: Always 37 | OverEmptyLines: 0 38 | SpaceBeforeCpp11BracedList: false 39 | MaxEmptyLinesToKeep: 1 40 | KeepEmptyLines: 41 | AtEndOfFile: false 42 | AtStartOfBlock: false 43 | AtStartOfFile: false 44 | SeparateDefinitionBlocks: Always 45 | AlwaysBreakTemplateDeclarations: Yes 46 | BreakTemplateDeclarations: Yes 47 | EmptyLineAfterAccessModifier: Always 48 | RequiresClausePosition: OwnLine 49 | SortUsingDeclarations: Lexicographic 50 | EmptyLineBeforeAccessModifier: LogicalBlock 51 | BreakBeforeTernaryOperators: true 52 | BreakBeforeBinaryOperators: NonAssignment 53 | IncludeBlocks: Regroup 54 | IncludeCategories: 55 | - Regex: ^<[[:alnum:].]+> 56 | Priority: 1 57 | - Regex: ^<.*> 58 | Priority: 2 59 | - Regex: ^".*" 60 | Priority: 3 61 | QualifierAlignment: Left 62 | PointerAlignment: Right 63 | ReferenceAlignment: Right 64 | AlignConsecutiveBitFields: false 65 | PenaltyBreakAssignment: 500 66 | PenaltyReturnTypeOnItsOwnLine: 300 67 | ObjCBreakBeforeNestedBlockParam: true 68 | BraceWrapping: 69 | AfterClass: true 70 | AfterControlStatement: false 71 | AfterEnum: false 72 | AfterExternBlock: false 73 | AfterFunction: false 74 | AfterNamespace: false 75 | AfterObjCDeclaration: false 76 | AfterStruct: false 77 | AfterUnion: false 78 | BeforeCatch: false 79 | BeforeElse: false 80 | BeforeLambdaBody: false 81 | BeforeWhile: false 82 | IndentBraces: false 83 | SplitEmptyFunction: false 84 | SplitEmptyNamespace: false 85 | SplitEmptyRecord: false 86 | -------------------------------------------------------------------------------- /src/MQTT/BrokerOptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Rapatas::Transmitron::MQTT { 9 | 10 | class BrokerOptions 11 | { 12 | public: 13 | 14 | using Port = uint16_t; 15 | 16 | static constexpr bool DefaultAutoReconnect = false; 17 | static constexpr const std::string_view DefaultHostname = "127.0.0.1"; 18 | static constexpr const std::string_view DefaultPassword{}; 19 | static constexpr const std::string_view DefaultUsername{}; 20 | static constexpr std::chrono::seconds DefaultTimeout{5}; 21 | static constexpr std::chrono::seconds DefaultKeepAliveInterval{60}; 22 | static constexpr size_t DefaultMaxReconnectRetries = 10; 23 | static constexpr size_t DefaultMaxInFlight = 10; 24 | static constexpr Port DefaultPort = 1883; 25 | static constexpr bool DefaultSSL = false; 26 | 27 | explicit BrokerOptions(); 28 | explicit BrokerOptions( 29 | bool autoReconnect, 30 | size_t maxInFlight, 31 | size_t maxReconnectRetries, 32 | Port port, 33 | bool ssl, 34 | std::chrono::seconds connectTimeout, 35 | std::chrono::seconds disconnectTimeout, 36 | std::chrono::seconds keepAliveInterval, 37 | std::string clientId, 38 | std::string hostname, 39 | std::string password, 40 | std::string username 41 | ); 42 | 43 | static BrokerOptions fromJson(const nlohmann::json &data); 44 | [[nodiscard]] nlohmann::json toJson() const; 45 | 46 | [[nodiscard]] bool getAutoReconnect() const; 47 | [[nodiscard]] std::chrono::seconds getConnectTimeout() const; 48 | [[nodiscard]] std::chrono::seconds getDisconnectTimeout() const; 49 | [[nodiscard]] std::chrono::seconds getKeepAliveInterval() const; 50 | [[nodiscard]] std::string getClientId() const; 51 | [[nodiscard]] std::string getHostname() const; 52 | [[nodiscard]] std::string getPassword() const; 53 | [[nodiscard]] std::string getUsername() const; 54 | [[nodiscard]] size_t getMaxInFlight() const; 55 | [[nodiscard]] size_t getMaxReconnectRetries() const; 56 | [[nodiscard]] Port getPort() const; 57 | [[nodiscard]] bool getSSL() const; 58 | 59 | void setHostname(std::string hostname); 60 | void setPort(Port port); 61 | 62 | private: 63 | 64 | bool mAutoReconnect; 65 | size_t mMaxInFlight; 66 | size_t mMaxReconnectRetries; 67 | Port mPort; 68 | bool mSsl; 69 | std::chrono::seconds mConnectTimeout; 70 | std::chrono::seconds mDisconnectTimeout; 71 | std::chrono::seconds mKeepAliveInterval; 72 | std::string mClientId; 73 | std::string mHostname; 74 | std::string mPassword; 75 | std::string mUsername; 76 | }; 77 | 78 | } // namespace Rapatas::Transmitron::MQTT 79 | -------------------------------------------------------------------------------- /src/GUI/Tabs/Homepage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "GUI/ArtProvider.hpp" 13 | #include "GUI/Models/Layouts.hpp" 14 | #include "GUI/Models/ProfilesWrapper.hpp" 15 | 16 | namespace Rapatas::Transmitron::GUI::Tabs { 17 | 18 | class Homepage : public wxPanel 19 | { 20 | public: 21 | 22 | explicit Homepage( 23 | wxWindow *parent, 24 | const ArtProvider &artProvider, 25 | wxFontInfo labelFont, 26 | int optionsHeight, 27 | const wxObjectDataPtr &profilesModel, 28 | const wxObjectDataPtr &layoutsModel 29 | ); 30 | 31 | void focus(); 32 | 33 | private: 34 | 35 | enum class ContextIDs : uint8_t { 36 | ProfilesConnect, 37 | ProfilesCreate, 38 | ProfilesEdit, 39 | }; 40 | 41 | std::shared_ptr mLogger; 42 | wxFontInfo mLabelFont; 43 | int mOptionsHeight; 44 | const ArtProvider &mArtProvider; 45 | 46 | // Profiles. 47 | wxPanel *mProfiles = nullptr; 48 | wxDataViewCtrl *mProfilesCtrl = nullptr; 49 | wxObjectDataPtr mProfilesModel; 50 | wxObjectDataPtr mProfilesModelWrapper; 51 | wxObjectDataPtr mLayoutsModel; 52 | wxButton *mProfileCreate = nullptr; 53 | wxButton *mProfileEdit = nullptr; 54 | wxButton *mProfileConnect = nullptr; 55 | 56 | // Recordings. 57 | wxPanel *mRecordings = nullptr; 58 | 59 | // Info. 60 | wxPanel *mInfo = nullptr; 61 | 62 | // Quick Connect. 63 | wxPanel *mQuickConnect = nullptr; 64 | wxTextCtrl *mQuickConnectUrl = nullptr; 65 | wxButton *mQuickConnectBtn = nullptr; 66 | 67 | void onCancelClicked(wxCommandEvent &event); 68 | void onRecordingOpen(wxCommandEvent &event); 69 | 70 | void onConnectClicked(wxCommandEvent &event); 71 | void onContextSelected(wxCommandEvent &event); 72 | 73 | void onProfileActivated(wxDataViewEvent &event); 74 | void onProfileContext(wxDataViewEvent &event); 75 | void onProfileCreate(wxCommandEvent &event); 76 | void onProfileEdit(wxCommandEvent &event); 77 | void onProfileConnect(wxCommandEvent &event); 78 | void onProfileSelected(wxDataViewEvent &event); 79 | 80 | void setupProfiles(wxPanel *parent); 81 | void setupQuickConnect(wxPanel *parent); 82 | void setupRecordings(wxPanel *parent); 83 | void setupInfo(wxPanel *parent); 84 | 85 | void onQuickConnect(); 86 | 87 | void connectTo(wxDataViewItem profile); 88 | }; 89 | 90 | } // namespace Rapatas::Transmitron::GUI::Tabs 91 | -------------------------------------------------------------------------------- /src/Common/XdgBaseDir.Linux.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | 3 | #include "XdgBaseDir.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "Env.hpp" 12 | #include "String.hpp" 13 | 14 | using namespace Rapatas::Transmitron::Common; 15 | 16 | std::string XdgBaseDir::readHome() { 17 | auto home = Env::get("HOME"); 18 | if (!home.empty()) { return home; } 19 | 20 | const auto initlen = sysconf(_SC_GETPW_R_SIZE_MAX); 21 | constexpr size_t DefaultLen = 1024; 22 | const size_t len = (initlen == -1) // 23 | ? DefaultLen 24 | : static_cast(initlen); 25 | 26 | struct passwd pwd{}; 27 | struct passwd *resultp = nullptr; 28 | std::string buffer; 29 | buffer.resize(len); 30 | getpwuid_r(getuid(), &pwd, buffer.data(), buffer.size(), &resultp); 31 | return pwd.pw_dir; 32 | } 33 | 34 | std::string XdgBaseDir::readConfigHome() { 35 | auto var = Env::get("XDG_CONFIG_HOME"); 36 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 37 | if (!var.empty()) { return var; } 38 | if (mHome.empty()) { return {}; } 39 | return fmt::format("{}/.config", mHome.string()); 40 | } 41 | 42 | std::string XdgBaseDir::readDataHome() { 43 | auto var = Env::get("XDG_DATA_HOME"); 44 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 45 | if (!var.empty()) { return var; } 46 | if (mHome.empty()) { return {}; } 47 | return fmt::format("{}/.local/share", mHome.string()); 48 | } 49 | 50 | std::string XdgBaseDir::readCacheHome() { 51 | auto var = Env::get("XDG_CACHE_HOME"); 52 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 53 | if (!var.empty()) { return var; } 54 | if (mHome.empty()) { return {}; } 55 | return fmt::format("{}/.cache", mHome.string()); 56 | } 57 | 58 | std::string XdgBaseDir::readStateHome() { 59 | auto var = Env::get("XDG_STATE_HOME"); 60 | if (!var.empty() && var.back() == '/') { var.pop_back(); } 61 | if (!var.empty()) { return var; } 62 | if (mHome.empty()) { return {}; } 63 | return fmt::format("{}/.local/state", mHome.string()); 64 | } 65 | 66 | // NOLINTNEXTLINE 67 | std::vector XdgBaseDir::readDataDirs() const { 68 | auto var = Env::get("XDG_DATA_DIRS"); 69 | if (var.empty()) { var = "/usr/local/share:/usr/share"; } 70 | auto dirs = String::split(var, ':'); 71 | for (auto &dir : dirs) { 72 | if (dir.back() == '/') { dir.pop_back(); } 73 | } 74 | return dirs; 75 | } 76 | 77 | // NOLINTNEXTLINE 78 | std::vector XdgBaseDir::readConfigDirs() const { 79 | auto var = Env::get("XDG_CONFIG_DIRS"); 80 | if (var.empty()) { var = "/etc/xdg"; } 81 | auto dirs = String::split(var, ':'); 82 | for (auto &dir : dirs) { 83 | if (dir.back() == '/') { dir.pop_back(); } 84 | } 85 | return dirs; 86 | } 87 | 88 | #endif // _WIN32 89 | -------------------------------------------------------------------------------- /src/GUI/Models/Layouts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Common/Filesystem.hpp" 12 | 13 | namespace Rapatas::Transmitron::GUI::Models { 14 | 15 | class Layouts : public wxDataViewModel 16 | { 17 | public: 18 | 19 | static constexpr std::string_view DefaultName = "Default"; 20 | using Perspective = std::string; 21 | 22 | enum class Column : uint8_t { 23 | Name, 24 | Max 25 | }; 26 | 27 | explicit Layouts(); 28 | 29 | bool load(const std::string &configDir); 30 | wxDataViewItem create(const Perspective &perspective); 31 | bool remove(wxDataViewItem item); 32 | 33 | // Getters 34 | static wxDataViewItem getDefault(); 35 | static bool isDeletable(wxDataViewItem item); 36 | [[nodiscard]] wxDataViewItem findItemByName(const std::string &name) const; 37 | [[nodiscard]] std::string getUniqueName() const; 38 | [[nodiscard]] const Perspective &getPerspective(wxDataViewItem item) const; 39 | [[nodiscard]] const std::string &getName(wxDataViewItem item) const; 40 | [[nodiscard]] wxArrayString getLabelArray() const; 41 | [[nodiscard]] std::vector getLabelVector() const; 42 | 43 | // wxDataViewModel interface. 44 | [[nodiscard]] unsigned GetColumnCount() const override; 45 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 46 | void GetValue( 47 | wxVariant &value, 48 | const wxDataViewItem &item, 49 | unsigned int col // 50 | ) const override; 51 | bool SetValue( 52 | const wxVariant &value, 53 | const wxDataViewItem &item, 54 | unsigned int col 55 | ) override; 56 | [[nodiscard]] bool IsEnabled( 57 | const wxDataViewItem &item, 58 | unsigned int col // 59 | ) const override; 60 | [[nodiscard]] wxDataViewItem GetParent( // 61 | const wxDataViewItem &item 62 | ) const override; 63 | [[nodiscard]] bool IsContainer(const wxDataViewItem &item) const override; 64 | unsigned int GetChildren( 65 | const wxDataViewItem &parent, 66 | wxDataViewItemArray &children 67 | ) const override; 68 | 69 | private: 70 | 71 | struct Node { 72 | using Id = size_t; 73 | std::string name; 74 | Perspective perspective; 75 | Common::fs::path path; 76 | bool saved = false; 77 | }; 78 | 79 | std::shared_ptr mLogger; 80 | Node::Id mAvailableId = 1; 81 | std::map> mLayouts; 82 | std::string mLayoutsDir; 83 | 84 | void injectDefaultLayouts(); 85 | wxDataViewItem loadLayoutFile(const Common::fs::path &filepath); 86 | bool save(size_t id); 87 | 88 | static Node::Id toId(const wxDataViewItem &item); 89 | static wxDataViewItem toItem(Node::Id id); 90 | }; 91 | 92 | } // namespace Rapatas::Transmitron::GUI::Models 93 | -------------------------------------------------------------------------------- /src/Common/Console.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include "Common/Console.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace Rapatas::Transmitron::Common; 13 | 14 | bool Console::release() { 15 | auto redirectToNul = [](FILE *stream) { 16 | FILE *fp = nullptr; 17 | if (freopen_s(&fp, "NUL:", "r", stream) != 0) { return false; } 18 | setvbuf(stream, NULL, _IONBF, 0); 19 | return true; 20 | }; 21 | 22 | bool hadFailure = false; 23 | // Just to be safe, redirect standard IO to NUL before releasing. 24 | hadFailure |= !redirectToNul(stdin); 25 | hadFailure |= !redirectToNul(stdout); 26 | hadFailure |= !redirectToNul(stderr); 27 | hadFailure |= !FreeConsole(); 28 | return !hadFailure; 29 | } 30 | 31 | bool Console::redirect() { 32 | auto redirect = [](DWORD handle, FILE *stream, std::string name) { 33 | if (GetStdHandle(handle) == INVALID_HANDLE_VALUE) { return false; } 34 | FILE *fp = nullptr; 35 | if (freopen_s(&fp, name.data(), "r", stream) != 0) { return false; } 36 | setvbuf(stream, NULL, _IONBF, 0); 37 | return true; 38 | }; 39 | 40 | bool hadFailure = false; 41 | hadFailure |= redirect(STD_INPUT_HANDLE, stdin, "CONIN$"); 42 | hadFailure |= redirect(STD_OUTPUT_HANDLE, stdout, "CONOUT$"); 43 | hadFailure |= redirect(STD_ERROR_HANDLE, stderr, "CONOUT$"); 44 | 45 | // Make C++ standard streams point to console as well. 46 | std::ios::sync_with_stdio(true); 47 | 48 | // Clear the error state for each of the C++ standard streams. 49 | std::wcout.clear(); 50 | std::cout.clear(); 51 | std::wcerr.clear(); 52 | std::cerr.clear(); 53 | std::wcin.clear(); 54 | std::cin.clear(); 55 | 56 | return !hadFailure; 57 | } 58 | 59 | void Console::adjustBuffer(int16_t minLength) { 60 | // Set the screen buffer to be big enough to scroll some text 61 | CONSOLE_SCREEN_BUFFER_INFO conInfo; 62 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &conInfo); 63 | if (conInfo.dwSize.Y < minLength) { conInfo.dwSize.Y = minLength; } 64 | SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), conInfo.dwSize); 65 | } 66 | 67 | bool Console::create(int16_t minLength) { 68 | // Release any current console and redirect IO to NUL 69 | (void)release(); 70 | 71 | // Attempt to create new console 72 | if (AllocConsole()) { 73 | adjustBuffer(minLength); 74 | return redirect(); 75 | } 76 | return false; 77 | } 78 | 79 | bool Console::attachToParent(int16_t minLength) { 80 | // Release any current console and redirect IO to NUL 81 | (void)release(); 82 | 83 | // Attempt to attach to parent process's console 84 | if (AttachConsole(ATTACH_PARENT_PROCESS)) { 85 | adjustBuffer(minLength); 86 | return redirect(); 87 | } 88 | 89 | return false; 90 | } 91 | 92 | #endif // _WIN32 93 | -------------------------------------------------------------------------------- /src/Common/Filesystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // clang-format off 3 | /* 4 | https://stackoverflow.com/a/53365539 5 | Modified to use Common::fs instead of std::filesystem. 6 | */ 7 | 8 | // NOLINTBEGIN(misc-unused-alias-decls) 9 | // NOLINTBEGIN(cppcoreguidelines-macro-usage) 10 | 11 | // We haven't checked which filesystem to include yet 12 | #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 13 | 14 | // Check for feature test macro for 15 | # if defined(__cpp_lib_filesystem) 16 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0 17 | 18 | // Check for feature test macro for 19 | # elif defined(__cpp_lib_experimental_filesystem) 20 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1 21 | 22 | // We can't check if headers exist... 23 | // Let's assume experimental to be safe 24 | # elif !defined(__has_include) 25 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1 26 | 27 | // Check if the header "" exists 28 | # elif __has_include() 29 | 30 | // If we're compiling on Visual Studio and are not compiling with C++17, we need to use experimental 31 | # ifdef _MSC_VER 32 | 33 | // Check and include header that defines "_HAS_CXX17" 34 | # if __has_include() 35 | # include 36 | 37 | // Check for enabled C++17 support 38 | # if defined(_HAS_CXX17) && _HAS_CXX17 39 | // We're using C++17, so let's use the normal version 40 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0 41 | # endif 42 | # endif 43 | 44 | // If the marco isn't defined yet, that means any of the other VS specific checks failed, so we need to use experimental 45 | # ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 46 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1 47 | # endif 48 | 49 | // Not on Visual Studio. Let's use the normal version 50 | # else // #ifdef _MSC_VER 51 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0 52 | # endif 53 | 54 | // Check if the header "" exists 55 | # elif __has_include() 56 | # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1 57 | 58 | // Fail if neither header is available with a nice error message 59 | # else 60 | # error Could not find system header "" or "" 61 | # endif 62 | 63 | #endif // #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 64 | 65 | // We priously determined that we need the exprimental version 66 | # if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 67 | // Include it 68 | # include 69 | 70 | // We need the alias from std::experimental::filesystem to Common::fs 71 | namespace Rapatas::Transmitron::Common 72 | { 73 | namespace fs = std::experimental::filesystem; 74 | } // namespace Rapatas::Transmitron::Common 75 | 76 | // We have a decent compiler and can use the normal version 77 | # else 78 | // Include it 79 | # include 80 | namespace Rapatas::Transmitron::Common 81 | { 82 | namespace fs = std::filesystem; 83 | } // namespace Rapatas::Transmitron::Common 84 | # endif 85 | 86 | // NOLINTEND(cppcoreguidelines-macro-usage) 87 | // NOLINTEND(misc-unused-alias-decls) 88 | // clang-format on 89 | -------------------------------------------------------------------------------- /src/Common/Url.cpp: -------------------------------------------------------------------------------- 1 | #include "Url.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | constexpr uint8_t NibbleFirst = 0xF0; 10 | constexpr uint8_t NibbleSeccond = 0x0F; 11 | constexpr uint8_t AsciiOffsetDigit = 48; 12 | constexpr uint8_t AsciiOffsetChar = 55; 13 | constexpr uint8_t MaxSingleDigit = 9; 14 | 15 | using namespace Rapatas::Transmitron::Common; 16 | 17 | bool Url::encodable(char value) { 18 | // clang-format off 19 | return !(false // NOLINT 20 | || isalnum(value) != 0 21 | || value == '-' 22 | || value == '_' 23 | || value == ',' 24 | || value == '.' 25 | ); 26 | // clang-format on 27 | } 28 | 29 | bool Url::isHexChar(char value) { 30 | return isdigit(value) != 0 || (value >= 'A' && value <= 'F'); 31 | } 32 | 33 | std::string Url::encode(const std::string &data) { 34 | const auto encodeCharCount = static_cast(std::count_if( 35 | std::begin(data), 36 | std::end(data), 37 | [](char value) { return encodable(value); } 38 | )); 39 | 40 | const auto length = (encodeCharCount * 3) + (data.length() - encodeCharCount); 41 | std::string result; 42 | result.resize(length); 43 | 44 | size_t index = 0; 45 | for (const auto &value : data) { 46 | if (!encodable(value)) { 47 | result[index++] = value; 48 | continue; 49 | } 50 | 51 | // NOLINTBEGIN(hicpp-signed-bitwise) 52 | const uint8_t first = (value & NibbleFirst) >> 4; 53 | const uint8_t second = value & NibbleSeccond; 54 | 55 | result[index++] = '%'; 56 | result[index++] = first <= MaxSingleDigit 57 | ? static_cast(first + AsciiOffsetDigit) 58 | : static_cast(first + AsciiOffsetChar); 59 | result[index++] = second <= MaxSingleDigit 60 | ? static_cast(second + AsciiOffsetDigit) 61 | : static_cast(second + AsciiOffsetChar); 62 | // NOLINTEND(hicpp-signed-bitwise) 63 | } 64 | 65 | return result; 66 | } 67 | 68 | std::string Url::decode(const std::string &data) { 69 | const auto encodeCharCount = static_cast( 70 | std::count(std::begin(data), std::end(data), '%') 71 | ); 72 | 73 | const auto length = (data.length() - encodeCharCount * 3) + encodeCharCount; 74 | std::string result; 75 | result.resize(length); 76 | 77 | auto charToNibble = [](char value) { 78 | if (!isHexChar(value)) { 79 | const auto msg = fmt::format( 80 | "Unexpected non-printable ASCII char '0x{:X}' '{}'", 81 | value, 82 | value 83 | ); 84 | throw std::runtime_error(msg.c_str()); 85 | } 86 | return (value >= '0' && value <= '9') 87 | ? static_cast(value - AsciiOffsetDigit) 88 | : static_cast(value - AsciiOffsetChar); 89 | }; 90 | 91 | size_t index = 0; 92 | for (auto it = data.begin(); it != data.end();) { 93 | if (*it != '%') { 94 | result[index++] = *it; 95 | it++; 96 | continue; 97 | } 98 | ++it; 99 | const uint8_t first = charToNibble(*it++); 100 | const uint8_t second = charToNibble(*it++); 101 | result[index++] = static_cast((first << 4U) + second); 102 | } 103 | 104 | return result; 105 | } 106 | -------------------------------------------------------------------------------- /src/MQTT/Client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "BrokerOptions.hpp" 11 | #include "Message.hpp" 12 | 13 | namespace Rapatas::Transmitron::MQTT { 14 | 15 | class Subscription; 16 | 17 | class Client : 18 | public virtual mqtt::iaction_listener, 19 | public virtual mqtt::callback, 20 | public std::enable_shared_from_this 21 | { 22 | public: 23 | 24 | using SubscriptionId = size_t; 25 | 26 | struct Observer { 27 | Observer() = default; 28 | Observer(const Observer &other) = default; 29 | Observer(Observer &&other) = default; 30 | Observer &operator=(const Observer &other) = default; 31 | Observer &operator=(Observer &&other) = default; 32 | virtual ~Observer() = default; 33 | 34 | virtual void onConnected() = 0; 35 | virtual void onDisconnected() = 0; 36 | virtual void onConnectionFailure() = 0; 37 | virtual void onConnectionLost() = 0; 38 | }; 39 | 40 | explicit Client(); 41 | size_t attachObserver(Observer *observer); 42 | void detachObserver(size_t id); 43 | 44 | // Actions. 45 | void connect(); 46 | void disconnect(); 47 | void cancel(); 48 | void publish(const Message &message); 49 | std::shared_ptr subscribe(const std::string &topic); 50 | void unsubscribe(size_t id); 51 | 52 | // Setters. 53 | void setBrokerOptions(BrokerOptions brokerOptions); 54 | 55 | // Getters. 56 | const BrokerOptions &brokerOptions() const; 57 | bool connected() const; 58 | 59 | private: 60 | 61 | std::shared_ptr mLogger; 62 | BrokerOptions mBrokerOptions; 63 | SubscriptionId mSubscriptionIds = 0; 64 | bool mShouldReconnect = false; 65 | bool mCanceled = false; 66 | mqtt::connect_options mConnectOptions; 67 | size_t mRetries = 0; 68 | std::map> mSubscriptions; 69 | std::map mObservers; 70 | std::shared_ptr mClient; 71 | 72 | // mqtt::iaction_listener interface. 73 | void on_success(const mqtt::token &tok) override; 74 | void on_failure(const mqtt::token &tok) override; 75 | 76 | // mqtt::callback interface. 77 | void connected(const std::string &cause) override; 78 | void connection_lost(const std::string &cause) override; 79 | void message_arrived(mqtt::const_message_ptr msg) override; 80 | void delivery_complete(mqtt::delivery_token_ptr token) override; 81 | 82 | void onSuccessConnect(const mqtt::token &tok); 83 | void onSuccessDisconnect(const mqtt::token &tok); 84 | void onSuccessPublish(const mqtt::token &tok); 85 | void onSuccessSubscribe(const mqtt::token &tok); 86 | void onSuccessUnsubscribe(const mqtt::token &tok); 87 | void onFailureConnect(const mqtt::token &tok); 88 | void onFailureDisconnect(const mqtt::token &tok); 89 | void onFailurePublish(const mqtt::token &tok); 90 | void onFailureSubscribe(const mqtt::token &tok); 91 | void onFailureUnsubscribe(const mqtt::token &tok); 92 | 93 | void reconnect(); 94 | void doSubscribe(size_t id); 95 | void cleanSubscriptions(); 96 | 97 | static const std::map &codeDescriptions(); 98 | static bool match(const std::string &filter, const std::string &topic); 99 | static std::string codeToStr(int code); 100 | }; 101 | 102 | } // namespace Rapatas::Transmitron::MQTT 103 | -------------------------------------------------------------------------------- /src/GUI/Widgets/Edit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "GUI/ArtProvider.hpp" 11 | #include "GUI/Events/Edit.hpp" 12 | #include "GUI/Events/TopicCtrl.hpp" 13 | #include "MQTT/Client.hpp" 14 | #include "MQTT/Message.hpp" 15 | #include "TopicCtrl.hpp" 16 | 17 | namespace Rapatas::Transmitron::GUI::Widgets { 18 | 19 | class Edit : public wxPanel 20 | { 21 | public: 22 | 23 | explicit Edit( 24 | wxWindow *parent, 25 | wxWindowID id, 26 | const ArtProvider &artProvider, 27 | int optionsHeight, 28 | bool darkMode 29 | ); 30 | 31 | void format(); 32 | 33 | [[nodiscard]] MQTT::Message getMessage() const; 34 | [[nodiscard]] std::string getPayload() const; 35 | [[nodiscard]] bool getReadOnly() const; 36 | [[nodiscard]] std::string getTopic() const; 37 | [[nodiscard]] MQTT::QoS getQos() const; 38 | [[nodiscard]] bool getRetained() const; 39 | 40 | void setReadOnly(bool readonly); 41 | void clear(); 42 | 43 | void setMessage(const MQTT::Message &message); 44 | void setPayload(const std::string &text); 45 | void setRetained(bool retained); 46 | void setTopic(const std::string &topic); 47 | void setQos(MQTT::QoS qos); 48 | void setTimestamp(const std::chrono::system_clock::time_point ×tamp); 49 | void setInfoLine(const std::string &info); 50 | void addKnownTopics( 51 | const wxObjectDataPtr &knownTopicsModel 52 | ); 53 | 54 | private: 55 | 56 | enum class Theme : uint8_t { 57 | Light, 58 | Dark, 59 | }; 60 | 61 | enum class Style : uint8_t { 62 | Comment, 63 | Editor, 64 | Error, 65 | Key, 66 | Keyword, 67 | Normal, 68 | Number, 69 | Special, 70 | String, 71 | Uri, 72 | }; 73 | 74 | enum class Format : uint8_t { 75 | Auto, 76 | Text, 77 | Json, 78 | Xml, 79 | Binary, 80 | }; 81 | 82 | using ThemeStyles = std::map>; 83 | 84 | std::shared_ptr mLogger; 85 | 86 | Theme mTheme; 87 | wxFont mFont; 88 | const ArtProvider &mArtProvider; 89 | 90 | bool mReadOnly = false; 91 | int mOptionsHeight; 92 | 93 | wxBoxSizer *mTop = nullptr; 94 | wxBoxSizer *mVsizer = nullptr; 95 | wxBoxSizer *mBottom = nullptr; 96 | 97 | TopicCtrl *mTopic = nullptr; 98 | 99 | bool mRetained = false; 100 | wxStaticBitmap *mRetainedTrue = nullptr; 101 | wxStaticBitmap *mRetainedFalse = nullptr; 102 | 103 | MQTT::QoS mQoS = MQTT::QoS::AtLeastOnce; 104 | wxStaticBitmap *mQos0 = nullptr; 105 | wxStaticBitmap *mQos1 = nullptr; 106 | wxStaticBitmap *mQos2 = nullptr; 107 | 108 | wxButton *mPublish = nullptr; 109 | 110 | wxStyledTextCtrl *mText = nullptr; 111 | std::string mPayload; 112 | 113 | wxStaticText *mInfoLine = nullptr; 114 | 115 | wxButton *mSaveMessage = nullptr; 116 | wxComboBox *mFormatSelect = nullptr; 117 | 118 | Format mCurrentFormat = Format::Auto; 119 | 120 | void onQosClicked(wxMouseEvent &event); 121 | void onRetainedClicked(wxMouseEvent &event); 122 | 123 | void setupScintilla(); 124 | void setStyle(Format format); 125 | void onFormatSelected(wxCommandEvent &event); 126 | void onTopicCtrlReturn(Events::TopicCtrl &event); 127 | 128 | std::string formatTry(const std::string &text, Format format); 129 | 130 | static Format formatGuess(const std::string &text); 131 | static const std::map &formats(); 132 | static const std::map &styles(); 133 | }; 134 | 135 | } // namespace Rapatas::Transmitron::GUI::Widgets 136 | -------------------------------------------------------------------------------- /src/GUI/Models/Subscriptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "GUI/Events/Subscription.hpp" 9 | #include "GUI/Types/Subscription.hpp" 10 | #include "MQTT/Client.hpp" 11 | 12 | namespace Rapatas::Transmitron::GUI::Models { 13 | 14 | class Subscriptions : public wxDataViewVirtualListModel 15 | { 16 | public: 17 | 18 | struct Observer { 19 | Observer() = default; 20 | virtual ~Observer() = default; 21 | Observer(const Observer &) = default; 22 | Observer(Observer &&) = default; 23 | Observer &operator=(const Observer &) = default; 24 | Observer &operator=(Observer &&) = default; 25 | 26 | virtual void onColorSet( 27 | MQTT::Subscription::Id subscriptionId, 28 | wxColor color 29 | ) = 0; 30 | virtual void onMuted(MQTT::Subscription::Id subscriptionId) = 0; 31 | virtual void onSolo(MQTT::Subscription::Id subscriptionId) = 0; 32 | virtual void onUnmuted(MQTT::Subscription::Id subscriptionId) = 0; 33 | virtual void onUnsubscribed(MQTT::Subscription::Id subscriptionId) = 0; 34 | virtual void onCleared(MQTT::Subscription::Id subscriptionId) = 0; 35 | virtual void onMessage( 36 | MQTT::Subscription::Id subscriptionId, 37 | const MQTT::Message &message 38 | ) = 0; 39 | }; 40 | 41 | enum class Column : uint8_t { 42 | Icon, 43 | Qos, 44 | Topic, 45 | Max 46 | }; 47 | 48 | explicit Subscriptions(std::shared_ptr client); 49 | explicit Subscriptions(); 50 | 51 | size_t attachObserver(Observer *observer); 52 | bool detachObserver(size_t id); 53 | bool load(const std::string &recording); 54 | 55 | void mute(wxDataViewItem item); 56 | void setColor(wxDataViewItem item, const wxColor &color); 57 | void solo(wxDataViewItem item); 58 | void subscribe(const std::string &topic, MQTT::QoS qos); 59 | void unmute(wxDataViewItem item); 60 | void unsubscribe(wxDataViewItem item); 61 | void clear(wxDataViewItem item); 62 | 63 | [[nodiscard]] bool getMuted(MQTT::Subscription::Id subscriptionId) const; 64 | [[nodiscard]] bool getMuted(wxDataViewItem item) const; 65 | [[nodiscard]] std::string getFilter(wxDataViewItem item) const; 66 | [[nodiscard]] wxColor getColor(MQTT::Subscription::Id subscriptionId) const; 67 | [[nodiscard]] nlohmann::json toJson() const; 68 | [[nodiscard]] std::string getFilter( // 69 | MQTT::Subscription::Id subscriptionId 70 | ) const; 71 | 72 | private: 73 | 74 | std::shared_ptr mLogger; 75 | std::shared_ptr mClient; 76 | std::map> 77 | mSubscriptions; 78 | std::vector mRemap; 79 | std::map mObservers; 80 | 81 | // wxDataViewVirtualListModel interface. 82 | [[nodiscard]] unsigned GetColumnCount() const override; 83 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 84 | [[nodiscard]] unsigned GetCount() const override; 85 | void GetValueByRow( 86 | wxVariant &variant, 87 | unsigned int row, 88 | unsigned int col // 89 | ) const override; 90 | bool GetAttrByRow( 91 | unsigned int row, 92 | unsigned int col, 93 | wxDataViewItemAttr &attr 94 | ) const override; 95 | bool SetValueByRow( 96 | const wxVariant &variant, 97 | unsigned int row, 98 | unsigned int col 99 | ) override; 100 | 101 | void onSubscribed(Events::Subscription &event); 102 | void onUnsubscribed(Events::Subscription &event); 103 | void onMessage(Events::Subscription &event); 104 | }; 105 | 106 | } // namespace Rapatas::Transmitron::GUI::Models 107 | -------------------------------------------------------------------------------- /src/GUI/ArtProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI/ArtProvider.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "Common/Filesystem.hpp" 7 | #include "Common/Log.hpp" 8 | 9 | using namespace Rapatas::Transmitron::GUI; 10 | using namespace Rapatas::Transmitron::Common; 11 | 12 | constexpr uint8_t ByteMax = 255; 13 | 14 | ArtProvider::ArtProvider() = default; 15 | 16 | void ArtProvider::initialize(const fs::path &base, wxSize size, bool dark) { 17 | mPlaceholder = wxArtProvider::GetBitmap(wxART_PLUS); 18 | mLogger = Log::create("ArtProvider"); 19 | 20 | mLogger->info("Loading icons with dimensions: {}x{}", size.x, size.y); 21 | 22 | static const std::map paths{ 23 | 24 | {Icon::Add, "add.svg"}, 25 | {Icon::Archive, "inventory_2.svg"}, 26 | {Icon::Cancel, "cancel.svg"}, 27 | {Icon::Clear, "clear_all.svg"}, 28 | {Icon::Connect, "arrow_forward.svg"}, 29 | {Icon::Connected, "task_alt.svg"}, 30 | {Icon::Connecting, "pending.svg"}, 31 | {Icon::Copy, "content_copy.svg"}, 32 | {Icon::Delete, "delete.svg"}, 33 | {Icon::Disconnected, "hide_source.svg"}, 34 | {Icon::Edit, "edit.svg"}, 35 | {Icon::File, "draft.svg"}, 36 | {Icon::FileFull, "description.svg"}, 37 | {Icon::Folder, "folder.svg"}, 38 | {Icon::History, "history.svg"}, 39 | {Icon::Home, "home.svg"}, 40 | {Icon::Mute, "volume_off.svg"}, 41 | {Icon::NewColor, "palette.svg"}, 42 | {Icon::NewDir, "create_new_folder.svg"}, 43 | {Icon::NewFile, "note_add.svg"}, 44 | {Icon::NewProfile, "person_add.svg"}, 45 | {Icon::Profile, "person.svg"}, 46 | {Icon::Publish, "send.svg"}, 47 | {Icon::Save, "save.svg"}, 48 | {Icon::SaveAs, "save_as.svg"}, 49 | {Icon::Search, "search.svg"}, 50 | {Icon::Settings, "settings.svg"}, 51 | {Icon::Solo, "music_note.svg"}, 52 | {Icon::Subscribe, "notification_add.svg"}, 53 | {Icon::Subscriptions, "notifications.svg"}, 54 | {Icon::Unmute, "volume_up.svg"}, 55 | {Icon::Unsubscribe, "notifications_off.svg"}, 56 | 57 | }; 58 | 59 | for (const auto &[icon, relative] : paths) { 60 | const auto fullpath = base / relative; 61 | if (!fs::exists(fullpath)) { 62 | mLogger->warn("Could not load icon: {}", fullpath.string()); 63 | continue; 64 | } 65 | auto bundle = wxBitmapBundle::FromSVGFile(fullpath.string(), size); 66 | if (!bundle.IsOk()) { 67 | mLogger->warn("Could not load icon: {}", fullpath.string()); 68 | continue; 69 | } 70 | auto bitmap = bundle.GetBitmap(size); 71 | 72 | if (dark) { 73 | wxAlphaPixelData bmdata(bitmap); 74 | wxAlphaPixelData::Iterator dst(bmdata); 75 | 76 | // NOLINTNEXTLINE(readability-identifier-length) 77 | for (int y = 0; y < bitmap.GetHeight(); ++y) { 78 | dst.MoveTo(bmdata, 0, y); 79 | // NOLINTNEXTLINE(readability-identifier-length) 80 | for (int x = 0; x < bitmap.GetWidth(); ++x) { 81 | const unsigned char alpha = dst.Alpha(); 82 | const unsigned char blue = dst.Blue() + 220; 83 | const unsigned char red = dst.Red() + 220; 84 | const unsigned char green = dst.Green() + 220; 85 | dst.Blue() = static_cast(blue * alpha / ByteMax); 86 | dst.Green() = static_cast(red * alpha / ByteMax); 87 | dst.Red() = static_cast(green * alpha / ByteMax); 88 | dst++; 89 | } 90 | } 91 | } 92 | 93 | mIcons.insert({icon, bitmap}); 94 | } 95 | } 96 | 97 | const wxBitmap &ArtProvider::bitmap(Icon icon) const { 98 | const auto it = mIcons.find(icon); 99 | if (it == mIcons.end()) { return mPlaceholder; } 100 | return it->second; 101 | } 102 | -------------------------------------------------------------------------------- /src/GUI/Models/History.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "GUI/Models/Subscriptions.hpp" 8 | #include "MQTT/Client.hpp" 9 | #include "MQTT/Message.hpp" 10 | #include "MQTT/Subscription.hpp" 11 | 12 | namespace Rapatas::Transmitron::GUI::Models { 13 | 14 | class History : 15 | public wxEvtHandler, 16 | public wxDataViewVirtualListModel, 17 | public Subscriptions::Observer 18 | { 19 | public: 20 | 21 | struct Observer { 22 | Observer() = default; 23 | virtual ~Observer() = default; 24 | Observer(const Observer &) = default; 25 | Observer(Observer &&) = default; 26 | Observer &operator=(const Observer &) = default; 27 | Observer &operator=(Observer &&) = default; 28 | 29 | virtual void onMessage(wxDataViewItem item) = 0; 30 | }; 31 | 32 | enum class Column : uint8_t { 33 | Icon, 34 | Qos, 35 | Topic, 36 | Dt, 37 | Max 38 | }; 39 | 40 | explicit History(const wxObjectDataPtr &subscriptions); 41 | 42 | size_t attachObserver(Observer *observer); 43 | bool detachObserver(size_t id); 44 | void clear(); 45 | bool load(const std::string &recording); 46 | void setFilter(const std::string &filter); 47 | void setSelected(const wxDataViewItem &item); 48 | void showDt(bool show); 49 | 50 | [[nodiscard]] std::string getPayload(const wxDataViewItem &item) const; 51 | [[nodiscard]] std::string getTopic(const wxDataViewItem &item) const; 52 | [[nodiscard]] MQTT::QoS getQos(const wxDataViewItem &item) const; 53 | [[nodiscard]] bool getRetained(const wxDataViewItem &item) const; 54 | [[nodiscard]] std::string getFilter() const; 55 | [[nodiscard]] nlohmann::json toJson() const; 56 | [[nodiscard]] const MQTT::Message &getMessage( // 57 | const wxDataViewItem &item 58 | ) const; 59 | 60 | private: 61 | 62 | struct Node { 63 | MQTT::Message message; 64 | MQTT::Subscription::Id subscriptionId{}; 65 | }; 66 | 67 | std::shared_ptr mLogger; 68 | std::vector mMessages; 69 | std::vector mRemap; 70 | wxObjectDataPtr mSubscriptions; 71 | std::map mObservers; 72 | std::string mFilter; 73 | wxDataViewItem mSelected; 74 | bool mShowDt = false; 75 | 76 | void remap(); 77 | void refresh(MQTT::Subscription::Id subscriptionId); 78 | std::chrono::milliseconds deltaToSelected(size_t row) const; 79 | 80 | // wxDataViewVirtualListModel interface. 81 | [[nodiscard]] unsigned GetColumnCount() const override; 82 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 83 | [[nodiscard]] unsigned GetCount() const override; 84 | void GetValueByRow( 85 | wxVariant &variant, 86 | unsigned int row, 87 | unsigned int col // 88 | ) const override; 89 | bool GetAttrByRow( 90 | unsigned int row, 91 | unsigned int col, 92 | wxDataViewItemAttr &attr 93 | ) const override; 94 | bool SetValueByRow( 95 | const wxVariant &variant, 96 | unsigned int row, 97 | unsigned int col 98 | ) override; 99 | 100 | // Models::Subscriptions::Observer interface. 101 | void onMuted(MQTT::Subscription::Id subscriptionId) override; 102 | void onUnmuted(MQTT::Subscription::Id subscriptionId) override; 103 | void onSolo(MQTT::Subscription::Id subscriptionId) override; 104 | void onUnsubscribed(MQTT::Subscription::Id subscriptionId) override; 105 | void onCleared(MQTT::Subscription::Id subscriptionId) override; 106 | void onColorSet( 107 | MQTT::Subscription::Id subscriptionId, 108 | wxColor color // 109 | ) override; 110 | void onMessage( 111 | MQTT::Subscription::Id subscriptionId, 112 | const MQTT::Message &message 113 | ) override; 114 | }; 115 | 116 | } // namespace Rapatas::Transmitron::GUI::Models 117 | -------------------------------------------------------------------------------- /src/GUI/Models/Messages.cpp: -------------------------------------------------------------------------------- 1 | #include "Messages.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "Common/Log.hpp" 10 | #include "MQTT/Message.hpp" 11 | 12 | using namespace Rapatas::Transmitron; 13 | using namespace GUI::Models; 14 | using namespace Common; 15 | 16 | constexpr std::string_view NewName{"New message"}; 17 | 18 | Messages::Messages(const ArtProvider &artProvider) : 19 | FsTree("Messages", static_cast(Column::Max), artProvider), 20 | mArtProvider(artProvider) // 21 | { 22 | mLogger = Common::Log::create("Models::Messages"); 23 | } 24 | 25 | bool Messages::load(const std::string &messagesDir) { 26 | mMessagesDir = messagesDir + "/snippets"; 27 | return FsTree::load(mMessagesDir); 28 | } 29 | 30 | MQTT::Message Messages::getMessage(wxDataViewItem item) const { 31 | auto *leaf = getLeaf(item); 32 | if (leaf == nullptr) { return {}; } 33 | 34 | // NOLINTNEXTLINE 35 | return *dynamic_cast(leaf); 36 | } 37 | 38 | std::set Messages::getKnownTopics() const { 39 | std::set result; 40 | for (const auto &[nodeId, leaf] : getLeafs()) { 41 | const auto &message = *dynamic_cast(leaf); 42 | result.insert(message.topic); 43 | } 44 | return result; 45 | } 46 | 47 | wxDataViewItem Messages::createMessage( 48 | wxDataViewItem parentItem, 49 | const MQTT::Message &message 50 | ) { 51 | const wxDataViewItem parent(nullptr); 52 | const std::string uniqueName = createUniqueName(parent, NewName); 53 | auto leaf = std::make_unique(message); 54 | return leafCreate(parentItem, std::move(leaf), uniqueName); 55 | } 56 | 57 | wxDataViewItem Messages::replace( 58 | wxDataViewItem item, 59 | const MQTT::Message &message 60 | ) { 61 | return FsTree::leafReplace(item, std::make_unique(message)); 62 | } 63 | 64 | void Messages::leafValue(Id id, wxDataViewIconText &value, unsigned int col) 65 | const { 66 | (void)col; 67 | 68 | const auto item = toItem(id); 69 | auto *leaf = getLeaf(item); 70 | auto &message = *dynamic_cast(leaf); 71 | 72 | wxIcon icon; 73 | if (message.payload.empty()) { 74 | icon.CopyFromBitmap(mArtProvider.bitmap(Icon::File)); 75 | } else { 76 | icon.CopyFromBitmap(mArtProvider.bitmap(Icon::FileFull)); 77 | } 78 | value.SetIcon(icon); 79 | } 80 | 81 | std::unique_ptr Messages::leafLoad( 82 | Id id, 83 | const Common::fs::path &path 84 | ) { 85 | (void)id; 86 | 87 | std::ifstream messageFile(path); 88 | if (!messageFile.is_open()) { return nullptr; } 89 | 90 | std::stringstream buffer; 91 | buffer << messageFile.rdbuf(); 92 | const std::string &sbuffer = buffer.str(); 93 | 94 | MQTT::Message message; 95 | 96 | if (!sbuffer.empty()) { 97 | if (!nlohmann::json::accept(sbuffer)) { return nullptr; } 98 | auto data = nlohmann::json::parse(sbuffer); 99 | message = MQTT::Message::fromJson(data); 100 | } 101 | 102 | auto leaf = std::make_unique(message); 103 | 104 | leaf->payload = message.payload; 105 | leaf->qos = message.qos; 106 | leaf->retained = message.retained; 107 | leaf->timestamp = message.timestamp; 108 | leaf->topic = message.topic; 109 | 110 | return leaf; 111 | } 112 | 113 | bool Messages::leafSave(Id id) { 114 | const auto path = getNodePath(id); 115 | std::ofstream output(path); 116 | if (!output.is_open()) { return false; } 117 | 118 | const auto item = toItem(id); 119 | auto *leaf = getLeaf(item); 120 | auto &message = *dynamic_cast(leaf); 121 | 122 | output << message.toJson(); 123 | 124 | return true; 125 | } 126 | 127 | bool Messages::isLeaf(const Common::fs::directory_entry &entry) const { 128 | return entry.status().type() != fs::file_type::directory; 129 | } 130 | 131 | Messages::Message::Message(const MQTT::Message &msg) : 132 | MQTT::Message(msg) // 133 | {} 134 | -------------------------------------------------------------------------------- /src/GUI/Models/Profiles.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "GUI/Events/Layout.hpp" 9 | #include "GUI/Models/FsTree.hpp" 10 | #include "GUI/Models/KnownTopics.hpp" 11 | #include "GUI/Types/ClientOptions.hpp" 12 | #include "Layouts.hpp" 13 | #include "MQTT/BrokerOptions.hpp" 14 | #include "Messages.hpp" 15 | 16 | namespace Rapatas::Transmitron::GUI::Models { 17 | 18 | class Profiles : public FsTree 19 | { 20 | public: 21 | 22 | enum class Column : uint8_t { 23 | Name, 24 | URL, 25 | Max 26 | }; 27 | 28 | explicit Profiles( 29 | const wxObjectDataPtr &layouts, 30 | const ArtProvider &artProvider 31 | ); 32 | 33 | bool load(const std::string &configDir, const std::string &cacheDir); 34 | 35 | bool updateBrokerOptions( 36 | wxDataViewItem item, 37 | const MQTT::BrokerOptions &brokerOptions 38 | ); 39 | bool updateClientOptions( 40 | wxDataViewItem item, 41 | const Types::ClientOptions &clientOptions 42 | ); 43 | wxDataViewItem createProfile(wxDataViewItem parentItem); 44 | void updateQuickConnect(const std::string &url); 45 | 46 | [[nodiscard]] const MQTT::BrokerOptions &getBrokerOptions( // 47 | wxDataViewItem item 48 | ) const; 49 | [[nodiscard]] const Types::ClientOptions &getClientOptions( // 50 | wxDataViewItem item 51 | ) const; 52 | [[nodiscard]] wxDataViewItem getQuickConnect() const; 53 | 54 | wxObjectDataPtr getMessagesModel(wxDataViewItem item); 55 | wxObjectDataPtr getTopicsSubscribed(wxDataViewItem item); 56 | wxObjectDataPtr getTopicsPublished(wxDataViewItem item); 57 | 58 | private: 59 | 60 | struct Profile : public FsTree::Leaf { 61 | Profile(const Profiles &profiles, const ArtProvider &artProvider); 62 | 63 | bool saveOptionsBroker(const Common::fs::path &path); 64 | bool saveOptionsClient(const Common::fs::path &path); 65 | bool save(const Common::fs::path &config, const Common::fs::path &cache); 66 | bool load(const Common::fs::path &config, const Common::fs::path &cache); 67 | [[nodiscard]] bool saveCache(const Common::fs::path &path) const; 68 | 69 | std::optional loadOptionsBroker( 70 | const Common::fs::path &directory 71 | ); 72 | std::optional loadOptionsClient( 73 | const Common::fs::path &directory 74 | ); 75 | 76 | std::shared_ptr mLogger; 77 | const Profiles &mProfiles; 78 | const ArtProvider &mArtProvider; 79 | 80 | Types::ClientOptions clientOptions; 81 | MQTT::BrokerOptions brokerOptions; 82 | wxObjectDataPtr messages; 83 | wxObjectDataPtr topicsSubscribed; 84 | wxObjectDataPtr topicsPublished; 85 | }; 86 | 87 | static constexpr std::string_view 88 | BrokerOptionsFilename = "broker-options.json"; 89 | static constexpr std::string_view 90 | ClientOptionsFilename = "client-options.json"; 91 | 92 | std::shared_ptr mLogger; 93 | const ArtProvider &mArtProvider; 94 | std::string mConfigProfilesDir; 95 | std::string mCacheProfilesDir; 96 | wxObjectDataPtr mLayoutsModel; 97 | Id mQuickConnectId{}; 98 | Profile mQuickConnect; 99 | 100 | void createQuickConnect(); 101 | [[nodiscard]] bool ensureDirectoryExists(const std::string &dir) const; 102 | void renameLayoutIfMissing(const std::string &newName); 103 | 104 | void onLayoutRemoved(Events::Layout &event); 105 | void onLayoutChanged(Events::Layout &event); 106 | 107 | [[nodiscard]] bool isLeaf( // 108 | const Common::fs::directory_entry &entry 109 | ) const override; 110 | std::unique_ptr leafLoad(Id id, const Common::fs::path &path) override; 111 | void leafValue( 112 | Id id, 113 | wxDataViewIconText &value, 114 | unsigned int col // 115 | ) const override; 116 | bool leafSave(Id id) override; 117 | }; 118 | 119 | } // namespace Rapatas::Transmitron::GUI::Models 120 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set( 3 | ${TRANSMITRON_BIN_NAME}_SOURCE 4 | 5 | Arguments.cpp 6 | Common/Console.cpp 7 | Common/Env.Linux.cpp 8 | Common/Env.Windows.cpp 9 | Common/Extract.cpp 10 | Common/Helpers.cpp 11 | Common/Log.cpp 12 | Common/String.cpp 13 | Common/Url.cpp 14 | Common/XdgBaseDir.Linux.cpp 15 | Common/XdgBaseDir.Windows.cpp 16 | Common/XdgBaseDir.cpp 17 | GUI/App.cpp 18 | GUI/ArtProvider.cpp 19 | GUI/Events/Connection.cpp 20 | GUI/Events/Edit.cpp 21 | GUI/Events/Layout.cpp 22 | GUI/Events/Profile.cpp 23 | GUI/Events/Recording.cpp 24 | GUI/Events/Subscription.cpp 25 | GUI/Events/TopicCtrl.cpp 26 | GUI/Models/FsTree.cpp 27 | GUI/Models/History.cpp 28 | GUI/Models/KnownTopics.cpp 29 | GUI/Models/Layouts.cpp 30 | GUI/Models/Messages.cpp 31 | GUI/Models/Profiles.cpp 32 | GUI/Models/ProfilesWrapper.cpp 33 | GUI/Models/Subscriptions.cpp 34 | GUI/Notifiers/Layouts.cpp 35 | GUI/Resources/history/history-18x14.cpp 36 | GUI/Resources/messages/messages-18x14.cpp 37 | GUI/Resources/pin/not-pinned-18x18.cpp 38 | GUI/Resources/pin/pinned-18x18.cpp 39 | GUI/Resources/preview/preview-18x14.cpp 40 | GUI/Resources/qos/qos-0.cpp 41 | GUI/Resources/qos/qos-1.cpp 42 | GUI/Resources/qos/qos-2.cpp 43 | GUI/Resources/send/send-18x14.cpp 44 | GUI/Resources/subscription/subscription-18x14.cpp 45 | GUI/Tabs/Client.cpp 46 | GUI/Tabs/Homepage.cpp 47 | GUI/Tabs/Settings.cpp 48 | GUI/Types/ClientOptions.cpp 49 | GUI/Types/Subscription.cpp 50 | GUI/Widgets/Edit.cpp 51 | GUI/Widgets/Layouts.cpp 52 | GUI/Widgets/TopicCtrl.cpp 53 | MQTT/BrokerOptions.cpp 54 | MQTT/Client.cpp 55 | MQTT/Message.cpp 56 | MQTT/Subscription.cpp 57 | main.cpp 58 | 59 | ) 60 | 61 | set(INFO_PRE_CONF "${CMAKE_CURRENT_SOURCE_DIR}/Common/Info.in.cpp") 62 | set(INFO_POST_CONF "${CMAKE_BINARY_DIR}/Common/Info.cpp") 63 | list(APPEND ${TRANSMITRON_BIN_NAME}_SOURCE ${INFO_POST_CONF}) 64 | 65 | set(VERSION_PRE_CONF "${CMAKE_CURRENT_SOURCE_DIR}/Common/Version.in.cpp") 66 | set(VERSION_POST_CONF "${CMAKE_BINARY_DIR}/Common/Version.cpp") 67 | list(APPEND ${TRANSMITRON_BIN_NAME}_SOURCE ${VERSION_POST_CONF}) 68 | 69 | configure_file(${INFO_PRE_CONF} ${INFO_POST_CONF} @ONLY) 70 | configure_file(${VERSION_PRE_CONF} ${VERSION_POST_CONF} @ONLY) 71 | 72 | if (WIN32) 73 | 74 | set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -static -static-libstdc++") 75 | set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -static -static-libgcc") 76 | 77 | set(RESOURCE_PRE_CONF "${CMAKE_CURRENT_SOURCE_DIR}/transmitron.in.rc") 78 | set(RESOURCE_POST_CONF "${CMAKE_BINARY_DIR}/transmitron.rc") 79 | list(APPEND ${PROJECT_NAME}_SOURCE ${RESOURCE_POST_CONF}) 80 | 81 | configure_file( 82 | ${RESOURCE_PRE_CONF} 83 | ${RESOURCE_POST_CONF} 84 | @ONLY 85 | ) 86 | 87 | endif() 88 | 89 | add_executable(${TRANSMITRON_BIN_NAME} ${${TRANSMITRON_BIN_NAME}_SOURCE}) 90 | 91 | target_compile_features( 92 | ${TRANSMITRON_BIN_NAME} 93 | PRIVATE 94 | cxx_std_17 95 | ) 96 | 97 | set_target_properties( 98 | ${TRANSMITRON_BIN_NAME} 99 | PROPERTIES 100 | CXX_STANDARD 17 101 | CXX_EXTENSIONS OFF 102 | ) 103 | 104 | target_include_directories(${TRANSMITRON_BIN_NAME} PRIVATE . ) 105 | 106 | set(gcc_warnings 107 | -Werror 108 | -Wall 109 | -Wextra 110 | -Wconversion 111 | -Wsign-conversion 112 | -Wfloat-conversion 113 | -Wpedantic 114 | ) 115 | 116 | target_compile_options( 117 | ${TRANSMITRON_BIN_NAME} 118 | PRIVATE 119 | $<$: ${gcc_warnings}> 120 | $<$: ${gcc_warnings}> 121 | ) 122 | 123 | target_compile_definitions( 124 | ${TRANSMITRON_BIN_NAME} 125 | PRIVATE 126 | wxUSE_THREADS=1 127 | ) 128 | 129 | target_link_options( 130 | ${TRANSMITRON_BIN_NAME} 131 | PRIVATE 132 | $<$,$>:-mwindows> 133 | ) 134 | 135 | target_link_libraries( 136 | ${TRANSMITRON_BIN_NAME} 137 | PRIVATE 138 | CLI11::CLI11 139 | PahoMqttCpp::paho-mqttpp3-static 140 | Threads::Threads 141 | date::date 142 | fmt::fmt 143 | nlohmann_json::nlohmann_json 144 | spdlog::spdlog 145 | stdc++fs 146 | tinyxml2::tinyxml2 147 | wxWidgets::wxWidgets 148 | ) 149 | -------------------------------------------------------------------------------- /resources/update-alternatives-clang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # --slave /usr/bin/$1 $1 /usr/bin/$1-\${version} \\ 4 | 5 | function register_clang_version { 6 | local version=$1 7 | local priority=$2 8 | 9 | update-alternatives \ 10 | --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-${version} ${priority} \ 11 | --slave /usr/bin/llvm-ar llvm-ar /usr/bin/llvm-ar-${version} \ 12 | --slave /usr/bin/llvm-as llvm-as /usr/bin/llvm-as-${version} \ 13 | --slave /usr/bin/llvm-bcanalyzer llvm-bcanalyzer /usr/bin/llvm-bcanalyzer-${version} \ 14 | --slave /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-${version} \ 15 | --slave /usr/bin/llvm-diff llvm-diff /usr/bin/llvm-diff-${version} \ 16 | --slave /usr/bin/llvm-dis llvm-dis /usr/bin/llvm-dis-${version} \ 17 | --slave /usr/bin/llvm-dwarfdump llvm-dwarfdump /usr/bin/llvm-dwarfdump-${version} \ 18 | --slave /usr/bin/llvm-extract llvm-extract /usr/bin/llvm-extract-${version} \ 19 | --slave /usr/bin/llvm-link llvm-link /usr/bin/llvm-link-${version} \ 20 | --slave /usr/bin/llvm-mc llvm-mc /usr/bin/llvm-mc-${version} \ 21 | --slave /usr/bin/llvm-mcmarkup llvm-mcmarkup /usr/bin/llvm-mcmarkup-${version} \ 22 | --slave /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-${version} \ 23 | --slave /usr/bin/llvm-objdump llvm-objdump /usr/bin/llvm-objdump-${version} \ 24 | --slave /usr/bin/llvm-ranlib llvm-ranlib /usr/bin/llvm-ranlib-${version} \ 25 | --slave /usr/bin/llvm-readobj llvm-readobj /usr/bin/llvm-readobj-${version} \ 26 | --slave /usr/bin/llvm-rtdyld llvm-rtdyld /usr/bin/llvm-rtdyld-${version} \ 27 | --slave /usr/bin/llvm-size llvm-size /usr/bin/llvm-size-${version} \ 28 | --slave /usr/bin/llvm-stress llvm-stress /usr/bin/llvm-stress-${version} \ 29 | --slave /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-${version} \ 30 | --slave /usr/bin/llvm-tblgen llvm-tblgen /usr/bin/llvm-tblgen-${version} 31 | 32 | update-alternatives \ 33 | --install /usr/bin/clang clang /usr/bin/clang-${version} ${priority} \ 34 | --slave /usr/bin/clang++ clang++ /usr/bin/clang++-${version} \ 35 | --slave /usr/bin/asan_symbolize asan_symbolize /usr/bin/asan_symbolize-${version} \ 36 | --slave /usr/bin/c-index-test c-index-test /usr/bin/c-index-test-${version} \ 37 | --slave /usr/bin/clang-check clang-check /usr/bin/clang-check-${version} \ 38 | --slave /usr/bin/clang-cl clang-cl /usr/bin/clang-cl-${version} \ 39 | --slave /usr/bin/clang-cpp clang-cpp /usr/bin/clang-cpp-${version} \ 40 | --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-${version} \ 41 | --slave /usr/bin/clang-format-diff clang-format-diff /usr/bin/clang-format-diff-${version} \ 42 | --slave /usr/bin/clang-import-test clang-import-test /usr/bin/clang-import-test-${version} \ 43 | --slave /usr/bin/clang-include-fixer clang-include-fixer /usr/bin/clang-include-fixer-${version} \ 44 | --slave /usr/bin/clang-offload-bundler clang-offload-bundler /usr/bin/clang-offload-bundler-${version} \ 45 | --slave /usr/bin/clang-query clang-query /usr/bin/clang-query-${version} \ 46 | --slave /usr/bin/clang-rename clang-rename /usr/bin/clang-rename-${version} \ 47 | --slave /usr/bin/clang-reorder-fields clang-reorder-fields /usr/bin/clang-reorder-fields-${version} \ 48 | --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${version} \ 49 | --slave /usr/bin/lldb lldb /usr/bin/lldb-${version} \ 50 | --slave /usr/bin/lldb-server lldb-server /usr/bin/lldb-server-${version} 51 | } 52 | 53 | register_clang_version $1 $2 54 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.1] - 2024-11-11 4 | 5 | ### Fixed 6 | 7 | - Crash when renaming the same profile twice 8 | 9 | ## [1.0.0] - 2024-11-05 10 | 11 | ### Added 12 | 13 | - Folders for profiles 14 | - Support for SSL connection 15 | - Option to display Δt of history 16 | - New layouts: `IDE`, `Casual`, `Default` 17 | 18 | ### Fixed 19 | 20 | - Crash on empty subscription 21 | - Emojis not displaying in tab name 22 | - Panels disappearing after switching layout 23 | - Failure to save profile when the name contains a dot 24 | 25 | ## [0.0.5] - 2024-01-29 26 | 27 | ### Added 28 | 29 | - Discrete buttons for profile settings 30 | - Forwarding of errors to Windows Event Viewer 31 | - Organized settings into sections 32 | - Quick connect field 33 | - Support for building on ubuntu 18.04 34 | - Support for dark mode icons 35 | - Tooltips to buttons 36 | - Upgrade wxWidgets to latest version (v3.2.4) 37 | - Visual connection status indicator 38 | - `--verbose` command line argument 39 | 40 | ### Fixed 41 | 42 | - Crash on first publish of new profile 43 | - Binary formatter corrupts payload 44 | - Message timestamp displaying in UTC instead of local time 45 | - Using legacy UI framework on windows 46 | - Visual artifact when hovering settings tab on windows 47 | 48 | ## [0.0.4] - 2022-07-02 49 | 50 | ### Added 51 | 52 | - History recording and playback. 53 | - Hex-dump payload formatting. 54 | - Cancel button in homepage. 55 | - Associated .TMRC file-type with Transmitron. 56 | 57 | ### Fixed 58 | 59 | - Windows desktop shortcut not created during installation. 60 | - Crash when saving snippet on a renamed profile. 61 | - MQTT username and password not applied to connection. 62 | - Client ID not populated during profile creation. 63 | - Deleting profile does not clear property grid. 64 | - Ctrl-A triggering the bell on Windows. 65 | 66 | ## [0.0.3] - 2022-05-29 67 | 68 | ### Added 69 | 70 | - Auto complete topics for publish and subscribe fields. 71 | - Command line argument to launch directly into a profile. 72 | - Layout field in profile options. 73 | - Keyboard shortcut to close tab (ctrl-w). 74 | - Keyboard shortcut to open new tab (ctrl-t). 75 | - Label to the store message button. 76 | - Label to the layouts drop-down menu. 77 | 78 | ### Fixed 79 | 80 | - Tab key navigating out of order. 81 | - Broker timeouts parsed as milliseconds. 82 | - Retained option applied when selecting a retained message. 83 | - Not allowing to remove subscribed topic while disconnected. 84 | - Button padding clipping icon on some GTK themes. 85 | 86 | ### Changed 87 | 88 | - Default timeout values set to 5 seconds. 89 | - When creating a new profile, start editing the name field. 90 | - Use Hostname as client ID when none is provided. 91 | 92 | ## [0.0.2] - 2021-11-05 93 | 94 | ### Added 95 | 96 | - Show selected snippet name on publish editor. 97 | 98 | ### Fixed 99 | 100 | - Black caret when using dark background. 101 | - Snippet remained highlighted when selecting a message from history. 102 | - Missing background color for lexical error in editors. 103 | 104 | ## [0.0.1] - 2021-10-21 105 | 106 | ### Added 107 | 108 | - Line numbers in editors. 109 | - Header and banner in NSIS installer. 110 | - Save icon in profile save button. 111 | - Label on format drop-down of editors. 112 | 113 | ### Fixed 114 | 115 | - Crash when subscribing to an invalid topic. 116 | - Package description for `.deb` installer 117 | - Overwrite snippet context option appearing for folders. 118 | 119 | ## [0.0.0] - 2021-09-18 120 | 121 | ### Added 122 | 123 | - **Profiles.** Store connections to brokers. 124 | - **Multiple Connections.** Connect to multiple `Profiles` at the same time using tabs. 125 | - **Snippets.** Store messages in a nested folder structure, ready to publish. 126 | - **Folding.** For messages with nested data. 127 | - **Syntax highlight, detection & formatting.** Supports JSON and XML. 128 | - **Flexible.** Resize, drag, detach or hide each sidebar separately. 129 | - **Layouts.** Store sidebar locations and sizes. 130 | - **XDG BaseDir.** Respects the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) specification. 131 | - **Native UI.** Using `wxWidgets` to integrate seamlessly with your desktop theme. 132 | - **Mute / Solo.** Hide or isolate messages for each subscription. 133 | - **History Filter.** Limit history using search terms. 134 | - **Cross-Platform.** Built for Windows and Linux. 135 | -------------------------------------------------------------------------------- /src/GUI/Tabs/Settings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "GUI/ArtProvider.hpp" 12 | #include "GUI/Models/Layouts.hpp" 13 | #include "GUI/Models/Profiles.hpp" 14 | 15 | namespace Rapatas::Transmitron::GUI::Tabs { 16 | 17 | class Settings : public wxPanel 18 | { 19 | public: 20 | 21 | explicit Settings( 22 | wxWindow *parent, 23 | const ArtProvider &artProvider, 24 | wxFontInfo labelFont, 25 | int optionsHeight, 26 | const wxObjectDataPtr &profilesModel, 27 | const wxObjectDataPtr &layoutsModel 28 | ); 29 | 30 | void createProfile(); 31 | void selectProfile(wxDataViewItem profile); 32 | 33 | private: 34 | 35 | enum class ContextIDs : uint8_t { 36 | LayoutsDelete, 37 | LayoutsRename, 38 | ProfilesDelete, 39 | ProfilesNewFolder, 40 | ProfilesNewProfile, 41 | ProfilesRename, 42 | }; 43 | 44 | enum Properties : uint8_t { 45 | AutoReconnect, 46 | ClientId, 47 | ConnectTimeout, 48 | DisconnectTimeout, 49 | Hostname, 50 | KeepAlive, 51 | MaxInFlight, 52 | MaxReconnectRetries, 53 | Password, 54 | Port, 55 | SSL, 56 | Username, 57 | Layout, 58 | Max, 59 | }; 60 | 61 | wxFontInfo mLabelFont; 62 | int mOptionsHeight; 63 | const ArtProvider &mArtProvider; 64 | 65 | std::shared_ptr mLogger; 66 | 67 | // Profiles. 68 | wxPanel *mProfiles = nullptr; 69 | wxDataViewCtrl *mProfilesCtrl = nullptr; 70 | wxObjectDataPtr mProfilesModel; 71 | wxBoxSizer *mProfileOptionsSizer = nullptr; 72 | wxButton *mProfileDelete = nullptr; 73 | wxButton *mConnect = nullptr; 74 | wxButton *mSave = nullptr; 75 | wxButton *mCancel = nullptr; 76 | bool mProfilesWasExpanded = false; 77 | std::pair mProfilesPossible; 78 | wxDataViewColumn *mProfileColumnName = nullptr; 79 | 80 | std::vector mProfileProperties; 81 | wxBoxSizer *mProfileButtonsSizer = nullptr; 82 | wxPanel *mProfileOptions = nullptr; 83 | wxPropertyCategory *mGridCategoryBroker = nullptr; 84 | wxPropertyCategory *mGridCategoryClient = nullptr; 85 | wxPropertyGrid *mProfileGrid = nullptr; 86 | 87 | // Layouts. 88 | wxPanel *mLayouts = nullptr; 89 | wxDataViewListCtrl *mLayoutsCtrl = nullptr; 90 | wxObjectDataPtr mLayoutsModel; 91 | wxDataViewColumn *mLayoutColumnName = nullptr; 92 | wxButton *mLayoutDelete = nullptr; 93 | 94 | // Navigation. 95 | wxListCtrl *mSections = nullptr; 96 | wxBoxSizer *mSectionSizer = nullptr; 97 | 98 | void setupLayouts(wxPanel *parent); 99 | void setupProfiles(wxPanel *parent); 100 | void setupProfileOptions(wxPanel *parent); 101 | void setupProfileButtons(wxPanel *parent); 102 | 103 | void propertyGridClear(); 104 | void propertyGridFill( 105 | const MQTT::BrokerOptions &brokerOptions, 106 | const Types::ClientOptions &clientOptions 107 | ); 108 | [[nodiscard]] MQTT::BrokerOptions brokerOptionsFromPropertyGrid() const; 109 | [[nodiscard]] Types::ClientOptions clientOptionsFromPropertyGrid() const; 110 | void allowSaveProfile(); 111 | void allowConnect(); 112 | void refreshLayouts(); 113 | 114 | void onContextSelected(wxCommandEvent &event); 115 | 116 | void onLayoutsContext(wxDataViewEvent &event); 117 | void onLayoutsDelete(wxCommandEvent &event); 118 | void onLayoutsRename(wxCommandEvent &event); 119 | void onLayoutsEdit(wxDataViewEvent &event); 120 | 121 | void onLayoutAdded(Events::Layout &event); 122 | void onLayoutChanged(Events::Layout &event); 123 | void onLayoutRemoved(Events::Layout &event); 124 | void onLayoutSelected(wxDataViewEvent &event); 125 | 126 | void onProfileContext(wxDataViewEvent &event); 127 | void onProfileDelete(wxCommandEvent &event); 128 | void onProfileRename(wxCommandEvent &event); 129 | void onProfileSelected(wxDataViewEvent &event); 130 | void onProfileDrag(wxDataViewEvent &event); 131 | void onProfileDrop(wxDataViewEvent &event); 132 | void onProfileDropPossible(wxDataViewEvent &event); 133 | void onProfileNewFolder(wxCommandEvent &event); 134 | void onProfileNewProfile(wxCommandEvent &event); 135 | 136 | void onProfileGridChanged(wxPropertyGridEvent &event); 137 | 138 | void onButtonClickedNewProfile(wxCommandEvent &event); 139 | void onButtonClickedProfileDelete(wxCommandEvent &event); 140 | void onButtonClickedCancel(wxCommandEvent &event); 141 | void onButtonClickedSave(wxCommandEvent &event); 142 | void onButtonClickedConnect(wxCommandEvent &event); 143 | 144 | void onSectionSelected(wxListEvent &event); 145 | }; 146 | 147 | } // namespace Rapatas::Transmitron::GUI::Tabs 148 | -------------------------------------------------------------------------------- /src/GUI/Models/FsTree.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "GUI/ArtProvider.hpp" 11 | 12 | namespace Rapatas::Transmitron::GUI::Models { 13 | 14 | class FsTree : public wxDataViewModel 15 | { 16 | public: 17 | 18 | using Id = size_t; 19 | using Item = wxDataViewItem; 20 | 21 | class Leaf 22 | { 23 | public: 24 | 25 | Leaf() = default; 26 | virtual ~Leaf() = default; 27 | Leaf(const Leaf &other) = delete; 28 | Leaf(Leaf &&other) = delete; 29 | Leaf &operator=(const Leaf &other) = delete; 30 | Leaf &operator=(Leaf &&other) = delete; 31 | }; 32 | 33 | enum Column : uint8_t { 34 | Name, 35 | Max 36 | }; 37 | 38 | explicit FsTree( 39 | const std::string &name, 40 | size_t columnCount, 41 | const ArtProvider &artProvider 42 | ); 43 | 44 | static Item getRootItem(); 45 | 46 | Item createFolder(Item parent); 47 | bool rename(Item item, const std::string &name); 48 | bool remove(Item item); 49 | 50 | Item moveBefore(Item item, Item sibling); 51 | Item moveAfter(Item item, Item sibling); 52 | Item moveInsideFirst(Item item, Item parent); 53 | Item moveInsideLast(Item item, Item parent); 54 | Item moveInsideAtIndex(Item item, Item parent, size_t index); 55 | 56 | [[nodiscard]] bool hasChildNamed(Item parent, const std::string &name) const; 57 | [[nodiscard]] Item getItemFromName(const std::string &name) const; 58 | [[nodiscard]] std::string getName(Item item) const; 59 | 60 | [[nodiscard]] unsigned GetColumnCount() const override; 61 | [[nodiscard]] wxString GetColumnType(unsigned int col) const override; 62 | void GetValue( 63 | wxVariant &variant, 64 | const Item &item, 65 | unsigned int col // 66 | ) const override; 67 | bool SetValue( 68 | const wxVariant &value, 69 | const Item &item, 70 | unsigned int col // 71 | ) override; 72 | [[nodiscard]] bool IsEnabled( 73 | const Item &item, 74 | unsigned int col // 75 | ) const override; 76 | [[nodiscard]] Item GetParent(const Item &item) const override; 77 | [[nodiscard]] bool IsContainer(const Item &item) const override; 78 | unsigned int GetChildren( 79 | const Item &parent, 80 | wxDataViewItemArray &array // 81 | ) const override; 82 | 83 | protected: 84 | 85 | static Id toId(const Item &item); 86 | static Item toItem(Id id); 87 | 88 | bool load(const std::string &baseDir); 89 | Item leafReplace(Item item, std::unique_ptr leaf); 90 | Item leafCreate( 91 | Item parent, 92 | std::unique_ptr leaf, 93 | const std::string &name 94 | ); 95 | Item leafInsert( 96 | const std::string &name, 97 | std::unique_ptr leaf, 98 | Item parent 99 | ); 100 | 101 | [[nodiscard]] virtual bool isLeaf(const Common::fs::directory_entry &entry 102 | ) const = 0; 103 | virtual std::unique_ptr leafLoad( 104 | Id id, 105 | const Common::fs::path &path 106 | ) = 0; 107 | virtual void leafValue(Id id, wxDataViewIconText &value, unsigned int col) 108 | const = 0; 109 | virtual bool leafSave(Id id) = 0; 110 | 111 | [[nodiscard]] std::map getLeafs() const; 112 | [[nodiscard]] Leaf *getLeaf(Item item) const; 113 | [[nodiscard]] std::string getNodePath(Id id) const; 114 | [[nodiscard]] std::vector getNodeSegments(Id id) const; 115 | [[nodiscard]] std::string createUniqueName(Item parent, std::string_view name) 116 | const; 117 | 118 | private: 119 | 120 | struct Node { 121 | enum class Type : uint8_t { 122 | Folder, 123 | Payload, 124 | }; 125 | 126 | Id parent = 0; 127 | std::string name; 128 | std::string encoded; 129 | Type type = Type::Folder; 130 | std::list children; 131 | std::unique_ptr payload; 132 | bool saved = false; 133 | }; 134 | 135 | std::shared_ptr mLogger; 136 | Id mNextAvailableId = 0; 137 | std::map mNodes; 138 | std::string mBaseDir; 139 | const ArtProvider &mArtProvider; 140 | std::set mIgnoreDirs; 141 | size_t mColumnCount; 142 | 143 | void clear(); 144 | void loadDirectoryRecursive(const Common::fs::path &path, Id parentId); 145 | void loadLeaf(const Common::fs::directory_entry &entry, Id parentId); 146 | bool indexFileRead(const Common::fs::path &path, Id id); 147 | bool indexFileWrite(Id id); 148 | bool save(Id id); 149 | bool saveLeaf(Id id); 150 | bool saveFolder(Id id); 151 | bool moveFile(Id nodeId, Id newParentId); 152 | bool moveCheck(Item item, Item parent, size_t index); 153 | void moveUnderNewParent(Id nodeId, Id newParentId, size_t index); 154 | void moveUnderSameParent(Id nodeId, Id newParentId, size_t index); 155 | Id getNextId(); 156 | 157 | [[nodiscard]] bool isRecursive(Item parent, Item item) const; 158 | }; 159 | 160 | } // namespace Rapatas::Transmitron::GUI::Models 161 | -------------------------------------------------------------------------------- /src/Common/Helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "Helpers.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::chrono; 13 | using namespace Rapatas::Transmitron; 14 | 15 | wxColor Common::Helpers::colorFromNumber(size_t number) { 16 | // NOLINTBEGIN(readability-identifier-length) 17 | // NOLINTBEGIN(hicpp-signed-bitwise) 18 | 19 | constexpr uint8_t ByteMask = 0xFF; 20 | constexpr uint8_t ByteSize = std::numeric_limits::digits; 21 | constexpr uint8_t MinValue = 150; 22 | constexpr uint8_t Offset = 100; 23 | 24 | uint8_t r = ((number >> (ByteSize * 0)) & ByteMask); 25 | uint8_t g = ((number >> (ByteSize * 1)) & ByteMask); 26 | uint8_t b = ((number >> (ByteSize * 2)) & ByteMask); 27 | 28 | if (r < MinValue && g < MinValue && b < MinValue) { 29 | r += Offset; 30 | g += Offset; 31 | b += Offset; 32 | } 33 | 34 | return {r, g, b}; 35 | 36 | // NOLINTEND(hicpp-signed-bitwise) 37 | // NOLINTEND(readability-identifier-length) 38 | } 39 | 40 | std::string Common::Helpers::timeToString( 41 | const system_clock::time_point ×tamp 42 | ) { 43 | const auto floored = floor(timestamp); 44 | const std::time_t nowc = std::chrono::system_clock::to_time_t(floored); 45 | std::tm nowtm{}; 46 | #ifndef _WIN32 47 | ::localtime_r(&nowc, &nowtm); 48 | #else 49 | ::localtime_s(&nowtm, &nowc); 50 | #endif // _WIN32 51 | std::stringstream sstream; 52 | sstream << std::put_time(&nowtm, "%Y-%m-%d %H:%M:%S"); 53 | return sstream.str(); 54 | } 55 | 56 | std::string Common::Helpers::durationToString( 57 | const std::chrono::milliseconds &dur 58 | ) { 59 | const bool negative = dur.count() < 0; 60 | const std::string prefix = negative ? "-" : ""; 61 | milliseconds abs{std::abs(dur.count())}; 62 | 63 | const auto dhours = duration_cast(abs); 64 | const auto dminutes = duration_cast(abs - dhours); 65 | const auto dseconds = duration_cast(abs - dhours - dminutes); 66 | const auto dmsec = duration_cast( 67 | abs - dhours - dminutes - dseconds 68 | ); 69 | 70 | if (dhours.count() == 0 && dminutes.count() == 0 && dseconds.count() == 0) { 71 | return fmt::format( // 72 | "{}{}ms", 73 | prefix, 74 | dmsec.count() 75 | ); 76 | } 77 | 78 | if (dhours.count() == 0 && dminutes.count() == 0) { 79 | return fmt::format( // 80 | "{}{}.{:0<3}s", 81 | prefix, 82 | dseconds.count(), 83 | dmsec.count() 84 | ); 85 | } 86 | 87 | if (dhours.count() == 0) { 88 | return fmt::format( 89 | "{}{:02}:{:02}.{:0<3}", 90 | prefix, 91 | dminutes.count(), 92 | dseconds.count(), 93 | dmsec.count() 94 | ); 95 | } 96 | 97 | return fmt::format( 98 | "{}{:02}:{:02}:{:02}.{:0<3}", 99 | prefix, 100 | dhours.count(), 101 | dminutes.count(), 102 | dseconds.count(), 103 | dmsec.count() 104 | ); 105 | } 106 | 107 | std::string Common::Helpers::timeToFilename( 108 | const system_clock::time_point ×tamp 109 | ) { 110 | const auto floored = floor(timestamp); 111 | const std::time_t nowc = std::chrono::system_clock::to_time_t(floored); 112 | std::tm nowtm{}; 113 | #ifndef _WIN32 114 | ::localtime_r(&nowc, &nowtm); 115 | #else 116 | ::localtime_s(&nowtm, &nowc); 117 | #endif // _WIN32 118 | std::stringstream sstream; 119 | sstream << std::put_time(&nowtm, "%Y%m%dT%H%M%S"); 120 | return sstream.str(); 121 | } 122 | 123 | std::chrono::system_clock::time_point Common::Helpers::stringToTime( 124 | const std::string &line 125 | ) { 126 | system_clock::time_point timestamp; 127 | std::stringstream sstream(line); 128 | sstream >> date::parse("%F %T", timestamp); 129 | return timestamp; 130 | } 131 | 132 | std::string Common::Helpers::hexDump( 133 | const std::vector &bytes, 134 | size_t columns 135 | ) { 136 | constexpr size_t FixedCharacterCount = 9; 137 | const size_t lineWidth = columns * 4 + FixedCharacterCount; 138 | 139 | std::vector buff(columns); 140 | size_t i = 0; // NOLINT 141 | std::string result; 142 | std::string line; 143 | line.reserve(lineWidth); 144 | 145 | for (i = 0; i < bytes.size(); i++) { 146 | if ((i % columns) == 0) { 147 | if (i != 0) { 148 | line += " "; 149 | for (const auto &byte : buff) { line += static_cast(byte); } 150 | result += line + '\n'; 151 | line.clear(); 152 | line.reserve(lineWidth); 153 | } 154 | 155 | line += fmt::format("{:06X}", i); 156 | } 157 | 158 | line += fmt::format(" {:02X}", bytes[i]); 159 | 160 | buff[i % columns] = ::isprint(bytes[i]) != 0 ? bytes[i] : '.'; 161 | } 162 | 163 | while ((i % columns) != 0) { 164 | line += " "; 165 | buff[i % columns] = ' '; 166 | i++; 167 | } 168 | 169 | line += " "; 170 | for (const auto &byte : buff) { line += static_cast(byte); } 171 | result += line + '\n'; 172 | 173 | return result; 174 | } 175 | -------------------------------------------------------------------------------- /src/GUI/Models/KnownTopics.cpp: -------------------------------------------------------------------------------- 1 | #include "KnownTopics.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Common/Log.hpp" 9 | 10 | using namespace Rapatas::Transmitron; 11 | using namespace Common; 12 | using namespace GUI::Models; 13 | 14 | KnownTopics::KnownTopics() { 15 | mLogger = Common::Log::create("Models::KnownTopics"); 16 | remap(); 17 | } 18 | 19 | KnownTopics::~KnownTopics() { save(); } 20 | 21 | bool KnownTopics::load(const Common::fs::path &filepath) { 22 | mLogger->debug("Loading {}", filepath.string()); 23 | mFilepath = filepath; 24 | const bool exists = fs::exists(mFilepath); 25 | if (!exists) { 26 | mLogger->debug( 27 | "Could not load file '{}': Not found. Creating new cache", 28 | mFilepath.string() 29 | ); 30 | save(); 31 | return false; 32 | } 33 | 34 | std::ifstream file(mFilepath); 35 | if (!file.is_open()) { 36 | const auto ec = std::error_code(errno, std::system_category()); 37 | mLogger->warn( 38 | "Could not load file '{}': {}", 39 | mFilepath.string(), 40 | ec.message() // 41 | ); 42 | return false; 43 | } 44 | 45 | std::string line; 46 | while (std::getline(file, line)) { 47 | if (line.empty()) { continue; } 48 | mTopics.insert(line); 49 | } 50 | 51 | remap(); 52 | 53 | return true; 54 | } 55 | 56 | void KnownTopics::clear() { 57 | mTopics.clear(); 58 | remap(); 59 | } 60 | 61 | void KnownTopics::setFilter(std::string filter) { 62 | mFilter = std::move(filter); 63 | remap(); 64 | } 65 | 66 | void KnownTopics::append(std::string topic) { 67 | if (topic == "#") { return; } 68 | if (topic.empty()) { return; } 69 | 70 | const auto it = std::find(std::begin(mTopics), std::end(mTopics), topic); 71 | if (it != std::end(mTopics)) { return; } 72 | 73 | mTopics.insert(std::move(topic)); 74 | remap(); 75 | } 76 | 77 | void KnownTopics::append(std::set topics) { 78 | topics.erase(""); 79 | mTopics.merge(topics); 80 | } 81 | 82 | const std::string &KnownTopics::getTopic(const wxDataViewItem &item) const { 83 | return *(mRemap.at(GetRow(item))); 84 | } 85 | 86 | const std::string &KnownTopics::getFilter() const { return mFilter; } 87 | 88 | void KnownTopics::remap() { 89 | const size_t before = mRemap.size(); 90 | mRemap.clear(); 91 | mRemap.reserve(mTopics.size()); 92 | 93 | for (auto it = std::begin(mTopics); it != std::end(mTopics); ++it) { 94 | const auto &topic = *it; 95 | const auto pos = topic.find(mFilter); 96 | if (mFilter.empty() || pos != std::string::npos) { mRemap.push_back(it); } 97 | } 98 | 99 | mRemap.shrink_to_fit(); 100 | const size_t after = mRemap.size(); 101 | 102 | const auto common = std::min(before, after); 103 | for (uint32_t i = 0; i != common; ++i) { RowChanged(i); } 104 | 105 | if (after > before) { 106 | const size_t diff = after - common; 107 | for (size_t i = 0; i < diff; ++i) { RowAppended(); } 108 | } else if (after < before) { 109 | wxArrayInt rows; 110 | const size_t diff = before - common; 111 | for (size_t i = 0; i < diff; ++i) { rows.Add(static_cast(after + i)); } 112 | RowsDeleted(rows); 113 | } 114 | } 115 | 116 | bool KnownTopics::save(const Common::fs::path &filepath) { 117 | if (filepath.empty()) { return true; } 118 | mFilepath = filepath; 119 | save(); 120 | return true; 121 | } 122 | 123 | void KnownTopics::save() { 124 | if (mFilepath.empty()) { return; } 125 | 126 | auto dir = mFilepath.parent_path(); 127 | fs::create_directories(dir); 128 | 129 | mLogger->info("Saving to {}", mFilepath.string()); 130 | std::ofstream file(mFilepath); 131 | if (!file.is_open()) { 132 | const auto ec = std::error_code(errno, std::system_category()); 133 | mLogger->warn( 134 | "Could not save file '{}': {}", 135 | mFilepath.string(), 136 | ec.message() // 137 | ); 138 | return; 139 | } 140 | 141 | for (const auto &topic : mTopics) { file << topic << "\n"; } 142 | } 143 | 144 | // wxDataViewVirtualListModel interface { 145 | 146 | unsigned KnownTopics::GetColumnCount() const { 147 | return static_cast(Column::Max); 148 | } 149 | 150 | wxString KnownTopics::GetColumnType(unsigned int col) const { 151 | if (static_cast(col) != Column::Topic) { return "string"; } 152 | 153 | return wxDataViewTextRenderer::GetDefaultType(); 154 | } 155 | 156 | unsigned KnownTopics::GetCount() const { 157 | return static_cast(mRemap.size()); 158 | } 159 | 160 | void KnownTopics::GetValueByRow( 161 | wxVariant &variant, 162 | unsigned int row, 163 | unsigned int col 164 | ) const { 165 | if (static_cast(col) != Column::Topic) { return; } 166 | 167 | const auto &topic = *(mRemap.at(row)); 168 | const auto wxs = wxString::FromUTF8(topic.data(), topic.length()); 169 | variant = wxs; 170 | } 171 | 172 | bool KnownTopics::GetAttrByRow( 173 | unsigned int /* row */, 174 | unsigned int /* col */, 175 | wxDataViewItemAttr & /* attr */ 176 | ) const { 177 | return false; 178 | } 179 | 180 | bool KnownTopics::SetValueByRow( 181 | const wxVariant & /* variant */, 182 | unsigned int /* row */, 183 | unsigned int /* col */ 184 | ) { 185 | return false; 186 | } 187 | 188 | // wxDataViewVirtualListModel interface } 189 | -------------------------------------------------------------------------------- /resources/windows/FileAssociation.nsh: -------------------------------------------------------------------------------- 1 | /* 2 | _____________________________________________________________________________ 3 | 4 | File Association 5 | _____________________________________________________________________________ 6 | 7 | Based on code taken from http://nsis.sourceforge.net/File_Association 8 | 9 | Usage in script: 10 | 1. !include "FileAssociation.nsh" 11 | 2. [Section|Function] 12 | ${FileAssociationFunction} "Param1" "Param2" "..." $var 13 | [SectionEnd|FunctionEnd] 14 | 15 | FileAssociationFunction=[RegisterExtension|UnRegisterExtension] 16 | 17 | _____________________________________________________________________________ 18 | 19 | ${RegisterExtension} "[executable]" "[extension]" "[description]" 20 | 21 | "[executable]" ; executable which opens the file format 22 | ; 23 | "[extension]" ; extension, which represents the file format to open 24 | ; 25 | "[description]" ; description for the extension. This will be display in Windows Explorer. 26 | ; 27 | 28 | 29 | ${UnRegisterExtension} "[extension]" "[description]" 30 | 31 | "[extension]" ; extension, which represents the file format to open 32 | ; 33 | "[description]" ; description for the extension. This will be display in Windows Explorer. 34 | ; 35 | 36 | _____________________________________________________________________________ 37 | 38 | Macros 39 | _____________________________________________________________________________ 40 | 41 | Change log window verbosity (default: 3=no script) 42 | 43 | Example: 44 | !include "FileAssociation.nsh" 45 | !insertmacro RegisterExtension 46 | ${FileAssociation_VERBOSE} 4 # all verbosity 47 | !insertmacro UnRegisterExtension 48 | ${FileAssociation_VERBOSE} 3 # no script 49 | */ 50 | 51 | 52 | !ifndef FileAssociation_INCLUDED 53 | !define FileAssociation_INCLUDED 54 | 55 | !include Util.nsh 56 | 57 | !verbose push 58 | !verbose 3 59 | !ifndef _FileAssociation_VERBOSE 60 | !define _FileAssociation_VERBOSE 3 61 | !endif 62 | !verbose ${_FileAssociation_VERBOSE} 63 | !define FileAssociation_VERBOSE `!insertmacro FileAssociation_VERBOSE` 64 | !verbose pop 65 | 66 | !macro FileAssociation_VERBOSE _VERBOSE 67 | !verbose push 68 | !verbose 3 69 | !undef _FileAssociation_VERBOSE 70 | !define _FileAssociation_VERBOSE ${_VERBOSE} 71 | !verbose pop 72 | !macroend 73 | 74 | 75 | 76 | !macro RegisterExtensionCall _EXECUTABLE _EXTENSION _DESCRIPTION 77 | !verbose push 78 | !verbose ${_FileAssociation_VERBOSE} 79 | Push `${_DESCRIPTION}` 80 | Push `${_EXTENSION}` 81 | Push `${_EXECUTABLE}` 82 | ${CallArtificialFunction} RegisterExtension_ 83 | !verbose pop 84 | !macroend 85 | 86 | !macro UnRegisterExtensionCall _EXTENSION _DESCRIPTION 87 | !verbose push 88 | !verbose ${_FileAssociation_VERBOSE} 89 | Push `${_EXTENSION}` 90 | Push `${_DESCRIPTION}` 91 | ${CallArtificialFunction} UnRegisterExtension_ 92 | !verbose pop 93 | !macroend 94 | 95 | 96 | 97 | !define RegisterExtension `!insertmacro RegisterExtensionCall` 98 | !define un.RegisterExtension `!insertmacro RegisterExtensionCall` 99 | 100 | !macro RegisterExtension 101 | !macroend 102 | 103 | !macro un.RegisterExtension 104 | !macroend 105 | 106 | !macro RegisterExtension_ 107 | !verbose push 108 | !verbose ${_FileAssociation_VERBOSE} 109 | 110 | Exch $R2 ;exe 111 | Exch 112 | Exch $R1 ;ext 113 | Exch 114 | Exch 2 115 | Exch $R0 ;desc 116 | Exch 2 117 | Push $0 118 | Push $1 119 | 120 | ReadRegStr $1 HKCR $R1 "" ; read current file association 121 | StrCmp "$1" "" NoBackup ; is it empty 122 | StrCmp "$1" "$R0" NoBackup ; is it our own 123 | WriteRegStr HKCR $R1 "backup_val" "$1" ; backup current value 124 | NoBackup: 125 | WriteRegStr HKCR $R1 "" "$R0" ; set our file association 126 | 127 | ReadRegStr $0 HKCR $R0 "" 128 | StrCmp $0 "" 0 Skip 129 | WriteRegStr HKCR "$R0" "" "$R0" 130 | WriteRegStr HKCR "$R0\shell" "" "open" 131 | WriteRegStr HKCR "$R0\DefaultIcon" "" "$R2,0" 132 | Skip: 133 | WriteRegStr HKCR "$R0\shell\open\command" "" '"$R2" "%1"' 134 | WriteRegStr HKCR "$R0\shell\edit" "" "Edit $R0" 135 | WriteRegStr HKCR "$R0\shell\edit\command" "" '"$R2" "%1"' 136 | 137 | Pop $1 138 | Pop $0 139 | Pop $R2 140 | Pop $R1 141 | Pop $R0 142 | 143 | !verbose pop 144 | !macroend 145 | 146 | 147 | 148 | !define UnRegisterExtension `!insertmacro UnRegisterExtensionCall` 149 | !define un.UnRegisterExtension `!insertmacro UnRegisterExtensionCall` 150 | 151 | !macro UnRegisterExtension 152 | !macroend 153 | 154 | !macro un.UnRegisterExtension 155 | !macroend 156 | 157 | !macro UnRegisterExtension_ 158 | !verbose push 159 | !verbose ${_FileAssociation_VERBOSE} 160 | 161 | Exch $R1 ;desc 162 | Exch 163 | Exch $R0 ;ext 164 | Exch 165 | Push $0 166 | Push $1 167 | 168 | ReadRegStr $1 HKCR $R0 "" 169 | StrCmp $1 $R1 0 NoOwn ; only do this if we own it 170 | ReadRegStr $1 HKCR $R0 "backup_val" 171 | StrCmp $1 "" 0 Restore ; if backup="" then delete the whole key 172 | DeleteRegKey HKCR $R0 173 | Goto NoOwn 174 | 175 | Restore: 176 | WriteRegStr HKCR $R0 "" $1 177 | DeleteRegValue HKCR $R0 "backup_val" 178 | DeleteRegKey HKCR $R1 ;Delete key with association name settings 179 | 180 | NoOwn: 181 | 182 | Pop $1 183 | Pop $0 184 | Pop $R1 185 | Pop $R0 186 | 187 | !verbose pop 188 | !macroend 189 | 190 | !endif # !FileAssociation_INCLUDED 191 | --------------------------------------------------------------------------------