--- /dev/null
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+PROJECT("dotnet-launcher")
+
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(${PROJECT_NAME} REQUIRED
+ aul
+ dlog
+ ecore
+ bundle
+ dlog
+ launchpad
+ )
+
+FOREACH(flag ${${PROJECT_NAME}_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wl,-zdefs" )
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fPIE")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64")
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+SET(CMAKE_C_FLAGS_RELEASE "-O2")
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
+
+SET(${PROJECT_NAME}_SOURCE_FILES
+ src/launcher.cc
+ src/waiter.cc
+)
+ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_FILES})
+
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} aul)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${${PROJECT_NAME}_LDFLAGS} "-pie")
+
+SET_TARGET_PROPERTIES(${PROJECT_NAME}
+ PROPERTIES SKIP_BUILD_RPATH TRUE
+ ) # remove rpath option that is automatically generated by cmake.
+
+CONFIGURE_FILE(${PACKAGE_NAME}.xml.in ${PACKAGE_NAME}.xml)
+
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)
+INSTALL(FILES ${PACKAGE_NAME}.xml DESTINATION ${MANIFESTDIR})
+INSTALL(FILES packaging/dotnet.loader DESTINATION ${LOADERDIR})
+
--- /dev/null
+[dotnet]
+libcoreclr = libcoreclr.so
+coreclr_dir = /usr/share/dotnet
+tpa_dirs = /usr/share/dotnet:/usr/share/assemblies
+native_so_search_dirs = /usr/share/dotnet:/usr/share/assemblies
--- /dev/null
+[Unit]
+Description=Start dotnet launchpad daemon
+After=dbus.service buxton2.service ac.service
+Requires=csf-content-engine.service
+
+[Service]
+User=owner
+Group=users
+EnvironmentFile=/run/tizen-mobile-ui
+ExecStart=/usr/bin/launch_app dotnet-launcher
+
+TimeoutStopSec=3s
+RestartSec=5
+
+[Install]
+WantedBy=starter.target
--- /dev/null
+Name: dotnet-launcher
+Summary: Launchpad plugin for dotnet apps
+Version: 0.1.0
+Release: 1
+Group: Application Framework/Daemons
+License: Apache License, Version 2.0
+Source0: %{name}-%{version}.tar.gz
+
+BuildRequires: cmake
+BuildRequires: pkgconfig(aul)
+BuildRequires: pkgconfig(bundle)
+BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(ecore)
+BuildRequires: pkgconfig(launchpad)
+BuildRequires: aul-devel
+Requires: aul
+
+Requires(post): /sbin/ldconfig
+Requires(post): /usr/bin/systemctl
+Requires(postun): /sbin/ldconfig
+Requires(postun): /usr/bin/systemctl
+Requires(preun): /usr/bin/systemctl
+
+%define _sys_bin %{TZ_SYS_BIN}
+%define _sys_sbin %{TZ_SYS_SBIN}
+%define _sys_share %{TZ_SYS_RO_SHARE}
+%define _manifestdir %{TZ_SYS_RO_PACKAGES}
+%define _loaderdir %{TZ_SYS_RO_SHARE}/aul
+
+%description
+Launchpad for dotnet apps
+
+%prep
+%setup -q
+
+%build
+cmake \
+ -DCMAKE_INSTALL_PREFIX=%{_prefix} \
+ -DMANIFESTDIR=%{_manifestdir} \
+ -DPACKAGE_NAME=%{name} \
+ -DBINDIR=%{_bindir} \
+ -DLOADERDIR=%{_loaderdir} \
+ -DVERSION=%{version}
+
+make %{?jobs:-j%jobs}
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+%post
+
+%files
+%manifest dotnet-launchpad.manifest
+%{_manifestdir}/%{name}.xml
+%{_loaderdir}/dotnet.loader
+%attr(0755,root,root) %{_bindir}/dotnet-loader
+%caps(cap_mac_admin,cap_mac_override,cap_setgid=ei) %{_bindir}/dotnet-loader
--- /dev/null
+[LOADER]
+NAME dotnet-loader
+EXE /usr/bin/dotnet-loader
+APP_TYPE dotnet
+DETECTION_METHOD TIMEOUT|DEMAND
+TIMEOUT 5000
--- /dev/null
+#ifndef __ENVIRONMENT_H__
+
+#ifndef TIZEN_CORECLR_PATH
+#define TIZEN_CORECLR_PATH "/usr/share/coreclr/libcoreclr.so"
+#endif
+
+#ifndef TIZEN_NETCORE_TPA_PATH
+#define TIZEN_NETCORE_TPA_PATH "/usr/share/assemblies"
+#endif
+
+
+#endif // __ENVIRONMENT_H__
--- /dev/null
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include <string>
+#include <set>
+#include <sstream>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "environment.h"
+#include "waiter.h"
+#include "log.h"
+
+#include "tini.hpp"
+
+#ifndef LAUNCHER_CONFIG
+#define LAUNCHER_CONFIG "/usr/share/dotnet/dotnet-launcher.ini"
+#endif
+
+static std::string AbsolutePath(const std::string& path)
+{
+ std::string absPath;
+
+ char realPath[PATH_MAX];
+ if (realpath(path, realPath) != nullptr && realPath[0] != '\0')
+ {
+ absPath.assign(realPath);
+ }
+
+ return absPath;
+}
+
+static std::string Basename(const std::string& path)
+{
+ auto pos = path.find_last_of('/');
+ if (pos != std::string::npos)
+ {
+ return path.substr(0, pos);
+ }
+ return path;
+}
+
+static std::string AssembliesInDirectory(const char *directory)
+{
+ const char * const nativeImageExtension = ".ni";
+ const char * const tpaExtensions[] = {".dll", ".exe"};
+ int niExtensionSize = strlen(nativeImageExtension);
+ int directoryPathSize = strlen(directory);
+
+ std::string tpaList;
+
+ DIR* dir = opendir(directory);
+
+ if (dir == nullptr) return std::string();
+
+ std::map<std::pair<std::string, std::string>, bool> addedAssemblies;
+
+ struct dirent *entry;
+ while ((entry = readdir(dir)) != nullptr)
+ {
+ switch (entry->d_type)
+ {
+ case DT_REG: break;
+ case DT_LNK:
+ case DT_UNKNOWN:
+ {
+ std::string fullname;
+ fullname.append(directory);
+ fullname.append("/");
+ fullname.append(entry->d_name);
+
+ struct stat sb;
+ if (stat(fullname.c_str(), &sb) == -1)
+ continue;
+
+ if (!S_ISREG(sb.st_mode))
+ continue;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ // Check the extension.
+ std::string filename(entry->d_name);
+ int extPos = filename.find_last_of('.');
+ if (extPos <= 0) continue;
+ for (auto ext : tpaExtensions)
+ {
+ int extLength = strlen(ext);
+ if (filename.compare(extPos-1, extLength, ext) != 0)
+ continue;
+ }
+
+
+ std::string filenameWithoutExt;
+ bool isNativeImage = extPos > niExtensionSize ?
+ filename.compare(extPos-niExtensionSize, niExtensionSize, nativeImageExtension) == 0 : false;
+ if (isNativeImage)
+ {
+ filenameWithoutExt = filename.substr(0, extPos-niExtensionSize);
+ }
+ else
+ {
+ filenameWithoutExt = filename.substr(0, extPos);
+ }
+ std::string ext = filename.substr(extPos);
+
+ std::pair<std::string, std::string> key(filenameWithoutExt, ext);
+ std::cout << " " << filenameWithoutExt << ext << " " << isNativeImage << std::endl;
+ if (addedAssemblies.count(key))
+ {
+ isNativeImage = isNativeImage || addedAssemblies[key];
+ }
+ addedAssemblies[key] = isNativeImage;
+ }
+
+ for (auto pair : addedAssemblies)
+ {
+ tpaList.append(directory);
+ if (directory[directoryPathSize-1] != '/')
+ tpaList.append("/");
+ tpaList.append(pair.first.first);
+ if (pair.second)
+ {
+ tpaList.append(nativeImageExtension);
+ }
+ tpaList.append(pair.first.second);
+ tpaList.append(":");
+ }
+
+ closedir(dir);
+ return tpaList;
+}
+
+Launcher::Launcher()
+{
+}
+
+Launcher::~Launcher()
+{
+}
+
+void Launcher::Initialize()
+{
+ std::ifstream iniStream(LAUNCHER_CONFIG);
+ std::stringstream iniString;
+ iniString << iniStream.rdbuf();
+ tini::ini launcherIni(iniString);
+
+ auto &dotnet = ini["dotnet"];
+ if (dotnet.find("libcoreclr") == dotnet.end())
+ dotnet["libcoreclr"] = "libcoreclr.so";
+ if (dotnet.find("coreclr_dir") == dotnet.end())
+ dotnet["coreclr_dir"] = "/usr/share/dotnet";
+ if (dotnet.find("tpa_dir") == dotnet.end())
+ dotnet["tpa_dir"] = dotnet["coreclr_dir"] + ":" + "/usr/share/assemblies";
+ if (dontet.find("native_so_search_dirs") == dotnet.end())
+ dotnet["native_so_search_dirs"] = dotnet["tpa_dir"];
+
+ std::string libcoreclr = dotnet["coreclr_dir"] + "/" + dotnet["libcoreclr"];
+ std::string tpaDirs = dotnet["tpa_dirs"];
+ std::string nativeSoSearchDirs = dotnet["native_so_search_dirs"];
+
+ void* coreclrLib = dlopen(libcoreclr, RTLD_NOW | RTLD_LOCAL);
+ if (coreclrLib == nullptr)
+ {
+ char *err = dlerror();
+ _ERR("dlopen failed to open libcoreclr.so with error %s", err);
+ goto CoreClrLibClose;
+ }
+
+ initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize");
+ executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly");
+ shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown");
+
+ if (coreclr_initialize_ptr == nullptr)
+ {
+ _ERR("coreclr_initialize_ptr not found in the libcoreclr.so");
+ }
+ else if (coreclr_execute_assembly_ptr == nullptr)
+ {
+ _ERR("coreclr_execute_assembly_ptr not found in the libcoreclr.so");
+ }
+ else if (coreclr_shutdown_ptr == nullptr)
+ {
+ _ERR("coreclr_shutdown_ptr not found in the libcoreclr.so");
+ }
+ else
+ {
+ std::istringstream f(tpaDirs);
+ std::string s;
+ while (std::getline(f, s, ':'))
+ {
+ TrustedPlatformAssemblies += AssembliesInDirectory(s);
+ }
+ NativeDllSearchDirectories = nativeSoSearchDirs;
+ AppDomainCompatSwitch = "UseLatestBehaviorWhenTFMNotSpecified";
+ }
+
+CoreClrLibClose:
+ if (dlclose(coreclrLib) != 0)
+ {
+ _ERR("libcoreclr.so close failed");
+ }
+}
+
+void Launcher::Dispose()
+{
+}
+
+void Launcher::Launch(const string& exe_path, int argc, char *argv[])
+{
+ std::string bin_path = Basename(exe_path);
+ std::string app_home = Basename(app_path);
+ std::string lib_path = app_home + "/lib";
+ std::string app_path = bin_path + ":" + lib_path
+ std::string app_ni_path = app_path;
+ std::string nativeDllSearchDirectories = NativeDllSearchDirectories + app_path;
+
+ const char *propertyKeys[] =
+ {
+ "TRUSTED_PLATFORM_ASSEMBLIES",
+ "APP_PATHS",
+ "APP_NI_PATHS",
+ "NATIVE_DLL_SEARCH_DIRECTORIES",
+ "AppDomainCompatSwitch"
+ };
+
+ const char *propertyValues[] =
+ {
+ TrustedPlatformAssemblies.c_str(),
+ app_path.c_str(),
+ app_ni_path.c_str(),
+ nativeDllSearchDirectories.c_str(),
+ AppDomainCompatSwitch.c_str()
+ };
+
+ void* hostHandle;
+ unsigned int domainId;
+
+ int st = initializeCoreCLR(exe_path,
+ "tizen_dotnet_launcher",
+ sizeof(propertyKeys) / sizeof(propertyKeys[0]),
+ propertyKeys,
+ propertyValues,
+ &hostHandle,
+ &domainId);
+ if (st < 0)
+ {
+ // initialize coreclr fail
+ exit(-1);
+ }
+
+ unsigned int exitCode;
+ st = executeAssembly(hostHandle, domainId, argc, argv, exe_path, &exitCode);
+ if (st < 0)
+ {
+ // execute coreclr fail
+ exit(-1);
+ }
+
+ st = shutdownCoreCLR(hostHandle, domainId);
+ if (st < 0)
+ {
+ // shutdown fail
+ exit(-1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ std::unique_ptr<Launcher> launcher(new Launcher());
+ auto on_prepare = [&launcher]()
+ {
+ launcher.Initialize();
+ };
+ auto on_requested = [&launcher]()
+ {
+ };
+ auto on_executed = [&launcher](const std::string& path, int argc, char *argv[])
+ {
+ launcher.launch(path, argc, argv);
+ }
+ std::unique_ptr<Waiter> waiter(new Waiter(on_prepare, on_requested, on_executed));
+}
--- /dev/null
+#include <string>
+#include <vector>
+
+namespace dotnet {
+namespace runtime {
+
+typedef int (*coreclr_initialize_ptr)(
+ const char* exePath,
+ const char* appDomainFriendlyName,
+ int propertyCount,
+ const char** propertyKeys,
+ const char** propertyValues,
+ void** hostHandle,
+ unsigned int* domainId);
+
+typedef int (*coreclr_execute_assembly_ptr)(
+ void* hostHandle,
+ unsigned int domainId,
+ int argc,
+ const char** argv,
+ const char* managedAssemblyPath,
+ unsigned int* exitCode);
+
+typedef int (*coreclr_shutdown_ptr)(
+ void* hostHandle,
+ unsigned int domainId);
+
+using std::string;
+using std::vector;
+
+class Launcher
+{
+ public:
+ Launcher();
+ ~Launcher();
+ void Initialize();
+ void Dispose();
+ void Launch(const string& exe_path, int argc, char *argv[]);
+
+ private:
+ coreclr_initialize_ptr initializeCoreCLR;
+ coreclr_execute_assembly_ptr executeAssembly;
+ coreclr_shutdown_ptr shutdownCoreCLR;
+
+ string TrustedPlatformAssemblies;
+ string NativeDllSearchDirectories;
+ string AppDomainCompatSwitch;
+};
+
+} // namespace runtime
+} // namespace dotnet
--- /dev/null
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#include <dlog.h>
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "NETCORE_LAUNCHER"
+
+#ifndef _ERR
+#define _ERR(fmt, args...) LOGE(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#ifndef _DBG
+#define _DBG(fmt, args...) LOGD(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#ifndef _INFO
+#define _INFO(fmt, args...) LOGI(fmt "\n", __func__, __LINE__, ##args)
+#endif
+
+#endif /* __LOG_H__ */
--- /dev/null
+#include <string>
+#include <map>
+#include <vector>
+#include <sstream>
+#include <algorithm>
+#include <functional>
+
+namespace tini
+{
+
+const std::string global = "___GLOBAL";
+
+inline void ltrim(std::string& s)
+{
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+}
+
+inline void rtrim(std::string& s)
+{
+ s.erase(std::find_if(s.rbegin(), s.rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+}
+
+inline void trim(std::string& s)
+{
+ ltrim(s);
+ rtrim(s);
+}
+
+inline int find_first_nonescaped(const std::string& str, char ch)
+{
+ if (!str.empty())
+ {
+ if (str[0] == ch) return 0;
+ for (size_t i=1; i<str.length(); i++)
+ {
+ if (str[i] == ch && str[i-1] != '\\')
+ return i;
+ }
+ }
+ return -1;
+}
+
+inline std::string uncomment(const std::string& str)
+{
+ int comment_start = find_first_nonescaped(str, '#');
+ if (comment_start < 0) return str;
+ return str.substr(0, comment_start);
+}
+
+inline bool get_header(const std::string& str, std::string& header)
+{
+ if (str.length() < 2 || str.front() != '[' || str.back() != ']') return false;
+ std::string expected = std::string(str.begin()+1, str.end()-1);
+ trim(expected);
+ if (expected.compare(header) != 0)
+ {
+ header = expected;
+ return true;
+ }
+ return false;
+}
+
+inline std::pair<std::string, std::string> get_pair(const std::string& str)
+{
+ std::string key, value;
+ std::string::size_type eq_pos = str.find('=');
+ if (eq_pos == std::string::npos)
+ {
+ key = str;
+ }
+ else
+ {
+ key = str.substr(0, eq_pos);
+ value = str.substr(eq_pos+1);
+ trim(value);
+ }
+ trim(key);
+ return std::pair<std::string, std::string>(key, value);
+}
+
+class ini
+{
+ public:
+ using pairs = std::map<std::string, std::string>;
+ ini()
+ {
+ }
+ ini(const std::string& str)
+ {
+ std::istringstream input(str);
+ init(input);
+ }
+ ini(std::istream &input)
+ {
+ init(input);
+ }
+ inline void init (std::istream &input)
+ {
+ std::string line;
+ std::string title(global);
+ while (std::getline(input, line))
+ {
+ line = uncomment(line);
+ if (line.empty()) continue;
+ trim(line);
+ if (get_header(line, title) && groups.find(title) == groups.end())
+ {
+ groups[title] = std::map<std::string, std::string>();
+ continue;
+ }
+
+ auto pair = get_pair(line);
+ groups[title][pair.first] = pair.second;
+ }
+ }
+ std::string to_string() const
+ {
+ std::stringstream str_out;
+ auto gpair = groups.find(global);
+ if (gpair != groups.end())
+ {
+ for (auto &pair : gpair->second)
+ {
+ str_out << pair.first << " = " << pair.second << std::endl;
+ }
+ }
+ for (auto &group : groups)
+ {
+ auto &title = group.first;
+ if (title == global) continue;
+ auto &pairs = group.second;
+ str_out << '[' << title << ']' << std::endl;
+ for (auto &pair : pairs)
+ {
+ str_out << pair.first << " = " << pair.second << std::endl;
+ }
+ }
+
+ return str_out.str();
+ }
+
+ pairs& operator [](const std::string& group_name)
+ {
+ if (groups.find(group_name) == groups.end())
+ {
+ groups[group_name] = pairs();
+ }
+ return groups[group_name];
+ }
+
+ std::map<std::string, pairs> groups;
+};
+
+std::ostream& operator << (std::ostream& os, const ini& rhs)
+{
+ os << rhs.to_string();
+ return os;
+}
+
+} // namespace tini
--- /dev/null
+#include "waiter.h"
+
+#include <poll.h>
+#include <launchpad.h>
+
+#include <memory>
+#include <vector>
+#include <map>
+#include <poll.h>
+
+namespace dotnet {
+namespace runtime {
+
+struct Waiter::FdHandler;
+{
+ pollfd *info;
+ Receiver re;
+};
+
+static volatile bool Waiting_;
+static std::vector<pollfd> Fdlist_;
+static std::map<int, FdHandler> Handlers_;
+static Waiter::AppInfo AppInfo_;
+
+void Waiter::OnPrepare()
+{
+ // preload the libraries.
+}
+
+void Waiter::OnLaunchRequested()
+{
+ // do some job on user id is still system
+}
+
+void Waiter::OnWaiting()
+{
+ // Start the loop
+ Waiting_ = true;
+
+ while (Waiting_)
+ {
+ if (poll(Fdlist_.data(), Fdlist_.size(), -1) < 0)
+ continue;
+
+ for (auto &p : Fdlist_)
+ {
+ if ( (p.revents | POLLIN) != 0 )
+ Handlers_[p.fd].receiver();
+ }
+ }
+}
+
+void Waiter::Stop()
+{
+ // Stop the loop
+
+ Waiting_ = false;
+}
+
+
+void Waiter::RegisterFd(int fd, Receiver receiver)
+{
+ // register fd should be caught in event loop
+
+ pollfd info;
+ info.fd = fd;
+ info.events = POLLIN;
+ info.revents = 0;
+
+ FdHandler handler;
+ Fdlist_.push_back(info);
+ handler.info = &Fdlist_.back();
+ handler.receiver = receiver;
+
+ Handlers_[fd] = handler;
+}
+
+void Waiter::DeregisterFd(int fd)
+{
+ // deregister fd should be caught in event loop
+
+ pollfd *info = Handlers_[fd].info;
+ Fdlist_.erase(Fdlist_.begin() - (info - &Fdlist_.front()));
+ Handlers_.erase(fd);
+}
+
+
+Waiter::Waiter(Action prepare, Action requested, Executor executor)
+{
+ prepare_ = prepare;
+ requested_ = requested;
+ executor_ = executor;
+}
+
+Waiter::~Waiter()
+{
+}
+
+void Waiter::WaitToLaunching(int argc, char *argv[])
+{
+ auto on_create = [](bundle *extra, int type, void *user_data)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->OnPrepare();
+ };
+
+ auto on_launch = [](int argc, char **argv, const char *app_path,
+ const char *appid, const char *pkgid,
+ const char *pkg_type, void *user_data)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+
+ AppInfo info = {
+ AppPath : app_path,
+ AppId : appid,
+ PkgId : pkgid,
+ PkgType : pkg_type
+ };
+
+ waiter->OnLaunchRequested(info);
+ };
+
+ auto on_terminate = [](int argc, char **argv, void *user_data)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->executor_(argv[LOADER_ARG_PATH], argc, argv);
+ };
+
+ auto on_start_loop = [](void *user_data)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->OnWaiting();
+ };
+
+ auto on_quit_loop = [](void *user_data)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->Stop();
+ };
+
+ auto on_add_fd = [](void *user_data, int fd, loader_receiver_cb receiver)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->RegisterFd(fd, receiver);
+ };
+
+ auto on_remove_fd = [](void *user_data, int fd)
+ {
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->DeregisterFd(fd);
+ };
+
+ loader_lifecycle_callback_s callbacks = {
+ .create = on_create,
+ .launch = on_launch,
+ .terminate = on_terminate
+ }
+
+ loader_adapter_s adapter = {
+ .loop_begin = on_start_loop,
+ .loop_quit = on_quit_loop,
+ .add_fd = on_add_fd,
+ .remove_fd = on_remove_fd
+ };
+
+ return launchpad_loader_main(argc, argv, &callbacks, &adapter, this);
+}
+
+} // namespace runtime
+} // namespace dotnet
--- /dev/null
+#include <string>
+
+using std::string;
+
+namespace dotnet {
+namespace runtime {
+
+class Waiter
+{
+ public:
+ struct AppInfo
+ {
+ string AppPath;
+ string AppId;
+ string PkgId;
+ string PkgType;
+ };
+ using Action = std::function<void(void)>;
+ using Receiver = std::function<void(int)>;
+ using Executor = std::function<void(const string&, int, char**)>;
+
+ Waiter(Action prepare, Action requested, Executor executor);
+ ~Waiter();
+
+ int WaitToLaunching(int argc, char *argv[]);
+ void Stop();
+
+ void RegisterFd(int fd, Receiver receiver);
+ void DeregisterFd(int fd);
+
+ protected:
+ void OnPrepare();
+ void OnLaunchRequested(const AppInfo&);
+ void OnWaiting();
+
+ private:
+
+ Action prepare_;
+ Action requested_;
+ Executor executor_;
+};
+
+} // namespace runtime
+} // namespace dotnet