Add example:weston-terminal
[staging/xdg-launcher.git] / src / runxdg.cpp
1 /*
2  * Copyright (c) 2017 Panasonic Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <stdarg.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29 #include <sys/wait.h>
30
31 #include <cstdio>
32
33 #include "cpptoml/cpptoml.h"
34
35 #include "runxdg.hpp"
36
37 #define RUNXDG_CONFIG "runxdg.toml"
38
39 void fatal(const char* format, ...)
40 {
41   va_list va_args;
42   va_start(va_args, format);
43   vfprintf(stderr, format, va_args);
44   va_end(va_args);
45
46   exit(EXIT_FAILURE);
47 }
48
49 void warn(const char* format, ...)
50 {
51   va_list va_args;
52   va_start(va_args, format);
53   vfprintf(stderr, format, va_args);
54   va_end(va_args);
55 }
56
57 void debug(const char* format, ...)
58 {
59   va_list va_args;
60   va_start(va_args, format);
61   vfprintf(stderr, format, va_args);
62   va_end(va_args);
63 }
64
65 void RunXDG::notify_ivi_control_cb (ilmObjectType object, t_ilm_uint id,
66                                     t_ilm_bool created)
67 {
68   if (object == ILM_SURFACE) {
69     struct ilmSurfaceProperties surf_props;
70
71     ilm_getPropertiesOfSurface(id, &surf_props);
72     pid_t surf_pid = surf_props.creatorPid;
73
74     if (!created) {
75       AGL_DEBUG("ivi surface (id=%d, pid=%d) destroyed.", id, surf_pid);
76       m_launcher->unregister_surfpid(surf_pid);
77       m_surfaces.erase(surf_pid);
78       return;
79     }
80
81     AGL_DEBUG("ivi surface (id=%d, pid=%d) is created.", id, surf_pid);
82
83     m_launcher->register_surfpid(surf_pid);
84     if (m_launcher->m_rid &&
85         surf_pid == m_launcher->find_surfpid_by_rid(m_launcher->m_rid)) {
86           m_ivi_id = id;
87           setup_surface();
88     }
89     m_surfaces[surf_pid] = id;
90   } else if (object == ILM_LAYER) {
91     if (created)
92       AGL_DEBUG("ivi layer: %d created.", id);
93     else
94       AGL_DEBUG("ivi layer: %d destroyed.", id);
95   }
96 }
97
98 void RunXDG::notify_ivi_control_cb_static (ilmObjectType object, t_ilm_uint id,
99                                            t_ilm_bool created, void *user_data)
100 {
101   RunXDG *runxdg = static_cast<RunXDG*>(user_data);
102   runxdg->notify_ivi_control_cb(object, id, created);
103 }
104
105 int POSIXLauncher::launch (std::string& name)
106 {
107   pid_t pid;
108
109   pid = fork();
110   if (pid < 0) {
111     AGL_DEBUG("cannot fork()");
112     return -1;
113   }
114
115   if (pid == 0) {
116     // child
117     const char **argv = new const char * [m_args_v.size() + 1];
118     for (unsigned int i = 0; i < m_args_v.size(); ++i) {
119       argv[i] = m_args_v[i].c_str();
120     }
121     argv[m_args_v.size()] = NULL;
122
123     execv(argv[0], (char **)argv);
124
125     AGL_FATAL("fail to execve(%s)", argv[0]);
126   }
127   // parent
128
129   return pid;
130 }
131
132 void POSIXLauncher::loop (volatile sig_atomic_t& e_flag)
133 {
134   int status;
135   pid_t ret;
136
137   while (!e_flag) {
138     ret = waitpid(m_rid, &status, 0);
139     if (ret < 0) {
140       if (errno == EINTR) {
141         AGL_DEBUG("catch EINTR while waitpid()");
142         continue;
143       }
144       break;
145     }
146   }
147
148   if (ret > 0) {
149     if (WIFEXITED(status)) {
150       AGL_DEBUG("%s terminated, return %d", m_args_v[0].c_str(),
151                 WEXITSTATUS(status));
152     }
153     if (WIFSIGNALED(status)) {
154       AGL_DEBUG("%s terminated by signal %d", m_args_v[0].c_str(),
155                 WTERMSIG(status));
156     }
157   }
158
159   if (e_flag) {
160     /* parent killed by someone, so need to kill children */
161     AGL_DEBUG("killpg(0, SIGTERM)");
162     killpg(0, SIGTERM);
163   }
164 }
165
166 int AFMDBusLauncher::get_dbus_message_bus (GBusType bus_type,
167                                            GDBusConnection * &conn)
168 {
169   GError* err = NULL;
170
171   conn = g_bus_get_sync(bus_type, NULL, &err);
172   if (err) {
173     AGL_WARN("Failed to get session bus: %s", err->message);
174     g_clear_error (&err);
175     return -1;
176   }
177
178   return 0;
179 }
180
181 int AFMDBusLauncher::launch (std::string &name)
182 {
183   GDBusMessage*       msg;
184   GDBusMessage*       re;
185   GDBusConnection*    conn;
186   GError*             err = NULL;
187   GVariant*           body;
188   char*               val;
189   const char*         xdg_app = name.c_str();
190
191   if (get_dbus_message_bus(G_BUS_TYPE_SESSION, conn)) {
192     return -1;
193   }
194
195   msg = g_dbus_message_new_method_call (
196       DBUS_SERVICE,
197       DBUS_PATH,
198       DBUS_INTERFACE,
199       "start");
200
201   if (msg == NULL) {
202     AGL_WARN("Failed to allocate the dbus message");
203     g_object_unref(conn);
204     return -1;
205   }
206
207   g_dbus_message_set_body(msg, g_variant_new("(s)", xdg_app));
208
209   re = g_dbus_connection_send_message_with_reply_sync (
210       conn, msg, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err);
211
212   if (err != NULL) {
213     AGL_WARN("unable to send message: %s", err->message);
214     g_clear_error(&err);
215     g_object_unref(conn);
216     g_object_unref(msg);
217     return -1;
218   }
219
220   g_dbus_connection_flush_sync(conn, NULL, &err);
221   if (err != NULL) {
222     AGL_WARN("unable to flush message queue: %s", err->message);
223     g_object_unref(conn);
224     g_object_unref(msg);
225     g_object_unref(re);
226     return -1;
227   }
228
229   body = g_dbus_message_get_body(re);
230   g_variant_get(body, "(&s)", &val);
231
232   AGL_DEBUG("dbus message get (%s)", val);
233
234   pid_t rid = std::stol(std::string(val));
235   AGL_DEBUG("RID = %d", rid);
236
237   g_object_unref(conn);
238   g_object_unref(msg);
239   g_object_unref(re);
240
241   return rid;
242 }
243
244 volatile sig_atomic_t e_flag = 0;
245
246 static void sigterm_handler (int signum)
247 {
248   e_flag = 1;
249 }
250
251 static void init_signal (void)
252 {
253   struct sigaction act, info;
254
255   /* Setup signal for SIGTERM */
256   if (!sigaction(SIGTERM, NULL, &info)) {
257     if (info.sa_handler == SIG_IGN) {
258       AGL_DEBUG("SIGTERM being ignored.");
259     } else if (info.sa_handler == SIG_DFL) {
260       AGL_DEBUG("SIGTERM being defaulted.");
261     }
262   }
263
264   act.sa_handler = &sigterm_handler;
265   if (sigemptyset(&act.sa_mask) != 0) {
266     AGL_FATAL("Cannot initialize sigaction");
267   }
268   act.sa_flags = 0;
269
270   if (sigaction(SIGTERM, &act, &info) != 0) {
271     AGL_FATAL("Cannot register signal handler for SIGTERM");
272   }
273 }
274
275 int RunXDG::init_wm (void)
276 {
277   m_wm = new LibWindowmanager();
278   if (m_wm->init(m_port, m_token.c_str())) {
279     AGL_DEBUG("cannot initialize windowmanager");
280     return -1;
281   }
282
283   std::function< void(json_object*) > h_active = [this](json_object* object) {
284     AGL_DEBUG("Got Event_Active");
285         t_ilm_surface s_ids[1] = { this->m_ivi_id };
286         ilm_setInputFocus(s_ids, 1, ILM_INPUT_DEVICE_KEYBOARD, ILM_TRUE);
287   };
288
289   std::function< void(json_object*) > h_inactive = [this](json_object* object) {
290     AGL_DEBUG("Got Event_Inactive");
291         t_ilm_surface s_ids[1] = { this->m_ivi_id };
292         ilm_setInputFocus(s_ids, 1, ILM_INPUT_DEVICE_KEYBOARD, ILM_FALSE);
293   };
294
295   std::function< void(json_object*) > h_visible = [](json_object* object) {
296     AGL_DEBUG("Got Event_Visible");
297   };
298
299   std::function< void(json_object*) > h_invisible = [](json_object* object) {
300     AGL_DEBUG("Got Event_Invisible");
301   };
302
303   std::function< void(json_object*) > h_syncdraw =
304       [this](json_object* object) {
305     AGL_DEBUG("Got Event_SyncDraw");
306     json_object* obj = json_object_new_object();
307     json_object_object_add(obj, this->m_wm->kKeyDrawingName,
308                            json_object_new_string(this->m_role.c_str()));
309     this->m_wm->endDraw(obj);
310   };
311
312   std::function< void(json_object*) > h_flushdraw= [](json_object* object) {
313     AGL_DEBUG("Got Event_FlushDraw");
314   };
315
316   m_wm->set_event_handler(LibWindowmanager::Event_Active, h_active);
317   m_wm->set_event_handler(LibWindowmanager::Event_Inactive, h_inactive);
318   m_wm->set_event_handler(LibWindowmanager::Event_Visible, h_visible);
319   m_wm->set_event_handler(LibWindowmanager::Event_Invisible, h_invisible);
320   m_wm->set_event_handler(LibWindowmanager::Event_SyncDraw, h_syncdraw);
321   m_wm->set_event_handler(LibWindowmanager::Event_FlushDraw, h_flushdraw);
322
323   return 0;
324 }
325
326 int RunXDG::init_hs (void)
327 {
328   m_hs = new LibHomeScreen();
329   if (m_hs->init(m_port, m_token.c_str())) {
330     AGL_DEBUG("cannot initialize homescreen");
331     return -1;
332   }
333
334   std::function< void(json_object*) > handler = [this] (json_object* object) {
335     json_object *val;
336
337     if (json_object_object_get_ex(object, "application_name", &val)) {
338       const char *name = json_object_get_string(val);
339
340           const char *app_id = this->m_id.c_str();
341           int len = strlen(name);
342
343       AGL_DEBUG("Event_TapShortcut <appid=%s,%s,%d>", name, app_id, len);
344
345           if (strcmp(name, app_id) == 0 ||
346                   (strncmp(name, app_id, len) == 0 && ('@' == app_id[len]))) {
347         // check app exist and re-launch if needed
348         AGL_DEBUG("Activesurface %s ", this->m_role.c_str());
349
350         json_object *obj = json_object_new_object();
351         json_object_object_add(obj, this->m_wm->kKeyDrawingName,
352                                json_object_new_string(this->m_role.c_str()));
353         json_object_object_add(obj, this->m_wm->kKeyDrawingArea,
354                                json_object_new_string("normal.full"));
355
356         this->m_wm->activateSurface(obj);
357       }
358     }
359   };
360   m_hs->set_event_handler(LibHomeScreen::Event_TapShortcut, handler);
361
362   std::function< void(json_object*) > h_default= [](json_object* object) {
363     const char *j_str = json_object_to_json_string(object);
364     AGL_DEBUG("Got event [%s]", j_str);
365   };
366   m_hs->set_event_handler(LibHomeScreen::Event_OnScreenMessage, h_default);
367
368   return 0;
369 }
370
371 int RunXDG::parse_config (const char *path_to_config)
372 {
373   auto config = cpptoml::parse_file(path_to_config);
374
375   if (config == nullptr) {
376     AGL_DEBUG("cannot parse %s", path_to_config);
377     return -1;
378   }
379
380   AGL_DEBUG("[%s] parsed", path_to_config);
381
382   auto app = config->get_table("application");
383   if (app == nullptr) {
384     AGL_DEBUG("cannto find [application]");
385     return -1;
386   }
387
388   m_role = *(app->get_as<std::string>("role"));
389   m_path = *(app->get_as<std::string>("path"));
390   if (m_role.empty() || m_path.empty()) {
391     AGL_FATAL("No name or path defined in config");
392   }
393
394   std::string method = *(app->get_as<std::string>("method"));
395   if (method.empty()) {
396     method = std::string("POSIX");
397   }
398
399   POSIXLauncher *pl;
400
401   /* Setup API of launcher */
402   if (method == "POSIX") {
403     pl = new POSIXLauncher();
404     m_launcher = pl;
405   } else if (method == "AFM_DBUS") {
406     m_launcher = new AFMDBusLauncher();
407     return 0;
408   } else if (method == "AFM_WEBSOCKET") {
409     m_launcher = new AFMWebSocketLauncher();
410     return 0;
411   } else {
412     AGL_FATAL("Unknown type of launcher");
413   }
414
415   // setup argv[0]
416   pl->m_args_v.push_back(m_path);
417
418   // setup argv[1..n]
419   auto params = app->get_array_of<std::string>("params");
420   for (const auto& param : *params)
421   {
422     // replace special string "@port@" and "@token@"
423     size_t found = param.find("@port@");
424     if (found != std::string::npos) {
425       std::string sub1 = param.substr(0, found);
426       std::string sub2 = param.substr(found + 6, param.size() - found);
427       std::string str = sub1 + std::to_string(m_port) + sub2;
428       pl->m_args_v.push_back(str);
429       AGL_DEBUG("params[%s] (match @port@)", str.c_str());
430       continue;
431     }
432
433     found = param.find("@token@");
434     if (found != std::string::npos) {
435       std::string sub1 = param.substr(0, found);
436       std::string sub2 = param.substr(found + 7, param.size() - found);
437       std::string str = sub1 + m_token + sub2;
438       pl->m_args_v.push_back(str);
439       AGL_DEBUG("params[%s] (match @token@)", str.c_str());
440       continue;
441     }
442
443     pl->m_args_v.push_back(param);
444
445     AGL_DEBUG("params[%s]", param.c_str());
446   }
447
448   return 0;
449 }
450
451 RunXDG::RunXDG (int port, const char* token, const char* id)
452 {
453   m_id = std::string(id);
454   m_port = port;
455   m_token = std::string(token);
456
457
458   auto path = std::string(getenv("AFM_APP_INSTALL_DIR"));
459   path = path + "/" + RUNXDG_CONFIG;
460
461   // Parse config file of runxdg
462   if (parse_config(path.c_str())) {
463     AGL_FATAL("Error in config");
464   }
465
466   AGL_DEBUG("id=[%s], role=[%s], path=[%s], port=%lu, token=[%s]",
467             m_id.c_str(), m_role.c_str(), m_path.c_str(),
468             m_port, m_token.c_str());
469
470   // Setup HomeScreen/WindowManager API
471   if (init_wm())
472     AGL_FATAL("cannot setup wm API");
473
474   if (init_hs())
475     AGL_FATAL("cannot setup hs API");
476
477   // Setup ilmController API
478   m_ic = new ILMControl(notify_ivi_control_cb_static, this);
479
480   AGL_DEBUG("RunXDG created.");
481 }
482
483 void RunXDG::setup_surface (void)
484 {
485   std::string sid = std::to_string(m_ivi_id);
486
487   // This surface is mine, register pair app_name and ivi id.
488   json_object *obj = json_object_new_object();
489   json_object_object_add(obj, m_wm->kKeyDrawingName,
490                          json_object_new_string(m_role.c_str()));
491   json_object_object_add(obj, m_wm->kKeyIviId,
492                          json_object_new_string(sid.c_str()));
493
494   AGL_DEBUG("requestSurfaceXDG(%s,%s)", m_role.c_str(), sid.c_str());
495   m_wm->requestSurfaceXDG(obj);
496
497   if (m_pending_create) {
498     // Recovering 1st time tap_shortcut is dropped because
499     // the application has not been run yet (1st time launch)
500     m_pending_create = false;
501
502     json_object *obj = json_object_new_object();
503     json_object_object_add(obj, m_wm->kKeyDrawingName,
504                            json_object_new_string(m_role.c_str()));
505     json_object_object_add(obj, m_wm->kKeyDrawingArea,
506                            json_object_new_string("normal.full"));
507     m_wm->activateSurface(obj);
508   }
509 }
510
511 void POSIXLauncher::register_surfpid (pid_t surf_pid)
512 {
513   if (surf_pid == m_rid) {
514     if (!std::count(m_pid_v.begin(), m_pid_v.end(), surf_pid)) {
515       AGL_DEBUG("surface creator(pid=%d) registered", surf_pid);
516       m_pid_v.push_back(surf_pid);
517       AGL_DEBUG("m_pid_v.count(%d) = %d", surf_pid,
518                 std::count(m_pid_v.begin(), m_pid_v.end(), surf_pid));
519     }
520   }
521 }
522
523 void POSIXLauncher::unregister_surfpid (pid_t surf_pid)
524 {
525   auto itr = m_pid_v.begin();
526   while (itr != m_pid_v.end()) {
527     if (*itr == surf_pid) {
528       m_pid_v.erase(itr++);
529     } else {
530       ++itr;
531     }
532   }
533 }
534
535 pid_t POSIXLauncher::find_surfpid_by_rid (pid_t rid)
536 {
537   AGL_DEBUG("find surfpid by rid(%d)", rid);
538   if (std::count(m_pid_v.begin(), m_pid_v.end(), rid)) {
539     AGL_DEBUG("found return(%d)", rid);
540     return rid;
541   }
542
543   return -1;
544 }
545
546 void AFMLauncher::register_surfpid (pid_t surf_pid)
547 {
548   pid_t pgid = 0;
549
550   pgid = getpgid(surf_pid);
551
552   if (pgid < 0) {
553     AGL_DEBUG("fail to get process group id");
554     return;
555   }
556
557   AGL_DEBUG("Surface creator is pid=%d, pgid=%d", surf_pid, pgid);
558
559   if (!m_pgids.count(pgid)) {
560     m_pgids[pgid] = surf_pid;
561   }
562 }
563
564 void AFMLauncher::unregister_surfpid (pid_t surf_pid)
565 {
566   auto itr = m_pgids.begin();
567   while (itr != m_pgids.end()) {
568     if (itr->second == surf_pid) {
569       m_pgids.erase(itr++);
570     } else {
571       ++itr;
572     }
573   }
574 }
575
576 pid_t AFMLauncher::find_surfpid_by_rid (pid_t rid)
577 {
578   auto itr = m_pgids.find(rid);
579   if (itr != m_pgids.end())
580     return itr->second;
581
582   return -1;
583 }
584
585 void RunXDG::start (void)
586 {
587   // Initialize SIGTERM handler
588   init_signal();
589
590   /* Launch XDG application */
591   m_launcher->m_rid = m_launcher->launch(m_id);
592   if (m_launcher->m_rid < 0) {
593     AGL_FATAL("cannot launch XDG app (%s)", m_id);
594   }
595
596   // take care 1st time launch
597   AGL_DEBUG("waiting for notification: surafce created");
598   m_pending_create = true;
599
600   // in case, target app has already run
601   if (m_launcher->m_rid) {
602     pid_t surf_pid = m_launcher->find_surfpid_by_rid(m_launcher->m_rid);
603     if (surf_pid > 0) {
604       AGL_DEBUG("match: surf:pid=%d, afm:rid=%d", surf_pid,
605                 m_launcher->m_rid);
606       auto itr = m_surfaces.find(surf_pid);
607       if (itr != m_surfaces.end()) {
608         int id = itr->second;
609         AGL_DEBUG("surface %d for <%s> already exists", id,
610                   m_role.c_str());
611                 m_ivi_id = id;
612         setup_surface();
613       }
614     }
615   }
616   m_launcher->loop(e_flag);
617 }
618
619 int main (int argc, const char* argv[])
620 {
621   // Set debug flags
622   // setenv("USE_HMI_DEBUG", "5", 1);
623   // setenv("WAYLAND_DEBUG", "1", 1);
624
625   // Parse args
626   int port;
627   const char *token;
628
629   if (argc < 3) {
630     AGL_FATAL("Missing port and token");
631   }
632
633   // Get app id
634   const char *afm_id = getenv("AFM_ID");
635   if (afm_id == NULL || !afm_id[0]) {
636     afm_id = argv[0];
637   }
638
639   try {
640     port = std::stol(argv[1]);
641     token = argv[2];
642   } catch (const std::invalid_argument& e) {
643     AGL_FATAL("Invalid argument");
644   } catch (const std::out_of_range& e) {
645     AGL_FATAL("Out of range");
646   }
647
648   RunXDG runxdg(port, token, afm_id);
649
650   runxdg.start();
651
652   return 0;
653 }