diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e75384..5ade5b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ find_package(GLESv2 REQUIRED) find_package(Protobuf REQUIRED) pkg_check_modules(SDL2 sdl2 REQUIRED) +pkg_check_modules(SDL2_IMAGE SDL2_image REQUIRED) pkg_check_modules(DBUS_CPP dbus-cpp REQUIRED) pkg_check_modules(DBUS dbus-1 REQUIRED) pkg_check_modules(LXC lxc REQUIRED) @@ -114,3 +115,6 @@ if (NOT "${HOST_CMAKE_C_COMPILER}" STREQUAL "") message(STATUS "Host C compiler: ${HOST_CMAKE_C_COMPILER}") message(STATUS "Host C compiler: ${HOST_CMAKE_CXX_COMPILER}") endif() + +install(FILES data/ui/loading-screen.png DESTINATION share/anbox/ui) + diff --git a/data/ui/loading-screen.png b/data/ui/loading-screen.png new file mode 100644 index 0000000..2678361 Binary files /dev/null and b/data/ui/loading-screen.png differ diff --git a/external/process-cpp-minimal/include/core/posix/child_process.h b/external/process-cpp-minimal/include/core/posix/child_process.h index d6b3b31..fde7977 100644 --- a/external/process-cpp-minimal/include/core/posix/child_process.h +++ b/external/process-cpp-minimal/include/core/posix/child_process.h @@ -114,6 +114,12 @@ public: */ wait::Result wait_for(const wait::Flags& flags); + /** + * @brief Mark the child process to not to be killed when the ChildProcess + * instance goes away. + */ + void dont_kill_on_cleanup(); + #ifndef ANDROID /** * @brief Access this process's stderr. diff --git a/external/process-cpp-minimal/src/core/posix/child_process.cpp b/external/process-cpp-minimal/src/core/posix/child_process.cpp index b121b89..ad16571 100644 --- a/external/process-cpp-minimal/src/core/posix/child_process.cpp +++ b/external/process-cpp-minimal/src/core/posix/child_process.cpp @@ -303,7 +303,7 @@ struct ChildProcess::Private ~Private() { // Check if we are in the original parent process. - if (original_parent_pid == getpid()) + if (original_parent_pid == getpid() && !dont_kill_on_cleanup) { // If so, check if we are considering a valid pid here. // If so, we kill the original child. @@ -333,6 +333,8 @@ struct ChildProcess::Private // is called from the child process. pid_t original_parent_pid; pid_t original_child_pid; + + bool dont_kill_on_cleanup = false; }; ChildProcess ChildProcess::invalid() @@ -395,6 +397,11 @@ wait::Result ChildProcess::wait_for(const wait::Flags& flags) return result; } +void ChildProcess::dont_kill_on_cleanup() +{ + d->dont_kill_on_cleanup = true; +} + #ifndef ANDROID std::istream& ChildProcess::cerr() { diff --git a/scripts/clean-build.sh b/scripts/clean-build.sh index d7219d7..ab659bb 100755 --- a/scripts/clean-build.sh +++ b/scripts/clean-build.sh @@ -32,6 +32,7 @@ apt-get install -qq -y \ libproperties-cpp-dev \ libprotobuf-dev \ libsdl2-dev \ + libsdl2-image-dev \ lxc-dev \ pkg-config \ protobuf-compiler diff --git a/snapcraft.yaml b/snapcraft.yaml index a32e30e..eebe70d 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -116,6 +116,7 @@ parts: - libprotobuf-dev - libproperties-cpp-dev - libsdl2-dev + - libsdl2-image-dev - pkg-config - protobuf-compiler stage-packages: @@ -128,4 +129,5 @@ parts: make test prime: - usr/bin/anbox + - usr/share/anbox - usr/lib/*-linux-*/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42f07bd..43eb183 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ include_directories( ${DBUS_CPP_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS} + ${SDL2_IMAGE_INCLUDE_DIRS} ${LXC_INCLUDE_DIRS} ${MIRCLIENT_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} @@ -206,6 +207,8 @@ set(SOURCES anbox/utils/environment_file.cpp + anbox/ui/splash_screen.cpp + anbox/do_not_copy_or_move.h anbox/optional.h anbox/defer_action.h) @@ -218,6 +221,8 @@ target_link_libraries(anbox-core ${DBUS_CPP_LIBRARIES} ${SDL2_LDFLAGS} ${SDL2_LIBRARIES} + ${SDL2_IMAGE_LDFLAGS} + ${SDL2_IMAGE_LIBRARIES} ${LXC_LDFLAGS} ${LXC_LIBRARIES} ${MIRCLIENT_LDFLAGS} diff --git a/src/anbox/android/intent.cpp b/src/anbox/android/intent.cpp index aa40f2c..4f5e659 100644 --- a/src/anbox/android/intent.cpp +++ b/src/anbox/android/intent.cpp @@ -22,16 +22,25 @@ namespace anbox { namespace android { std::ostream &operator<<(std::ostream &out, const Intent &intent) { - out << "[" - << "action=" << intent.action << " " - << "uri=" << intent.uri << " " - << "type=" << intent.type << " " - << "flags=" << intent.flags << " " - << "package=" << intent.package << " " - << "component=" << intent.component << " " - << "categories=[ "; - for (const auto &category : intent.categories) out << category << " "; - out << "]]"; + out << "["; + if (!intent.action.empty()) + out << " " << "action=" << intent.action << " "; + if (!intent.uri.empty()) + out << " " << "uri=" << intent.uri << " "; + if (!intent.type.empty()) + out << " " << "type=" << intent.type << " "; + if (intent.flags > 0) + out << " " << "flags=" << intent.flags << " "; + if (!intent.package.empty()) + out << " " << "package=" << intent.package << " "; + if (!intent.component.empty()) + out << "component=" << intent.component << " "; + if (intent.categories.size() > 0) { + out << "categories=[ "; + for (const auto &category : intent.categories) out << category << " "; + out << "] "; + } + out << "]"; return out; } } // namespace android diff --git a/src/anbox/cmds/launch.cpp b/src/anbox/cmds/launch.cpp index a73e2b6..69830aa 100644 --- a/src/anbox/cmds/launch.cpp +++ b/src/anbox/cmds/launch.cpp @@ -19,6 +19,8 @@ #include "anbox/common/wait_handle.h" #include "anbox/dbus/stub/application_manager.h" #include "anbox/common/dispatcher.h" +#include "anbox/ui/splash_screen.h" +#include "anbox/config.h" #include "anbox/runtime.h" #include "anbox/logger.h" @@ -26,12 +28,31 @@ #include +#include "core/posix/exec.h" +#include "core/posix/fork.h" #include "core/posix/signal.h" namespace fs = boost::filesystem; namespace { -const boost::posix_time::seconds max_wait_timeout{30}; +const boost::posix_time::seconds max_wait_timeout{240}; +const int max_restart_attempts{3}; +const std::chrono::seconds restart_interval{5}; +} + +bool anbox::cmds::Launch::try_launch_activity(const std::shared_ptr &stub) { + try { + DEBUG("Sending launch intent %s to Android ..", intent_); + stub->launch(intent_, graphics::Rect::Invalid, stack_); + } catch (const std::exception &err) { + ERROR("Failed to launch activity: %s", err.what()); + return false; + } catch (...) { + ERROR("Failed to launch activity"); + return false; + } + + return true; } anbox::cmds::Launch::Launch() @@ -69,42 +90,91 @@ anbox::cmds::Launch::Launch() auto bus = std::make_shared(core::dbus::WellKnownBus::session); bus->install_executor(core::dbus::asio::make_executor(bus, rt->service())); + const auto snap_path = utils::get_env_value("SNAP"); + if (!snap_path.empty()) { + const auto resource_path = fs::path(snap_path) / "usr" / "share" / "anbox"; + SystemConfiguration::instance().set_resource_path(resource_path); + } + + std::shared_ptr ss; + + // Instead of relying on the user session init system to start our + // session manager process we also attempt to start it on our own + // if not already running. This will help to mitigate problems with + // a crashing or a not yet started session manager instance. std::shared_ptr stub; - try { - stub = dbus::stub::ApplicationManager::create_for_bus(bus); - } catch (...) { - ERROR("Anbox session manager service isn't running!"); + for (auto n = 0; n < max_restart_attempts; n++) { + try { + stub = dbus::stub::ApplicationManager::create_for_bus(bus); + break; + } catch (std::exception &err) { + WARNING("Anbox session manager service isn't running, trying to start it."); + + // Give us a splash screen as long as we're trying to connect + // with the session manager so the user knows something is + // happening after he started Anbox. + if (!ss) + ss = std::make_shared(); + + std::vector args = {"session-manager"}; + + std::map env; + core::posix::this_process::env::for_each([&](const std::string &name, const std::string &value) { + env.insert({name, value}); + }); + + const auto exe_path = utils::process_get_exe_path(::getpid()); + if (!fs::exists(exe_path)) { + ERROR("Can't find correct anbox executable to run. Found %s but does not exist", exe_path); + return EXIT_FAILURE; + } + + try { + const auto flags = core::posix::StandardStream::stdout | core::posix::StandardStream::stderr; + auto child = core::posix::fork([&]() { + auto grandchild = core::posix::exec(exe_path, args, env, flags); + grandchild.dont_kill_on_cleanup(); + return core::posix::exit::Status::success; + }, flags); + + // We don't wait for the grandchild but the child as we use double forking + // here to break through the process hierarchy and make the grandchild a + // direct child of the init process so it keeps running on its own and + // indepent of our short living process here. + child.wait_for(core::posix::wait::Flags::untraced); + + DEBUG("Started session manager, will now try to connect .."); + } + catch (...) { + ERROR("Failed to start session manager instance"); + } + + std::this_thread::sleep_for(restart_interval); + } + } + + if (!stub) { + ERROR("Couldn't get a connection with the session manager"); return EXIT_FAILURE; } - auto dispatcher = anbox::common::create_dispatcher_for_runtime(rt); - bool success = false; - + auto dispatcher = anbox::common::create_dispatcher_for_runtime(rt); dispatcher->dispatch([&]() { if (stub->ready()) { - try { - stub->launch(intent_, graphics::Rect::Invalid, stack_); - success = true; - } catch (std::exception &err) { - ERROR("err %s", err.what()); - } + ss.reset(); + success = try_launch_activity(stub); trap->stop(); return; } - DEBUG("Android hasn't fully booted yet. Waiting a bit.."); + DEBUG("Android hasn't fully booted yet. Waiting a bit .."); stub->ready().changed().connect([&](bool ready) { if (!ready) return; - try { - stub->launch(intent_, graphics::Rect::Invalid, stack_); - success = true; - } catch (std::exception &err) { - ERROR("Failed to launch activity: %s", err.what()); - success = false; - } + ss.reset(); + success = try_launch_activity(stub); trap->stop(); }); }); @@ -112,8 +182,10 @@ anbox::cmds::Launch::Launch() boost::asio::deadline_timer timer(rt->service()); timer.expires_from_now(max_wait_timeout); timer.async_wait([&](const boost::system::error_code&) { - WARNING("Stop waiting as we're already waiting for too long. Something is wrong"); - WARNING("with your setup and the container may have failed to boot."); + WARNING("Stopped waiting as we've already waited for too long. Something"); + WARNING("is wrong with your setup or the container has failed to boot."); + WARNING("If you think you found a bug please don't hesitate to file on"); + WARNING("at https://github.com/anbox/anbox/issues/new"); trap->stop(); }); diff --git a/src/anbox/cmds/launch.h b/src/anbox/cmds/launch.h index b7a6dfe..7fc71c9 100644 --- a/src/anbox/cmds/launch.h +++ b/src/anbox/cmds/launch.h @@ -23,6 +23,7 @@ #include #include "anbox/android/intent.h" +#include "anbox/dbus/stub/application_manager.h" #include "anbox/wm/stack.h" #include "anbox/cli.h" @@ -33,6 +34,8 @@ class Launch : public cli::CommandWithFlagsAndAction { Launch(); private: + bool try_launch_activity(const std::shared_ptr &stub); + android::Intent intent_; wm::Stack::Id stack_; }; diff --git a/src/anbox/config.cpp b/src/anbox/config.cpp index 7c0961e..a13aa43 100644 --- a/src/anbox/config.cpp +++ b/src/anbox/config.cpp @@ -41,6 +41,10 @@ void anbox::SystemConfiguration::set_data_path(const std::string &path) { data_path = path; } +void anbox::SystemConfiguration::set_resource_path(const fs::path &path) { + resource_path = path; +} + fs::path anbox::SystemConfiguration::data_dir() const { return data_path; } @@ -76,6 +80,10 @@ std::string anbox::SystemConfiguration::application_item_dir() const { return dir.string(); } +std::string anbox::SystemConfiguration::resource_dir() const { + return resource_path.string(); +} + anbox::SystemConfiguration& anbox::SystemConfiguration::instance() { static SystemConfiguration config; return config; diff --git a/src/anbox/config.h b/src/anbox/config.h index 91f72c6..0fce571 100644 --- a/src/anbox/config.h +++ b/src/anbox/config.h @@ -31,6 +31,7 @@ class SystemConfiguration { virtual ~SystemConfiguration() = default; void set_data_path(const std::string &path); + void set_resource_path(const boost::filesystem::path &path); boost::filesystem::path data_dir() const; std::string rootfs_dir() const; @@ -40,11 +41,13 @@ class SystemConfiguration { std::string container_socket_path() const; std::string input_device_dir() const; std::string application_item_dir() const; + std::string resource_dir() const; protected: SystemConfiguration() = default; boost::filesystem::path data_path = "/var/lib/anbox"; + boost::filesystem::path resource_path = "/usr/share/anbox"; }; } // namespace anbox diff --git a/src/anbox/dbus/skeleton/service.cpp b/src/anbox/dbus/skeleton/service.cpp index 40910c1..8f309c2 100644 --- a/src/anbox/dbus/skeleton/service.cpp +++ b/src/anbox/dbus/skeleton/service.cpp @@ -25,10 +25,8 @@ namespace skeleton { std::shared_ptr Service::create_for_bus( const core::dbus::Bus::Ptr &bus, const std::shared_ptr &application_manager) { - auto service = core::dbus::Service::add_service( - bus, anbox::dbus::interface::Service::name()); - auto object = - service->add_object_for_path(anbox::dbus::interface::Service::path()); + auto service = core::dbus::Service::add_service(bus, anbox::dbus::interface::Service::name()); + auto object = service->add_object_for_path(anbox::dbus::interface::Service::path()); return std::make_shared(bus, service, object, application_manager); } diff --git a/src/anbox/dbus/stub/application_manager.cpp b/src/anbox/dbus/stub/application_manager.cpp index e11176f..743d73e 100644 --- a/src/anbox/dbus/stub/application_manager.cpp +++ b/src/anbox/dbus/stub/application_manager.cpp @@ -25,7 +25,7 @@ namespace dbus { namespace stub { std::shared_ptr ApplicationManager::create_for_bus(const core::dbus::Bus::Ptr &bus) { auto service = core::dbus::Service::use_service_or_throw_if_not_available(bus, anbox::dbus::interface::Service::name()); - auto object = service->add_object_for_path(anbox::dbus::interface::Service::path()); + auto object = service->object_for_path(anbox::dbus::interface::Service::path()); return std::make_shared(bus, service, object); } diff --git a/src/anbox/ui/splash_screen.cpp b/src/anbox/ui/splash_screen.cpp new file mode 100644 index 0000000..b52c817 --- /dev/null +++ b/src/anbox/ui/splash_screen.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#include "anbox/ui/splash_screen.h" +#include "anbox/config.h" +#include "anbox/utils.h" + +#include + +namespace anbox { +namespace ui { +SplashScreen::SplashScreen() { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { + const auto message = utils::string_format("Failed to initialize SDL: %s", SDL_GetError()); + BOOST_THROW_EXCEPTION(std::runtime_error(message)); + } + + const auto width = 1024, height = 768; + window_ = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS); + if (!window_) { + const auto message = utils::string_format("Failed to create window: %s", SDL_GetError()); + BOOST_THROW_EXCEPTION(std::runtime_error(message)); + } + + auto surface = SDL_GetWindowSurface(window_); + if (!surface) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not retrieve surface for our window")); + + SDL_FillRect(surface, nullptr, SDL_MapRGB(surface->format, 0xee, 0xee, 0xee)); + SDL_UpdateWindowSurface(window_); + + auto renderer = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED); + if (!renderer) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not create renderer")); + + const auto icon_path = utils::string_format("%s/ui/loading-screen.png", SystemConfiguration::instance().resource_dir()); + auto img = IMG_LoadTexture(renderer, icon_path.c_str()); + if (!img) { + const auto msg = utils::string_format("Failed to create texture from %s", icon_path); + BOOST_THROW_EXCEPTION(std::runtime_error(msg)); + } + + SDL_RenderClear(renderer); + + SDL_Rect r{0, 0, width, height}; + SDL_RenderCopy(renderer, img, nullptr, &r); + SDL_RenderPresent(renderer); + + SDL_ShowWindow(window_); + + event_thread_ = std::thread(&SplashScreen::process_events, this); +} + +SplashScreen::~SplashScreen() { + if (event_thread_running_) { + event_thread_running_ = false; + if (event_thread_.joinable()) + event_thread_.join(); + } + + if (window_) + SDL_DestroyWindow(window_); +} + +void SplashScreen::process_events() { + event_thread_running_ = true; + while (event_thread_running_) { + SDL_Event event; + while (SDL_WaitEventTimeout(&event, 100)) { + // Keep running until we're terminated + } + } +} +} // namespace ui +} // namespace anbox diff --git a/src/anbox/ui/splash_screen.h b/src/anbox/ui/splash_screen.h new file mode 100644 index 0000000..bee6f6d --- /dev/null +++ b/src/anbox/ui/splash_screen.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_UI_SPLASH_SCREEN_H_ +#define ANBOX_UI_SPLASH_SCREEN_H_ + +#include + +#include + +namespace anbox { +namespace ui { +class SplashScreen { + public: + SplashScreen(); + ~SplashScreen(); + + private: + void process_events(); + + std::thread event_thread_; + bool event_thread_running_; + SDL_Window *window_; +}; +} // namespace ui +} // namespace anbox + +#endif