/* * Copyright (c) 2017 Panasonic Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "cpptoml/cpptoml.h" #include "runxdg.hpp" #define RUNXDG_CONFIG "runxdg.toml" #define AREA_NORMAL_FULL "normal.full" void fatal(const char* format, ...) { va_list va_args; va_start(va_args, format); vfprintf(stderr, format, va_args); va_end(va_args); exit(EXIT_FAILURE); } void warn(const char* format, ...) { va_list va_args; va_start(va_args, format); vfprintf(stderr, format, va_args); va_end(va_args); } void debug(const char* format, ...) { va_list va_args; va_start(va_args, format); vfprintf(stderr, format, va_args); va_end(va_args); } void RunXDG::notify_ivi_control_cb (ilmObjectType object, t_ilm_uint id, t_ilm_bool created) { if (object == ILM_SURFACE) { struct ilmSurfaceProperties surf_props; ilm_getPropertiesOfSurface(id, &surf_props); pid_t surf_pid = surf_props.creatorPid; if (!created) { AGL_DEBUG("ivi surface (id=%d, pid=%d) destroyed.", id, surf_pid); m_launcher->unregister_surfpid(surf_pid); m_surfaces.erase(surf_pid); return; } AGL_DEBUG("ivi surface (id=%d, pid=%d) is created.", id, surf_pid); m_launcher->register_surfpid(surf_pid); if (m_launcher->m_rid && surf_pid == m_launcher->find_surfpid_by_rid(m_launcher->m_rid)) { m_ivi_id = id; setup_surface(); } m_surfaces[surf_pid] = id; } else if (object == ILM_LAYER) { if (created) AGL_DEBUG("ivi layer: %d created.", id); else AGL_DEBUG("ivi layer: %d destroyed.", id); } } void RunXDG::notify_ivi_control_cb_static (ilmObjectType object, t_ilm_uint id, t_ilm_bool created, void *user_data) { RunXDG *runxdg = static_cast(user_data); runxdg->notify_ivi_control_cb(object, id, created); } int POSIXLauncher::launch (std::string& name) { pid_t pid; pid = fork(); if (pid < 0) { AGL_DEBUG("cannot fork()"); return -1; } if (pid == 0) { // child const char **argv = new const char * [m_args_v.size() + 1]; for (unsigned int i = 0; i < m_args_v.size(); ++i) { argv[i] = m_args_v[i].c_str(); } argv[m_args_v.size()] = NULL; execv(argv[0], (char **)argv); AGL_FATAL("fail to execve(%s)", argv[0]); } // parent return pid; } void POSIXLauncher::loop (volatile sig_atomic_t& e_flag) { int status; pid_t ret; while (!e_flag) { ret = waitpid(m_rid, &status, 0); if (ret < 0) { if (errno == EINTR) { AGL_DEBUG("catch EINTR while waitpid()"); continue; } break; } } if (ret > 0) { if (WIFEXITED(status)) { AGL_DEBUG("%s terminated, return %d", m_args_v[0].c_str(), WEXITSTATUS(status)); } if (WIFSIGNALED(status)) { AGL_DEBUG("%s terminated by signal %d", m_args_v[0].c_str(), WTERMSIG(status)); } } if (e_flag) { /* parent killed by someone, so need to kill children */ AGL_DEBUG("killpg(0, SIGTERM)"); killpg(0, SIGTERM); } } int AFMDBusLauncher::get_dbus_message_bus (GBusType bus_type, GDBusConnection * &conn) { GError* err = NULL; conn = g_bus_get_sync(bus_type, NULL, &err); if (err) { AGL_WARN("Failed to get session bus: %s", err->message); g_clear_error (&err); return -1; } return 0; } int AFMDBusLauncher::launch (std::string &name) { GDBusMessage* msg; GDBusMessage* re; GDBusConnection* conn; GError* err = NULL; GVariant* body; char* val; const char* xdg_app = name.c_str(); if (get_dbus_message_bus(G_BUS_TYPE_SESSION, conn)) { return -1; } msg = g_dbus_message_new_method_call ( DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE, "start"); if (msg == NULL) { AGL_WARN("Failed to allocate the dbus message"); g_object_unref(conn); return -1; } g_dbus_message_set_body(msg, g_variant_new("(s)", xdg_app)); re = g_dbus_connection_send_message_with_reply_sync ( conn, msg, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err); if (err != NULL) { AGL_WARN("unable to send message: %s", err->message); g_clear_error(&err); g_object_unref(conn); g_object_unref(msg); return -1; } g_dbus_connection_flush_sync(conn, NULL, &err); if (err != NULL) { AGL_WARN("unable to flush message queue: %s", err->message); g_object_unref(conn); g_object_unref(msg); g_object_unref(re); return -1; } body = g_dbus_message_get_body(re); g_variant_get(body, "(&s)", &val); AGL_DEBUG("dbus message get (%s)", val); pid_t rid = std::stol(std::string(val)); AGL_DEBUG("RID = %d", rid); g_object_unref(conn); g_object_unref(msg); g_object_unref(re); return rid; } volatile sig_atomic_t e_flag = 0; static void sigterm_handler (int signum) { e_flag = 1; } static void init_signal (void) { struct sigaction act, info; /* Setup signal for SIGTERM */ if (!sigaction(SIGTERM, NULL, &info)) { if (info.sa_handler == SIG_IGN) { AGL_DEBUG("SIGTERM being ignored."); } else if (info.sa_handler == SIG_DFL) { AGL_DEBUG("SIGTERM being defaulted."); } } act.sa_handler = &sigterm_handler; if (sigemptyset(&act.sa_mask) != 0) { AGL_FATAL("Cannot initialize sigaction"); } act.sa_flags = 0; if (sigaction(SIGTERM, &act, &info) != 0) { AGL_FATAL("Cannot register signal handler for SIGTERM"); } } int RunXDG::init_wm (void) { m_wm = new LibWindowmanager(); if (m_wm->init(m_port, m_token.c_str())) { AGL_DEBUG("cannot initialize windowmanager"); return -1; } std::function< void(json_object*) > h_active = [this](json_object* object) { AGL_DEBUG("Got Event_Active"); t_ilm_surface s_ids[1] = { this->m_ivi_id }; ilm_setInputFocus(s_ids, 1, ILM_INPUT_DEVICE_KEYBOARD, ILM_TRUE); }; std::function< void(json_object*) > h_inactive = [this](json_object* object) { AGL_DEBUG("Got Event_Inactive"); t_ilm_surface s_ids[1] = { this->m_ivi_id }; ilm_setInputFocus(s_ids, 1, ILM_INPUT_DEVICE_KEYBOARD, ILM_FALSE); }; std::function< void(json_object*) > h_visible = [](json_object* object) { AGL_DEBUG("Got Event_Visible"); }; std::function< void(json_object*) > h_invisible = [](json_object* object) { AGL_DEBUG("Got Event_Invisible"); }; std::function< void(json_object*) > h_syncdraw = [this](json_object* object) { AGL_DEBUG("Got Event_SyncDraw"); this->m_wm->endDraw(this->m_role.c_str()); }; std::function< void(json_object*) > h_flushdraw= [](json_object* object) { AGL_DEBUG("Got Event_FlushDraw"); }; m_wm->set_event_handler(LibWindowmanager::Event_Active, h_active); m_wm->set_event_handler(LibWindowmanager::Event_Inactive, h_inactive); m_wm->set_event_handler(LibWindowmanager::Event_Visible, h_visible); m_wm->set_event_handler(LibWindowmanager::Event_Invisible, h_invisible); m_wm->set_event_handler(LibWindowmanager::Event_SyncDraw, h_syncdraw); m_wm->set_event_handler(LibWindowmanager::Event_FlushDraw, h_flushdraw); return 0; } int RunXDG::init_hs (void) { m_hs = new LibHomeScreen(); if (m_hs->init(m_port, m_token.c_str())) { AGL_DEBUG("cannot initialize homescreen"); return -1; } std::function< void(json_object*) > handler = [this] (json_object* object) { AGL_DEBUG("Activesurface %s ", this->m_role.c_str()); this->m_wm->activateWindow(this->m_role.c_str(), AREA_NORMAL_FULL); }; m_hs->set_event_handler(LibHomeScreen::Event_TapShortcut, handler); std::function< void(json_object*) > h_default= [](json_object* object) { const char *j_str = json_object_to_json_string(object); AGL_DEBUG("Got event [%s]", j_str); }; m_hs->set_event_handler(LibHomeScreen::Event_OnScreenMessage, h_default); return 0; } int RunXDG::parse_config (const char *path_to_config) { auto config = cpptoml::parse_file(path_to_config); if (config == nullptr) { AGL_DEBUG("cannot parse %s", path_to_config); return -1; } AGL_DEBUG("[%s] parsed", path_to_config); auto app = config->get_table("application"); if (app == nullptr) { AGL_DEBUG("cannto find [application]"); return -1; } m_role = *(app->get_as("role")); m_path = *(app->get_as("path")); if (m_role.empty() || m_path.empty()) { AGL_FATAL("No name or path defined in config"); } std::string method = *(app->get_as("method")); if (method.empty()) { method = std::string("POSIX"); } POSIXLauncher *pl; /* Setup API of launcher */ if (method == "POSIX") { pl = new POSIXLauncher(); m_launcher = pl; } else if (method == "AFM_DBUS") { m_launcher = new AFMDBusLauncher(); return 0; } else if (method == "AFM_WEBSOCKET") { m_launcher = new AFMWebSocketLauncher(); return 0; } else { AGL_FATAL("Unknown type of launcher"); } // setup argv[0] pl->m_args_v.push_back(m_path); // setup argv[1..n] auto params = app->get_array_of("params"); for (const auto& param : *params) { // replace special string "@port@" and "@token@" size_t found = param.find("@port@"); if (found != std::string::npos) { std::string sub1 = param.substr(0, found); std::string sub2 = param.substr(found + 6, param.size() - found); std::string str = sub1 + std::to_string(m_port) + sub2; pl->m_args_v.push_back(str); AGL_DEBUG("params[%s] (match @port@)", str.c_str()); continue; } found = param.find("@token@"); if (found != std::string::npos) { std::string sub1 = param.substr(0, found); std::string sub2 = param.substr(found + 7, param.size() - found); std::string str = sub1 + m_token + sub2; pl->m_args_v.push_back(str); AGL_DEBUG("params[%s] (match @token@)", str.c_str()); continue; } pl->m_args_v.push_back(param); AGL_DEBUG("params[%s]", param.c_str()); } return 0; } RunXDG::RunXDG (int port, const char* token, const char* id) { m_id = std::string(id); m_port = port; m_token = std::string(token); auto path = std::string(getenv("AFM_APP_INSTALL_DIR")); path = path + "/" + RUNXDG_CONFIG; // Parse config file of runxdg if (parse_config(path.c_str())) { AGL_FATAL("Error in config"); } AGL_DEBUG("id=[%s], name=[%s], path=[%s], port=%lu, token=[%s]", m_id.c_str(), m_role.c_str(), m_path.c_str(), m_port, m_token.c_str()); // Setup HomeScreen/WindowManager API if (init_wm()) AGL_FATAL("cannot setup wm API"); if (init_hs()) AGL_FATAL("cannot setup hs API"); // Setup ilmController API m_ic = new ILMControl(notify_ivi_control_cb_static, this); AGL_DEBUG("RunXDG created."); } void RunXDG::setup_surface (void) { std::string sid = std::to_string(m_ivi_id); // This surface is mine, register pair app_name and ivi id. AGL_DEBUG("requestSurfaceXDG(%s,%d)", m_role.c_str(), m_ivi_id); m_wm->requestSurfaceXDG(this->m_role.c_str(), (unsigned int)m_ivi_id); if (m_pending_create) { // Recovering 1st time tap_shortcut is dropped because // the application has not been run yet (1st time launch) m_pending_create = false; m_wm->activateWindow(this->m_role.c_str(), AREA_NORMAL_FULL); } } void POSIXLauncher::register_surfpid (pid_t surf_pid) { if (surf_pid == m_rid) { if (!std::count(m_pid_v.begin(), m_pid_v.end(), surf_pid)) { AGL_DEBUG("surface creator(pid=%d) registered", surf_pid); m_pid_v.push_back(surf_pid); AGL_DEBUG("m_pid_v.count(%d) = %d", surf_pid, std::count(m_pid_v.begin(), m_pid_v.end(), surf_pid)); } } } void POSIXLauncher::unregister_surfpid (pid_t surf_pid) { auto itr = m_pid_v.begin(); while (itr != m_pid_v.end()) { if (*itr == surf_pid) { m_pid_v.erase(itr++); } else { ++itr; } } } pid_t POSIXLauncher::find_surfpid_by_rid (pid_t rid) { AGL_DEBUG("find surfpid by rid(%d)", rid); if (std::count(m_pid_v.begin(), m_pid_v.end(), rid)) { AGL_DEBUG("found return(%d)", rid); return rid; } return -1; } void AFMLauncher::register_surfpid (pid_t surf_pid) { pid_t pgid = 0; pgid = getpgid(surf_pid); if (pgid < 0) { AGL_DEBUG("fail to get process group id"); return; } AGL_DEBUG("Surface creator is pid=%d, pgid=%d", surf_pid, pgid); if (!m_pgids.count(pgid)) { m_pgids[pgid] = surf_pid; } } void AFMLauncher::unregister_surfpid (pid_t surf_pid) { auto itr = m_pgids.begin(); while (itr != m_pgids.end()) { if (itr->second == surf_pid) { m_pgids.erase(itr++); } else { ++itr; } } } pid_t AFMLauncher::find_surfpid_by_rid (pid_t rid) { auto itr = m_pgids.find(rid); if (itr != m_pgids.end()) return itr->second; return -1; } void RunXDG::start (void) { // Initialize SIGTERM handler init_signal(); /* Launch XDG application */ m_launcher->m_rid = m_launcher->launch(m_id); if (m_launcher->m_rid < 0) { AGL_FATAL("cannot launch XDG app (%s)", m_id); } // take care 1st time launch AGL_DEBUG("waiting for notification: surafce created"); m_pending_create = true; ilm_commitChanges(); // in case, target app has already run if (m_launcher->m_rid) { pid_t surf_pid = m_launcher->find_surfpid_by_rid(m_launcher->m_rid); if (surf_pid > 0) { AGL_DEBUG("match: surf:pid=%d, afm:rid=%d", surf_pid, m_launcher->m_rid); auto itr = m_surfaces.find(surf_pid); if (itr != m_surfaces.end()) { int id = itr->second; AGL_DEBUG("surface %d for <%s> already exists", id, m_role.c_str()); m_ivi_id = id; setup_surface(); } } } ilm_commitChanges(); m_launcher->loop(e_flag); } int main (int argc, const char* argv[]) { // Set debug flags // setenv("USE_HMI_DEBUG", "5", 1); // setenv("WAYLAND_DEBUG", "1", 1); // Parse args int port; const char *token; if (argc < 3) { AGL_FATAL("Missing port and token"); } // Get app id const char *afm_id = getenv("AFM_ID"); if (afm_id == NULL || !afm_id[0]) { afm_id = argv[0]; } try { port = std::stol(argv[1]); token = argv[2]; } catch (const std::invalid_argument& e) { AGL_FATAL("Invalid argument"); } catch (const std::out_of_range& e) { AGL_FATAL("Out of range"); } RunXDG runxdg(port, token, afm_id); runxdg.start(); return 0; }