├── VERSION ├── assets └── preview.png ├── README.md ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── .clang-format ├── .clang-tidy └── src └── main.cpp /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 -------------------------------------------------------------------------------- /assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprwm/hyprland-welcome/HEAD/assets/preview.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## hyprland-welcome 2 | 3 | > [!IMPORTANT] 4 | > Archived, as this has been moved into [guiutils](https://github.com/hyprwm/hyprland-guiutils) 5 | 6 | Hyprland's cute welcome app, written with hyprtoolkit. 7 | 8 | ![](./assets/preview.png) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | CMakeUserPresets.json 13 | 14 | # CLion 15 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 16 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 17 | # and can be added to the global gitignore or merged into this file. For a more nuclear 18 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 19 | #cmake-build-* 20 | 21 | build/ 22 | .vscode/ 23 | .cache/ 24 | 25 | protocols/*.cpp 26 | protocols/*.hpp 27 | 28 | *.inc 29 | src/renderer/gl/shaders/Shaders.hpp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Hypr Development 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) 4 | string(STRIP ${VER_RAW} HYPRLAND_WELCOME_VERSION) 5 | 6 | add_compile_definitions(HYPRLAND_WELCOME_VERSION="${HYPRLAND_WELCOME_VERSION}") 7 | 8 | project( 9 | hyprland-welcome 10 | VERSION ${HYPRLAND_WELCOME_VERSION} 11 | DESCRIPTION "Volume management center for Hyprland") 12 | 13 | include(CTest) 14 | include(CheckIncludeFile) 15 | include(GNUInstallDirs) 16 | 17 | set(PREFIX ${CMAKE_INSTALL_PREFIX}) 18 | set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 19 | set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) 20 | 21 | find_package(PkgConfig REQUIRED) 22 | pkg_check_modules( 23 | deps 24 | REQUIRED 25 | IMPORTED_TARGET 26 | hyprtoolkit 27 | pixman-1 28 | libdrm 29 | hyprutils 30 | ) 31 | 32 | set(CMAKE_CXX_STANDARD 23) 33 | add_compile_options( 34 | -Wall 35 | -Wextra 36 | -Wno-unused-parameter 37 | -Wno-unused-value 38 | -Wno-missing-field-initializers 39 | -Wpedantic) 40 | set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) 41 | 42 | if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) 43 | message(STATUS "Configuring hyprland-welcome in Debug") 44 | add_compile_definitions(hyprland-welcome_DEBUG) 45 | else() 46 | add_compile_options(-O3) 47 | message(STATUS "Configuring hyprland-welcome in Release") 48 | endif() 49 | 50 | file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") 51 | 52 | add_executable(hyprland-welcome ${SRCFILES}) 53 | 54 | target_link_libraries(hyprland-welcome PkgConfig::deps) 55 | 56 | install( 57 | FILES contrib/hyprland-welcome.desktop 58 | DESTINATION "share/applications") 59 | 60 | install(TARGETS hyprland-welcome) -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: true 9 | AlignEscapedNewlines: Right 10 | AlignOperands: false 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: false 27 | BreakConstructorInitializers: AfterColon 28 | ColumnLimit: 180 29 | CompactNamespaces: false 30 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 31 | ExperimentalAutoDetectBinPacking: false 32 | FixNamespaceComments: false 33 | IncludeBlocks: Preserve 34 | IndentCaseLabels: true 35 | IndentWidth: 4 36 | PointerAlignment: Left 37 | ReflowComments: false 38 | SortIncludes: false 39 | SortUsingDeclarations: false 40 | SpaceAfterCStyleCast: false 41 | SpaceAfterLogicalNot: false 42 | SpaceAfterTemplateKeyword: true 43 | SpaceBeforeCtorInitializerColon: true 44 | SpaceBeforeInheritanceColon: true 45 | SpaceBeforeParens: ControlStatements 46 | SpaceBeforeRangeBasedForLoopColon: true 47 | SpaceInEmptyParentheses: false 48 | SpacesBeforeTrailingComments: 1 49 | SpacesInAngles: false 50 | SpacesInCStyleCastParentheses: false 51 | SpacesInContainerLiterals: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Auto 55 | TabWidth: 4 56 | UseTab: Never 57 | 58 | AllowShortEnumsOnASingleLine: false 59 | 60 | BraceWrapping: 61 | AfterEnum: false 62 | 63 | AlignConsecutiveDeclarations: AcrossEmptyLines 64 | 65 | NamespaceIndentation: All 66 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | WarningsAsErrors: '*' 2 | HeaderFilterRegex: '.*\.hpp' 3 | FormatStyle: 'file' 4 | Checks: > 5 | -*, 6 | bugprone-*, 7 | -bugprone-easily-swappable-parameters, 8 | -bugprone-forward-declaration-namespace, 9 | -bugprone-forward-declaration-namespace, 10 | -bugprone-macro-parentheses, 11 | -bugprone-narrowing-conversions, 12 | -bugprone-branch-clone, 13 | -bugprone-assignment-in-if-condition, 14 | concurrency-*, 15 | -concurrency-mt-unsafe, 16 | cppcoreguidelines-*, 17 | -cppcoreguidelines-owning-memory, 18 | -cppcoreguidelines-avoid-magic-numbers, 19 | -cppcoreguidelines-pro-bounds-constant-array-index, 20 | -cppcoreguidelines-avoid-const-or-ref-data-members, 21 | -cppcoreguidelines-non-private-member-variables-in-classes, 22 | -cppcoreguidelines-avoid-goto, 23 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 24 | -cppcoreguidelines-avoid-do-while, 25 | -cppcoreguidelines-avoid-non-const-global-variables, 26 | -cppcoreguidelines-special-member-functions, 27 | -cppcoreguidelines-explicit-virtual-functions, 28 | -cppcoreguidelines-avoid-c-arrays, 29 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 30 | -cppcoreguidelines-narrowing-conversions, 31 | -cppcoreguidelines-pro-type-union-access, 32 | -cppcoreguidelines-pro-type-member-init, 33 | -cppcoreguidelines-macro-usage, 34 | -cppcoreguidelines-macro-to-enum, 35 | -cppcoreguidelines-init-variables, 36 | -cppcoreguidelines-pro-type-cstyle-cast, 37 | -cppcoreguidelines-pro-type-vararg, 38 | -cppcoreguidelines-pro-type-reinterpret-cast, 39 | google-global-names-in-headers, 40 | -google-readability-casting, 41 | google-runtime-operator, 42 | misc-*, 43 | -misc-unused-parameters, 44 | -misc-no-recursion, 45 | -misc-non-private-member-variables-in-classes, 46 | -misc-include-cleaner, 47 | -misc-use-anonymous-namespace, 48 | -misc-const-correctness, 49 | modernize-*, 50 | -modernize-return-braced-init-list, 51 | -modernize-use-trailing-return-type, 52 | -modernize-use-using, 53 | -modernize-use-override, 54 | -modernize-avoid-c-arrays, 55 | -modernize-macro-to-enum, 56 | -modernize-loop-convert, 57 | -modernize-use-nodiscard, 58 | -modernize-pass-by-value, 59 | -modernize-use-auto, 60 | performance-*, 61 | -performance-avoid-endl, 62 | -performance-unnecessary-value-param, 63 | portability-std-allocator-const, 64 | readability-*, 65 | -readability-function-cognitive-complexity, 66 | -readability-function-size, 67 | -readability-identifier-length, 68 | -readability-magic-numbers, 69 | -readability-uppercase-literal-suffix, 70 | -readability-braces-around-statements, 71 | -readability-redundant-access-specifiers, 72 | -readability-else-after-return, 73 | -readability-container-data-pointer, 74 | -readability-implicit-bool-conversion, 75 | -readability-avoid-nested-conditional-operator, 76 | -readability-redundant-member-init, 77 | -readability-redundant-string-init, 78 | -readability-avoid-const-params-in-decls, 79 | -readability-named-parameter, 80 | -readability-convert-member-functions-to-static, 81 | -readability-qualified-auto, 82 | -readability-make-member-function-const, 83 | -readability-isolate-declaration, 84 | -readability-inconsistent-declaration-parameter-name, 85 | -clang-diagnostic-error, 86 | 87 | CheckOptions: 88 | performance-for-range-copy.WarnOnAllAutoCopies: true 89 | performance-inefficient-string-concatenation.StrictMode: true 90 | readability-braces-around-statements.ShortStatementLines: 0 91 | readability-identifier-naming.ClassCase: CamelCase 92 | readability-identifier-naming.ClassIgnoredRegexp: I.* 93 | readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? 94 | readability-identifier-naming.EnumCase: CamelCase 95 | readability-identifier-naming.EnumPrefix: e 96 | readability-identifier-naming.EnumConstantCase: UPPER_CASE 97 | readability-identifier-naming.FunctionCase: camelBack 98 | readability-identifier-naming.NamespaceCase: CamelCase 99 | readability-identifier-naming.StructPrefix: S 100 | readability-identifier-naming.StructCase: CamelCase 101 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace Hyprutils::Memory; 25 | using namespace Hyprutils::Math; 26 | using namespace Hyprutils::String; 27 | using namespace Hyprutils::OS; 28 | using namespace Hyprtoolkit; 29 | 30 | #define SP CSharedPointer 31 | #define ASP CAtomicSharedPointer 32 | #define WP CWeakPointer 33 | #define UP CUniquePointer 34 | 35 | constexpr const size_t TABS_NUMBER = 5; 36 | constexpr const size_t INNER_NULL_MARGIN = 5; 37 | 38 | constexpr std::array TITLES = { 39 | "Welcome to Hyprland!", "Getting started", "Basic configuration", "Default apps", "That's it!", 40 | }; 41 | 42 | constexpr std::array TERMINALS = { 43 | "kitty", "alacritty", "wezterm", "foot", "konsole", "gnome-terminal", 44 | }; 45 | 46 | constexpr std::array FILE_MANAGERS = {"dolphin", "thunar", "pcmanfm", "nautilus", "nemo"}; 47 | 48 | constexpr const char* TAB1_CONTENT = 49 | R"#(We hope you enjoy your stay. In order to help you get accomodated to Hyprland in an easier manner, we prepared a little basic setup tutorial, just for you. 50 | 51 | If you feel adventurous, or are an advanced user, you can click the "Thanks, but I don't need help" button on the bottom. It will close this window and never show it again. 52 | 53 | If you want to manually launch this welcome app, just execute hyprland-welcome in your terminal. 54 | 55 | Click the "next" button to proceed to the next step of your setup :) 56 | )#"; 57 | 58 | constexpr const char* TAB2_CONTENT = 59 | R"#(The first thing we'll need to do is get some packages installed that you absolutely need in order for your system to be working properly. 60 | Apps with a * are absolutely necessary for a working system. All other are highly recommended, as they provide core parts of a working environment. 61 | You can proceed without any of those, but it's not advised. 62 | 63 | There is a possibility that this app is unable to detect some of your installed binaries. In that case, it's okay to ignore them. Nix users might have the issue. 64 | 65 | Use the launch terminal button to launch a terminal. 66 | Use SUPER+M to exit hyprland. 67 | Supported terminals: kitty, alacritty, foot, wezterm, konsole, gnome-terminal, xterm. 68 | 69 | Hint: Hover on the different components to see what options are accepted. Green means the component is found to be installed, blue means it's running. 70 | This list refreshes automatically.)#"; 71 | 72 | constexpr const char* TAB3_CONTENT = 73 | R"#(Now that you've installed the basic apps, make sure to add them to autostart. Hyprland doesn't automatically start anything for you, you need to tell it to. 74 | Go to ~/.config/hypr/hyprland.conf, and add "exec-once = appname" to launch your apps, for example: 75 | exec-once = hyprpaper 76 | exec-once = waybar 77 | 78 | In general, configuring apps is something for you to do. Each app you install may come with its own config file and options. 79 | 80 | A great point to start is the Hyprland wiki at https://wiki.hypr.land. There, the master tutorial will teach you everything and link to further docs. 81 | 82 | If you prefer pre-configured settings, or "dotfiles", you can see the "preconfigured configs" section on the wiki, or search online. Important note: dotfiles can run anything on your computer. Make sure you trust the source.)#"; 83 | 84 | constexpr const char* TAB4_PREAMBLE = 85 | R"#(We know that not everyone uses kitty and dolphin. That's why we let you choose. 86 | If you wish to change the defaults, use the dropdowns below.)#"; 87 | 88 | constexpr const char* TAB5_CONTENT = 89 | R"#(That's it for this small introduction! Explore the wiki, and various apps, and enjoy your journey! 90 | 91 | Here are some important default shortcuts: 92 | • SUPER + Q = Terminal 93 | • SUPER + E = File Manager 94 | • SUPER + C = Close window 95 | • SUPER + V = Toggle floating 96 | • SUPER + M = Exit Hyprland 97 | • SUPER + [1 - 9] = Workspaces 1 - 9 98 | • SUPER + SHIFT + [1 - 9] = Move window to workspace 1 - 9 99 | • SUPER + Arrows = Move focus around 100 | 101 | You can easily change these in your hyprland.conf. 102 | 103 | Thank you for choosing Hyprland! ❤️)#"; 104 | 105 | struct SAppState { 106 | std::string name; 107 | std::vector binaryNames; 108 | bool mandatory = false; 109 | SP labelEl; 110 | }; 111 | 112 | static struct { 113 | SP backend; 114 | SP tabContainer; 115 | std::array, TABS_NUMBER> tabs; 116 | SP topText; 117 | SP buttonLayout; 118 | SP buttonSpacer; 119 | SP buttonBack, buttonNext, buttonQuit, buttonFinish, buttonOpenWiki, buttonLaunchTerm; 120 | size_t tab = 0; 121 | std::vector> appStates; 122 | ASP appRefreshTimer, wikiOpenTimer; 123 | } state; 124 | 125 | static bool appExists(std::string binName) { 126 | static auto PATH = getenv("PATH"); 127 | 128 | if (!PATH) 129 | return false; 130 | 131 | static CVarList paths(PATH, 0, ':', true); 132 | 133 | for (const auto& p : paths) { 134 | std::error_code ec; 135 | if (!std::filesystem::exists(std::filesystem::path(p) / binName, ec) || ec) 136 | continue; 137 | return true; 138 | } 139 | 140 | return false; 141 | } 142 | 143 | static bool appIsRunning(std::string binName) { 144 | // loop over /proc/ entries, check exe 145 | std::error_code ec_it; 146 | for (const std::filesystem::path& procEntry : std::filesystem::directory_iterator("/proc", ec_it)) { 147 | if (ec_it) 148 | continue; 149 | 150 | std::error_code ec; 151 | 152 | if (!std::filesystem::exists(procEntry / "exe", ec) || ec) 153 | continue; 154 | 155 | const auto CANONICAL = std::filesystem::canonical(procEntry / "exe", ec); 156 | 157 | if (ec) 158 | continue; 159 | 160 | if (!CANONICAL.has_filename()) 161 | continue; 162 | 163 | if (CANONICAL.filename() == binName) 164 | return true; 165 | } 166 | 167 | return false; 168 | } 169 | 170 | static void updateApps() { 171 | if (state.tab != 1) 172 | return; 173 | 174 | state.appRefreshTimer = state.backend->addTimer(std::chrono::seconds(1), [](ASP t, void* d) { updateApps(); }, nullptr); 175 | 176 | for (const auto& a : state.appStates) { 177 | 178 | bool found = false; 179 | 180 | for (const auto& bn : a->binaryNames) { 181 | if (!appIsRunning(bn)) 182 | continue; 183 | 184 | found = true; 185 | 186 | a->labelEl->rebuild() 187 | ->text(std::format("{}{}: Running: {}", a->name, (a->mandatory ? "*" : ""), bn)) 188 | ->commence(); 189 | break; 190 | } 191 | if (!found) { 192 | for (const auto& bn : a->binaryNames) { 193 | if (!appExists(bn)) 194 | continue; 195 | 196 | found = true; 197 | 198 | a->labelEl->rebuild() 199 | ->text(std::format("{}{}: Installed: {}", a->name, (a->mandatory ? "*" : ""), bn)) 200 | ->commence(); 201 | break; 202 | } 203 | } 204 | 205 | if (!found) 206 | a->labelEl->rebuild() 207 | ->text(std::format("{}{}: Missing", a->name, (a->mandatory ? "*" : ""))) 208 | ->commence(); 209 | } 210 | } 211 | 212 | static void updateTab() { 213 | state.tabContainer->clearChildren(); 214 | state.tabContainer->addChild(state.tabs[state.tab]); 215 | state.topText->rebuild()->text(TITLES[state.tab])->commence(); 216 | updateApps(); 217 | 218 | state.buttonLayout->clearChildren(); 219 | 220 | if (state.tab == 0) { 221 | state.buttonLayout->addChild(state.buttonSpacer); 222 | state.buttonLayout->addChild(state.buttonQuit); 223 | state.buttonLayout->addChild(state.buttonNext); 224 | } else if (state.tab == 1) { 225 | state.buttonLayout->addChild(state.buttonBack); 226 | state.buttonLayout->addChild(state.buttonSpacer); 227 | state.buttonLayout->addChild(state.buttonLaunchTerm); 228 | state.buttonLayout->addChild(state.buttonNext); 229 | } else if (state.tab == 2) { 230 | state.buttonLayout->addChild(state.buttonBack); 231 | state.buttonLayout->addChild(state.buttonSpacer); 232 | state.buttonLayout->addChild(state.buttonOpenWiki); 233 | state.buttonLayout->addChild(state.buttonNext); 234 | } else if (state.tab == 3) { 235 | state.buttonLayout->addChild(state.buttonBack); 236 | state.buttonLayout->addChild(state.buttonSpacer); 237 | state.buttonLayout->addChild(state.buttonNext); 238 | } else if (state.tab == 4) { 239 | state.buttonLayout->addChild(state.buttonBack); 240 | state.buttonLayout->addChild(state.buttonSpacer); 241 | state.buttonLayout->addChild(state.buttonOpenWiki); 242 | state.buttonLayout->addChild(state.buttonFinish); 243 | } 244 | } 245 | 246 | static void tabBack() { 247 | if (state.tab == 0) 248 | return; 249 | 250 | state.tab--; 251 | updateTab(); 252 | } 253 | 254 | static void tabNext() { 255 | if (state.tab == TITLES.size() - 1) 256 | return; 257 | 258 | state.tab++; 259 | updateTab(); 260 | } 261 | 262 | static void registerAppState(std::string&& name, std::vector&& binaries, bool mandatory, const std::string& recommend = "", const std::string& note = "") { 263 | auto appState = makeShared(); 264 | appState->name = std::move(name); 265 | appState->binaryNames = std::move(binaries); 266 | appState->labelEl = CTextBuilder::begin()->color([] { return state.backend->getPalette()->m_colors.text; })->fontSize({CFontSize::HT_FONT_TEXT})->text("")->commence(); 267 | appState->mandatory = mandatory; 268 | 269 | std::string acceptedStr = ""; 270 | for (const auto& b : appState->binaryNames) { 271 | acceptedStr += b + ", "; 272 | } 273 | if (!acceptedStr.empty()) 274 | acceptedStr = acceptedStr.substr(0, acceptedStr.length() - 2); 275 | 276 | std::string tooltip = recommend.empty() ? std::format("Accepted: {}", acceptedStr) : std::format("Recommended: {}\nAccepted: {}", recommend, acceptedStr); 277 | if (!note.empty()) 278 | tooltip += std::format("\n{}", note); 279 | 280 | appState->labelEl->setTooltip(std::move(tooltip)); 281 | 282 | state.appStates.emplace_back(std::move(appState)); 283 | } 284 | 285 | static SP spaceOut(std::string&& label, SP el) { 286 | auto text = CTextBuilder::begin()->text(std::move(label))->commence(); 287 | auto spacer = CNullBuilder::begin()->commence(); 288 | spacer->setGrow(true, false); 289 | auto layout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 290 | layout->addChild(text); 291 | layout->addChild(spacer); 292 | layout->addChild(el); 293 | return layout; 294 | } 295 | 296 | static std::optional readFileAsString(const std::string& path) { 297 | std::error_code ec; 298 | 299 | if (!std::filesystem::exists(path, ec) || ec) 300 | return std::nullopt; 301 | 302 | std::ifstream file(path); 303 | if (!file.good()) 304 | return std::nullopt; 305 | 306 | return trim(std::string((std::istreambuf_iterator(file)), (std::istreambuf_iterator()))); 307 | } 308 | 309 | static std::optional updateDefaultConfigVar(const std::string& var, const char* newValue) { 310 | const auto HOME = getenv("HOME"); 311 | if (!HOME) 312 | return "Can't save: no $HOME env"; 313 | 314 | const auto PATH = std::string{HOME} + "/.config/hypr/hyprland.conf"; 315 | 316 | const auto STR = readFileAsString(PATH); 317 | 318 | if (!STR) 319 | return "Can't save: failed to read config"; 320 | 321 | std::string newConfig = *STR; 322 | 323 | size_t varPos = newConfig.find("\n$" + var); 324 | if (varPos == std::string::npos) 325 | return "Can't save: config isn't default, doesn't have variable"; 326 | 327 | varPos++; 328 | size_t varEnd = newConfig.find('\n', varPos + 1); 329 | 330 | if (varEnd == std::string::npos) 331 | newConfig = std::format("{}${} = {}", newConfig.substr(0, varPos), var, newValue); 332 | else 333 | newConfig = std::format("{}${} = {}{}", newConfig.substr(0, varPos), var, newValue, newConfig.substr(varEnd)); 334 | 335 | std::ofstream ofs(PATH, std::ios::trunc); 336 | ofs << newConfig; 337 | ofs.close(); 338 | 339 | return std::nullopt; 340 | } 341 | 342 | static void initTabs() { 343 | { 344 | // Tab 1 345 | auto nullEl = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 346 | auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 347 | auto text = CTextBuilder::begin()->text(TAB1_CONTENT)->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 348 | auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 349 | spacer->setGrow(true); 350 | 351 | layout->addChild(text); 352 | layout->addChild(spacer); 353 | nullEl->addChild(layout); 354 | nullEl->setGrow(true); 355 | nullEl->setMargin(INNER_NULL_MARGIN); 356 | state.tabs[0] = nullEl; 357 | } 358 | 359 | { 360 | // Tab 2 361 | auto nullEl = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 362 | auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(20)->commence(); 363 | auto text = CTextBuilder::begin()->text(TAB2_CONTENT)->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 364 | auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 365 | spacer->setGrow(true); 366 | 367 | layout->addChild(text); 368 | 369 | nullEl->addChild(layout); 370 | nullEl->setGrow(true); 371 | nullEl->setMargin(INNER_NULL_MARGIN); 372 | 373 | auto appLayoutParent = CRowLayoutBuilder::begin()->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 374 | SP appLayouts[2] = { 375 | CColumnLayoutBuilder::begin()->gap(4)->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(), 376 | CColumnLayoutBuilder::begin()->gap(4)->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(), 377 | }; 378 | 379 | appLayouts[0]->setGrow(false, true); 380 | appLayouts[0]->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 381 | appLayouts[0]->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_LEFT, true); 382 | appLayouts[1]->setGrow(false, true); 383 | appLayouts[1]->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 384 | appLayouts[1]->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_LEFT, true); 385 | 386 | appLayoutParent->addChild(appLayouts[0]); 387 | appLayoutParent->addChild(appLayouts[1]); 388 | 389 | layout->addChild(appLayoutParent); 390 | 391 | // app states 392 | registerAppState("Authentication agent", {"hyprpolkitagent", "polkit-kde-agent"}, true, "hyprpolkitagent"); 393 | registerAppState("File manager", {"dolphin", "ranger", "thunar", "pcmanfm", "nautilus", "nemo", "nnn", "yazi"}, true); 394 | registerAppState("Terminal", {"kitty", "alacritty", "wezterm", "foot", "konsole", "gnome-terminal"}, true, "kitty"); 395 | registerAppState("Pipewire", {"pipewire", "wireplumber"}, true); 396 | registerAppState("Wallpaper", {"hyprpaper", "swww", "awww", "swaybg", "wpaperd"}, false, "hyprpaper"); 397 | registerAppState("XDG Desktop Portal", {"xdg-desktop-portal-hyprland"}, true); 398 | registerAppState("Notification Daemon", {"dunst", "mako"}, true, "", "Please note you can have custom notification daemons with your shell, e.g. quickshell."); 399 | registerAppState("Status bar / shell", {"quickshell", "waybar", "eww", "ags"}, false, "", "For new users we recommend waybar, for advanced users quickshell."); 400 | registerAppState("Application launcher", {"hyprlauncher", "fuzzel", "wofi", "rofi", "anyrun", "walker", "tofi"}, false, "hyprlauncher"); 401 | registerAppState("Clipboard", {"wl-copy"}, true, "", "wl-copy is provided by wl-clipboard in most distros."); 402 | 403 | // register them 404 | bool flip = false; 405 | for (const auto& e : state.appStates) { 406 | appLayouts[flip ? 1 : 0]->addChild(e->labelEl); 407 | flip = !flip; 408 | } 409 | 410 | state.tabs[1] = nullEl; 411 | } 412 | 413 | { 414 | // Tab 3 415 | auto nullEl = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 416 | auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 417 | auto text = CTextBuilder::begin()->text(TAB3_CONTENT)->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 418 | auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 419 | spacer->setGrow(true); 420 | 421 | layout->addChild(text); 422 | layout->addChild(spacer); 423 | nullEl->addChild(layout); 424 | nullEl->setGrow(true); 425 | nullEl->setMargin(INNER_NULL_MARGIN); 426 | state.tabs[2] = nullEl; 427 | } 428 | 429 | { 430 | // Tab 4 431 | auto nullEl = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 432 | auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(4)->commence(); 433 | auto text = CTextBuilder::begin()->text(TAB4_PREAMBLE)->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 434 | auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 435 | auto hr = CRectangleBuilder::begin() 436 | ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, 11.F}}) 437 | ->color([] { return state.backend->getPalette()->m_colors.base; }) 438 | ->commence(); 439 | auto hr2 = CRectangleBuilder::begin() 440 | ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, 11.F}}) 441 | ->color([] { return state.backend->getPalette()->m_colors.base; }) 442 | ->commence(); 443 | auto terminalText = CTextBuilder::begin()->text("")->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 444 | auto terminalTextNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 445 | auto fileManagerText = CTextBuilder::begin()->text("")->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 446 | auto fileManagerNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 447 | auto defaultContainer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.6F, 1.F}})->commence(); 448 | auto defaultLayout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(4)->commence(); 449 | spacer->setGrow(true); 450 | hr->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 451 | hr->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); 452 | hr->setMargin(5); 453 | hr2->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 454 | hr2->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); 455 | hr2->setMargin(5); 456 | terminalText->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 457 | terminalText->setPositionFlag(sc(Hyprtoolkit::IElement::HT_POSITION_FLAG_VCENTER | Hyprtoolkit::IElement::HT_POSITION_FLAG_RIGHT), 458 | true); 459 | fileManagerText->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 460 | fileManagerText->setPositionFlag(sc(Hyprtoolkit::IElement::HT_POSITION_FLAG_VCENTER | Hyprtoolkit::IElement::HT_POSITION_FLAG_RIGHT), 461 | true); 462 | 463 | std::vector terms, fms; 464 | terms.reserve(TERMINALS.size()); 465 | fms.reserve(FILE_MANAGERS.size()); 466 | for (const auto& t : TERMINALS) { 467 | terms.emplace_back(t); 468 | } 469 | for (const auto& f : FILE_MANAGERS) { 470 | fms.emplace_back(f); 471 | } 472 | 473 | auto updateText = [](SP textEl, const char* app, std::string err = "") -> void { 474 | if (!err.empty()) { 475 | textEl->rebuild()->text(std::format("⚠ Error: {}", err))->commence(); 476 | return; 477 | } 478 | 479 | if (appExists(app)) 480 | textEl->rebuild()->text(std::format("✓ {} is installed", app))->commence(); 481 | else 482 | textEl->rebuild()->text(std::format("⚠ {} is not installed", app))->commence(); 483 | }; 484 | 485 | defaultContainer->addChild(defaultLayout); 486 | defaultLayout->addChild(spaceOut("Terminal", 487 | CComboboxBuilder::begin() 488 | ->items(std::move(terms)) 489 | ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {200, 25}}) 490 | ->onChanged([tt = terminalText, updateText](SP el, size_t idx) { 491 | const auto& TERM_NAME = TERMINALS[idx]; 492 | const auto RESULT = updateDefaultConfigVar("terminal", TERM_NAME); 493 | if (RESULT) 494 | updateText(tt, TERM_NAME, *RESULT); 495 | else 496 | updateText(tt, TERM_NAME); 497 | }) 498 | ->commence())); 499 | terminalTextNull->addChild(terminalText); 500 | defaultLayout->addChild(terminalTextNull); 501 | 502 | defaultLayout->addChild(spaceOut("File Manager", 503 | CComboboxBuilder::begin() 504 | ->items(std::move(fms)) 505 | ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {200, 25}}) 506 | ->onChanged([tt = fileManagerText, updateText](SP el, size_t idx) { 507 | const auto& FM_NAME = FILE_MANAGERS[idx]; 508 | const auto RESULT = updateDefaultConfigVar("fileManager", FM_NAME); 509 | if (RESULT) 510 | updateText(tt, FM_NAME, *RESULT); 511 | else 512 | updateText(tt, FM_NAME); 513 | }) 514 | ->commence())); 515 | fileManagerNull->addChild(fileManagerText); 516 | defaultLayout->addChild(fileManagerNull); 517 | 518 | updateText(terminalText, TERMINALS[0]); 519 | updateText(fileManagerText, FILE_MANAGERS[0]); 520 | 521 | layout->addChild(text); 522 | layout->addChild(hr); 523 | layout->addChild(defaultContainer); 524 | layout->addChild(hr); 525 | layout->addChild(CTextBuilder::begin() 526 | ->text("You can always change these later in your hyprland.conf") 527 | ->color([] { return state.backend->getPalette()->m_colors.text; }) 528 | ->commence()); 529 | layout->addChild(spacer); 530 | nullEl->addChild(layout); 531 | nullEl->setGrow(true); 532 | nullEl->setMargin(INNER_NULL_MARGIN); 533 | state.tabs[3] = nullEl; 534 | } 535 | 536 | { 537 | // Tab 5 538 | auto nullEl = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 539 | auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); 540 | auto text = CTextBuilder::begin()->text(TAB5_CONTENT)->color([] { return state.backend->getPalette()->m_colors.text; })->commence(); 541 | auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 542 | spacer->setGrow(true); 543 | 544 | layout->addChild(text); 545 | layout->addChild(spacer); 546 | nullEl->addChild(layout); 547 | nullEl->setGrow(true); 548 | nullEl->setMargin(INNER_NULL_MARGIN); 549 | state.tabs[4] = nullEl; 550 | } 551 | } 552 | 553 | int main(int argc, char** argv, char** envp) { 554 | state.backend = IBackend::create(); 555 | 556 | const auto FONT_SIZE = CFontSize{CFontSize::HT_FONT_TEXT}.ptSize(); 557 | const auto WINDOW_SIZE = Vector2D{FONT_SIZE * 90.F, FONT_SIZE * 50.F}; 558 | 559 | // 560 | auto window = 561 | CWindowBuilder::begin()->preferredSize(WINDOW_SIZE)->minSize(WINDOW_SIZE)->maxSize(WINDOW_SIZE)->appTitle("Welcome to Hyprland")->appClass("hyprland-welcome")->commence(); 562 | 563 | initTabs(); 564 | 565 | window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return state.backend->getPalette()->m_colors.background; })->commence()); 566 | 567 | auto rootLayout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})->gap(10)->commence(); 568 | rootLayout->setMargin(3); 569 | 570 | window->m_rootElement->addChild(rootLayout); 571 | 572 | // top null: title 573 | 574 | auto topNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 10}})->commence(); 575 | topNull->setMargin(4); 576 | 577 | state.topText = CTextBuilder::begin()->color([] { return state.backend->getPalette()->m_colors.text; })->text(TITLES[state.tab])->fontSize(CFontSize::HT_FONT_H2)->commence(); 578 | state.topText->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); 579 | state.topText->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true); 580 | 581 | topNull->addChild(state.topText); 582 | rootLayout->addChild(topNull); 583 | 584 | // // content 585 | state.tabContainer = CRectangleBuilder::begin() 586 | ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) 587 | ->color([] { return state.backend->getPalette()->m_colors.background; }) 588 | ->borderThickness(1) 589 | ->borderColor([] { return state.backend->getPalette()->m_colors.background.brighten(0.2F); }) 590 | ->rounding(state.backend->getPalette()->m_vars.smallRounding) 591 | ->commence(); 592 | state.tabContainer->setGrow(false, true); 593 | 594 | rootLayout->addChild(state.tabContainer); 595 | 596 | state.buttonLayout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(5)->commence(); 597 | state.buttonLayout->setMargin(2); 598 | 599 | state.buttonBack = CButtonBuilder::begin()->label("Back")->onMainClick([](SP self) { tabBack(); })->commence(); 600 | state.buttonNext = CButtonBuilder::begin()->label("Next")->onMainClick([](SP self) { tabNext(); })->commence(); 601 | state.buttonQuit = CButtonBuilder::begin() 602 | ->label("Thanks, but I don't need help") 603 | ->onMainClick([w = WP{window}](SP self) { 604 | if (w) 605 | w->close(); 606 | state.backend->destroy(); 607 | }) 608 | ->commence(); 609 | state.buttonLaunchTerm = CButtonBuilder::begin() 610 | ->label("Launch terminal") 611 | ->onMainClick([w = WP{window}](SP self) { 612 | for (const auto& t : TERMINALS) { 613 | if (!appExists(t)) 614 | continue; 615 | 616 | CProcess proc(t, {""}); 617 | proc.runAsync(); 618 | } 619 | }) 620 | ->commence(); 621 | state.buttonFinish = CButtonBuilder::begin() 622 | ->label("Finish") 623 | ->onMainClick([w = WP{window}](SP self) { 624 | if (w) 625 | w->close(); 626 | state.backend->destroy(); 627 | }) 628 | ->commence(); 629 | state.buttonOpenWiki = CButtonBuilder::begin() 630 | ->label("🔗 Open wiki") 631 | ->onMainClick([w = WP{window}](SP self) { 632 | CProcess proc("xdg-open", {"https://wiki.hypr.land/"}); 633 | proc.runAsync(); 634 | 635 | state.buttonOpenWiki->rebuild()->label("🔗 Opened in your browser")->commence(); 636 | state.wikiOpenTimer = state.backend->addTimer( 637 | std::chrono::seconds(1), [](ASP t, void* d) { state.buttonOpenWiki->rebuild()->label("🔗 Open wiki")->commence(); }, nullptr); 638 | }) 639 | ->commence(); 640 | 641 | state.buttonSpacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); 642 | state.buttonSpacer->setGrow(true); 643 | 644 | rootLayout->addChild(state.buttonLayout); 645 | 646 | window->m_events.closeRequest.listenStatic([w = WP{window}] { 647 | w->close(); 648 | state.backend->destroy(); 649 | }); 650 | 651 | updateTab(); 652 | 653 | window->open(); 654 | 655 | state.backend->enterLoop(); 656 | 657 | return 0; 658 | } --------------------------------------------------------------------------------