├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── build_linux.yml │ └── build_windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── example └── simple │ ├── CMakeLists.txt │ └── main.cpp └── tray ├── include ├── components │ ├── button.hpp │ ├── imagebutton.hpp │ ├── label.hpp │ ├── separator.hpp │ ├── submenu.hpp │ ├── syncedtoggle.hpp │ └── toggle.hpp ├── core │ ├── entry.hpp │ ├── icon.hpp │ ├── image.hpp │ ├── linux │ │ └── tray.hpp │ ├── traybase.hpp │ └── windows │ │ └── tray.hpp └── tray.hpp └── src ├── components ├── button.cpp ├── imagebutton.cpp ├── label.cpp ├── separator.cpp ├── submenu.cpp ├── syncedtoggle.cpp └── toggle.cpp └── core ├── entry.cpp ├── linux ├── icon.cpp ├── image.cpp └── tray.cpp ├── traybase.cpp └── windows ├── icon.cpp ├── image.cpp └── tray.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: false 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: true 12 | AllowAllConstructorInitializersOnNextLine: true 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: Never 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Empty 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: MultiLine 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterCaseLabel: false 28 | AfterClass: true 29 | AfterControlStatement: true 30 | AfterEnum: true 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterObjCDeclaration: true 34 | AfterStruct: true 35 | AfterUnion: false 36 | AfterExternBlock: true 37 | BeforeCatch: true 38 | BeforeElse: true 39 | IndentBraces: false 40 | SplitEmptyFunction: true 41 | SplitEmptyRecord: true 42 | SplitEmptyNamespace: true 43 | BreakBeforeBinaryOperators: None 44 | BreakBeforeBraces: Custom 45 | BreakBeforeInheritanceComma: false 46 | BreakInheritanceList: BeforeColon 47 | BreakBeforeTernaryOperators: true 48 | BreakConstructorInitializersBeforeComma: false 49 | BreakConstructorInitializers: BeforeColon 50 | BreakAfterJavaFieldAnnotations: false 51 | BreakStringLiterals: true 52 | ColumnLimit: 120 53 | CommentPragmas: '^ IWYU pragma:' 54 | CompactNamespaces: false 55 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 56 | ConstructorInitializerIndentWidth: 4 57 | ContinuationIndentWidth: 4 58 | Cpp11BracedListStyle: true 59 | DeriveLineEnding: true 60 | DerivePointerAlignment: false 61 | DisableFormat: false 62 | ExperimentalAutoDetectBinPacking: false 63 | FixNamespaceComments: true 64 | IndentCaseLabels: false 65 | IndentGotoLabels: true 66 | IndentPPDirectives: None 67 | IndentWidth: 4 68 | IndentWrappedFunctionNames: false 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | MacroBlockBegin: '' 73 | MacroBlockEnd: '' 74 | MaxEmptyLinesToKeep: 1 75 | NamespaceIndentation: All 76 | ObjCBinPackProtocolList: Auto 77 | ObjCBlockIndentWidth: 2 78 | ObjCSpaceAfterProperty: false 79 | ObjCSpaceBeforeProtocolList: true 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 19 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyBreakTemplateDeclaration: 10 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 1000 88 | PointerAlignment: Right 89 | ReflowComments: true 90 | SortIncludes: true 91 | SortUsingDeclarations: true 92 | SpaceAfterCStyleCast: false 93 | SpaceAfterLogicalNot: false 94 | SpaceAfterTemplateKeyword: true 95 | SpaceBeforeAssignmentOperators: true 96 | SpaceBeforeCpp11BracedList: false 97 | SpaceBeforeCtorInitializerColon: true 98 | SpaceBeforeInheritanceColon: true 99 | SpaceBeforeParens: ControlStatements 100 | SpaceBeforeRangeBasedForLoopColon: true 101 | SpaceInEmptyBlock: false 102 | SpaceInEmptyParentheses: false 103 | SpacesBeforeTrailingComments: 1 104 | SpacesInAngles: false 105 | SpacesInConditionalStatement: false 106 | SpacesInContainerLiterals: true 107 | SpacesInCStyleCastParentheses: false 108 | SpacesInParentheses: false 109 | SpacesInSquareBrackets: false 110 | SpaceBeforeSquareBrackets: false 111 | Standard: Latest 112 | StatementMacros: 113 | - Q_UNUSED 114 | - QT_REQUIRE_VERSION 115 | TabWidth: 4 116 | UseCRLF: false 117 | UseTab: Never -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: "*,\ 2 | -google-runtime-int,\ 3 | -google-explicit-constructor,\ 4 | -llvm-header-guard,\ 5 | -llvmlibc-*,\ 6 | -readability-magic-numbers,\ 7 | -cppcoreguidelines-avoid-magic-numbers,\ 8 | -cppcoreguidelines-pro-bounds-constant-array-index,\ 9 | -cppcoreguidelines-pro-bounds-pointer-arithmetic,\ 10 | -cppcoreguidelines-pro-type-reinterpret-cast,\ 11 | -cppcoreguidelines-no-malloc,\ 12 | -cppcoreguidelines-owning-memory,\ 13 | -cppcoreguidelines-macro-usage,\ 14 | -cppcoreguidelines-pro-type-vararg,\ 15 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\ 16 | -fuchsia-overloaded-operator,\ 17 | -fuchsia-default-arguments,\ 18 | -fuchsia-multiple-inheritance,\ 19 | -fuchsia-default-arguments-calls,\ 20 | -fuchsia-trailing-return,\ 21 | -modernize-use-trailing-return-type,\ 22 | -misc-definitions-in-headers,\ 23 | -cert-err58-cpp,\ 24 | -clang-diagnostic-padded,\ 25 | -modernize-concat-nested-namespaces,\ 26 | -altera-struct-pack-align,\ 27 | -modernize-use-nodiscard,\ 28 | -misc-non-private-member-variables-in-classes,\ 29 | -cppcoreguidelines-pro-type-member-init,\ 30 | -fuchsia-statically-constructed-objects,\ 31 | -cppcoreguidelines-avoid-non-const-global-variables,\ 32 | -readability-function-cognitive-complexity,\ 33 | -modernize-use-emplace,\ 34 | -cppcoreguidelines-interfaces-global-init,\ 35 | -modernize-loop-convert,\ 36 | -modernize-avoid-c-arrays,\ 37 | -cppcoreguidelines-avoid-c-arrays,\ 38 | -cert-env33-c,\ 39 | -abseil-*,\ 40 | -fuchsia-default-arguments-declarations,\ 41 | -readability-redundant-access-specifiers,\ 42 | -readability-convert-member-functions-to-static,\ 43 | -readability-implicit-bool-conversion,\ 44 | -bugprone-inaccurate-erase,\ 45 | -readability-uppercase-literal-suffix,\ 46 | -hicpp*,\ 47 | -readability-braces-around-statements,\ 48 | -google-readability-braces-around-statements,\ 49 | -cppcoreguidelines-non-private-member-variables-in-classes,\ 50 | -cppcoreguidelines-pro-type-union-access,\ 51 | -readability-static-accessed-through-instance,\ 52 | -cppcoreguidelines-special-member-functions,\ 53 | -readability-isolate-declaration,\ 54 | -altera*" -------------------------------------------------------------------------------- /.github/workflows/build_linux.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - "**/README.md" 5 | - "**/build_windows.yml" 6 | pull_request: 7 | 8 | name: Build on Linux 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-20.04 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | language: ["cpp"] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install build dependencies 24 | run: "sudo apt-get install git build-essential cmake libappindicator3-dev" 25 | 26 | - name: Compile 27 | run: "mkdir build && cd build && cmake ../example/simple && cmake --build . --config Release" -------------------------------------------------------------------------------- /.github/workflows/build_windows.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - "**/README.md" 5 | - "**/build_linux.yml" 6 | pull_request: 7 | 8 | name: Build on Windows 9 | jobs: 10 | build-windows: 11 | runs-on: windows-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | language: ["cpp"] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | submodules: recursive 22 | 23 | - name: Compile 24 | run: "mkdir build && cd build && cmake ../example/simple && cmake --build . --config Release" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | cmake-build-debug 3 | .idea 4 | .vscode 5 | .history 6 | .cache 7 | example_build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(tray VERSION 0.2 DESCRIPTION "A cross-platform C++ system tray library") 3 | 4 | file(GLOB src 5 | "example/simple/main.cpp" 6 | "tray/src/*.cpp" 7 | "tray/src/*/*.cpp" 8 | "tray/src/*/*/*.cpp" 9 | ) 10 | 11 | add_library(tray STATIC ${src}) 12 | 13 | if (UNIX) 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(GTK3 REQUIRED gtk+-3.0) 16 | pkg_check_modules(APPINDICATOR REQUIRED appindicator3-0.1) 17 | 18 | target_link_libraries(tray INTERFACE ${GTK3_LIBRARIES} ${APPINDICATOR_LIBRARIES}) 19 | 20 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 21 | target_compile_options(tray PRIVATE -Wall -Wextra -Werror -pedantic -Wno-unused-lambda-capture) 22 | endif() 23 | 24 | target_include_directories(tray SYSTEM PUBLIC ${GTK3_INCLUDE_DIRS} ${APPINDICATOR_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}) 25 | endif() 26 | 27 | target_include_directories(tray SYSTEM PUBLIC "tray/include") 28 | 29 | target_compile_features(tray PRIVATE cxx_std_17) 30 | set_target_properties(tray PROPERTIES 31 | CXX_STANDARD 17 32 | CXX_EXTENSIONS OFF 33 | CXX_STANDARD_REQUIRED ON) 34 | 35 | set_target_properties(tray PROPERTIES VERSION ${PROJECT_VERSION}) 36 | set_target_properties(tray PROPERTIES PROJECT_NAME ${PROJECT_NAME}) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Soundux 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## traypp 2 | A cross-platform C++17 library that allows you to create simple tray menus. 3 | 4 | ## Compatibility 5 | | Platform | Implementation | 6 | | -------- | -------------- | 7 | | Windows | WinAPI | 8 | | Linux | AppIndicator | 9 | 10 | ## Dependencies 11 | - Linux 12 | - libappindicator-gtk3 13 | 14 | ## Basic Usage 15 | ```cpp 16 | #include 17 | using Tray::Tray; 18 | using Tray::Button; 19 | 20 | int main() 21 | { 22 | Tray tray("My Tray", "icon.ico"); 23 | tray.addEntry(Button("Exit", [&]{ 24 | tray.exit(); 25 | })); 26 | 27 | tray.run(); 28 | 29 | return 0; 30 | } 31 | ``` 32 | > On Windows it is not necessary to pass an icon path as icon, you can also use an icon-resource or an existing HICON. 33 | 34 | ## Menu components 35 | ### Button 36 | ```cpp 37 | Button(std::string text, std::function callback); 38 | ``` 39 | **Parameters:** 40 | - `callback` - The function that is called when the button is pressed 41 | ---- 42 | ### ImageButton 43 | ```cpp 44 | ImageButton(std::string text, Image image, std::function callback); 45 | ``` 46 | **Parameters:** 47 | - `image` - The image tho show 48 | - Windows 49 | > Image should either be a path to a bitmap or an HBITMAP 50 | - Linux 51 | > Image should either be a path to a png or a GtkImage 52 | - `callback` - The function that is called when the button is pressed 53 | ---- 54 | ### Toggle 55 | ```cpp 56 | Toggle(std::string text, bool state, std::function callback); 57 | ``` 58 | **Parameters:** 59 | - `state` - The default state of the Toggle 60 | - `callback` - The function that is called when the toggle is pressed 61 | ---- 62 | ### Synced Toggle 63 | ```cpp 64 | SyncedToggle(std::string text, bool &state, std::function callback); 65 | ``` 66 | **Parameters:** 67 | - `state` - Reference to a boolean that holds the toggle state 68 | > The provided boolean will influence the toggled state and **will be modified** if the toggle-state changes 69 | - `callback` - The function that is called when the toggle is pressed 70 | ---- 71 | ### Submenu 72 | ```cpp 73 | Submenu(std::string text); 74 | 75 | template 76 | Submenu(std::string text, const T &...entries); 77 | ``` 78 | **Parameters:** 79 | - `entries` - The entries that should be added upon construction 80 | > Can be empty - you can add children later with `addEntry`/`addEntries` 81 | ---- 82 | ### Label 83 | ```cpp 84 | Label(std::string text); 85 | ``` 86 | ---- 87 | ### Separator 88 | ```cpp 89 | Separator(); 90 | ``` 91 | ---- 92 | 93 | ## Installation 94 | 95 | - Add the library to your project 96 | ```cmake 97 | add_subdirectory(/path/to/traypp EXCLUDE_FROM_ALL) 98 | link_libraries(tray) 99 | ``` 100 | - Use the library 101 | - See `example` for examples -------------------------------------------------------------------------------- /example/simple/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(tray-example VERSION 0.1) 3 | 4 | add_executable(tray-example "main.cpp") 5 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_SOURCE_DIR}/../../example_build EXCLUDE_FROM_ALL) 6 | target_link_libraries(tray-example tray) 7 | 8 | target_compile_features(tray-example PUBLIC cxx_std_17) 9 | set_target_properties(tray-example PROPERTIES CMAKE_CXX_STANDARD 17) 10 | set_target_properties(tray-example PROPERTIES CMAKE_CXX_EXTENSIONS Off) 11 | set_target_properties(tray-example PROPERTIES CMAKE_CXX_STANDARD_REQUIRED On) 12 | set_target_properties(tray-example PROPERTIES VERSION ${PROJECT_VERSION}) 13 | set_target_properties(tray-example PROPERTIES PROJECT_NAME ${PROJECT_NAME}) -------------------------------------------------------------------------------- /example/simple/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | Tray::Tray tray("test", "icon.ico"); 7 | 8 | tray.addEntry(Tray::Button("Exit", [&] { tray.exit(); })); 9 | tray.addEntry(Tray::Button("Test"))->setDisabled(true); 10 | tray.addEntry(Tray::Separator()); 11 | tray.addEntry(Tray::Label("Test Label")); 12 | tray.addEntry(Tray::Toggle("Test Toggle", false, [](bool state) { printf("State: %i\n", state); })); 13 | 14 | tray.addEntry(Tray::Separator()); 15 | tray.addEntry(Tray::Submenu("Test Submenu"))->addEntry(Tray::Button("Submenu button!"))->setDisabled(true); 16 | 17 | tray.run(); 18 | 19 | return 0; 20 | } -------------------------------------------------------------------------------- /tray/include/components/button.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class Button : public TrayEntry 8 | { 9 | std::function callback; 10 | 11 | public: 12 | ~Button() override = default; 13 | Button( 14 | std::string text, std::function callback = [] {}); 15 | 16 | void clicked(); 17 | void setCallback(std::function); 18 | }; 19 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/imagebutton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "button.hpp" 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class ImageButton : public Button 8 | { 9 | Image image; 10 | 11 | public: 12 | ~ImageButton() override = default; 13 | ImageButton( 14 | std::string text, Image image, std::function callback = [] {}); 15 | 16 | Image getImage(); 17 | void setImage(Image); 18 | }; 19 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/label.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class Label : public TrayEntry 8 | { 9 | public: 10 | Label(std::string text); 11 | ~Label() override = default; 12 | }; 13 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/separator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class Separator : public TrayEntry 8 | { 9 | public: 10 | Separator(); 11 | ~Separator() override = default; 12 | }; 13 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/submenu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Tray 9 | { 10 | class Submenu : public TrayEntry 11 | { 12 | std::vector> entries; 13 | 14 | public: 15 | Submenu(std::string text); 16 | ~Submenu() override = default; 17 | 18 | template Submenu(std::string text, const T &...entries) : Submenu(text) 19 | { 20 | addEntries(entries...); 21 | } 22 | 23 | template void addEntries(const T &...entries) 24 | { 25 | (addEntry(entries), ...); 26 | } 27 | 28 | template > * = nullptr> 29 | auto addEntry(const T &entry) 30 | { 31 | entries.emplace_back(std::make_shared(entry)); 32 | auto back = entries.back(); 33 | back->setParent(parent); 34 | 35 | if (parent) 36 | { 37 | parent->update(); 38 | } 39 | 40 | return std::dynamic_pointer_cast(back); 41 | } 42 | 43 | void update(); 44 | std::vector> getEntries(); 45 | }; 46 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/syncedtoggle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class SyncedToggle : public TrayEntry 8 | { 9 | bool &toggled; 10 | std::function callback; 11 | 12 | public: 13 | ~SyncedToggle() override = default; 14 | SyncedToggle( 15 | std::string text, bool &state, std::function callback = [](bool & /**/) {}); 16 | 17 | void onToggled(); 18 | bool isToggled() const; 19 | }; 20 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/components/toggle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Tray 6 | { 7 | class Toggle : public TrayEntry 8 | { 9 | bool toggled; 10 | std::function callback; 11 | 12 | public: 13 | ~Toggle() override = default; 14 | Toggle( 15 | std::string text, bool state, std::function callback = [](bool /**/) {}); 16 | 17 | void onToggled(); 18 | bool isToggled() const; 19 | }; 20 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/core/entry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Tray 5 | { 6 | class BaseTray; 7 | class TrayEntry 8 | { 9 | protected: 10 | std::string text; 11 | bool disabled = false; 12 | BaseTray *parent = nullptr; 13 | 14 | public: 15 | TrayEntry(std::string text); 16 | virtual ~TrayEntry() = default; 17 | 18 | BaseTray *getParent(); 19 | void setParent(BaseTray *); 20 | 21 | std::string getText(); 22 | void setText(std::string); 23 | 24 | void setDisabled(bool); 25 | bool isDisabled() const; 26 | }; 27 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/core/icon.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if defined(_WIN32) 4 | #include 5 | #endif 6 | 7 | namespace Tray 8 | { 9 | #if defined(__linux__) 10 | class Icon 11 | { 12 | std::string iconPath; 13 | 14 | public: 15 | Icon(std::string path); 16 | Icon(const char *path); 17 | operator const char *(); 18 | }; 19 | #elif defined(_WIN32) 20 | class Icon 21 | { 22 | HICON hIcon; 23 | 24 | public: 25 | Icon(HICON icon); 26 | Icon(WORD resource); 27 | Icon(const char *path); 28 | Icon(const std::string &path); 29 | 30 | operator HICON(); 31 | }; 32 | #endif 33 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/core/image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if defined(_WIN32) 4 | #include "icon.hpp" 5 | #include 6 | #elif defined(__linux__) 7 | #include 8 | #endif 9 | 10 | namespace Tray 11 | { 12 | #if defined(__linux__) 13 | class Image 14 | { 15 | GtkWidget *image; 16 | 17 | public: 18 | Image(GtkWidget *image); 19 | Image(const char *path); 20 | Image(const std::string &path); 21 | 22 | operator GtkWidget *(); 23 | }; 24 | #elif defined(_WIN32) 25 | class Image 26 | { 27 | HBITMAP image; 28 | 29 | public: 30 | Image(HBITMAP image); 31 | Image(WORD resource); 32 | Image(const char *path); 33 | Image(const std::string &path); 34 | 35 | operator HBITMAP(); 36 | }; 37 | #endif 38 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/core/linux/tray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(__linux__) 3 | #include 4 | #include 5 | 6 | namespace Tray 7 | { 8 | class Tray : public BaseTray 9 | { 10 | AppIndicator *appIndicator; 11 | static void callback(GtkWidget *, gpointer); 12 | std::vector> imageWidgets; 13 | static GtkMenuShell *construct(const std::vector> &, Tray *parent); 14 | 15 | public: 16 | Tray(std::string identifier, Icon icon); 17 | template Tray(std::string identifier, Icon icon, const T &...entries) : Tray(identifier, icon) 18 | { 19 | addEntries(entries...); 20 | } 21 | 22 | void run() override; 23 | void exit() override; 24 | void update() override; 25 | }; 26 | } // namespace Tray 27 | #endif -------------------------------------------------------------------------------- /tray/include/core/traybase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "entry.hpp" 3 | #include "icon.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Tray 11 | { 12 | class BaseTray 13 | { 14 | protected: 15 | Icon icon; 16 | std::string identifier; 17 | std::vector> entries; 18 | 19 | public: 20 | BaseTray(std::string identifier, Icon icon); 21 | 22 | template void addEntries(const T &...entries) 23 | { 24 | (addEntry(entries), ...); 25 | } 26 | template ::value> * = nullptr> 27 | auto addEntry(const T &entry) 28 | { 29 | entries.emplace_back(std::make_shared(entry)); 30 | auto back = entries.back(); 31 | back->setParent(this); 32 | update(); 33 | 34 | return std::dynamic_pointer_cast>(back); 35 | } 36 | 37 | virtual void run() = 0; 38 | virtual void exit() = 0; 39 | virtual void update() = 0; 40 | std::vector> getEntries(); 41 | }; 42 | } // namespace Tray -------------------------------------------------------------------------------- /tray/include/core/windows/tray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(_WIN32) 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Tray 9 | { 10 | class Tray : public BaseTray 11 | { 12 | HWND hwnd = nullptr; 13 | HMENU menu = nullptr; 14 | WNDCLASSEX windowClass; 15 | NOTIFYICONDATA notifyData; 16 | 17 | std::vector> allocations; 18 | static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); 19 | static std::map> trayList; 20 | static HMENU construct(const std::vector> &, Tray *parent, bool cleanup = false); 21 | 22 | public: 23 | ~Tray(); 24 | Tray(std::string identifier, Icon icon); 25 | template Tray(std::string identifier, Icon icon, const T &...entries) : Tray(identifier, icon) 26 | { 27 | addEntries(entries...); 28 | } 29 | 30 | void run() override; 31 | void exit() override; 32 | void update() override; 33 | }; 34 | } // namespace Tray 35 | #endif -------------------------------------------------------------------------------- /tray/include/tray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(_WIN32) 11 | #include 12 | #elif defined(__linux__) 13 | #include 14 | #endif -------------------------------------------------------------------------------- /tray/src/components/button.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Tray::Button::Button(std::string text, std::function callback) 5 | : TrayEntry(std::move(text)), callback(std::move(callback)) 6 | { 7 | } 8 | 9 | void Tray::Button::clicked() 10 | { 11 | if (callback) 12 | { 13 | callback(); 14 | } 15 | } 16 | 17 | void Tray::Button::setCallback(std::function newCallback) 18 | { 19 | callback = std::move(newCallback); 20 | } -------------------------------------------------------------------------------- /tray/src/components/imagebutton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | Tray::ImageButton::ImageButton(std::string text, Image image, std::function callback) 6 | : Button(std::move(text), std::move(callback)), image(image) 7 | { 8 | } 9 | 10 | void Tray::ImageButton::setImage(Tray::Image newImage) 11 | { 12 | image = newImage; 13 | if (parent) 14 | { 15 | parent->update(); 16 | } 17 | } 18 | 19 | Tray::Image Tray::ImageButton::getImage() 20 | { 21 | return image; 22 | } -------------------------------------------------------------------------------- /tray/src/components/label.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Tray::Label::Label(std::string text) : TrayEntry(std::move(text)) {} -------------------------------------------------------------------------------- /tray/src/components/separator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Tray::Separator::Separator() : TrayEntry("") {} -------------------------------------------------------------------------------- /tray/src/components/submenu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Tray::Submenu::Submenu(std::string text) : TrayEntry(std::move(text)) {} 5 | void Tray::Submenu::update() 6 | { 7 | if (parent) 8 | { 9 | parent->update(); 10 | } 11 | } 12 | 13 | std::vector> Tray::Submenu::getEntries() 14 | { 15 | return entries; 16 | } -------------------------------------------------------------------------------- /tray/src/components/syncedtoggle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Tray::SyncedToggle::SyncedToggle(std::string text, bool &state, std::function callback) 4 | : TrayEntry(std::move(text)), toggled(state), callback(std::move(callback)) 5 | { 6 | } 7 | 8 | bool Tray::SyncedToggle::isToggled() const 9 | { 10 | return toggled; 11 | } 12 | 13 | void Tray::SyncedToggle::onToggled() 14 | { 15 | toggled = !toggled; 16 | callback(toggled); 17 | } -------------------------------------------------------------------------------- /tray/src/components/toggle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Tray::Toggle::Toggle(std::string text, bool state, std::function callback) 4 | : TrayEntry(std::move(text)), toggled(state), callback(std::move(callback)) 5 | { 6 | } 7 | 8 | bool Tray::Toggle::isToggled() const 9 | { 10 | return toggled; 11 | } 12 | 13 | void Tray::Toggle::onToggled() 14 | { 15 | toggled = !toggled; 16 | callback(toggled); 17 | } -------------------------------------------------------------------------------- /tray/src/core/entry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Tray::TrayEntry::TrayEntry(std::string text) : text(std::move(text)) {} 5 | 6 | Tray::BaseTray *Tray::TrayEntry::getParent() 7 | { 8 | return parent; 9 | } 10 | 11 | void Tray::TrayEntry::setParent(BaseTray *newParent) 12 | { 13 | parent = newParent; 14 | } 15 | 16 | std::string Tray::TrayEntry::getText() 17 | { 18 | return text; 19 | } 20 | 21 | void Tray::TrayEntry::setText(std::string newText) 22 | { 23 | text = std::move(newText); 24 | if (parent) 25 | { 26 | parent->update(); 27 | } 28 | } 29 | 30 | bool Tray::TrayEntry::isDisabled() const 31 | { 32 | return disabled; 33 | } 34 | 35 | void Tray::TrayEntry::setDisabled(bool state) 36 | { 37 | disabled = state; 38 | if (parent) 39 | { 40 | parent->update(); 41 | } 42 | } -------------------------------------------------------------------------------- /tray/src/core/linux/icon.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) 2 | #include 3 | 4 | Tray::Icon::Icon(std::string path) : iconPath(std::move(path)) {} 5 | Tray::Icon::Icon(const char *path) : iconPath(path) {} 6 | Tray::Icon::operator const char *() 7 | { 8 | return iconPath.c_str(); 9 | } 10 | 11 | #endif -------------------------------------------------------------------------------- /tray/src/core/linux/image.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) 2 | #include 3 | 4 | Tray::Image::Image(GtkWidget *image) : image(image) {} 5 | Tray::Image::Image(const char *path) : Image(std::string(path)) {} 6 | Tray::Image::Image(const std::string &path) : image(gtk_image_new_from_file(path.c_str())) {} 7 | 8 | Tray::Image::operator GtkWidget *() 9 | { 10 | return image; 11 | } 12 | #endif -------------------------------------------------------------------------------- /tray/src/core/linux/tray.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | Tray::Tray::Tray(std::string identifier, Icon icon) : BaseTray(std::move(identifier), std::move(icon)) 15 | { 16 | if (gtk_init_check(nullptr, nullptr) != TRUE) 17 | { 18 | throw std::runtime_error("Gtk init check failed"); 19 | return; 20 | } 21 | 22 | appIndicator = app_indicator_new(this->identifier.c_str(), this->icon, APP_INDICATOR_CATEGORY_APPLICATION_STATUS); 23 | app_indicator_set_status(appIndicator, APP_INDICATOR_STATUS_ACTIVE); 24 | } 25 | 26 | void Tray::Tray::exit() 27 | { 28 | g_idle_add( 29 | [](gpointer data) -> gboolean { 30 | auto *tray = reinterpret_cast(data); 31 | g_object_unref(tray->appIndicator); 32 | tray->appIndicator = nullptr; 33 | return FALSE; 34 | }, 35 | this); 36 | } 37 | 38 | void Tray::Tray::update() 39 | { 40 | if (appIndicator) 41 | { 42 | app_indicator_set_menu(appIndicator, reinterpret_cast(construct(entries, this))); 43 | } 44 | } 45 | 46 | GtkMenuShell *Tray::Tray::construct(const std::vector> &entries, Tray *parent) 47 | { 48 | auto *menu = reinterpret_cast(gtk_menu_new()); 49 | 50 | for (const auto &entry : entries) 51 | { 52 | auto *item = entry.get(); 53 | GtkWidget *gtkItem = nullptr; 54 | 55 | if (auto *toggle = dynamic_cast(item); toggle) 56 | { 57 | gtkItem = gtk_check_menu_item_new_with_label(toggle->getText().c_str()); 58 | gtk_check_menu_item_set_active(reinterpret_cast(gtkItem), toggle->isToggled()); 59 | } 60 | else if (auto *syncedToggle = dynamic_cast(item); syncedToggle) 61 | { 62 | gtkItem = gtk_check_menu_item_new_with_label(syncedToggle->getText().c_str()); 63 | gtk_check_menu_item_set_active(reinterpret_cast(gtkItem), syncedToggle->isToggled()); 64 | } 65 | else if (auto *submenu = dynamic_cast(item); submenu) 66 | { 67 | gtkItem = gtk_menu_item_new_with_label(submenu->getText().c_str()); 68 | gtk_menu_item_set_submenu(reinterpret_cast(gtkItem), 69 | reinterpret_cast(construct(submenu->getEntries(), parent))); 70 | } 71 | else if (auto *iconButton = dynamic_cast(item); iconButton) 72 | { 73 | gtkItem = gtk_menu_item_new(); 74 | 75 | GtkWidget *image = iconButton->getImage(); 76 | auto *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); 77 | auto *label = gtk_label_new(iconButton->getText().c_str()); 78 | 79 | bool handled = false; 80 | if (parent) 81 | { 82 | for (std::size_t i = 0; parent->imageWidgets.size() > i; i++) 83 | { 84 | const auto &[container, widget] = parent->imageWidgets.at(i); 85 | 86 | if (widget == image) 87 | { 88 | g_object_ref(widget); // NOLINT 89 | 90 | gtk_container_remove(container, widget); 91 | gtk_container_add(reinterpret_cast(box), 92 | widget); // TODO(performance): This takes ages - find a way to improve it. 93 | 94 | parent->imageWidgets.erase(parent->imageWidgets.begin() + i); 95 | handled = true; 96 | break; 97 | } 98 | } 99 | } 100 | 101 | if (!handled) 102 | { 103 | gtk_container_add(reinterpret_cast(box), image); 104 | } 105 | 106 | gtk_label_set_xalign(reinterpret_cast(label), 0.0); 107 | gtk_box_pack_end(reinterpret_cast(box), label, TRUE, TRUE, 0); 108 | 109 | gtk_container_add(reinterpret_cast(gtkItem), box); 110 | 111 | if (parent) 112 | { 113 | parent->imageWidgets.emplace_back(reinterpret_cast(box), 114 | reinterpret_cast(image)); 115 | } 116 | } 117 | else if (dynamic_cast