diff --git a/.gitignore b/.gitignore index c23a067..99e4d04 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,12 @@ build cmake-build-release cmake-build-debug +subprojects/cpr +subprojects/fmt +subprojects/ftxui +subprojects/nlohmann_json-* +subprojects/spdlog-* +subprojects/packagecache # Prerequisites *.d diff --git a/CMakeLists.txt b/CMakeLists.txt index eecea16..f071848 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ pkg_check_modules( FetchContent_Declare(ftxui GIT_REPOSITORY "https://github.com/arthursonzogni/ftxui.git" - GIT_TAG "cecd54df42dd66fdf8386ed461e16b725bffc827" + GIT_TAG "602392c43d8bc2272c6548ae75b8802ab7ad0b2a" ) FetchContent_MakeAvailable(ftxui) @@ -49,7 +49,7 @@ FetchContent_MakeAvailable(spdlog) FetchContent_Declare(fmt GIT_REPOSITORY "https://github.com/fmtlib/fmt.git" - GIT_TAG "491ba2dda5a04c2438abb6bd90219de773793ec0" + GIT_TAG "3a951a66cb0fb53ff5a9d5ce9c77e05ef9d382ce" ) FetchContent_MakeAvailable(fmt) @@ -61,7 +61,7 @@ FetchContent_MakeAvailable(nlohmann_json) FetchContent_Declare(cpr GIT_REPOSITORY "https://github.com/libcpr/cpr.git" - GIT_TAG "f229f82c1b38febebbf4e9958cebcd64caa32947" + GIT_TAG "beb9e98806bb84bcc130a2cebfbcbbc6ce62b335" ) FetchContent_MakeAvailable(cpr) @@ -89,6 +89,7 @@ add_executable(${PROJECT_NAME} src/screen_service.hpp src/screen_service.cpp src/config.cpp src/config.hpp src/utils.cpp src/utils.hpp + src/widgets.cpp src/widgets.hpp src/tui.cpp src/tui.hpp src/main.cpp ) @@ -96,6 +97,7 @@ add_executable(${PROJECT_NAME} add_executable(test-exec-interactive src/config.cpp src/utils.cpp + src/widgets.cpp src/widgets.hpp src/tui.cpp src/tui.hpp src/main_test.cpp ) diff --git a/meson.build b/meson.build index dec27d4..63d3e57 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,7 @@ src_files = files( 'src/screen_service.hpp', 'src/screen_service.cpp', 'src/config.cpp', 'src/config.hpp', 'src/utils.cpp', 'src/utils.hpp', + 'src/widgets.cpp', 'src/widgets.hpp', 'src/tui.cpp', 'src/tui.hpp', 'src/main.cpp', ) diff --git a/src/tui.cpp b/src/tui.cpp index 2febd63..8c4867d 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -2,10 +2,12 @@ #include "config.hpp" #include "definitions.hpp" #include "utils.hpp" +#include "widgets.hpp" /* clang-format off */ #include // for raise #include // for transform +#include // for exists, is_directory #include // for __shared_ptr_access #include // for basic_string #include // for ftxui @@ -16,113 +18,33 @@ /* clang-format on */ using namespace ftxui; +namespace fs = std::filesystem; namespace tui { -ftxui::Element centered_widget(ftxui::Component& container, const std::string_view& title, const ftxui::Element& widget) { - return vbox({ - // -------- Title -------------- - text(title.data()) | bold, - filler(), - // -------- Center Menu -------------- - hbox({ - filler(), - border(vbox({ - widget, - separator(), - container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25), - })), - filler(), - }) | center, - filler(), - }); -} +// Revised to deal with partion sizes now being displayed to the user +bool confirm_mount([[maybe_unused]] const std::string_view& part_user) { +#ifdef NDEVENV + const auto& ret_status = utils::exec(fmt::format("mount | grep {}", part_user), true); + if (ret_status != "0") { + detail::infobox_widget("\nMount Failed!\n"); + std::this_thread::sleep_for(std::chrono::seconds(2)); + return false; + } +#endif + // auto* config_instance = Config::instance(); + // auto& config_data = config_instance->data(); + // const auto& partition = std::get(config_data["PARTITION"]); + // auto& partitions = std::get>(config_data["PARTITIONS"]); -ftxui::Component controls_widget(const std::array&& titles, const std::array, 2>&& callbacks, ftxui::ButtonOption* button_option) { - /* clang-format off */ - auto button_ok = Button(titles[0].data(), callbacks[0], button_option); - auto button_quit = Button(titles[1].data(), callbacks[1], button_option); - /* clang-format on */ + detail::infobox_widget("\nMount Successful!\n"); + std::this_thread::sleep_for(std::chrono::seconds(2)); - auto container = Container::Horizontal({ - button_ok, - Renderer([] { return filler() | size(WIDTH, GREATER_THAN, 3); }), - button_quit, - }); - - return container; -} - -ftxui::Element centered_interative_multi(const std::string_view& title, ftxui::Component& widgets) { - return vbox({ - // -------- Title -------------- - text(title.data()) | bold, - filler(), - // -------- Center Menu -------------- - hbox({ - filler(), - border(vbox({ - widgets->Render(), - })), - filler(), - }) | center, - filler(), - }); -} - -ftxui::Element multiline_text(const std::vector& lines) { - Elements multiline; - - std::transform(lines.cbegin(), lines.cend(), std::back_inserter(multiline), - [=](const std::string& line) -> Element { return text(line); }); - return vbox(std::move(multiline)) | frame; -} - -void msgbox_widget(const std::string_view& content, Decorator boxsize = size(HEIGHT, GREATER_THAN, 5)) { - auto screen = ScreenInteractive::Fullscreen(); - /* clang-format off */ - auto button_option = ButtonOption(); - button_option.border = false; - auto button_back = Button("OK", screen.ExitLoopClosure(), &button_option); - /* clang-format on */ - - auto container = Container::Horizontal({ - button_back, - }); - - std::string tmp{content.data()}; - auto renderer = Renderer(container, [&] { - return tui::centered_widget(container, "New CLI Installer", multiline_text(utils::make_multiline(tmp)) | hcenter | boxsize); - }); - - screen.Loop(renderer); -} - -void menu_widget(const std::vector& entries, const std::function&& ok_callback, std::int32_t* selected) { - auto screen = ScreenInteractive::Fullscreen(); - auto menu = Menu(&entries, selected); - auto content = Renderer(menu, [&] { - return menu->Render() | center | size(HEIGHT, GREATER_THAN, 10) | size(WIDTH, GREATER_THAN, 40); - }); - - ButtonOption button_option{.border = false}; - auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); - - auto controls = Renderer(controls_container, [&] { - return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); - }); - - auto global = Container::Vertical({ - content, - Renderer([] { return separator(); }), - controls, - }); - - auto renderer = Renderer(global, [&] { - return tui::centered_interative_multi("New CLI Installer", global); - }); - - screen.Loop(renderer); + // const auto& temp = utils::exec(fmt::format("echo {0} | sed \"s~{1} [0-9]*[G-M]~~\" | sed \"s~{1} [0-9]*\\.[0-9]*[G-M]~~\" | sed s~{1}$\' -\'~~", partitions, partition)); + // spdlog::info("human-info-about-partitions: {}", temp); + // PARTITIONS=$() + // NUMBER_PARTITIONS=$(( NUMBER_PARTITIONS - 1 )) + return true; } // BIOS and UEFI @@ -172,16 +94,13 @@ void auto_partition() noexcept { auto button_option = ButtonOption(); button_option.border = false; auto button_back = Button("Back", screen.ExitLoopClosure(), &button_option); + + auto container = Container::Horizontal({button_back}); + auto renderer = Renderer(container, [&] { + return detail::centered_widget(container, "New CLI Installer", detail::multiline_text(utils::make_multiline(disklist)) | size(HEIGHT, GREATER_THAN, 5)); + }); /* clang-format on */ - auto container = Container::Horizontal({ - button_back, - }); - - auto renderer = Renderer(container, [&] { - return tui::centered_widget(container, "New CLI Installer", multiline_text(utils::make_multiline(disklist)) | size(HEIGHT, GREATER_THAN, 5)); - }); - screen.Loop(renderer); } @@ -194,16 +113,13 @@ void show_devices() noexcept { auto button_option = ButtonOption(); button_option.border = false; auto button_back = Button("Back", screen.ExitLoopClosure(), &button_option); + + auto container = Container::Horizontal({button_back}); + auto renderer = Renderer(container, [&] { + return detail::centered_widget(container, "New CLI Installer", detail::multiline_text(utils::make_multiline(lsblk)) | size(HEIGHT, GREATER_THAN, 5)); + }); /* clang-format on */ - auto container = Container::Horizontal({ - button_back, - }); - - auto renderer = Renderer(container, [&] { - return tui::centered_widget(container, "New CLI Installer", multiline_text(utils::make_multiline(lsblk)) | size(HEIGHT, GREATER_THAN, 5)); - }); - screen.Loop(renderer); } @@ -224,7 +140,7 @@ bool select_device() noexcept { success = true; std::raise(SIGINT); }; - menu_widget(devices_list, ok_callback, &selected); + detail::menu_widget(devices_list, ok_callback, &selected); return success; } @@ -236,7 +152,7 @@ bool select_filesystem() noexcept { auto* config_instance = Config::instance(); auto& config_data = config_instance->data(); // prep variables - config_data["fs_opts"] = ""; + config_data["fs_opts"] = std::vector{}; config_data["CHK_NUM"] = 0; std::vector menu_entries = { @@ -262,8 +178,7 @@ bool select_filesystem() noexcept { const auto& file_sys = lines[0]; if (file_sys == "btrfs") { config_data["FILESYSTEM"] = "mkfs.btrfs -f"; - config_data["CHK_NUM"] = 16; - config_data["fs_opts"] = "autodefrag compress=zlib compress=lzo compress=zstd compress=no compress-force=zlib compress-force=lzo compress-force=zstd discard noacl noatime nodatasum nospace_cache recovery skip_balance space_cache nossd ssd ssd_spread commit=120"; + config_data["fs_opts"] = std::vector{"autodefrag", "compress=zlib", "compress=lzo", "compress=zstd", "compress=no", "compress-force=zlib", "compress-force=lzo", "compress-force=zstd", "discard", "noacl", "noatime", "nodatasum", "nospace_cache", "recovery", "skip_balance", "space_cache", "nossd", "ssd", "ssd_spread", "commit=120"}; #ifdef NDEVENV utils::exec("modprobe btrfs"); #endif @@ -273,59 +188,255 @@ bool select_filesystem() noexcept { config_data["FILESYSTEM"] = "mkfs.ext3 -q"; } else if (file_sys == "ext4") { config_data["FILESYSTEM"] = "mkfs.ext4 -q"; - config_data["CHK_NUM"] = 8; - config_data["fs_opts"] = "data=journal data=writeback dealloc discard noacl noatime nobarrier nodelalloc"; + config_data["fs_opts"] = std::vector{"data=journal", "data=writeback", "dealloc", "discard", "noacl", "noatime", "nobarrier", "nodelalloc"}; } else if (file_sys == "f2fs") { config_data["FILESYSTEM"] = "mkfs.f2fs -q"; - config_data["fs_opts"] = "data_flush disable_roll_forward disable_ext_identify discard fastboot flush_merge inline_xattr inline_data inline_dentry no_heap noacl nobarrier noextent_cache noinline_data norecovery"; - config_data["CHK_NUM"] = 16; + config_data["fs_opts"] = std::vector{"data_flush", "disable_roll_forward", "disable_ext_identify", "discard", "fastboot", "flush_merge", "inline_xattr", "inline_data", "inline_dentry", "no_heap", "noacl", "nobarrier", "noextent_cache", "noinline_data", "norecovery"}; #ifdef NDEVENV utils::exec("modprobe f2fs"); #endif } else if (file_sys == "jfs") { config_data["FILESYSTEM"] = "mkfs.jfs -q"; - config_data["CHK_NUM"] = 4; - config_data["fs_opts"] = "discard errors=continue errors=panic nointegrity"; + config_data["fs_opts"] = std::vector{"discard", "errors=continue", "errors=panic", "nointegrity"}; } else if (file_sys == "nilfs2") { config_data["FILESYSTEM"] = "mkfs.nilfs2 -fq"; - config_data["CHK_NUM"] = 7; - config_data["fs_opts"] = "discard nobarrier errors=continue errors=panic order=relaxed order=strict norecovery"; + config_data["fs_opts"] = std::vector{"discard", "nobarrier", "errors=continue", "errors=panic", "order=relaxed", "order=strict", "norecovery"}; } else if (file_sys == "ntfs") { config_data["FILESYSTEM"] = "mkfs.ntfs -q"; } else if (file_sys == "reiserfs") { config_data["FILESYSTEM"] = "mkfs.reiserfs -q"; - config_data["CHK_NUM"] = 5; - config_data["fs_opts"] = "acl nolog notail replayonly user_xattr"; + config_data["fs_opts"] = std::vector{"acl", "nolog", "notail", "replayonly", "user_xattr"}; } else if (file_sys == "vfat") { config_data["FILESYSTEM"] = "mkfs.vfat -F32"; } else if (file_sys == "xfs") { config_data["FILESYSTEM"] = "mkfs.xfs -f"; - config_data["CHK_NUM"] = 9; - config_data["fs_opts"] = "discard filestreams ikeep largeio noalign nobarrier norecovery noquota wsync"; + config_data["fs_opts"] = std::vector{"discard", "filestreams", "ikeep", "largeio", "noalign", "nobarrier", "norecovery", "noquota", "wsync"}; } success = true; std::raise(SIGINT); }; - menu_widget(menu_entries, ok_callback, &selected); + detail::menu_widget(menu_entries, ok_callback, &selected); - if (!success) - return false; + /* clang-format off */ + if (!success) { return false; } + /* clang-format on */ + // Warn about formatting! const auto& file_sys = std::get(config_data["FILESYSTEM"]); const auto& partition = std::get(config_data["PARTITION"]); const auto& content = fmt::format("\nMount {}\n\n! Data on {} will be lost !\n", file_sys, partition); - msgbox_widget(content, size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 70)); + const auto& do_mount = detail::yesno_widget(content, size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 75)); + if (do_mount) { +#ifdef NDEVENV + utils::exec(fmt::format("{} {}", file_sys, partition)); +#endif + spdlog::info("mount.{} {}", partition, file_sys); + } return success; } -bool mount_current_partition() noexcept { return true; } + +// This subfunction allows for special mounting options to be applied for relevant fs's. +// Seperate subfunction for neatness. +void mount_opts() noexcept { + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + + const auto& file_sys = std::get(config_data["FILESYSTEM"]); + const auto& fs_opts = std::get>(config_data["fs_opts"]); + const auto& partition = std::get(config_data["PARTITION"]); + const auto& format_name = utils::exec(fmt::format("echo {} | rev | cut -d/ -f1 | rev", partition)); + const auto& format_device = utils::exec(fmt::format("lsblk -i | tac | sed -r 's/^[^[:alnum:]]+//' | sed -n -e \"/{}/,/disk/p\" | {}", format_name, "awk \'/disk/ {print $1}\'")); + + const auto& rotational_queue = (utils::exec(fmt::format("cat /sys/block/{}/queue/rotational", format_device)) == "1"); + + std::unique_ptr fs_opts_state{new bool[fs_opts.size()]{false}}; + for (size_t i = 0; i < fs_opts.size(); ++i) { + const auto& fs_opt = fs_opts[i]; + auto& fs_opt_state = fs_opts_state[i]; + if (rotational_queue) { + fs_opt_state = ((fs_opt == "autodefrag") + || (fs_opt == "compress=zlip") + || (fs_opt == "nossd")); + } else { + fs_opt_state = ((fs_opt == "compress=lzo") + || (fs_opt == "space_cache") + || (fs_opt == "commit=120") + || (fs_opt == "ssd")); + } + + /* clang-format off */ + if (!fs_opt_state) { fs_opt_state = (fs_opt == "noatime"); } + /* clang-format on */ + } + + auto flags = Container::Vertical(detail::from_vector_checklist(fs_opts, fs_opts_state.get())); + + auto screen = ScreenInteractive::Fullscreen(); + auto content = Renderer(flags, [&] { + return flags->Render() | center | size(HEIGHT, GREATER_THAN, 10) | size(WIDTH, GREATER_THAN, 40) | vscroll_indicator; + }); + + auto& mount_opts_info = std::get(config_data["MOUNT_OPTS"]); + auto ok_callback = [&] { + mount_opts_info = detail::from_checklist_string(fs_opts, fs_opts_state.get()); + std::raise(SIGINT); + }; + + ButtonOption button_option{.border = false}; + auto controls_container = detail::controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + + auto controls = Renderer(controls_container, [&] { + return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); + }); + + std::string mount_options_body = "\nUse [Space] to de/select the desired mount\noptions and review carefully. Please do not\nselect multiple versions of the same option.\n"; + auto global = Container::Vertical({ + Renderer([&] { return detail::multiline_text(utils::make_multiline(mount_options_body)); }), + Renderer([] { return separator(); }), + content, + Renderer([] { return separator(); }), + controls, + }); + + auto renderer = Renderer(global, [&] { + const auto& file_sys_formated = utils::exec(fmt::format("echo {} | sed \"s/.*\\.//g;s/-.*//g\"", file_sys)); + const auto& title = fmt::format("New CLI Installer | {}", file_sys_formated); + return detail::centered_interative_multi(title, global); + }); + + screen.Loop(renderer); + + // Now clean up the file + mount_opts_info = utils::exec(fmt::format("echo \"{}\" | sed \'s/ /,/g\'", mount_opts_info)); + mount_opts_info = utils::exec(fmt::format("echo \"{}\" | sed \'$s/,$//\'", mount_opts_info)); + + // If mount options selected, confirm choice + if (!mount_opts_info.empty()) { + auto confirm_text = Container::Vertical({ + Renderer([] { return paragraphAlignLeft("Confirm the following mount options:"); }), + Renderer([&] { return text(mount_opts_info) | dim; }), + }); + const auto& do_mount = detail::yesno_widget(confirm_text, size(HEIGHT, LESS_THAN, 10) | size(WIDTH, LESS_THAN, 75)); + /* clang-format off */ + if (!do_mount) { mount_opts_info = ""; } + /* clang-format on */ + } +} + +bool mount_current_partition() noexcept { + auto* config_instance = Config::instance(); + auto& config_data = config_instance->data(); + const auto& mountpoint = std::get(config_data["MOUNTPOINT"]); + const auto& mount_dev = std::get(config_data["MOUNT"]); + +#ifdef NDEVENV + // Make the mount directory + fs::path mount_dir(fmt::format("{}{}", mountpoint, mount_dev)); + fs::create_directories(mount_dir); +#endif + + config_data["MOUNT_OPTS"] = ""; + /* clang-format off */ + // Get mounting options for appropriate filesystems + const auto& fs_opts = std::get>(config_data["fs_opts"]); + if (!fs_opts.empty()) { mount_opts(); } + /* clang-format on */ + + // TODO: use libmount instead. + // see https://github.com/util-linux/util-linux/blob/master/sys-utils/mount.c#L734 +#ifdef NDEVENV + const auto& partition = std::get(config_data["PARTITION"]); + const auto& mount_opts_info = std::get(config_data["MOUNT_OPTS"]); + if (!mount_opts_info.empty()) { + // check_for_error "mount ${PARTITION} $(cat ${MOUNT_OPTS})" + const auto& mount_status = utils::exec(fmt::format("mount -o {} {} {}{}", mount_opts_info, partition, mountpoint, mount_dev)); + spdlog::info("{}", mount_status); + } else { + // check_for_error "mount ${PARTITION}" + const auto& mount_status = utils::exec(fmt::format("mount {} {}{}", partition, mountpoint, mount_dev)); + spdlog::info("{}", mount_status); + } +#endif + confirm_mount(fmt::format("{}{}", mountpoint, mount_dev)); + /* + // Identify if mounted partition is type "crypt" (LUKS on LVM, or LUKS alone) + if [[ $(lsblk -lno TYPE ${PARTITION} | grep "crypt") != "" ]]; then + // cryptname for bootloader configuration either way + LUKS=1 + LUKS_NAME=$(echo ${PARTITION} | sed "s~^/dev/mapper/~~g") + + # Check if LUKS on LVM (parent = lvm /dev/mapper/...) + cryptparts=$(lsblk -lno NAME,FSTYPE,TYPE | grep "lvm" | grep -i "crypto_luks" | uniq | awk '{print "/dev/mapper/"$1}') + for i in ${cryptparts}; do + if [[ $(lsblk -lno NAME ${i} | grep $LUKS_NAME) != "" ]]; then + LUKS_DEV="$LUKS_DEV cryptdevice=${i}:$LUKS_NAME" + LVM=1 + return 0; + fi + done + + // Check if LVM on LUKS + cryptparts=$(lsblk -lno NAME,FSTYPE,TYPE | grep " crypt$" | grep -i "LVM2_member" | uniq | awk '{print "/dev/mapper/"$1}') + for i in ${cryptparts}; do + if [[ $(lsblk -lno NAME ${i} | grep $LUKS_NAME) != "" ]]; then + LUKS_DEV="$LUKS_DEV cryptdevice=${i}:$LUKS_NAME" + LVM=1 + return 0; + fi + done + + // Check if LUKS alone (parent = part /dev/...) + cryptparts=$(lsblk -lno NAME,FSTYPE,TYPE | grep "part" | grep -i "crypto_luks" | uniq | awk '{print "/dev/"$1}') + for i in ${cryptparts}; do + if [[ $(lsblk -lno NAME ${i} | grep $LUKS_NAME) != "" ]]; then + LUKS_UUID=$(lsblk -lno UUID,TYPE,FSTYPE ${i} | grep "part" | grep -i "crypto_luks" | awk '{print $1}') + LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME" + return 0; + fi + done + + // If LVM logical volume.... + elif [[ $(lsblk -lno TYPE ${PARTITION} | grep "lvm") != "" ]]; then + LVM=1 + + // First get crypt name (code above would get lv name) + cryptparts=$(lsblk -lno NAME,TYPE,FSTYPE | grep "crypt" | grep -i "lvm2_member" | uniq | awk '{print "/dev/mapper/"$1}') + for i in ${cryptparts}; do + if [[ $(lsblk -lno NAME ${i} | grep $(echo $PARTITION | sed "s~^/dev/mapper/~~g")) != "" ]]; then + LUKS_NAME=$(echo ${i} | sed s~/dev/mapper/~~g) + return 0; + fi + done + + // Now get the device (/dev/...) for the crypt name + cryptparts=$(lsblk -lno NAME,FSTYPE,TYPE | grep "part" | grep -i "crypto_luks" | uniq | awk '{print "/dev/"$1}') + for i in ${cryptparts}; do + if [[ $(lsblk -lno NAME ${i} | grep $LUKS_NAME) != "" ]]; then + # Create UUID for comparison + LUKS_UUID=$(lsblk -lno UUID,TYPE,FSTYPE ${i} | grep "part" | grep -i "crypto_luks" | awk '{print $1}') + + # Check if not already added as a LUKS DEVICE (i.e. multiple LVs on one crypt). If not, add. + if [[ $(echo $LUKS_DEV | grep $LUKS_UUID) == "" ]]; then + LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME" + LUKS=1 + fi + + return 0; + fi + done + fi*/ + return true; +} + +void make_swap() noexcept { } void mount_partitions() noexcept { auto* config_instance = Config::instance(); auto& config_data = config_instance->data(); // Warn users that they CAN mount partitions without formatting them! static constexpr std::string_view content = "\nIMPORTANT: Partitions can be mounted without formatting them\nby selecting the 'Do not format' option listed at the top of\nthe file system menu.\n\nEnsure the correct choices for mounting and formatting\nare made as no warnings will be provided, with the exception of\nthe UEFI boot partition.\n"; - msgbox_widget(content, size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 70)); + detail::msgbox_widget(content, size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 70)); // LVM Detection. If detected, activate. // lvm_detect @@ -381,7 +492,7 @@ void mount_partitions() noexcept { success = true; std::raise(SIGINT); }; - menu_widget(partitions, ok_callback, &selected); + detail::menu_widget(partitions, ok_callback, &selected); if (!success) return; } @@ -424,7 +535,7 @@ void mount_partitions() noexcept { // done // Identify and create swap, if applicable - // tui::make_swap(); + make_swap(); // Now that swap is done we put the legacy partitions back, unless they are already mounted /*for i in $(zfs_list_datasets "legacy"); do @@ -522,7 +633,7 @@ void create_partitions() noexcept { }; ButtonOption button_option{.border = false}; - auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + auto controls_container = detail::controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); auto controls = Renderer(controls_container, [&] { return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); @@ -535,7 +646,7 @@ void create_partitions() noexcept { }); auto renderer = Renderer(global, [&] { - return tui::centered_interative_multi("New CLI Installer", global); + return detail::centered_interative_multi("New CLI Installer", global); }); screen.Loop(renderer); @@ -607,7 +718,7 @@ void prep_menu() noexcept { }; ButtonOption button_option{.border = false}; - auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + auto controls_container = detail::controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); auto controls = Renderer(controls_container, [&] { return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); @@ -620,7 +731,7 @@ void prep_menu() noexcept { }); auto renderer = Renderer(global, [&] { - return tui::centered_interative_multi("New CLI Installer", global); + return detail::centered_interative_multi("New CLI Installer", global); }); screen.Loop(renderer); @@ -665,7 +776,10 @@ void init() noexcept { break; } case 3: { - utils::check_mount(); + if (!utils::check_mount()) { + screen.ExitLoopClosure(); + std::raise(SIGINT); + } tui::install_custom_menu(); break; } @@ -680,7 +794,7 @@ void init() noexcept { }; ButtonOption button_option{.border = false}; - auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + auto controls_container = detail::controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); auto controls = Renderer(controls_container, [&] { return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); @@ -693,9 +807,10 @@ void init() noexcept { }); auto renderer = Renderer(global, [&] { - return tui::centered_interative_multi("New CLI Installer", global); + return detail::centered_interative_multi("New CLI Installer", global); }); screen.Loop(renderer); } + } // namespace tui diff --git a/src/tui.hpp b/src/tui.hpp index 5f5bd45..27a1f98 100644 --- a/src/tui.hpp +++ b/src/tui.hpp @@ -1,14 +1,7 @@ #ifndef TUI_HPP #define TUI_HPP -/* clang-format off */ -#include // for string_view -#include // for Component -#include // for Element -/* clang-format on */ - namespace tui { -ftxui::Element centered_widget(ftxui::Component& container, const std::string_view& title, const ftxui::Element& widget); void create_partitions() noexcept; void init() noexcept; } // namespace tui diff --git a/src/utils.cpp b/src/utils.cpp index 9ea1545..4d5826d 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -11,6 +11,7 @@ #include // for exit, WIFEXITED, WIFSIGNALED #include // for exists, is_directory #include // for basic_istream, cin +#include // for std::views::join #include // for regex_search, match_results<>::_Base_type #include // for operator==, string, basic_string, allocator #include // for mount @@ -127,21 +128,34 @@ bool prompt_char(const char* prompt, const char* color, char* read) noexcept { } auto make_multiline(std::string& str, bool reverse, const std::string_view&& delim) noexcept -> std::vector { + const auto& view = str + | std::views::split(delim) + | std::views::transform([](auto&& rng) { + const auto& tmp = std::string(&*rng.begin(), static_cast(std::ranges::distance(rng))); + return tmp; + }); + std::vector lines{}; - - std::size_t start{}; - std::size_t end = str.find(delim); - while (end != std::string::npos) { - lines.push_back(str.substr(start, end - start)); - start = end + delim.size(); - end = str.find(delim, start); - } - lines.push_back(str.substr(start, end - start)); + std::ranges::copy(view, std::back_inserter(lines)); if (reverse) { - std::reverse(lines.begin(), lines.end()); + std::ranges::reverse(lines); + } + return lines; +} + +auto make_multiline(std::vector& multiline, bool reverse, const std::string_view&& delim) noexcept -> std::string { + std::string res{}; + // for (const char c : multiline | std::views::join) res += c; + for (const auto& line : multiline) { + res += line; + res += delim.data(); } - return lines; + if (reverse) { + std::ranges::reverse(res.begin(), res.end()); + } + + return res; } // install a pkg in the live session if not installed diff --git a/src/utils.hpp b/src/utils.hpp index 313de0a..4c1aef7 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -14,10 +14,12 @@ void print_banner() noexcept; bool prompt_char(const char* prompt, const char* color = RESET, char* read = nullptr) noexcept; void clear_screen() noexcept; [[nodiscard]] auto make_multiline(std::string& str, bool reverse = false, const std::string_view&& delim = "\n") noexcept -> std::vector; +[[nodiscard]] auto make_multiline(std::vector& multiline, bool reverse = false, const std::string_view&& delim = "\n") noexcept -> std::string; void secure_wipe() noexcept; -bool check_mount() noexcept; -std::string list_mounted() noexcept; -std::string list_containing_crypt() noexcept; +[[nodiscard]] bool check_mount() noexcept; +[[nodiscard]] std::string list_mounted() noexcept; +[[nodiscard]] std::string list_containing_crypt() noexcept; +[[nodiscard]] std::string list_non_crypt() noexcept; void umount_partitions() noexcept; void find_partitions() noexcept; diff --git a/src/widgets.cpp b/src/widgets.cpp new file mode 100644 index 0000000..4c1a2dc --- /dev/null +++ b/src/widgets.cpp @@ -0,0 +1,248 @@ +#include "widgets.hpp" +#include "config.hpp" +#include "utils.hpp" + +/* clang-format off */ +#include // for raise +#include // for transform +#include // for basic_string +#include // for Renderer, Button +#include // for ButtonOption +#include // for Component, ScreenI... +/* clang-format on */ + +using namespace ftxui; + +namespace tui::detail { + +Element centered_widget(Component& container, const std::string_view& title, const Element& widget) noexcept { + return vbox({ + // -------- Title -------------- + text(title.data()) | bold, + filler(), + // -------- Center Menu -------------- + hbox({ + filler(), + border(vbox({ + widget, + separator(), + container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25), + })), + filler(), + }) | center, + filler(), + }); +} + +Element centered_widget_nocontrols(const std::string_view& title, const Element& widget) noexcept { + return vbox({ + // -------- Title -------------- + text(title.data()) | bold, + filler(), + // -------- Center Menu -------------- + hbox({ + filler(), + border(vbox({widget})), + filler(), + }) | center, + filler(), + }); +} + +Component controls_widget(const std::array&& titles, const std::array, 2>&& callbacks, ButtonOption* button_option) noexcept { + /* clang-format off */ + auto button_ok = Button(titles[0].data(), callbacks[0], button_option); + auto button_quit = Button(titles[1].data(), callbacks[1], button_option); + /* clang-format on */ + + auto container = Container::Horizontal({ + button_ok, + Renderer([] { return filler() | size(WIDTH, GREATER_THAN, 3); }), + button_quit, + }); + + return container; +} + +Element centered_interative_multi(const std::string_view& title, Component& widgets) noexcept { + return vbox({ + // -------- Title -------------- + text(title.data()) | bold, + filler(), + // -------- Center Menu -------------- + hbox({ + filler(), + border(vbox({ + widgets->Render(), + })), + filler(), + }) | center, + filler(), + }); +} + +Element multiline_text(const std::vector& lines) noexcept { + Elements multiline; + + std::transform(lines.cbegin(), lines.cend(), std::back_inserter(multiline), + [=](const std::string& line) -> Element { return text(line); }); + return vbox(std::move(multiline)) | frame; +} + +Components from_vector_checklist(const std::vector& opts, bool* opts_state) noexcept { + Components components; + + for (size_t i = 0; i < opts.size(); ++i) { + auto component = Checkbox(&opts[i], &opts_state[i]); + components.emplace_back(component); + } + return components; +} + +std::string from_checklist_string(const std::vector& opts, bool* opts_state) noexcept { + std::string res{}; + + for (size_t i = 0; i < opts.size(); ++i) { + if (opts_state[i]) { + res += opts[i] + " "; + } + } + /* clang-format off */ + if (res.ends_with(" ")) { res.pop_back(); } + /* clang-format on */ + return res; +} + +std::vector from_checklist_vector(const std::vector& opts, bool* opts_state) noexcept { + std::vector res{}; + + for (size_t i = 0; i < opts.size(); ++i) { + if (opts_state[i]) { + res.push_back(opts[i]); + } + } + return res; +} + +void msgbox_widget(const std::string_view& content, Decorator boxsize) noexcept { + auto screen = ScreenInteractive::Fullscreen(); + /* clang-format off */ + auto button_option = ButtonOption(); + button_option.border = false; + auto button_back = Button("OK", screen.ExitLoopClosure(), &button_option); + /* clang-format on */ + + auto container = Container::Horizontal({ + button_back, + }); + + std::string tmp{content.data()}; + auto renderer = Renderer(container, [&] { + return centered_widget(container, "New CLI Installer", multiline_text(utils::make_multiline(tmp)) | hcenter | boxsize); + }); + + screen.Loop(renderer); +} + +void infobox_widget(const std::string_view& content, Decorator boxsize) noexcept { + auto screen = Screen::Create( + Dimension::Full(), // Width + Dimension::Full() // Height + ); + + std::string tmp{content.data()}; + auto element = centered_widget_nocontrols("New CLI Installer", multiline_text(utils::make_multiline(tmp)) | vcenter | boxsize); + Render(screen, element); + screen.Print(); +} + +bool yesno_widget(const std::string_view& content, Decorator boxsize) noexcept { + auto screen = ScreenInteractive::Fullscreen(); + + bool success{}; + auto ok_callback = [&] { + success = true; + std::raise(SIGINT); + }; + ButtonOption button_option{.border = false}; + auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + + auto controls = Renderer(controls_container, [&] { + return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); + }); + + auto container = Container::Horizontal({ + controls, + }); + + auto renderer = Renderer(container, [&] { + return centered_widget(container, "New CLI Installer", text(content.data()) | hcenter | boxsize); + }); + + screen.Loop(renderer); + + return success; +} + +bool yesno_widget(ftxui::Component& container, Decorator boxsize) noexcept { + auto screen = ScreenInteractive::Fullscreen(); + + auto content = Renderer(container, [&] { + return container->Render() | hcenter | boxsize; + }); + + bool success{}; + auto ok_callback = [&] { + success = true; + std::raise(SIGINT); + }; + ButtonOption button_option{.border = false}; + auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + + auto controls = Renderer(controls_container, [&] { + return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); + }); + + auto global = Container::Vertical({ + content, + Renderer([] { return separator(); }), + controls, + }); + + auto renderer = Renderer(global, [&] { + return centered_interative_multi("New CLI Installer", global); + }); + + screen.Loop(renderer); + + return success; +} + +void menu_widget(const std::vector& entries, const std::function&& ok_callback, std::int32_t* selected) noexcept { + auto screen = ScreenInteractive::Fullscreen(); + auto menu = Menu(&entries, selected); + auto content = Renderer(menu, [&] { + return menu->Render() | center | size(HEIGHT, GREATER_THAN, 10) | size(WIDTH, GREATER_THAN, 40); + }); + + ButtonOption button_option{.border = false}; + auto controls_container = controls_widget({"OK", "Cancel"}, {ok_callback, screen.ExitLoopClosure()}, &button_option); + + auto controls = Renderer(controls_container, [&] { + return controls_container->Render() | hcenter | size(HEIGHT, LESS_THAN, 3) | size(WIDTH, GREATER_THAN, 25); + }); + + auto global = Container::Vertical({ + content, + Renderer([] { return separator(); }), + controls, + }); + + auto renderer = Renderer(global, [&] { + return centered_interative_multi("New CLI Installer", global); + }); + + screen.Loop(renderer); +} + +} // namespace tui::detail diff --git a/src/widgets.hpp b/src/widgets.hpp new file mode 100644 index 0000000..1beeae1 --- /dev/null +++ b/src/widgets.hpp @@ -0,0 +1,28 @@ +#ifndef WIDGETS_HPP +#define WIDGETS_HPP + +/* clang-format off */ +#include // for string_view +#include // for Component +#include // for ButtonOption +#include // for Element, operator|, size +/* clang-format on */ + +namespace tui { +namespace detail { + auto centered_widget(ftxui::Component& container, const std::string_view& title, const ftxui::Element& widget) noexcept -> ftxui::Element; + auto controls_widget(const std::array&& titles, const std::array, 2>&& callbacks, ftxui::ButtonOption* button_option) noexcept -> ftxui::Component; + auto centered_interative_multi(const std::string_view& title, ftxui::Component& widgets) noexcept -> ftxui::Element; + auto multiline_text(const std::vector& lines) noexcept -> ftxui::Element; + auto from_vector_checklist(const std::vector& opts, bool* opts_state) noexcept -> ftxui::Components; + auto from_checklist_string(const std::vector& opts, bool* opts_state) noexcept -> std::string; + auto from_checklist_vector(const std::vector& opts, bool* opts_state) noexcept -> std::vector; + void msgbox_widget(const std::string_view& content, ftxui::Decorator boxsize = size(ftxui::HEIGHT, ftxui::GREATER_THAN, 5)) noexcept; + void infobox_widget(const std::string_view& content, ftxui::Decorator boxsize = size(ftxui::HEIGHT, ftxui::GREATER_THAN, 5)) noexcept; + bool yesno_widget(const std::string_view& content, ftxui::Decorator boxsize = size(ftxui::HEIGHT, ftxui::GREATER_THAN, 5)) noexcept; + bool yesno_widget(ftxui::Component& container, ftxui::Decorator boxsize = size(ftxui::HEIGHT, ftxui::GREATER_THAN, 5)) noexcept; + void menu_widget(const std::vector& entries, const std::function&& ok_callback, std::int32_t* selected) noexcept; +} // namespace detail +} // namespace tui + +#endif // WIDGETS_HPP diff --git a/subprojects/cpr.wrap b/subprojects/cpr.wrap index 0376162..e2d06eb 100644 --- a/subprojects/cpr.wrap +++ b/subprojects/cpr.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/libcpr/cpr.git -revision = f229f82c1b38febebbf4e9958cebcd64caa32947 +revision = beb9e98806bb84bcc130a2cebfbcbbc6ce62b335 patch_directory = cpr diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index e00b034..85dfc15 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/fmtlib/fmt.git -revision = c472a2781852778cc3f06521a3384a2c5c5822d5 +revision = 3a951a66cb0fb53ff5a9d5ce9c77e05ef9d382ce patch_directory = fmt diff --git a/subprojects/ftxui.wrap b/subprojects/ftxui.wrap index c712be0..faa0924 100644 --- a/subprojects/ftxui.wrap +++ b/subprojects/ftxui.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/arthursonzogni/ftxui.git -revision = cecd54df42dd66fdf8386ed461e16b725bffc827 +revision = 602392c43d8bc2272c6548ae75b8802ab7ad0b2a patch_directory = ftxui diff --git a/subprojects/packagefiles/cpr/meson.build b/subprojects/packagefiles/cpr/meson.build index b662614..06cd5b7 100644 --- a/subprojects/packagefiles/cpr/meson.build +++ b/subprojects/packagefiles/cpr/meson.build @@ -1,5 +1,5 @@ project('cpr', 'cpp', - version: '1.7.0', + version: '1.7.2', license : 'MIT', default_options: ['cpp_std=c++11'] ) diff --git a/subprojects/packagefiles/ftxui/meson.build b/subprojects/packagefiles/ftxui/meson.build index c524727..1044849 100644 --- a/subprojects/packagefiles/ftxui/meson.build +++ b/subprojects/packagefiles/ftxui/meson.build @@ -29,6 +29,7 @@ ftxui_screen_lib = static_library('ftxui_screen', ftxui_dom_lib = static_library('ftxui_dom', sources : [ 'include/ftxui/dom/elements.hpp', + 'include/ftxui/dom/flexbox_config.hpp', 'include/ftxui/dom/node.hpp', 'include/ftxui/dom/requirement.hpp', 'include/ftxui/dom/take_any_args.hpp', @@ -43,12 +44,15 @@ ftxui_dom_lib = static_library('ftxui_dom', 'src/ftxui/dom/dbox.cpp', 'src/ftxui/dom/dim.cpp', 'src/ftxui/dom/flex.cpp', + 'src/ftxui/dom/flexbox.cpp', + 'src/ftxui/dom/flexbox_config.cpp', + 'src/ftxui/dom/flexbox_helper.cpp', + 'src/ftxui/dom/flexbox_helper.hpp', 'src/ftxui/dom/frame.cpp', 'src/ftxui/dom/gauge.cpp', 'src/ftxui/dom/graph.cpp', 'src/ftxui/dom/gridbox.cpp', 'src/ftxui/dom/hbox.cpp', - 'src/ftxui/dom/hflow.cpp', 'src/ftxui/dom/inverted.cpp', 'src/ftxui/dom/node.cpp', 'src/ftxui/dom/node_decorator.hpp',