diff --git a/CMakeLists.txt b/CMakeLists.txt index 833891b..78341fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.14) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + ## ## PROJECT ## name and version @@ -15,6 +17,9 @@ message(STATUS "BUILD: ${CMAKE_BUILD_TYPE}") ## ## INCLUDE ## +include(CompilerWarnings) +include(EnableCcache) +include(ClangTidy) include(FetchContent) find_package(PkgConfig REQUIRED) @@ -22,13 +27,16 @@ pkg_check_modules(GTK3 REQUIRED gtkmm-3.0) FetchContent_Declare(fmt GIT_REPOSITORY "https://github.com/fmtlib/fmt.git" - GIT_TAG "d9fd695ac737f84f7de2d0a2aa346b25efb9afbf" + GIT_TAG "2742611cad4aee6b1a5638bd1ebf132908f4a3d9" ) FetchContent_MakeAvailable(fmt) ## ## CONFIGURATION ## +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") + if(UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "-fcolor-diagnostics ${CMAKE_CXX_FLAGS}") @@ -40,13 +48,19 @@ if(UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") endif() endif() -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fwhole-program") +endif() + +add_library(project_warnings INTERFACE) +set_project_warnings(project_warnings) ## ## Target ## add_executable(${PROJECT_NAME} - src/main.cc + src/hello.cpp src/hello.hpp + src/main.cpp ) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) diff --git a/cmake/ClangTidy.cmake b/cmake/ClangTidy.cmake new file mode 100644 index 0000000..cbe36ae --- /dev/null +++ b/cmake/ClangTidy.cmake @@ -0,0 +1,15 @@ + +option(ENABLE_TIDY "Enable clang-tidy [default: OFF]" OFF) +if(ENABLE_TIDY) + find_program(CLANG_TIDY_EXE + NAMES clang-tidy-9 clang-tidy-8 clang-tidy-7 clang-tidy + DOC "Path to clang-tidy executable") + if(NOT CLANG_TIDY_EXE) + message(STATUS "[clang-tidy] Not found.") + else() + message(STATUS "[clang-tidy] found: ${CLANG_TIDY_EXE}") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}") + endif() +else() + message(STATUS "[clang-tidy] Disabled.") +endif() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..12f698f --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,76 @@ +function(set_project_warnings project_name) + option(WARNINGS_AS_ERROR "Treat compiler warnings as error" ON) + + set(MSVC_WARNINGS + /W4 # Base + /w14242 # Conversion + /w14254 # Operator convers. + /w14263 # Func member doesn't override + /w14265 # class has vfuncs, but destructor is not + /w14287 # unsigned/negative constant mismatch + /we4289 # nonstandard extension used: loop control var + /w14296 # expression is always 'boolean_value' + /w14311 # pointer trunc from one tipe to another + /w14545 # expression before comma evaluates to a function which missign an argument list + + /w14546 # function call before comma missing argument list + /w14547 # operator before comma has no effect; expected operator with side-effect + /w14549 # operator before comma has no effect; did you intend operator? + + /w14555 # expresion has no effect; expected expression with side-effect + /w14619 # pragma warning + /w14640 # Enable warning on thread; static member + + /w14826 # Conversion from one tipe to another is sign-extended cause unexpected runtime behavior. + /w14928 # illegal copy-initialization; more than user-defined. + /X + /constexpr + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # standard + -Wshadow + + -Wnon-virtual-dtor + + -Wold-style-cast # c-style cast + -Wcast-align + -Wunused + -Woverloaded-virtual + + -Wpedantic # non-standard C++ + -Wconversion # type conversion that may lose data + -Wsign-conversion + -Wnull-dereference + -Wdouble-promotion # float to double + + -Wformat=2 + ) + + if(WARNINGS_AS_ERRORS) + set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) + set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) + endif() + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation + + -Wduplicated-cond + -Wduplicated-branches + -Wlogical-op + + -Wuseless-cast + ) + + if(MSVC) + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + else() + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + endif() + + target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) +endfunction() diff --git a/cmake/EnableCcache.cmake b/cmake/EnableCcache.cmake new file mode 100644 index 0000000..bc51a8b --- /dev/null +++ b/cmake/EnableCcache.cmake @@ -0,0 +1,13 @@ +# Setup ccache. +# +# The ccache is auto-enabled if the tool is found. +# To disable set -DCCACHE=OFF option. +if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) + find_program(CCACHE ccache DOC "ccache tool path; set to OFF to disable") + if(CCACHE) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) + message(STATUS "[ccache] Enabled: ${CCACHE}") + else() + message(STATUS "[ccache] Disabled.") + endif() +endif() diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..9de6474 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,42 @@ +-std=gnu++17 +-DFMT_LOCALE +-I/usr/include/gtkmm-3.0 +-I/usr/lib/gtkmm-3.0/include +-I/usr/include/giomm-2.4 +-I/usr/lib/giomm-2.4/include +-I/usr/include/glib-2.0 +-I/usr/lib/glib-2.0/include +-I/usr/include/libmount +-I/usr/include/blkid +-I/usr/include/glibmm-2.4 +-I/usr/lib/glibmm-2.4/include +-I/usr/include/sigc++-2.0 +-I/usr/lib/sigc++-2.0/include +-I/usr/include/gtk-3.0 +-I/usr/include/pango-1.0 +-I/usr/include/harfbuzz +-I/usr/include/freetype2 +-I/usr/include/libpng16 +-I/usr/include/fribidi +-I/usr/include/cairo +-I/usr/include/lzo +-I/usr/include/pixman-1 +-I/usr/include/gdk-pixbuf-2.0 +-I/usr/include/gio-unix-2.0 +-I/usr/include/cloudproviders +-I/usr/include/atk-1.0 +-I/usr/include/at-spi2-atk/2.0 +-I/usr/include/dbus-1.0 +-I/usr/lib/dbus-1.0/include +-I/usr/include/at-spi-2.0 +-I/usr/include/cairomm-1.0 +-I/usr/lib/cairomm-1.0/include +-I/usr/include/pangomm-1.4 +-I/usr/lib/pangomm-1.4/include +-I/usr/include/atkmm-1.6 +-I/usr/lib/atkmm-1.6/include +-I/usr/include/gtk-3.0/unix-print +-I/usr/include/gdkmm-3.0 +-I/usr/lib/gdkmm-3.0/include +-Isrc +-xc++ diff --git a/src/hello.cpp b/src/hello.cpp new file mode 100644 index 0000000..8434861 --- /dev/null +++ b/src/hello.cpp @@ -0,0 +1,362 @@ +#include "hello.hpp" +#include "helper.hpp" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace fs = std::filesystem; + +namespace { +Hello* g_refHello; + +std::string fix_path(std::string&& path) noexcept { + if (path[0] != '~') { + return path; + } + replace_all(path, "~", Glib::get_home_dir().c_str()); + return path; +} + +nlohmann::json read_json(const std::string_view& path) { + // read a JSON file + std::ifstream i(fix_path(path.data())); + nlohmann::json j; + i >> j; + + return j; +} + +void write_json(const std::string_view& path, const nlohmann::json& content) { + // write data to JSON file + std::ofstream o(fix_path(path.data())); + o << content << '\n'; +} + +// Read information from the lsb-release file. +// +// @Returns args from lsb-release file +std::array get_lsb_infos() { + std::unordered_map lsb{}; + + try { + std::ifstream lsb_release("/etc/lsb-release"); + std::string line; + while (std::getline(lsb_release, line)) { + if (line.find('=') != std::string::npos) { + auto var = tokenize(line, "="); + remove_all(var.first, "DISTRIB_"); + remove_all(var.second, "\""); + lsb[var.first] = var.second; + } + } + } catch (const std::exception& e) { + std::cerr << e.what() << '\n'; + return {"not CachyOS", "0.0"}; + } + return {lsb["ID"], lsb["RELEASE"]}; +} +} // namespace + +Hello::Hello(int argc, char** argv) { + set_title("CachyOS Hello"); + set_border_width(6); + if (argc > 1 && (strncmp(argv[1], "--dev", 5) == 0)) { + m_dev = true; + } + + g_refHello = this; + + auto screen = Gdk::Screen::get_default(); + + // Load preferences + if (m_dev) { + m_preferences = read_json("data/preferences.json"); + m_preferences["data_path"] = "data/"; + m_preferences["desktop_path"] = fmt::format("{}/{}.desktop", fs::current_path().string(), m_app); + m_preferences["locale_path"] = "locale/"; + m_preferences["ui_path"] = fmt::format("ui/{}.glade", m_app); + m_preferences["style_path"] = "ui/style.css"; + } else { + m_preferences = read_json(fmt::format("/usr/share/{}/data/preferences.json", m_app)); + } + + // Get saved infos + const auto& save_path = fix_path(m_preferences["save_path"]); + m_save = (!fs::exists(save_path)) ? nlohmann::json({{"locale", ""}}) : read_json(save_path); + + // Import Css + auto provider = Gtk::CssProvider::create(); + provider->load_from_path(m_preferences["style_path"]); + Gtk::StyleContext::add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + // Init window + m_builder = Gtk::Builder::create_from_file(m_preferences["ui_path"]); + gtk_builder_add_callback_symbol(m_builder->gobj(), "on_languages_changed", G_CALLBACK(on_languages_changed)); + gtk_builder_add_callback_symbol(m_builder->gobj(), "on_action_clicked", G_CALLBACK(on_action_clicked)); + gtk_builder_add_callback_symbol(m_builder->gobj(), "on_btn_clicked", G_CALLBACK(on_btn_clicked)); + gtk_builder_add_callback_symbol(m_builder->gobj(), "on_link_clicked", G_CALLBACK(on_link_clicked)); + gtk_builder_add_callback_symbol(m_builder->gobj(), "on_delete_window", G_CALLBACK(on_delete_window)); + gtk_builder_connect_signals(m_builder->gobj(), nullptr); + Gtk::Window* ref_window; + m_builder->get_widget("window", ref_window); + gobject_ = reinterpret_cast(ref_window->gobj()); + + // Subtitle of headerbar + Gtk::HeaderBar* header; + m_builder->get_widget("headerbar", header); + const auto& lsb_info = get_lsb_infos(); + header->set_subtitle(lsb_info[0] + " " + lsb_info[1]); + + // Load images + if (fs::is_regular_file(m_preferences["logo_path"])) { + const auto& logo = Gdk::Pixbuf::create_from_file(m_preferences["logo_path"]); + set_icon(logo); + + Gtk::Image* image; + m_builder->get_widget("distriblogo", image); + image->set(logo); + + Gtk::AboutDialog* dialog; + m_builder->get_widget("aboutdialog", dialog); + dialog->set_logo(logo); + } + + Gtk::Box* social_box; + m_builder->get_widget("social", social_box); + for (const auto& btn : social_box->get_children()) { + const auto& name = btn->get_name(); + const auto& icon_path = fmt::format("{}img/{}.png", m_preferences["data_path"], name.c_str()); + Gtk::Image* image; + m_builder->get_widget(name, image); + image->set(icon_path); + } + + Gtk::Grid* homepage_grid; + m_builder->get_widget("homepage", homepage_grid); + for (const auto& widget : homepage_grid->get_children()) { + if (!G_TYPE_CHECK_INSTANCE_TYPE(widget->gobj(), GTK_TYPE_BUTTON)) { + continue; + } + const auto& casted_widget = Glib::wrap(GTK_BUTTON(widget->gobj())); + if (gtk_button_get_image_position(casted_widget->gobj()) != GtkPositionType::GTK_POS_RIGHT) { + continue; + } + + const auto& image_path = fmt::format("{}img/external-link.png", m_preferences["data_path"]); + Gtk::Image image; + image.set(image_path); + image.set_margin_start(2); + casted_widget->set_image(image); + } + + // Create pages + m_pages = fmt::format("{}pages/{}", m_preferences["data_path"], m_preferences["default_locale"]); + + for (const auto& page : fs::directory_iterator(m_pages)) { + auto* scrolled_window = gtk_scrolled_window_new(nullptr, nullptr); + auto* viewport = gtk_viewport_new(nullptr, nullptr); + gtk_container_set_border_width(GTK_CONTAINER(viewport), 10); + auto* label = gtk_label_new(nullptr); + gtk_label_set_line_wrap(GTK_LABEL(label), true); + auto* image = gtk_image_new_from_icon_name("go-previous", GTK_ICON_SIZE_BUTTON); + auto* backBtn = gtk_button_new(); + gtk_button_set_image(GTK_BUTTON(backBtn), image); + gtk_widget_set_name(backBtn, "home"); + g_signal_connect(backBtn, "clicked", G_CALLBACK(&on_btn_clicked), nullptr); + + auto* grid = GTK_GRID(gtk_grid_new()); + gtk_grid_attach(grid, backBtn, 0, 1, 1, 1); + gtk_grid_attach(grid, label, 1, 2, 1, 1); + gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(grid)); + gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(viewport)); + gtk_widget_show_all(scrolled_window); + + Glib::RefPtr stack = m_builder->get_object("stack"); + const auto& child_name = page.path().filename().string() + "page"; + gtk_stack_add_named(GTK_STACK(stack->gobj()), scrolled_window, child_name.c_str()); + } + + // Init translation + const std::string& locale_path = m_preferences["locale_path"]; + bindtextdomain(m_app, locale_path.c_str()); + textdomain(m_app); + Gtk::ComboBoxText* languages; + m_builder->get_widget("languages", languages); + languages->set_active_id(get_best_locale()); + + // Set autostart switcher state + m_autostart = fs::is_regular_file(fix_path(m_preferences["autostart_path"])); + Gtk::Switch* autostart_switch; + m_builder->get_widget("autostart", autostart_switch); + autostart_switch->set_active(m_autostart); +} + +/// Returns the best locale, based on user's preferences. +auto Hello::get_best_locale() const noexcept -> std::string { + const auto& binary_path = fmt::format("{}{}{}.mo", m_preferences["locale_path"], "{}/LC_MESSAGES/", m_app); + const auto& saved_locale = fmt::vformat(binary_path, fmt::make_format_args(m_save["locale"])); + if (fs::is_regular_file(saved_locale)) { + return m_save["locale"]; + } else if (m_save["locale"] == m_preferences["default_locale"]) { + return m_preferences["default_locale"]; + } + + const auto& locale_name = std::locale("").name(); + std::string sys_locale = locale_name.substr(0, locale_name.find('.')); + const auto& user_locale = fmt::vformat(binary_path, fmt::make_format_args(sys_locale)); + const auto& two_letters = sys_locale.substr(0, 2); + + // If user's locale is supported + if (fs::is_regular_file(user_locale)) { + if (sys_locale.find('_') != std::string::npos) { + replace_all(sys_locale, "_", "-"); + } + return sys_locale; + } + // If two first letters of user's locale is supported (ex: en_US -> en) + else if (fs::is_regular_file(fmt::vformat(binary_path, fmt::make_format_args(two_letters)))) { + return two_letters; + } + + return m_preferences["default_locale"]; +} + +/// Sets locale of ui and pages. +void Hello::set_locale(const std::string_view& use_locale) noexcept { + fmt::print( + "┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", + "", fmt::format("Locale changed to {}", use_locale), 40); + + bind_textdomain_codeset(m_app, use_locale.data()); + + m_save["locale"] = use_locale; + + // Real-time locale changing + /* clang-format off */ + nlohmann::json elts = { + {"comments", {"aboutdialog"}}, + {"label", { + "autostartlabel", + "development", + "discover", + "donate", + "firstcategory", + "forum", + "install", + "installlabel", + "involved", + "mailling", + "readme", + "release", + "secondcategory", + "thirdcategory", + "welcomelabel", + "welcometitle", + "wiki"} + }, + {"tooltip_text", { + "about", + "development", + "discover", + "donate", + "forum", + "mailling", + "wiki"} + }}; + /* clang-format on */ + + for (const auto& method : elts.items()) { + if (!m_default_texts.contains(method.key())) { + m_default_texts[method.key()] = {}; + } + for (const auto& elt : elts[method.key()].items()) { + const std::string& elt_value = elt.value(); + if (!m_default_texts[method.key()].contains(elt_value)) { + Gtk::Widget* item; + m_builder->get_widget(elt_value, item); + gchar* item_buf; + g_object_get(G_OBJECT(item->gobj()), method.key().c_str(), &item_buf, nullptr); + m_default_texts[method.key()][elt_value] = item_buf; + g_free(item_buf); + } + } + } + + // Change content of pages + for (const auto& page : fs::directory_iterator(m_pages)) { + Gtk::Stack* stack; + m_builder->get_widget("stack", stack); + const auto& child = stack->get_child_by_name((page.path().filename().string() + "page").c_str()); + if (child == nullptr) { + fmt::print(stderr, "child not found\n"); + continue; + } + const auto& first_child = reinterpret_cast(child)->get_children(); + const auto& second_child = reinterpret_cast(first_child[0])->get_children(); + const auto& third_child = reinterpret_cast(second_child[0])->get_children(); + + const auto& label = reinterpret_cast(third_child[0]); + label->set_markup(get_page(page.path().filename().string())); + } +} + +auto Hello::get_page(const std::string& name) const noexcept -> std::string { + auto filename = fmt::format("{}pages/{}/{}", m_preferences["data_path"], m_save["locale"], name); + if (!fs::is_regular_file(filename)) { + filename = fmt::format("{}pages/{}/{}", m_preferences["data_path"], m_preferences["default_locale"], name); + } + + return read_whole_file(filename); +} + +// Handlers +void Hello::on_languages_changed(GtkComboBox* combobox) noexcept { + const auto& active_id = gtk_combo_box_get_active_id(combobox); + g_refHello->set_locale(active_id); +} + +void Hello::on_action_clicked(GtkWidget* widget) noexcept { + const auto& name = gtk_widget_get_name(widget); + if (strncmp(name, "install", 7) == 0) { + fmt::print("install\n"); + return; + } else if (strncmp(name, "autostart", 9) == 0) { + const auto& action = Glib::wrap(GTK_SWITCH(widget)); + //set_autostart(action->get_active()); + fmt::print("autostart\n"); + + return; + } + + Gtk::AboutDialog* dialog; + g_refHello->m_builder->get_widget("aboutdialog", dialog); + dialog->set_decorated(false); + dialog->run(); + dialog->hide(); +} + +void Hello::on_btn_clicked(GtkWidget* widget) noexcept { + const auto& name = gtk_widget_get_name(widget); + Gtk::Stack* stack; + g_refHello->m_builder->get_widget("stack", stack); + stack->set_visible_child(fmt::format("{}page", name).c_str()); +} +void Hello::on_link_clicked(GtkWidget* widget) noexcept { + const auto& name = gtk_widget_get_name(widget); + const std::string uri = g_refHello->m_preferences["urls"][name]; + gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, nullptr); +} +void Hello::on_delete_window(GtkWidget* /*widget*/) noexcept { + write_json(g_refHello->m_preferences["save_path"].get(), g_refHello->m_save); + const auto& application = g_refHello->get_application(); + application->quit(); +} \ No newline at end of file diff --git a/src/hello.hpp b/src/hello.hpp new file mode 100644 index 0000000..9058d58 --- /dev/null +++ b/src/hello.hpp @@ -0,0 +1,35 @@ +#ifndef HELLO_HPP_ +#define HELLO_HPP_ + +#include +#include + +class Hello final : public Gtk::Window { + public: + Hello(int argc, char** argv); + + protected: + // Handlers + static void on_languages_changed(GtkComboBox* combobox) noexcept; + static void on_action_clicked(GtkWidget* widget) noexcept; + static void on_btn_clicked(GtkWidget* widget) noexcept; + static void on_link_clicked(GtkWidget* widget) noexcept; + static void on_delete_window(GtkWidget* /*widget*/) noexcept; + + private: + static constexpr auto m_app = "cachyos-hello"; + bool m_dev{}; + bool m_autostart{}; + + std::string m_pages; + Glib::RefPtr m_builder; + nlohmann::json m_preferences; + nlohmann::json m_save; + nlohmann::json m_default_texts; + + auto get_best_locale() const noexcept -> std::string; + void set_locale(const std::string_view& use_locale) noexcept; + auto get_page(const std::string& name) const noexcept -> std::string; +}; + +#endif // HELLO_HPP_ diff --git a/src/helper.hpp b/src/helper.hpp new file mode 100644 index 0000000..7a8e740 --- /dev/null +++ b/src/helper.hpp @@ -0,0 +1,51 @@ +// Helper macroses +#ifndef HELPER_HPP_ +#define HELPER_HPP_ + +#include +#include +#include + +#include + +inline std::pair tokenize(std::string& str, const std::string_view& delim) { + int start{}; + int end = str.find(delim.data()); + std::string key; + while (end != -1) { + key = str.substr(start, end - start); + start = end + delim.length(); + end = str.find(delim.data(), start); + } + return {key, str.substr(start, end - start)}; +} + +inline std::size_t replace_all(std::string& inout, const std::string_view& what, const std::string_view& with) { + std::size_t count{}; + std::size_t pos{}; + while (inout.npos != (pos = inout.find(what.data(), pos, what.length()))) { + inout.replace(pos, what.length(), with.data(), with.length()); + pos += with.length(), ++count; + } + return count; +} + +inline std::size_t remove_all(std::string& inout, const std::string_view& what) { + return replace_all(inout, what, ""); +} + +auto read_whole_file(const std::string_view& path) noexcept -> std::string { + static constexpr auto read_size = 4096; + std::ifstream stream{path.data()}; + stream.exceptions(std::ios_base::badbit); + + std::string file{}; + std::string buf(read_size, '\0'); + while (stream.read(&buf[0], read_size)) { + file.append(buf, 0, stream.gcount()); + } + file.append(buf, 0, stream.gcount()); + return file; +} + +#endif // HELPER_HPP_ diff --git a/src/main.cc b/src/main.cc deleted file mode 100644 index d0accd5..0000000 --- a/src/main.cc +++ /dev/null @@ -1,226 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace fs = std::filesystem; - -Glib::RefPtr g_app; -Glib::RefPtr g_refGlade; -Gtk::Window* g_refWindow; -nlohmann::json preferences; - -std::pair tokenize(std::string str, std::string delim) { - int start = 0; - int end = str.find(delim); - std::string key{}; - while (end != -1) { - key = str.substr(start, end - start); - start = end + delim.size(); - end = str.find(delim, start); - } - return {key, str.substr(start, end - start)}; -} - -std::size_t replace_all(std::string& inout, std::string_view what, std::string_view with) { - std::size_t count{}; - for (std::string::size_type pos{}; - inout.npos != (pos = inout.find(what.data(), pos, what.length())); - pos += with.length(), ++count) { - inout.replace(pos, what.length(), with.data(), with.length()); - } - return count; -} - -std::size_t remove_all(std::string& inout, std::string_view what) { - return replace_all(inout, what, ""); -} - -// Read informations from the lsb-release file. -// -// @Returns args from lsb-release file -std::array get_lsb_infos() { - std::unordered_map lsb{}; - - try { - std::ifstream lsb_release("/etc/lsb-release"); - std::string line; - while (std::getline(lsb_release, line)) { - if (line.find("=") != std::string::npos) { - auto var = tokenize(line, "="); - remove_all(var.first, "DISTRIB_"); - remove_all(var.second, "\""); - lsb[var.first] = var.second; - } - } - } catch (const std::exception& e) { - std::cerr << e.what() << '\n'; - return {"not CachyOS", "0.0"}; - } - return {lsb["ID"], lsb["RELEASE"]}; -} - -void on_action_clicked(GtkWidget* widget) { - const auto& name = gtk_widget_get_name(widget); - if (strncmp(name, "install", 7) == 0) { - fmt::print("install\n"); - return; - } else if (strncmp(name, "autostart", 9) == 0) { - //const auto& action = GTK_ACTION(widget); - //set_autostart(action->get_active()); - fmt::print("autostart\n"); - - return; - } - - Gtk::AboutDialog* dialog; - g_refGlade->get_widget("aboutdialog", std::ref(dialog)); - dialog->set_decorated(false); - dialog->run(); - dialog->hide(); -} - -void on_btn_clicked(GtkWidget* widget) { - const auto& name = gtk_widget_get_name(widget); - Gtk::Stack* stack; - g_refGlade->get_widget("stack", std::ref(stack)); - stack->set_visible_child(fmt::format("{}page", name).c_str()); -} - -void on_link_clicked(GtkWidget* widget) { - const auto& name = gtk_widget_get_name(widget); - const std::string uri = preferences["urls"][name]; - gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, nullptr); -} - -void on_delete_window(GtkWidget* widget) { - g_app->quit(); -} - -nlohmann::json read_json(const std::string& path) { - // read a JSON file - std::ifstream i(path); - nlohmann::json j; - i >> j; - - return j; -} - -int main(int argc, char** argv) { - g_app = Gtk::Application::create(); - auto screen = Gdk::Screen::get_default(); - - bool is_dev = false; - if (argc > 1 && (strncmp(argv[1], "--dev", 5) == 0)) { - is_dev = true; - } - - // Load preferences - if (is_dev) { - preferences = read_json("data/preferences.json"); - preferences["data_path"] = "data/"; - preferences["desktop_path"] = (fs::current_path() / "cachyos-hello.desktop").string(); - preferences["locale_path"] = "locale/"; - preferences["ui_path"] = "ui/cachyos-hello.glade"; - preferences["style_path"] = "ui/style.css"; - } else { - preferences = read_json("/usr/share/cachyos-hello/data/preferences.json"); - } - - // Get saved infos - //const auto& save = read_json(preferences["save_path"]); - - // Import Css - auto provider = Gtk::CssProvider::create(); - provider->load_from_path(preferences["style_path"]); - Gtk::StyleContext::add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - // Init window - g_refGlade = Gtk::Builder::create_from_file(preferences["ui_path"]); - gtk_builder_add_callback_symbol(g_refGlade->gobj(), "on_action_clicked", G_CALLBACK(on_action_clicked)); - gtk_builder_add_callback_symbol(g_refGlade->gobj(), "on_btn_clicked", G_CALLBACK(on_btn_clicked)); - gtk_builder_add_callback_symbol(g_refGlade->gobj(), "on_link_clicked", G_CALLBACK(on_link_clicked)); - gtk_builder_add_callback_symbol(g_refGlade->gobj(), "on_delete_window", G_CALLBACK(on_delete_window)); - gtk_builder_connect_signals(g_refGlade->gobj(), nullptr); - g_refGlade->get_widget("window", std::ref(g_refWindow)); - - // Subtitle of headerbar - Gtk::HeaderBar* header; - g_refGlade->get_widget("headerbar", std::ref(header)); - const auto& lsb_info = get_lsb_infos(); - header->set_subtitle(lsb_info[0] + " " + lsb_info[1]); - - // Load images - if (fs::is_regular_file(preferences["logo_path"])) { - const auto& logo = Gdk::Pixbuf::create_from_file(preferences["logo_path"]); - g_refWindow->set_icon(logo); - - Gtk::Image* image; - g_refGlade->get_widget("distriblogo", std::ref(image)); - image->set(logo); - - Gtk::AboutDialog* dialog; - g_refGlade->get_widget("aboutdialog", std::ref(dialog)); - dialog->set_logo(logo); - } - - Gtk::Box* social_box; - g_refGlade->get_widget("social", std::ref(social_box)); - for (const auto& btn : social_box->get_children()) { - const auto& name = btn->get_name(); - const auto& icon_path = fmt::format("{}img/{}.png", preferences["data_path"], name.c_str()); - Gtk::Image* image; - g_refGlade->get_widget(name, image); - image->set(icon_path); - } - - Gtk::Grid* homepage_grid; - g_refGlade->get_widget("homepage", std::ref(homepage_grid)); - for (const auto& widget : homepage_grid->get_children()) { - if (!G_TYPE_CHECK_INSTANCE_TYPE(widget->gobj(), GTK_TYPE_BUTTON)) { - continue; - } - const auto& casted_widget = GTK_BUTTON(widget->gobj()); - if (gtk_button_get_image_position(casted_widget) != GtkPositionType::GTK_POS_RIGHT) { - continue; - } - - Gtk::Image image(fmt::format("{}/img/external-link.png", preferences["data_path"])); - image.set_margin_start(2); - gtk_button_set_image(casted_widget, (GtkWidget*)image.gobj()); - } - - // Create pages - const auto& pages = fmt::format("{}/pages/{}", preferences["data_path"], preferences["default_locale"]); - - for (const auto& page : fs::directory_iterator(pages)) { - Gtk::ScrolledWindow scrolled_window; - Gtk::Viewport viewport(Gtk::Adjustment::create(1, 1, 1), Gtk::Adjustment::create(1, 1, 1)); - Gtk::Label label; - label.set_line_wrap(true); - Gtk::Image image(Gtk::Stock::GO_BACK, Gtk::ICON_SIZE_BUTTON); - Gtk::Button backBtn; - backBtn.set_image(image); - backBtn.set_name("home"); - backBtn.signal_clicked().connect(sigc::bind(sigc::ptr_fun(on_btn_clicked), (GtkWidget*)&backBtn)); - - Gtk::Grid grid; - grid.attach(backBtn, 0, 1, 1, 1); - grid.attach(label, 1, 2, 1, 1); - viewport.add(grid); - scrolled_window.add(viewport); - scrolled_window.show_all(); - - Gtk::Stack* stack; - g_refGlade->get_widget("stack", std::ref(stack)); - stack->add(scrolled_window, page.path().stem().string() + "page"); - } - - // Shows the window and returns when it is closed. - return g_app->run(*g_refWindow); -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cab6824 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,10 @@ +#include "hello.hpp" + +int main(int argc, char** argv) { + auto app = Gtk::Application::create(); + + Hello hello(argc, argv); + + // Shows the window and returns when it is closed. + return app->run(hello); +}