#include "options.hpp" #include "backend.hpp" #include "daemon_backend.hpp" #include "proxy_backend.hpp" #include "log.hpp" #include "thirdparty/CLI11.hpp" #include "data.h" #include "license.hpp" #include "assets/assets.h" #ifdef __EMSCRIPTEN__ #include #endif #include "web_functions.hpp" using namespace Looper; using namespace Looper::Options; using namespace Looper::Log; #ifdef __EMSCRIPTEN__ extern "C" { void quit(); } #endif std::unordered_set license_data; std::unordered_set &get_license_data() { return license_data; } char *executable_path; #ifdef __ANDROID__ #include #include extern jclass MainActivity; extern jobject mainActivity; extern jmethodID GetUserDir_Method; extern jmethodID OpenFilePicker_Method; extern jmethodID GetPickedFile_Method; extern jmethodID ClearSelected_Method; extern jmethodID IsLoading_Method; extern JNIEnv *env; void initNative() { MainActivity = reinterpret_cast(env->NewGlobalRef(env->FindClass("com/complecwaft/looper/MainActivity"))); GetUserDir_Method = env->GetStaticMethodID(MainActivity, "GetUserDir", "()Ljava/lang/String;"); OpenFilePicker_Method = env->GetStaticMethodID(MainActivity, "OpenFilePicker", "(Ljava/lang/Object;)V"); GetPickedFile_Method = env->GetStaticMethodID(MainActivity, "GetPickedFile", "()Ljava/lang/String;"); ClearSelected_Method = env->GetStaticMethodID(MainActivity, "ClearSelected", "()V"); IsLoading_Method = env->GetStaticMethodID(MainActivity, "IsLoading", "()Z"); jfieldID singleton = env->GetStaticFieldID(MainActivity, "mSingleton", "Lcom/complecwaft/looper/MainActivity;"); mainActivity = reinterpret_cast(env->NewGlobalRef(env->GetStaticObjectField(MainActivity, singleton))); } #endif extern "C" int looper_run_as_executable(std::vector args) { #ifdef __ANDROID__ env = (JNIEnv*)SDL_AndroidGetJNIEnv(); initNative(); #endif #ifdef __EMSCRIPTEN__ EM_ASM({ Module.wasmTable = wasmTable; }); #endif CLI::App app{DESCRIPTION}; std::string filename = ""; app.allow_extras(); std::string ui_backend_option = ""; bool full_help = false; bool open_window = false; app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs."); app.add_option("-u, --ui-backend", ui_backend_option, "Specifies which UI backend to use."); #ifdef DBUS_ENABLED bool daemonize = false; bool disable_gui = false; bool quit = false; app.add_flag("-d, --daemon", daemonize, "Daemonizes the program."); app.add_flag("-n, --no-gui", disable_gui, "Don't open the GUI when there is a daemon and there are settings or commands for it. Ignored in daemon mode, or when no changes in state are issued."); app.add_flag("-q, --quit", quit, "Quits an existing instance."); #endif try { app.parse(args); } catch (const CLI::ParseError &e) { if (app.get_help_ptr()->get_callback_run()) { full_help = true; } else { exit(app.exit(e)); } } #ifdef DBUS_ENABLED if (daemonize) { ui_backend_option = "daemon"; } #endif args.clear(); args = app.remaining(false); int new_argc = args.size(); char **new_argv = (char**)malloc(new_argc * sizeof(char*)); #ifdef DBUS_ENABLED if (quit) { DBusAPISender *sender = DBusAPISender::Create(); if (sender != nullptr) { sender->Quit(); } else { ERROR.writeln("Did not find existing instance to quit."); return 1; } return 0; } #endif { auto looper_mit = LicenseData("Looper (MIT)", "MIT"); auto looper_gpl = LicenseData("Looper (GPL)", "GPL-3.0-or-later"); auto looper = LicenseData("Looper", "MIT OR GPL-3.0-or-later"); auto tomlpp = LicenseData("TOML++", "MIT"); auto smx = LicenseData("SDL Mixer X", "Zlib"); auto cli11 = LicenseData("CLI11", "BSD-3-Clause"); auto jsoncpp = LicenseData("JsonCpp", "MIT"); auto soundtouch = LicenseData("SoundTouch", "LGPL-2.1-only"); LOAD_LICENSE(looper_mit, looper_mit); LOAD_LICENSE(looper_gpl, looper_gpl); LOAD_LICENSE(looper, looper); LOAD_LICENSE(tomlpp, tomlplusplus); LOAD_LICENSE(smx, sdl_mixer_x); LOAD_LICENSE(cli11, cli11); LOAD_LICENSE(jsoncpp, jsoncpp); LOAD_LICENSE(soundtouch, lgpl_2_1); license_data.insert(looper); license_data.insert(looper_gpl); license_data.insert(looper_mit); license_data.insert(tomlpp); license_data.insert(smx); license_data.insert(cli11); license_data.insert(jsoncpp); license_data.insert(soundtouch); } DEBUG.writeln("Command line arguments after first parse:"); for (size_t i = 0; i < new_argc; i++) { auto &arg = args[i]; DEBUG.writefln(" - '%s'", arg.c_str()); new_argv[i] = strdup(arg.c_str()); } DEBUG.writeln("Initializing frontends..."); init_backends(); init_playback_backends(); init_audio_data(); #ifdef DBUS_ENABLED ProxyGlueBackend *proxy_backend = nullptr; if ((disable_gui && !daemonize) || quit) { if (!DBusAPISender::isOnlyInstance()) { UIBackend::register_backend(); if (disable_gui) { proxy_backend = UIBackend::get_backend().value_or(nullptr); } } if (quit && proxy_backend == nullptr) { proxy_backend = UIBackend::get_backend().value_or(nullptr); } } if (daemonize) { UIBackend::register_backend(); } #endif for (auto kv : UIBackend::backends) { kv.second->add_licenses(); } DEBUG.writeln("Loaded frontends: "); for (auto kv : UIBackend::backends) { DEBUG.writefln(" - '%s'", kv.first.c_str()); } DEBUG.writeln("Loaded playback backends: "); for (auto kv : PlaybackBackendHelper()) { DEBUG.writefln(" - '%s'", kv.first.c_str()); } DEBUG.writeln("Loading options file..."); load_options(); std::string backend_id = get_option("ui.frontend", "imgui"); UIBackend *backend = UIBackend::get_backend(ui_backend_option).value_or(UIBackend::get_backend(backend_id).value_or(UIBackend::get_first_backend())); int output = 0; if (backend == nullptr) { ERROR.writeln("No UI backend could be found."); return -1; } else { DEBUG.writefln("Using backend: '%s'...", backend->get_id().c_str()); UIBackend::running_ui_backend = backend; if (full_help) { args.clear(); args.push_back("--help"); } try { #ifdef DBUS_ENABLED if (proxy_backend != nullptr && !quit) { if (!proxy_backend->run(args, new_argc, new_argv)) { throw 0; } proxy_backend = nullptr; } if (!quit) { UIBackend::unregister_backend(); } #endif output = backend->run(args, new_argc, new_argv); #ifdef DBUS_ENABLED if (quit && proxy_backend != nullptr) { proxy_backend->quitDaemon(); proxy_backend->unregister_self(); } #endif } catch (int return_code) { if (full_help) { std::string helpstr = app.help(); helpstr = helpstr.substr(helpstr.find("\n") + 1); helpstr = helpstr.substr(helpstr.find("\n") + 1); helpstr = helpstr.substr(helpstr.find("\n") + 1); helpstr = helpstr.substr(helpstr.find("\n") + 1); helpstr = helpstr.substr(helpstr.find("\n") + 1); printf("%s", helpstr.c_str()); } output = return_code; } } DEBUG.writeln("Exiting..."); UIBackend::deinit_backends(); for (int i = 0; i < new_argc; i++) { free(new_argv[i]); } free(new_argv); save_options(); #ifdef __EMSCRIPTEN__ quit(); #endif return output; } std::string current_process_type; extern int looper_run_playback_process(std::vector args); int main(int argc, char **argv) { if (argc == 0) { throw std::exception(); } #ifdef _WIN32 size_t size = std::max(10240, PATH_MAX) + 1; executable_path = malloc(size); size = GetModuleFileNameA(NULL, executable_path, size); realloc(executable_path, size); #else executable_path = strdup(fs::canonical("/proc/self/exe").c_str()); #endif CLI::App app{DESCRIPTION}; std::string process_type = "normal"; app.add_option("--process-type", process_type); app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs."); app.allow_extras(); init_logging(); try { app.parse(argc, argv); executable_path = argv[0]; current_process_type = "host"; if (process_type == "playback") { current_process_type = "playback"; return looper_run_playback_process(app.remaining(false)); } else if (process_type == "daemon") { std::vector opts = app.remaining(false); opts.push_back("-d"); return looper_run_as_executable(opts); } else { return looper_run_as_executable(app.remaining(false)); } } catch (CLI::CallForHelp) { looper_run_as_executable(std::vector({"--help"})); } free(executable_path); }