diff --git a/include/DLLoader.hpp b/include/DLLoader.hpp index cd6b247..c033b6e 100644 --- a/include/DLLoader.hpp +++ b/include/DLLoader.hpp @@ -15,8 +15,10 @@ class DLLoader { public: explicit DLLoader(const std::string& filepath) : _handle(nullptr) { _handle = dlopen(filepath.c_str(), RTLD_LAZY); - if (!_handle) - throw ARCError(dlerror()); + if (!_handle) { + const char* error = dlerror(); + throw ARCError(std::string("dlopen error: ") + (error ? error : "unknown error")); + } } ~DLLoader() { @@ -35,13 +37,18 @@ class DLLoader { if (this != &other) { if (_handle) dlclose(_handle); - _handle = other._handle = nullptr; + _handle = other._handle = other._handle = nullptr; } return *this; } bool hasSymbol(const std::string& symbolName) const { - return dlsym(_handle, symbolName.c_str()) != nullptr; + void* sym = dlsym(_handle, symbolName.c_str()); + if (sym) return true; + + std::string macOSName = "_" + symbolName; + sym = dlsym(_handle, macOSName.c_str()); + return sym != nullptr; } std::unique_ptr getInstance(const std::string& entryPointName = "entryPoint") { @@ -49,16 +56,26 @@ class DLLoader { void* sym = dlsym(_handle, entryPointName.c_str()); + if (!sym) { + std::string macOSName = "_" + entryPointName; + sym = dlsym(_handle, macOSName.c_str()); + } + const char *dlsym_error = dlerror(); if (dlsym_error) - throw ARCError(std::string("dlsym error : ") + dlsym_error); + throw ARCError(std::string("dlsym error: ") + dlsym_error); + + if (!sym) + throw ARCError(std::string("cannot find symbol: ") + entryPointName); using EntryPointFunc = T* (*)(); EntryPointFunc createFunc = reinterpret_cast(sym); if (!createFunc) - throw ARCError(std::string("cannot find entryPointName: ") + entryPointName); + throw ARCError(std::string("invalid function pointer for: ") + entryPointName); + return std::unique_ptr(createFunc()); } }; + } diff --git a/include/DirectoryScanner.hpp b/include/DirectoryScanner.hpp index da5284b..f02f9e7 100644 --- a/include/DirectoryScanner.hpp +++ b/include/DirectoryScanner.hpp @@ -4,8 +4,8 @@ #include #include #include -#include "Errors.hpp" #include "DLLoader.hpp" +#include "Errors.hpp" namespace Arcade { class DirectoryScanner { @@ -18,8 +18,11 @@ class DirectoryScanner { return; for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (entry.path().extension() == ".so") { + std::string extension = entry.path().extension().string(); + + if (extension == ".so" || extension == ".dylib") { std::string filepath = entry.path().string(); + try { DLLoader inspector(filepath); @@ -28,7 +31,7 @@ class DirectoryScanner { } else if (inspector.hasSymbol("createGraphics")) { graphicLibs.push_back(filepath); } else { - throw ARCError("Invalid symbol"); + throw ARCError("Invalid symbol."); } } catch (const ARCError& e) { std::cerr << "Error loading " << filepath << ": " << e.what() << std::endl; diff --git a/include/MenuSelector.hpp b/include/MenuSelector.hpp new file mode 100644 index 0000000..df63b78 --- /dev/null +++ b/include/MenuSelector.hpp @@ -0,0 +1,35 @@ +#ifndef MENUSELECTOR_HPP +#define MENUSELECTOR_HPP + +#include +#include +#include +#include + +namespace Arcade { + +class MenuSelector { +public: + struct MenuOption { + std::string name; + std::string path; + std::function action; + }; + + MenuSelector(); + ~MenuSelector(); + + std::string run(const std::vector& availableLibs); + +private: + void displayMenu(const std::vector& options, int selectedIdx); + void clearScreen(); + int getchNonBlock(); + + bool _running; + int _selectedIdx; +}; + +} // namespace Arcade + +#endif diff --git a/src/Core.cpp b/src/Core.cpp index 46e0247..bcaf641 100644 --- a/src/Core.cpp +++ b/src/Core.cpp @@ -160,4 +160,4 @@ void Core::run() { } } -} +} diff --git a/src/MenuSelector.cpp b/src/MenuSelector.cpp new file mode 100644 index 0000000..5cf284a --- /dev/null +++ b/src/MenuSelector.cpp @@ -0,0 +1,160 @@ +#include "MenuSelector.hpp" +#include +#include +#include +#include +#include + +namespace Arcade { + +MenuSelector::MenuSelector() : _running(true), _selectedIdx(0) { + struct termios term; + tcgetattr(STDIN_FILENO, &term); + term.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &term); +} + +MenuSelector::~MenuSelector() { + struct termios term; + tcgetattr(STDIN_FILENO, &term); + term.c_lflag |= (ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &term); +} + +void MenuSelector::clearScreen() { + std::cout << "\033[2J\033[H"; +} + +int MenuSelector::getchNonBlock() { + int flags = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); + int ch = getchar(); + fcntl(STDIN_FILENO, F_SETFL, flags); + return ch; +} + +void MenuSelector::displayMenu(const std::vector& options, int selectedIdx) { + clearScreen(); + + std::cout << "\n"; + std::cout << " ╔══════════════════════════════════════╗\n"; + std::cout << " ║ ARCADE - MENU PRINCIPAL ║\n"; + std::cout << " ╠══════════════════════════════════════╣\n"; + std::cout << " ║ ║\n"; + std::cout << " ║ Choisissez une librairie graphique: ║\n"; + std::cout << " ║ ║\n"; + + for (size_t i = 0; i < options.size(); ++i) { + if (static_cast(i) == selectedIdx) { + std::cout << " ║ \033[7m"; + } else { + std::cout << " ║ "; + } + + std::string displayName = options[i].name; + size_t lastSlash = displayName.find_last_of('/'); + if (lastSlash != std::string::npos) { + displayName = displayName.substr(lastSlash + 1); + } + size_t dotPos = displayName.find(".so"); + if (dotPos != std::string::npos) { + displayName = displayName.substr(0, dotPos); + } + dotPos = displayName.find(".dylib"); + if (dotPos != std::string::npos) { + displayName = displayName.substr(0, dotPos); + } + + printf(" ► %-31s", displayName.c_str()); + + if (static_cast(i) == selectedIdx) { + std::cout << "\033[0m"; + } + std::cout << "║\n"; + } + + std::cout << " ║ ║\n"; + + if (selectedIdx == static_cast(options.size())) { + std::cout << " ║ \033[7m ► Quitter\033[0m ║\n"; + } else { + std::cout << " ║ ► Quitter ║\n"; + } + + std::cout << " ║ ║\n"; + std::cout << " ╚══════════════════════════════════════╝\n"; + std::cout << "\n"; + std::cout << " Utilisez les flèches ↑/↓ pour naviguer\n"; + std::cout << " Appuyez sur Entrée pour sélectionner\n"; + std::cout << " Appuyez sur 'q' pour quitter\n"; + + fflush(stdout); +} + +std::string MenuSelector::run(const std::vector& availableLibs) { + std::vector options; + + for (const auto& lib : availableLibs) { + MenuOption opt; + opt.name = lib; + opt.path = lib; + options.push_back(opt); + } + + _selectedIdx = 0; + _running = true; + + displayMenu(options, _selectedIdx); + + while (_running) { + int ch = getchNonBlock(); + + if (ch == EOF) { + usleep(10000); + continue; + } + + switch (ch) { + case 27: + ch = getchNonBlock(); + if (ch == 91) { + ch = getchNonBlock(); + switch (ch) { + case 65: + _selectedIdx--; + if (_selectedIdx < 0) + _selectedIdx = options.size(); + displayMenu(options, _selectedIdx); + break; + case 66: + _selectedIdx++; + if (_selectedIdx > static_cast(options.size())) + _selectedIdx = 0; + displayMenu(options, _selectedIdx); + break; + } + } + break; + + case '\n': + case '\r': + if (_selectedIdx == static_cast(options.size())) { + return ""; + } else if (_selectedIdx >= 0 && _selectedIdx < static_cast(options.size())) { + return options[_selectedIdx].path; + } + break; + + case 'q': + case 'Q': + return ""; + break; + } + + usleep(10000); + } + + return ""; +} + +} diff --git a/src/main.cpp b/src/main.cpp index dce260e..f98cabb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,28 +1,27 @@ -/* -** EPITECH PROJECT, 2026 -** arcade -** File description: -** arcade -*/ - #include "Core.hpp" #include "Errors.hpp" +#include "MenuSelector.hpp" +#include "DirectoryScanner.hpp" #include +#define SUCCESS 0 +#define HELP 2026 +#define ERROR 84 + static unsigned int handle_args(int argc, const char *argv[]) { - if (argc != 2 || !argv || !argv[1]) { - std::cerr << - "Usage:\n\tarcade [options] \nOptions:\n\t-h, --help Show this help" << - std::endl; + if (argc > 2) { + std::cerr << "Usage:\n\t./arcade\n\t./arcade [graphical_library.so]\n\t./arcade -h, --help" << std::endl; return ERROR; } - std::string flag(argv[1]); - if (flag == "--help" || flag == "-h") { - std::cout << - "Usage:\n\tarcade [options] \nOptions:\n\t-h, --help Show this help" << - std::endl; - return HELP; + if (argc == 2) { + std::string flag(argv[1]); + if (flag == "--help" || flag == "-h") { + std::cout << "Usage:\n\t./arcade [graphical_library.so]\n\n" + "If no library is specified, a menu will let you choose.\n\n" + "Options:\n\t-h, --help Show this help" << std::endl; + return HELP; + } } return SUCCESS; } @@ -35,16 +34,36 @@ int main(int argc, const char *argv[]) { return ERROR; default: try { - Arcade::Core core(argv[1]); + std::string selectedLib; + + if (argc == 1) { + std::vector gameLibs; + std::vector graphicalLibs; + Arcade::DirectoryScanner::scan("./lib/", gameLibs, graphicalLibs); + + if (graphicalLibs.empty()) { + throw Arcade::ARCError("No graphical library found in ./lib/"); + } + + Arcade::MenuSelector menu; + selectedLib = menu.run(graphicalLibs); + + if (selectedLib.empty()) { return SUCCESS; } + } else { selectedLib = argv[1]; } + + Arcade::Core core(selectedLib); core.run(); return SUCCESS; } catch (const Arcade::ARCError &error) { std::cerr << "Error: " << error.what() << std::endl; return ERROR; + } catch (const std::exception& e) { + std::cerr << "Uncaught Error: " << e.what() << std::endl; + return ERROR; } catch (...) { std::cerr << "Uncaught Error." << std::endl; return ERROR; } - } + } }