Merge pull request #214 from morphis/f/fork-session-mgr
Start session manager when not already running
This commit is contained in:
commit
da20611a6c
16 changed files with 290 additions and 40 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
BIN
data/ui/loading-screen.png
Normal file
BIN
data/ui/loading-screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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-*/
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <boost/filesystem.hpp>
|
||||
|
||||
#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<dbus::stub::ApplicationManager> &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::Bus>(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<ui::SplashScreen> 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<dbus::stub::ApplicationManager> 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<ui::SplashScreen>();
|
||||
|
||||
std::vector<std::string> args = {"session-manager"};
|
||||
|
||||
std::map<std::string,std::string> 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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <memory>
|
||||
|
||||
#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<dbus::stub::ApplicationManager> &stub);
|
||||
|
||||
android::Intent intent_;
|
||||
wm::Stack::Id stack_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -25,10 +25,8 @@ namespace skeleton {
|
|||
std::shared_ptr<Service> Service::create_for_bus(
|
||||
const core::dbus::Bus::Ptr &bus,
|
||||
const std::shared_ptr<anbox::application::Manager> &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<Service>(bus, service, object, application_manager);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace dbus {
|
|||
namespace stub {
|
||||
std::shared_ptr<ApplicationManager> 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<ApplicationManager>(bus, service, object);
|
||||
}
|
||||
|
||||
|
|
|
|||
90
src/anbox/ui/splash_screen.cpp
Normal file
90
src/anbox/ui/splash_screen.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Simon Fels <morphis@gravedo.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "anbox/ui/splash_screen.h"
|
||||
#include "anbox/config.h"
|
||||
#include "anbox/utils.h"
|
||||
|
||||
#include <SDL2/SDL_image.h>
|
||||
|
||||
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
|
||||
42
src/anbox/ui/splash_screen.h
Normal file
42
src/anbox/ui/splash_screen.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Simon Fels <morphis@gravedo.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ANBOX_UI_SPLASH_SCREEN_H_
|
||||
#define ANBOX_UI_SPLASH_SCREEN_H_
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue