RunXDG 91/12591/2
authorTadao Tanikawa <tanikawa.tadao@jp.panasonic.com>
Thu, 14 Dec 2017 10:46:19 +0000 (19:46 +0900)
committerTadao Tanikawa <tanikawa.tadao@jp.panasonic.com>
Thu, 14 Dec 2017 11:09:20 +0000 (20:09 +0900)
The launcher of XDG application on AGL HomeScreen/WindowManager.
For detail, check README.txt.

Bug-AGL: SPEC-1096

Change-Id: Ia20d185c3d64788f894b69c6e40f0c5c7cb0ff8c
Signed-off-by: Tadao Tanikawa <tanikawa.tadao@jp.panasonic.com>
20 files changed:
.gitignore [new file with mode: 0644]
.gitreview [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
README.txt [new file with mode: 0644]
include/cpptoml/LICENSE [new file with mode: 0644]
include/cpptoml/cpptoml.h [new file with mode: 0644]
package/hvac/bin/runxdg [new file with mode: 0755]
package/hvac/config.xml [new file with mode: 0644]
package/hvac/icon.svg [new file with mode: 0644]
package/hvac/runxdg.toml [new file with mode: 0644]
package/navi/bin/runxdg [new file with mode: 0755]
package/navi/config.xml [new file with mode: 0644]
package/navi/icon.svg [new file with mode: 0644]
package/navi/runxdg.toml [new file with mode: 0644]
package/simple-egl/bin/runxdg [new file with mode: 0755]
package/simple-egl/config.xml [new file with mode: 0644]
package/simple-egl/icon.svg [new file with mode: 0644]
package/simple-egl/runxdg.toml [new file with mode: 0644]
src/runxdg.cpp [new file with mode: 0644]
src/runxdg.hpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..567609b
--- /dev/null
@@ -0,0 +1 @@
+build/
diff --git a/.gitreview b/.gitreview
new file mode 100644 (file)
index 0000000..c22e5d1
--- /dev/null
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.automotivelinux.org
+port=29418
+project=apps/app-templates
+defaultbranch=master
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..196e7f5
--- /dev/null
@@ -0,0 +1,72 @@
+project (runxdg)
+
+cmake_minimum_required(VERSION 3.0)
+
+set(PROJECT_NAME "runxdg")
+set(PROJECT_PRETTY_NAME "Run XDG Application")
+set(PROJECT_MAJOR_VERSION "1.0")
+set(PROJECT_MINOR_VERSION "1")
+set(PROJECT_VERSION "${PROJECT_MAJOR_VERSION}.${PROJECT_MINOR_VERSION}")
+
+add_definitions(-DTARGET_APP_ID=${TARGET_APP_ID})
+add_definitions(-DRUNXDG_NAME=${RUNXDG_NAME})
+
+set(CMAKE_CXX_FLAGS "-Wall -fpermissive")
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GLIB REQUIRED glib-2.0)
+pkg_check_modules(GIO REQUIRED gio-2.0)
+pkg_check_modules(GMODULE REQUIRED gmodule-2.0)
+
+INCLUDE(FindPkgConfig)
+
+INCLUDE_DIRECTORIES(
+  include
+  ${GLIB_INCLUDE_DIRS}
+  ${GIO_INCLUDE_DIRS}
+  )
+
+LINK_DIRECTORIES(
+  ${GLIB_LIBRARY_DIRS}
+  ${GIO_LIBRARY_DIRS}
+  )
+
+SET(LIBRARIES
+  libwindowmanager.so
+  libhomescreen.so
+  libilmControl.so
+  libilmCommon.so
+  libafbwsc.so
+  libjson-c.so
+  libEGL.so
+  libGLESv2.so
+  libwayland-egl.so
+  libwayland-client.so
+  libpthread.so
+  ${GLIB_LIBRARIES}
+  ${GIO_LIBRARIES}
+)
+
+SET(SRC_FILES
+    src/runxdg.cpp
+)
+
+add_executable(${PROJECT_NAME} ${SRC_FILES})
+
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${LIBRARIES})
+
+add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+  COMMAND cp -rf ${CMAKE_CURRENT_SOURCE_DIR}/package ${PROJECT_BINARY_DIR})
+
+add_custom_target(widget
+
+  COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/package/simple-egl.wgt ${PROJECT_BINARY_DIR}/package/simple-egl
+
+  # override hvac to test runxdg
+  COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/package/hvac.wgt ${PROJECT_BINARY_DIR}/package/hvac
+
+  # override navigation to test runxdg
+  COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/package/navi.wgt ${PROJECT_BINARY_DIR}/package/navi
+)
+
+install (TARGETS ${PROJECT_NAME} DESTINATION bin)
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..5006c2d
--- /dev/null
@@ -0,0 +1,44 @@
+How to build with SDK
+
+1. make build directory
+   $ mkdir build
+
+2. do cmake
+   $ cd build
+   $ cmake ..
+
+   You could find binary executable "runxdg" is built.
+   "runxdg" should be installed into the directory
+   on the target. (e.g. /usr/bin or /usr/local/bin)
+
+3. Prepare configuration for RunXDG, 'runxdg.toml'
+   See 'package/{runxdg|navi|hvac}/runxdg.toml for detail.
+
+   'role', 'method', 'path' is mondatory.
+
+   Only "POSIX" as 'method' works, others not tested so far.
+
+   'role' should be the same which defined in layers.json of WindowManager.
+
+   e.g. followings are predefined role by default
+   "role": "MediaPlayer|Radio|Phone|Navigation|HVAC|Settings|Dashboard|POI|Mixer"
+
+3. Prepare config.xml for widget
+
+   <content> should be follow.
+     <content src="bin/runxdg" type="application/vnd.agl.native"/>
+
+   following <feature> is mandatory.
+     <feature name="urn:AGL:widget:required-api">
+       <param name="homescreen" value="ws" />
+       <param name="windowmanager" value="ws" />
+     </feature>
+
+4. Make widgets
+   $ make widget
+
+   The following wgt would be made.
+   - runxdg.wgt     XDG Launcher
+   - navi.wgt       for test, XDG Launcher installed as Navigation
+   - hvac.wgt       for test, XDG Lanncher installed as HVAC
+
diff --git a/include/cpptoml/LICENSE b/include/cpptoml/LICENSE
new file mode 100644 (file)
index 0000000..8802c4f
--- /dev/null
@@ -0,0 +1,18 @@
+Copyright (c) 2014 Chase Geigle
+
+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.
diff --git a/include/cpptoml/cpptoml.h b/include/cpptoml/cpptoml.h
new file mode 100644 (file)
index 0000000..95c3820
--- /dev/null
@@ -0,0 +1,3274 @@
+/**
+ * @file cpptoml.h
+ * @author Chase Geigle
+ * @date May 2013
+ */
+
+#ifndef _CPPTOML_H_
+#define _CPPTOML_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#if __cplusplus > 201103L
+#define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
+#elif defined(__clang__)
+#define CPPTOML_DEPRECATED(reason) __attribute__((deprecated(reason)))
+#elif defined(__GNUG__)
+#define CPPTOML_DEPRECATED(reason) __attribute__((deprecated))
+#elif defined(_MSC_VER)
+#if _MSC_VER < 1910
+#define CPPTOML_DEPRECATED(reason) __declspec(deprecated)
+#else
+#define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
+#endif
+#endif
+
+namespace cpptoml
+{
+class writer; // forward declaration
+class base;   // forward declaration
+#if defined(CPPTOML_USE_MAP)
+// a std::map will ensure that entries a sorted, albeit at a slight
+// performance penalty relative to the (default) unordered_map
+using string_to_base_map = std::map<std::string, std::shared_ptr<base>>;
+#else
+// by default an unordered_map is used for best performance as the
+// toml specification does not require entries to be sorted
+using string_to_base_map
+    = std::unordered_map<std::string, std::shared_ptr<base>>;
+#endif
+
+template <class T>
+class option
+{
+  public:
+    option() : empty_{true}
+    {
+        // nothing
+    }
+
+    option(T value) : empty_{false}, value_{std::move(value)}
+    {
+        // nothing
+    }
+
+    explicit operator bool() const
+    {
+        return !empty_;
+    }
+
+    const T& operator*() const
+    {
+        return value_;
+    }
+
+    const T* operator->() const
+    {
+        return &value_;
+    }
+
+    const T& value_or(const T& alternative) const
+    {
+        if (!empty_)
+            return value_;
+        return alternative;
+    }
+
+  private:
+    bool empty_;
+    T value_;
+};
+
+struct local_date
+{
+    int year = 0;
+    int month = 0;
+    int day = 0;
+};
+
+struct local_time
+{
+    int hour = 0;
+    int minute = 0;
+    int second = 0;
+    int microsecond = 0;
+};
+
+struct zone_offset
+{
+    int hour_offset = 0;
+    int minute_offset = 0;
+};
+
+struct local_datetime : local_date, local_time
+{
+};
+
+struct offset_datetime : local_datetime, zone_offset
+{
+    static inline struct offset_datetime from_zoned(const struct tm& t)
+    {
+        offset_datetime dt;
+        dt.year = t.tm_year + 1900;
+        dt.month = t.tm_mon + 1;
+        dt.day = t.tm_mday;
+        dt.hour = t.tm_hour;
+        dt.minute = t.tm_min;
+        dt.second = t.tm_sec;
+
+        char buf[16];
+        strftime(buf, 16, "%z", &t);
+
+        int offset = std::stoi(buf);
+        dt.hour_offset = offset / 100;
+        dt.minute_offset = offset % 100;
+        return dt;
+    }
+
+    CPPTOML_DEPRECATED("from_local has been renamed to from_zoned")
+    static inline struct offset_datetime from_local(const struct tm& t)
+    {
+        return from_zoned(t);
+    }
+
+    static inline struct offset_datetime from_utc(const struct tm& t)
+    {
+        offset_datetime dt;
+        dt.year = t.tm_year + 1900;
+        dt.month = t.tm_mon + 1;
+        dt.day = t.tm_mday;
+        dt.hour = t.tm_hour;
+        dt.minute = t.tm_min;
+        dt.second = t.tm_sec;
+        return dt;
+    }
+};
+
+CPPTOML_DEPRECATED("datetime has been renamed to offset_datetime")
+typedef offset_datetime datetime;
+
+class fill_guard
+{
+  public:
+    fill_guard(std::ostream& os) : os_(os), fill_{os.fill()}
+    {
+        // nothing
+    }
+
+    ~fill_guard()
+    {
+        os_.fill(fill_);
+    }
+
+  private:
+    std::ostream& os_;
+    std::ostream::char_type fill_;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const local_date& dt)
+{
+    fill_guard g{os};
+    os.fill('0');
+
+    using std::setw;
+    os << setw(4) << dt.year << "-" << setw(2) << dt.month << "-" << setw(2)
+       << dt.day;
+
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const local_time& ltime)
+{
+    fill_guard g{os};
+    os.fill('0');
+
+    using std::setw;
+    os << setw(2) << ltime.hour << ":" << setw(2) << ltime.minute << ":"
+       << setw(2) << ltime.second;
+
+    if (ltime.microsecond > 0)
+    {
+        os << ".";
+        int power = 100000;
+        for (int curr_us = ltime.microsecond; curr_us; power /= 10)
+        {
+            auto num = curr_us / power;
+            os << num;
+            curr_us -= num * power;
+        }
+    }
+
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const zone_offset& zo)
+{
+    fill_guard g{os};
+    os.fill('0');
+
+    using std::setw;
+
+    if (zo.hour_offset != 0 || zo.minute_offset != 0)
+    {
+        if (zo.hour_offset > 0)
+        {
+            os << "+";
+        }
+        else
+        {
+            os << "-";
+        }
+        os << setw(2) << std::abs(zo.hour_offset) << ":" << setw(2)
+           << std::abs(zo.minute_offset);
+    }
+    else
+    {
+        os << "Z";
+    }
+
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const local_datetime& dt)
+{
+    return os << static_cast<const local_date&>(dt) << "T"
+              << static_cast<const local_time&>(dt);
+}
+
+inline std::ostream& operator<<(std::ostream& os, const offset_datetime& dt)
+{
+    return os << static_cast<const local_datetime&>(dt)
+              << static_cast<const zone_offset&>(dt);
+}
+
+template <class T, class... Ts>
+struct is_one_of;
+
+template <class T, class V>
+struct is_one_of<T, V> : std::is_same<T, V>
+{
+};
+
+template <class T, class V, class... Ts>
+struct is_one_of<T, V, Ts...>
+{
+    const static bool value
+        = std::is_same<T, V>::value || is_one_of<T, Ts...>::value;
+};
+
+template <class T>
+class value;
+
+template <class T>
+struct valid_value
+    : is_one_of<T, std::string, int64_t, double, bool, local_date, local_time,
+                local_datetime, offset_datetime>
+{
+};
+
+template <class T, class Enable = void>
+struct value_traits;
+
+template <class T>
+struct valid_value_or_string_convertible
+{
+
+    const static bool value = valid_value<typename std::decay<T>::type>::value
+                              || std::is_convertible<T, std::string>::value;
+};
+
+template <class T>
+struct value_traits<T, typename std::
+                           enable_if<valid_value_or_string_convertible<T>::
+                                         value>::type>
+{
+    using value_type = typename std::
+        conditional<valid_value<typename std::decay<T>::type>::value,
+                    typename std::decay<T>::type, std::string>::type;
+
+    using type = value<value_type>;
+
+    static value_type construct(T&& val)
+    {
+        return value_type(val);
+    }
+};
+
+template <class T>
+struct value_traits<T,
+                    typename std::
+                        enable_if<!valid_value_or_string_convertible<T>::value
+                                  && std::is_floating_point<
+                                         typename std::decay<T>::type>::value>::
+                            type>
+{
+    using value_type = typename std::decay<T>::type;
+
+    using type = value<double>;
+
+    static value_type construct(T&& val)
+    {
+        return value_type(val);
+    }
+};
+
+template <class T>
+struct value_traits<T,
+                    typename std::
+                        enable_if<!valid_value_or_string_convertible<T>::value
+                                  && std::is_signed<typename std::decay<T>::
+                                                        type>::value>::type>
+{
+    using value_type = int64_t;
+
+    using type = value<int64_t>;
+
+    static value_type construct(T&& val)
+    {
+        if (val < std::numeric_limits<int64_t>::min())
+            throw std::underflow_error{"constructed value cannot be "
+                                       "represented by a 64-bit signed "
+                                       "integer"};
+
+        if (val > std::numeric_limits<int64_t>::max())
+            throw std::overflow_error{"constructed value cannot be represented "
+                                      "by a 64-bit signed integer"};
+
+        return static_cast<int64_t>(val);
+    }
+};
+
+template <class T>
+struct value_traits<T,
+                    typename std::
+                        enable_if<!valid_value_or_string_convertible<T>::value
+                                  && std::is_unsigned<typename std::decay<T>::
+                                                          type>::value>::type>
+{
+    using value_type = int64_t;
+
+    using type = value<int64_t>;
+
+    static value_type construct(T&& val)
+    {
+        if (val > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
+            throw std::overflow_error{"constructed value cannot be represented "
+                                      "by a 64-bit signed integer"};
+
+        return static_cast<int64_t>(val);
+    }
+};
+
+class array;
+class table;
+class table_array;
+
+template <class T>
+struct array_of_trait
+{
+    using return_type = option<std::vector<T>>;
+};
+
+template <>
+struct array_of_trait<array>
+{
+    using return_type = option<std::vector<std::shared_ptr<array>>>;
+};
+
+template <class T>
+inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val);
+inline std::shared_ptr<array> make_array();
+template <class T>
+inline std::shared_ptr<T> make_element();
+inline std::shared_ptr<table> make_table();
+inline std::shared_ptr<table_array> make_table_array();
+
+/**
+ * A generic base TOML value used for type erasure.
+ */
+class base : public std::enable_shared_from_this<base>
+{
+  public:
+    virtual ~base() = default;
+
+    virtual std::shared_ptr<base> clone() const = 0;
+
+    /**
+     * Determines if the given TOML element is a value.
+     */
+    virtual bool is_value() const
+    {
+        return false;
+    }
+
+    /**
+     * Determines if the given TOML element is a table.
+     */
+    virtual bool is_table() const
+    {
+        return false;
+    }
+
+    /**
+     * Converts the TOML element into a table.
+     */
+    std::shared_ptr<table> as_table()
+    {
+        if (is_table())
+            return std::static_pointer_cast<table>(shared_from_this());
+        return nullptr;
+    }
+    /**
+     * Determines if the TOML element is an array of "leaf" elements.
+     */
+    virtual bool is_array() const
+    {
+        return false;
+    }
+
+    /**
+     * Converts the TOML element to an array.
+     */
+    std::shared_ptr<array> as_array()
+    {
+        if (is_array())
+            return std::static_pointer_cast<array>(shared_from_this());
+        return nullptr;
+    }
+
+    /**
+     * Determines if the given TOML element is an array of tables.
+     */
+    virtual bool is_table_array() const
+    {
+        return false;
+    }
+
+    /**
+     * Converts the TOML element into a table array.
+     */
+    std::shared_ptr<table_array> as_table_array()
+    {
+        if (is_table_array())
+            return std::static_pointer_cast<table_array>(shared_from_this());
+        return nullptr;
+    }
+
+    /**
+     * Attempts to coerce the TOML element into a concrete TOML value
+     * of type T.
+     */
+    template <class T>
+    std::shared_ptr<value<T>> as();
+
+    template <class T>
+    std::shared_ptr<const value<T>> as() const;
+
+    template <class Visitor, class... Args>
+    void accept(Visitor&& visitor, Args&&... args) const;
+
+  protected:
+    base()
+    {
+        // nothing
+    }
+};
+
+/**
+ * A concrete TOML value representing the "leaves" of the "tree".
+ */
+template <class T>
+class value : public base
+{
+    struct make_shared_enabler
+    {
+        // nothing; this is a private key accessible only to friends
+    };
+
+    template <class U>
+    friend std::shared_ptr<typename value_traits<U>::type>
+    cpptoml::make_value(U&& val);
+
+  public:
+    static_assert(valid_value<T>::value, "invalid value type");
+
+    std::shared_ptr<base> clone() const override;
+
+    value(const make_shared_enabler&, const T& val) : value(val)
+    {
+        // nothing; note that users cannot actually invoke this function
+        // because they lack access to the make_shared_enabler.
+    }
+
+    bool is_value() const override
+    {
+        return true;
+    }
+
+    /**
+     * Gets the data associated with this value.
+     */
+    T& get()
+    {
+        return data_;
+    }
+
+    /**
+     * Gets the data associated with this value. Const version.
+     */
+    const T& get() const
+    {
+        return data_;
+    }
+
+  private:
+    T data_;
+
+    /**
+     * Constructs a value from the given data.
+     */
+    value(const T& val) : data_(val)
+    {
+    }
+
+    value(const value& val) = delete;
+    value& operator=(const value& val) = delete;
+};
+
+template <class T>
+std::shared_ptr<typename value_traits<T>::type> make_value(T&& val)
+{
+    using value_type = typename value_traits<T>::type;
+    using enabler = typename value_type::make_shared_enabler;
+    return std::make_shared<value_type>(
+        enabler{}, value_traits<T>::construct(std::forward<T>(val)));
+}
+
+template <class T>
+inline std::shared_ptr<value<T>> base::as()
+{
+    return std::dynamic_pointer_cast<value<T>>(shared_from_this());
+}
+
+// special case value<double> to allow getting an integer parameter as a
+// double value
+template <>
+inline std::shared_ptr<value<double>> base::as()
+{
+    if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this()))
+        return v;
+
+    if (auto v = std::dynamic_pointer_cast<value<int64_t>>(shared_from_this()))
+        return make_value<double>(static_cast<double>(v->get()));
+
+    return nullptr;
+}
+
+template <class T>
+inline std::shared_ptr<const value<T>> base::as() const
+{
+    return std::dynamic_pointer_cast<const value<T>>(shared_from_this());
+}
+
+// special case value<double> to allow getting an integer parameter as a
+// double value
+template <>
+inline std::shared_ptr<const value<double>> base::as() const
+{
+    if (auto v
+        = std::dynamic_pointer_cast<const value<double>>(shared_from_this()))
+        return v;
+
+    if (auto v = as<int64_t>())
+    {
+        // the below has to be a non-const value<double> due to a bug in
+        // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
+        return make_value<double>(static_cast<double>(v->get()));
+    }
+
+    return nullptr;
+}
+
+/**
+ * Exception class for array insertion errors.
+ */
+class array_exception : public std::runtime_error
+{
+  public:
+    array_exception(const std::string& err) : std::runtime_error{err}
+    {
+    }
+};
+
+class array : public base
+{
+  public:
+    friend std::shared_ptr<array> make_array();
+
+    std::shared_ptr<base> clone() const override;
+
+    virtual bool is_array() const override
+    {
+        return true;
+    }
+
+    using size_type = std::size_t;
+
+    /**
+     * arrays can be iterated over
+     */
+    using iterator = std::vector<std::shared_ptr<base>>::iterator;
+
+    /**
+     * arrays can be iterated over.  Const version.
+     */
+    using const_iterator = std::vector<std::shared_ptr<base>>::const_iterator;
+
+    iterator begin()
+    {
+        return values_.begin();
+    }
+
+    const_iterator begin() const
+    {
+        return values_.begin();
+    }
+
+    iterator end()
+    {
+        return values_.end();
+    }
+
+    const_iterator end() const
+    {
+        return values_.end();
+    }
+
+    /**
+     * Obtains the array (vector) of base values.
+     */
+    std::vector<std::shared_ptr<base>>& get()
+    {
+        return values_;
+    }
+
+    /**
+     * Obtains the array (vector) of base values. Const version.
+     */
+    const std::vector<std::shared_ptr<base>>& get() const
+    {
+        return values_;
+    }
+
+    std::shared_ptr<base> at(size_t idx) const
+    {
+        return values_.at(idx);
+    }
+
+    /**
+     * Obtains an array of value<T>s. Note that elements may be
+     * nullptr if they cannot be converted to a value<T>.
+     */
+    template <class T>
+    std::vector<std::shared_ptr<value<T>>> array_of() const
+    {
+        std::vector<std::shared_ptr<value<T>>> result(values_.size());
+
+        std::transform(values_.begin(), values_.end(), result.begin(),
+                       [&](std::shared_ptr<base> v) { return v->as<T>(); });
+
+        return result;
+    }
+
+    /**
+     * Obtains a option<vector<T>>. The option will be empty if the array
+     * contains values that are not of type T.
+     */
+    template <class T>
+    inline typename array_of_trait<T>::return_type get_array_of() const
+    {
+        std::vector<T> result;
+        result.reserve(values_.size());
+
+        for (const auto& val : values_)
+        {
+            if (auto v = val->as<T>())
+                result.push_back(v->get());
+            else
+                return {};
+        }
+
+        return {std::move(result)};
+    }
+
+    /**
+     * Obtains an array of arrays. Note that elements may be nullptr
+     * if they cannot be converted to a array.
+     */
+    std::vector<std::shared_ptr<array>> nested_array() const
+    {
+        std::vector<std::shared_ptr<array>> result(values_.size());
+
+        std::transform(values_.begin(), values_.end(), result.begin(),
+                       [&](std::shared_ptr<base> v) -> std::shared_ptr<array> {
+                           if (v->is_array())
+                               return std::static_pointer_cast<array>(v);
+                           return std::shared_ptr<array>{};
+                       });
+
+        return result;
+    }
+
+    /**
+     * Add a value to the end of the array
+     */
+    template <class T>
+    void push_back(const std::shared_ptr<value<T>>& val)
+    {
+        if (values_.empty() || values_[0]->as<T>())
+        {
+            values_.push_back(val);
+        }
+        else
+        {
+            throw array_exception{"Arrays must be homogenous."};
+        }
+    }
+
+    /**
+     * Add an array to the end of the array
+     */
+    void push_back(const std::shared_ptr<array>& val)
+    {
+        if (values_.empty() || values_[0]->is_array())
+        {
+            values_.push_back(val);
+        }
+        else
+        {
+            throw array_exception{"Arrays must be homogenous."};
+        }
+    }
+
+    /**
+     * Convenience function for adding a simple element to the end
+     * of the array.
+     */
+    template <class T>
+    void push_back(T&& val, typename value_traits<T>::type* = 0)
+    {
+        push_back(make_value(std::forward<T>(val)));
+    }
+
+    /**
+     * Insert a value into the array
+     */
+    template <class T>
+    iterator insert(iterator position, const std::shared_ptr<value<T>>& value)
+    {
+        if (values_.empty() || values_[0]->as<T>())
+        {
+            return values_.insert(position, value);
+        }
+        else
+        {
+            throw array_exception{"Arrays must be homogenous."};
+        }
+    }
+
+    /**
+     * Insert an array into the array
+     */
+    iterator insert(iterator position, const std::shared_ptr<array>& value)
+    {
+        if (values_.empty() || values_[0]->is_array())
+        {
+            return values_.insert(position, value);
+        }
+        else
+        {
+            throw array_exception{"Arrays must be homogenous."};
+        }
+    }
+
+    /**
+     * Convenience function for inserting a simple element in the array
+     */
+    template <class T>
+    iterator insert(iterator position, T&& val,
+                    typename value_traits<T>::type* = 0)
+    {
+        return insert(position, make_value(std::forward<T>(val)));
+    }
+
+    /**
+     * Erase an element from the array
+     */
+    iterator erase(iterator position)
+    {
+        return values_.erase(position);
+    }
+
+    /**
+     * Clear the array
+     */
+    void clear()
+    {
+        values_.clear();
+    }
+
+    /**
+     * Reserve space for n values.
+     */
+    void reserve(size_type n)
+    {
+        values_.reserve(n);
+    }
+
+  private:
+    array() = default;
+
+    template <class InputIterator>
+    array(InputIterator begin, InputIterator end) : values_{begin, end}
+    {
+        // nothing
+    }
+
+    array(const array& obj) = delete;
+    array& operator=(const array& obj) = delete;
+
+    std::vector<std::shared_ptr<base>> values_;
+};
+
+inline std::shared_ptr<array> make_array()
+{
+    struct make_shared_enabler : public array
+    {
+        make_shared_enabler()
+        {
+            // nothing
+        }
+    };
+
+    return std::make_shared<make_shared_enabler>();
+}
+
+template <>
+inline std::shared_ptr<array> make_element<array>()
+{
+    return make_array();
+}
+
+/**
+ * Obtains a option<vector<T>>. The option will be empty if the array
+ * contains values that are not of type T.
+ */
+template <>
+inline typename array_of_trait<array>::return_type
+array::get_array_of<array>() const
+{
+    std::vector<std::shared_ptr<array>> result;
+    result.reserve(values_.size());
+
+    for (const auto& val : values_)
+    {
+        if (auto v = val->as_array())
+            result.push_back(v);
+        else
+            return {};
+    }
+
+    return {std::move(result)};
+}
+
+class table;
+
+class table_array : public base
+{
+    friend class table;
+    friend std::shared_ptr<table_array> make_table_array();
+
+  public:
+    std::shared_ptr<base> clone() const override;
+
+    using size_type = std::size_t;
+
+    /**
+     * arrays can be iterated over
+     */
+    using iterator = std::vector<std::shared_ptr<table>>::iterator;
+
+    /**
+     * arrays can be iterated over.  Const version.
+     */
+    using const_iterator = std::vector<std::shared_ptr<table>>::const_iterator;
+
+    iterator begin()
+    {
+        return array_.begin();
+    }
+
+    const_iterator begin() const
+    {
+        return array_.begin();
+    }
+
+    iterator end()
+    {
+        return array_.end();
+    }
+
+    const_iterator end() const
+    {
+        return array_.end();
+    }
+
+    virtual bool is_table_array() const override
+    {
+        return true;
+    }
+
+    std::vector<std::shared_ptr<table>>& get()
+    {
+        return array_;
+    }
+
+    const std::vector<std::shared_ptr<table>>& get() const
+    {
+        return array_;
+    }
+
+    /**
+     * Add a table to the end of the array
+     */
+    void push_back(const std::shared_ptr<table>& val)
+    {
+        array_.push_back(val);
+    }
+
+    /**
+     * Insert a table into the array
+     */
+    iterator insert(iterator position, const std::shared_ptr<table>& value)
+    {
+        return array_.insert(position, value);
+    }
+
+    /**
+     * Erase an element from the array
+     */
+    iterator erase(iterator position)
+    {
+        return array_.erase(position);
+    }
+
+    /**
+     * Clear the array
+     */
+    void clear()
+    {
+        array_.clear();
+    }
+
+    /**
+     * Reserve space for n tables.
+     */
+    void reserve(size_type n)
+    {
+        array_.reserve(n);
+    }
+
+  private:
+    table_array()
+    {
+        // nothing
+    }
+
+    table_array(const table_array& obj) = delete;
+    table_array& operator=(const table_array& rhs) = delete;
+
+    std::vector<std::shared_ptr<table>> array_;
+};
+
+inline std::shared_ptr<table_array> make_table_array()
+{
+    struct make_shared_enabler : public table_array
+    {
+        make_shared_enabler()
+        {
+            // nothing
+        }
+    };
+
+    return std::make_shared<make_shared_enabler>();
+}
+
+template <>
+inline std::shared_ptr<table_array> make_element<table_array>()
+{
+    return make_table_array();
+}
+
+// The below are overloads for fetching specific value types out of a value
+// where special casting behavior (like bounds checking) is desired
+
+template <class T>
+typename std::enable_if<!std::is_floating_point<T>::value
+                            && std::is_signed<T>::value,
+                        option<T>>::type
+get_impl(const std::shared_ptr<base>& elem)
+{
+    if (auto v = elem->as<int64_t>())
+    {
+        if (v->get() < std::numeric_limits<T>::min())
+            throw std::underflow_error{
+                "T cannot represent the value requested in get"};
+
+        if (v->get() > std::numeric_limits<T>::max())
+            throw std::overflow_error{
+                "T cannot represent the value requested in get"};
+
+        return {static_cast<T>(v->get())};
+    }
+    else
+    {
+        return {};
+    }
+}
+
+template <class T>
+typename std::enable_if<!std::is_same<T, bool>::value
+                            && std::is_unsigned<T>::value,
+                        option<T>>::type
+get_impl(const std::shared_ptr<base>& elem)
+{
+    if (auto v = elem->as<int64_t>())
+    {
+        if (v->get() < 0)
+            throw std::underflow_error{"T cannot store negative value in get"};
+
+        if (static_cast<uint64_t>(v->get()) > std::numeric_limits<T>::max())
+            throw std::overflow_error{
+                "T cannot represent the value requested in get"};
+
+        return {static_cast<T>(v->get())};
+    }
+    else
+    {
+        return {};
+    }
+}
+
+template <class T>
+typename std::enable_if<!std::is_integral<T>::value
+                            || std::is_same<T, bool>::value,
+                        option<T>>::type
+get_impl(const std::shared_ptr<base>& elem)
+{
+    if (auto v = elem->as<T>())
+    {
+        return {v->get()};
+    }
+    else
+    {
+        return {};
+    }
+}
+
+/**
+ * Represents a TOML keytable.
+ */
+class table : public base
+{
+  public:
+    friend class table_array;
+    friend std::shared_ptr<table> make_table();
+
+    std::shared_ptr<base> clone() const override;
+
+    /**
+     * tables can be iterated over.
+     */
+    using iterator = string_to_base_map::iterator;
+
+    /**
+     * tables can be iterated over. Const version.
+     */
+    using const_iterator = string_to_base_map::const_iterator;
+
+    iterator begin()
+    {
+        return map_.begin();
+    }
+
+    const_iterator begin() const
+    {
+        return map_.begin();
+    }
+
+    iterator end()
+    {
+        return map_.end();
+    }
+
+    const_iterator end() const
+    {
+        return map_.end();
+    }
+
+    bool is_table() const override
+    {
+        return true;
+    }
+
+    bool empty() const
+    {
+        return map_.empty();
+    }
+
+    /**
+     * Determines if this key table contains the given key.
+     */
+    bool contains(const std::string& key) const
+    {
+        return map_.find(key) != map_.end();
+    }
+
+    /**
+     * Determines if this key table contains the given key. Will
+     * resolve "qualified keys". Qualified keys are the full access
+     * path separated with dots like "grandparent.parent.child".
+     */
+    bool contains_qualified(const std::string& key) const
+    {
+        return resolve_qualified(key);
+    }
+
+    /**
+     * Obtains the base for a given key.
+     * @throw std::out_of_range if the key does not exist
+     */
+    std::shared_ptr<base> get(const std::string& key) const
+    {
+        return map_.at(key);
+    }
+
+    /**
+     * Obtains the base for a given key. Will resolve "qualified
+     * keys". Qualified keys are the full access path separated with
+     * dots like "grandparent.parent.child".
+     *
+     * @throw std::out_of_range if the key does not exist
+     */
+    std::shared_ptr<base> get_qualified(const std::string& key) const
+    {
+        std::shared_ptr<base> p;
+        resolve_qualified(key, &p);
+        return p;
+    }
+
+    /**
+     * Obtains a table for a given key, if possible.
+     */
+    std::shared_ptr<table> get_table(const std::string& key) const
+    {
+        if (contains(key) && get(key)->is_table())
+            return std::static_pointer_cast<table>(get(key));
+        return nullptr;
+    }
+
+    /**
+     * Obtains a table for a given key, if possible. Will resolve
+     * "qualified keys".
+     */
+    std::shared_ptr<table> get_table_qualified(const std::string& key) const
+    {
+        if (contains_qualified(key) && get_qualified(key)->is_table())
+            return std::static_pointer_cast<table>(get_qualified(key));
+        return nullptr;
+    }
+
+    /**
+     * Obtains an array for a given key.
+     */
+    std::shared_ptr<array> get_array(const std::string& key) const
+    {
+        if (!contains(key))
+            return nullptr;
+        return get(key)->as_array();
+    }
+
+    /**
+     * Obtains an array for a given key. Will resolve "qualified keys".
+     */
+    std::shared_ptr<array> get_array_qualified(const std::string& key) const
+    {
+        if (!contains_qualified(key))
+            return nullptr;
+        return get_qualified(key)->as_array();
+    }
+
+    /**
+     * Obtains a table_array for a given key, if possible.
+     */
+    std::shared_ptr<table_array> get_table_array(const std::string& key) const
+    {
+        if (!contains(key))
+            return nullptr;
+        return get(key)->as_table_array();
+    }
+
+    /**
+     * Obtains a table_array for a given key, if possible. Will resolve
+     * "qualified keys".
+     */
+    std::shared_ptr<table_array>
+    get_table_array_qualified(const std::string& key) const
+    {
+        if (!contains_qualified(key))
+            return nullptr;
+        return get_qualified(key)->as_table_array();
+    }
+
+    /**
+     * Helper function that attempts to get a value corresponding
+     * to the template parameter from a given key.
+     */
+    template <class T>
+    option<T> get_as(const std::string& key) const
+    {
+        try
+        {
+            return get_impl<T>(get(key));
+        }
+        catch (const std::out_of_range&)
+        {
+            return {};
+        }
+    }
+
+    /**
+     * Helper function that attempts to get a value corresponding
+     * to the template parameter from a given key. Will resolve "qualified
+     * keys".
+     */
+    template <class T>
+    option<T> get_qualified_as(const std::string& key) const
+    {
+        try
+        {
+            return get_impl<T>(get_qualified(key));
+        }
+        catch (const std::out_of_range&)
+        {
+            return {};
+        }
+    }
+
+    /**
+     * Helper function that attempts to get an array of values of a given
+     * type corresponding to the template parameter for a given key.
+     *
+     * If the key doesn't exist, doesn't exist as an array type, or one or
+     * more keys inside the array type are not of type T, an empty option
+     * is returned. Otherwise, an option containing a vector of the values
+     * is returned.
+     */
+    template <class T>
+    inline typename array_of_trait<T>::return_type
+    get_array_of(const std::string& key) const
+    {
+        if (auto v = get_array(key))
+        {
+            std::vector<T> result;
+            result.reserve(v->get().size());
+
+            for (const auto& b : v->get())
+            {
+                if (auto val = b->as<T>())
+                    result.push_back(val->get());
+                else
+                    return {};
+            }
+            return {std::move(result)};
+        }
+
+        return {};
+    }
+
+    /**
+     * Helper function that attempts to get an array of values of a given
+     * type corresponding to the template parameter for a given key. Will
+     * resolve "qualified keys".
+     *
+     * If the key doesn't exist, doesn't exist as an array type, or one or
+     * more keys inside the array type are not of type T, an empty option
+     * is returned. Otherwise, an option containing a vector of the values
+     * is returned.
+     */
+    template <class T>
+    inline typename array_of_trait<T>::return_type
+    get_qualified_array_of(const std::string& key) const
+    {
+        if (auto v = get_array_qualified(key))
+        {
+            std::vector<T> result;
+            result.reserve(v->get().size());
+
+            for (const auto& b : v->get())
+            {
+                if (auto val = b->as<T>())
+                    result.push_back(val->get());
+                else
+                    return {};
+            }
+            return {std::move(result)};
+        }
+
+        return {};
+    }
+
+    /**
+     * Adds an element to the keytable.
+     */
+    void insert(const std::string& key, const std::shared_ptr<base>& value)
+    {
+        map_[key] = value;
+    }
+
+    /**
+     * Convenience shorthand for adding a simple element to the
+     * keytable.
+     */
+    template <class T>
+    void insert(const std::string& key, T&& val,
+                typename value_traits<T>::type* = 0)
+    {
+        insert(key, make_value(std::forward<T>(val)));
+    }
+
+    /**
+     * Removes an element from the table.
+     */
+    void erase(const std::string& key)
+    {
+        map_.erase(key);
+    }
+
+  private:
+    table()
+    {
+        // nothing
+    }
+
+    table(const table& obj) = delete;
+    table& operator=(const table& rhs) = delete;
+
+    std::vector<std::string> split(const std::string& value,
+                                   char separator) const
+    {
+        std::vector<std::string> result;
+        std::string::size_type p = 0;
+        std::string::size_type q;
+        while ((q = value.find(separator, p)) != std::string::npos)
+        {
+            result.emplace_back(value, p, q - p);
+            p = q + 1;
+        }
+        result.emplace_back(value, p);
+        return result;
+    }
+
+    // If output parameter p is specified, fill it with the pointer to the
+    // specified entry and throw std::out_of_range if it couldn't be found.
+    //
+    // Otherwise, just return true if the entry could be found or false
+    // otherwise and do not throw.
+    bool resolve_qualified(const std::string& key,
+                           std::shared_ptr<base>* p = nullptr) const
+    {
+        auto parts = split(key, '.');
+        auto last_key = parts.back();
+        parts.pop_back();
+
+        auto table = this;
+        for (const auto& part : parts)
+        {
+            table = table->get_table(part).get();
+            if (!table)
+            {
+                if (!p)
+                    return false;
+
+                throw std::out_of_range{key + " is not a valid key"};
+            }
+        }
+
+        if (!p)
+            return table->map_.count(last_key) != 0;
+
+        *p = table->map_.at(last_key);
+        return true;
+    }
+
+    string_to_base_map map_;
+};
+
+/**
+ * Helper function that attempts to get an array of arrays for a given
+ * key.
+ *
+ * If the key doesn't exist, doesn't exist as an array type, or one or
+ * more keys inside the array type are not of type T, an empty option
+ * is returned. Otherwise, an option containing a vector of the values
+ * is returned.
+ */
+template <>
+inline typename array_of_trait<array>::return_type
+table::get_array_of<array>(const std::string& key) const
+{
+    if (auto v = get_array(key))
+    {
+        std::vector<std::shared_ptr<array>> result;
+        result.reserve(v->get().size());
+
+        for (const auto& b : v->get())
+        {
+            if (auto val = b->as_array())
+                result.push_back(val);
+            else
+                return {};
+        }
+
+        return {std::move(result)};
+    }
+
+    return {};
+}
+
+/**
+ * Helper function that attempts to get an array of arrays for a given
+ * key. Will resolve "qualified keys".
+ *
+ * If the key doesn't exist, doesn't exist as an array type, or one or
+ * more keys inside the array type are not of type T, an empty option
+ * is returned. Otherwise, an option containing a vector of the values
+ * is returned.
+ */
+template <>
+inline typename array_of_trait<array>::return_type
+table::get_qualified_array_of<array>(const std::string& key) const
+{
+    if (auto v = get_array_qualified(key))
+    {
+        std::vector<std::shared_ptr<array>> result;
+        result.reserve(v->get().size());
+
+        for (const auto& b : v->get())
+        {
+            if (auto val = b->as_array())
+                result.push_back(val);
+            else
+                return {};
+        }
+
+        return {std::move(result)};
+    }
+
+    return {};
+}
+
+std::shared_ptr<table> make_table()
+{
+    struct make_shared_enabler : public table
+    {
+        make_shared_enabler()
+        {
+            // nothing
+        }
+    };
+
+    return std::make_shared<make_shared_enabler>();
+}
+
+template <>
+inline std::shared_ptr<table> make_element<table>()
+{
+    return make_table();
+}
+
+template <class T>
+std::shared_ptr<base> value<T>::clone() const
+{
+    return make_value(data_);
+}
+
+inline std::shared_ptr<base> array::clone() const
+{
+    auto result = make_array();
+    result->reserve(values_.size());
+    for (const auto& ptr : values_)
+        result->values_.push_back(ptr->clone());
+    return result;
+}
+
+inline std::shared_ptr<base> table_array::clone() const
+{
+    auto result = make_table_array();
+    result->reserve(array_.size());
+    for (const auto& ptr : array_)
+        result->array_.push_back(ptr->clone()->as_table());
+    return result;
+}
+
+inline std::shared_ptr<base> table::clone() const
+{
+    auto result = make_table();
+    for (const auto& pr : map_)
+        result->insert(pr.first, pr.second->clone());
+    return result;
+}
+
+/**
+ * Exception class for all TOML parsing errors.
+ */
+class parse_exception : public std::runtime_error
+{
+  public:
+    parse_exception(const std::string& err) : std::runtime_error{err}
+    {
+    }
+
+    parse_exception(const std::string& err, std::size_t line_number)
+        : std::runtime_error{err + " at line " + std::to_string(line_number)}
+    {
+    }
+};
+
+inline bool is_number(char c)
+{
+    return c >= '0' && c <= '9';
+}
+
+/**
+ * Helper object for consuming expected characters.
+ */
+template <class OnError>
+class consumer
+{
+  public:
+    consumer(std::string::iterator& it, const std::string::iterator& end,
+             OnError&& on_error)
+        : it_(it), end_(end), on_error_(std::forward<OnError>(on_error))
+    {
+        // nothing
+    }
+
+    void operator()(char c)
+    {
+        if (it_ == end_ || *it_ != c)
+            on_error_();
+        ++it_;
+    }
+
+    template <std::size_t N>
+    void operator()(const char (&str)[N])
+    {
+        std::for_each(std::begin(str), std::end(str) - 1,
+                      [&](char c) { (*this)(c); });
+    }
+
+    int eat_digits(int len)
+    {
+        int val = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            if (!is_number(*it_) || it_ == end_)
+                on_error_();
+            val = 10 * val + (*it_++ - '0');
+        }
+        return val;
+    }
+
+    void error()
+    {
+        on_error_();
+    }
+
+  private:
+    std::string::iterator& it_;
+    const std::string::iterator& end_;
+    OnError on_error_;
+};
+
+template <class OnError>
+consumer<OnError> make_consumer(std::string::iterator& it,
+                                const std::string::iterator& end,
+                                OnError&& on_error)
+{
+    return consumer<OnError>(it, end, std::forward<OnError>(on_error));
+}
+
+// replacement for std::getline to handle incorrectly line-ended files
+// https://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
+namespace detail
+{
+inline std::istream& getline(std::istream& input, std::string& line)
+{
+    line.clear();
+
+    std::istream::sentry sentry{input, true};
+    auto sb = input.rdbuf();
+
+    while (true)
+    {
+        auto c = sb->sbumpc();
+        if (c == '\r')
+        {
+            if (sb->sgetc() == '\n')
+                c = sb->sbumpc();
+        }
+
+        if (c == '\n')
+            return input;
+
+        if (c == std::istream::traits_type::eof())
+        {
+            if (line.empty())
+                input.setstate(std::ios::eofbit);
+            return input;
+        }
+
+        line.push_back(static_cast<char>(c));
+    }
+}
+}
+
+/**
+ * The parser class.
+ */
+class parser
+{
+  public:
+    /**
+     * Parsers are constructed from streams.
+     */
+    parser(std::istream& stream) : input_(stream)
+    {
+        // nothing
+    }
+
+    parser& operator=(const parser& parser) = delete;
+
+    /**
+     * Parses the stream this parser was created on until EOF.
+     * @throw parse_exception if there are errors in parsing
+     */
+    std::shared_ptr<table> parse()
+    {
+        std::shared_ptr<table> root = make_table();
+
+        table* curr_table = root.get();
+
+        while (detail::getline(input_, line_))
+        {
+            line_number_++;
+            auto it = line_.begin();
+            auto end = line_.end();
+            consume_whitespace(it, end);
+            if (it == end || *it == '#')
+                continue;
+            if (*it == '[')
+            {
+                curr_table = root.get();
+                parse_table(it, end, curr_table);
+            }
+            else
+            {
+                parse_key_value(it, end, curr_table);
+                consume_whitespace(it, end);
+                eol_or_comment(it, end);
+            }
+        }
+        return root;
+    }
+
+  private:
+#if defined _MSC_VER
+    __declspec(noreturn)
+#elif defined __GNUC__
+    __attribute__((noreturn))
+#endif
+        void throw_parse_exception(const std::string& err)
+    {
+        throw parse_exception{err, line_number_};
+    }
+
+    void parse_table(std::string::iterator& it,
+                     const std::string::iterator& end, table*& curr_table)
+    {
+        // remove the beginning keytable marker
+        ++it;
+        if (it == end)
+            throw_parse_exception("Unexpected end of table");
+        if (*it == '[')
+            parse_table_array(it, end, curr_table);
+        else
+            parse_single_table(it, end, curr_table);
+    }
+
+    void parse_single_table(std::string::iterator& it,
+                            const std::string::iterator& end,
+                            table*& curr_table)
+    {
+        if (it == end || *it == ']')
+            throw_parse_exception("Table name cannot be empty");
+
+        std::string full_table_name;
+        bool inserted = false;
+        while (it != end && *it != ']')
+        {
+            auto part = parse_key(it, end,
+                                  [](char c) { return c == '.' || c == ']'; });
+
+            if (part.empty())
+                throw_parse_exception("Empty component of table name");
+
+            if (!full_table_name.empty())
+                full_table_name += ".";
+            full_table_name += part;
+
+            if (curr_table->contains(part))
+            {
+                auto b = curr_table->get(part);
+                if (b->is_table())
+                    curr_table = static_cast<table*>(b.get());
+                else if (b->is_table_array())
+                    curr_table = std::static_pointer_cast<table_array>(b)
+                                     ->get()
+                                     .back()
+                                     .get();
+                else
+                    throw_parse_exception("Key " + full_table_name
+                                          + "already exists as a value");
+            }
+            else
+            {
+                inserted = true;
+                curr_table->insert(part, make_table());
+                curr_table = static_cast<table*>(curr_table->get(part).get());
+            }
+            consume_whitespace(it, end);
+            if (it != end && *it == '.')
+                ++it;
+            consume_whitespace(it, end);
+        }
+
+        if (it == end)
+            throw_parse_exception(
+                "Unterminated table declaration; did you forget a ']'?");
+
+        // table already existed
+        if (!inserted)
+        {
+            auto is_value
+                = [](const std::pair<const std::string&,
+                                     const std::shared_ptr<base>&>& p) {
+                      return p.second->is_value();
+                  };
+
+            // if there are any values, we can't add values to this table
+            // since it has already been defined. If there aren't any
+            // values, then it was implicitly created by something like
+            // [a.b]
+            if (curr_table->empty() || std::any_of(curr_table->begin(),
+                                                   curr_table->end(), is_value))
+            {
+                throw_parse_exception("Redefinition of table "
+                                      + full_table_name);
+            }
+        }
+
+        ++it;
+        consume_whitespace(it, end);
+        eol_or_comment(it, end);
+    }
+
+    void parse_table_array(std::string::iterator& it,
+                           const std::string::iterator& end, table*& curr_table)
+    {
+        ++it;
+        if (it == end || *it == ']')
+            throw_parse_exception("Table array name cannot be empty");
+
+        std::string full_ta_name;
+        while (it != end && *it != ']')
+        {
+            auto part = parse_key(it, end,
+                                  [](char c) { return c == '.' || c == ']'; });
+
+            if (part.empty())
+                throw_parse_exception("Empty component of table array name");
+
+            if (!full_ta_name.empty())
+                full_ta_name += ".";
+            full_ta_name += part;
+
+            consume_whitespace(it, end);
+            if (it != end && *it == '.')
+                ++it;
+            consume_whitespace(it, end);
+
+            if (curr_table->contains(part))
+            {
+                auto b = curr_table->get(part);
+
+                // if this is the end of the table array name, add an
+                // element to the table array that we just looked up
+                if (it != end && *it == ']')
+                {
+                    if (!b->is_table_array())
+                        throw_parse_exception("Key " + full_ta_name
+                                              + " is not a table array");
+                    auto v = b->as_table_array();
+                    v->get().push_back(make_table());
+                    curr_table = v->get().back().get();
+                }
+                // otherwise, just keep traversing down the key name
+                else
+                {
+                    if (b->is_table())
+                        curr_table = static_cast<table*>(b.get());
+                    else if (b->is_table_array())
+                        curr_table = std::static_pointer_cast<table_array>(b)
+                                         ->get()
+                                         .back()
+                                         .get();
+                    else
+                        throw_parse_exception("Key " + full_ta_name
+                                              + " already exists as a value");
+                }
+            }
+            else
+            {
+                // if this is the end of the table array name, add a new
+                // table array and a new table inside that array for us to
+                // add keys to next
+                if (it != end && *it == ']')
+                {
+                    curr_table->insert(part, make_table_array());
+                    auto arr = std::static_pointer_cast<table_array>(
+                        curr_table->get(part));
+                    arr->get().push_back(make_table());
+                    curr_table = arr->get().back().get();
+                }
+                // otherwise, create the implicitly defined table and move
+                // down to it
+                else
+                {
+                    curr_table->insert(part, make_table());
+                    curr_table
+                        = static_cast<table*>(curr_table->get(part).get());
+                }
+            }
+        }
+
+        // consume the last "]]"
+        if (it == end)
+            throw_parse_exception("Unterminated table array name");
+        ++it;
+        if (it == end)
+            throw_parse_exception("Unterminated table array name");
+        ++it;
+
+        consume_whitespace(it, end);
+        eol_or_comment(it, end);
+    }
+
+    void parse_key_value(std::string::iterator& it, std::string::iterator& end,
+                         table* curr_table)
+    {
+        auto key = parse_key(it, end, [](char c) { return c == '='; });
+        if (curr_table->contains(key))
+            throw_parse_exception("Key " + key + " already present");
+        if (it == end || *it != '=')
+            throw_parse_exception("Value must follow after a '='");
+        ++it;
+        consume_whitespace(it, end);
+        curr_table->insert(key, parse_value(it, end));
+        consume_whitespace(it, end);
+    }
+
+    template <class Function>
+    std::string parse_key(std::string::iterator& it,
+                          const std::string::iterator& end, Function&& fun)
+    {
+        consume_whitespace(it, end);
+        if (*it == '"')
+        {
+            return parse_quoted_key(it, end);
+        }
+        else
+        {
+            auto bke = std::find_if(it, end, std::forward<Function>(fun));
+            return parse_bare_key(it, bke);
+        }
+    }
+
+    std::string parse_bare_key(std::string::iterator& it,
+                               const std::string::iterator& end)
+    {
+        if (it == end)
+        {
+            throw_parse_exception("Bare key missing name");
+        }
+
+        auto key_end = end;
+        --key_end;
+        consume_backwards_whitespace(key_end, it);
+        ++key_end;
+        std::string key{it, key_end};
+
+        if (std::find(it, key_end, '#') != key_end)
+        {
+            throw_parse_exception("Bare key " + key + " cannot contain #");
+        }
+
+        if (std::find_if(it, key_end,
+                         [](char c) { return c == ' ' || c == '\t'; })
+            != key_end)
+        {
+            throw_parse_exception("Bare key " + key
+                                  + " cannot contain whitespace");
+        }
+
+        if (std::find_if(it, key_end,
+                         [](char c) { return c == '[' || c == ']'; })
+            != key_end)
+        {
+            throw_parse_exception("Bare key " + key
+                                  + " cannot contain '[' or ']'");
+        }
+
+        it = end;
+        return key;
+    }
+
+    std::string parse_quoted_key(std::string::iterator& it,
+                                 const std::string::iterator& end)
+    {
+        return string_literal(it, end, '"');
+    }
+
+    enum class parse_type
+    {
+        STRING = 1,
+        LOCAL_TIME,
+        LOCAL_DATE,
+        LOCAL_DATETIME,
+        OFFSET_DATETIME,
+        INT,
+        FLOAT,
+        BOOL,
+        ARRAY,
+        INLINE_TABLE
+    };
+
+    std::shared_ptr<base> parse_value(std::string::iterator& it,
+                                      std::string::iterator& end)
+    {
+        parse_type type = determine_value_type(it, end);
+        switch (type)
+        {
+            case parse_type::STRING:
+                return parse_string(it, end);
+            case parse_type::LOCAL_TIME:
+                return parse_time(it, end);
+            case parse_type::LOCAL_DATE:
+            case parse_type::LOCAL_DATETIME:
+            case parse_type::OFFSET_DATETIME:
+                return parse_date(it, end);
+            case parse_type::INT:
+            case parse_type::FLOAT:
+                return parse_number(it, end);
+            case parse_type::BOOL:
+                return parse_bool(it, end);
+            case parse_type::ARRAY:
+                return parse_array(it, end);
+            case parse_type::INLINE_TABLE:
+                return parse_inline_table(it, end);
+            default:
+                throw_parse_exception("Failed to parse value");
+        }
+    }
+
+    parse_type determine_value_type(const std::string::iterator& it,
+                                    const std::string::iterator& end)
+    {
+        if (*it == '"' || *it == '\'')
+        {
+            return parse_type::STRING;
+        }
+        else if (is_time(it, end))
+        {
+            return parse_type::LOCAL_TIME;
+        }
+        else if (auto dtype = date_type(it, end))
+        {
+            return *dtype;
+        }
+        else if (is_number(*it) || *it == '-' || *it == '+')
+        {
+            return determine_number_type(it, end);
+        }
+        else if (*it == 't' || *it == 'f')
+        {
+            return parse_type::BOOL;
+        }
+        else if (*it == '[')
+        {
+            return parse_type::ARRAY;
+        }
+        else if (*it == '{')
+        {
+            return parse_type::INLINE_TABLE;
+        }
+        throw_parse_exception("Failed to parse value type");
+    }
+
+    parse_type determine_number_type(const std::string::iterator& it,
+                                     const std::string::iterator& end)
+    {
+        // determine if we are an integer or a float
+        auto check_it = it;
+        if (*check_it == '-' || *check_it == '+')
+            ++check_it;
+        while (check_it != end && is_number(*check_it))
+            ++check_it;
+        if (check_it != end && *check_it == '.')
+        {
+            ++check_it;
+            while (check_it != end && is_number(*check_it))
+                ++check_it;
+            return parse_type::FLOAT;
+        }
+        else
+        {
+            return parse_type::INT;
+        }
+    }
+
+    std::shared_ptr<value<std::string>> parse_string(std::string::iterator& it,
+                                                     std::string::iterator& end)
+    {
+        auto delim = *it;
+        assert(delim == '"' || delim == '\'');
+
+        // end is non-const here because we have to be able to potentially
+        // parse multiple lines in a string, not just one
+        auto check_it = it;
+        ++check_it;
+        if (check_it != end && *check_it == delim)
+        {
+            ++check_it;
+            if (check_it != end && *check_it == delim)
+            {
+                it = ++check_it;
+                return parse_multiline_string(it, end, delim);
+            }
+        }
+        return make_value<std::string>(string_literal(it, end, delim));
+    }
+
+    std::shared_ptr<value<std::string>>
+    parse_multiline_string(std::string::iterator& it,
+                           std::string::iterator& end, char delim)
+    {
+        std::stringstream ss;
+
+        auto is_ws = [](char c) { return c == ' ' || c == '\t'; };
+
+        bool consuming = false;
+        std::shared_ptr<value<std::string>> ret;
+
+        auto handle_line
+            = [&](std::string::iterator& it, std::string::iterator& end) {
+                  if (consuming)
+                  {
+                      it = std::find_if_not(it, end, is_ws);
+
+                      // whole line is whitespace
+                      if (it == end)
+                          return;
+                  }
+
+                  consuming = false;
+
+                  while (it != end)
+                  {
+                      // handle escaped characters
+                      if (delim == '"' && *it == '\\')
+                      {
+                          auto check = it;
+                          // check if this is an actual escape sequence or a
+                          // whitespace escaping backslash
+                          ++check;
+                          consume_whitespace(check, end);
+                          if (check == end)
+                          {
+                              consuming = true;
+                              break;
+                          }
+
+                          ss << parse_escape_code(it, end);
+                          continue;
+                      }
+
+                      // if we can end the string
+                      if (std::distance(it, end) >= 3)
+                      {
+                          auto check = it;
+                          // check for """
+                          if (*check++ == delim && *check++ == delim
+                              && *check++ == delim)
+                          {
+                              it = check;
+                              ret = make_value<std::string>(ss.str());
+                              break;
+                          }
+                      }
+
+                      ss << *it++;
+                  }
+              };
+
+        // handle the remainder of the current line
+        handle_line(it, end);
+        if (ret)
+            return ret;
+
+        // start eating lines
+        while (detail::getline(input_, line_))
+        {
+            ++line_number_;
+
+            it = line_.begin();
+            end = line_.end();
+
+            handle_line(it, end);
+
+            if (ret)
+                return ret;
+
+            if (!consuming)
+                ss << std::endl;
+        }
+
+        throw_parse_exception("Unterminated multi-line basic string");
+    }
+
+    std::string string_literal(std::string::iterator& it,
+                               const std::string::iterator& end, char delim)
+    {
+        ++it;
+        std::string val;
+        while (it != end)
+        {
+            // handle escaped characters
+            if (delim == '"' && *it == '\\')
+            {
+                val += parse_escape_code(it, end);
+            }
+            else if (*it == delim)
+            {
+                ++it;
+                consume_whitespace(it, end);
+                return val;
+            }
+            else
+            {
+                val += *it++;
+            }
+        }
+        throw_parse_exception("Unterminated string literal");
+    }
+
+    std::string parse_escape_code(std::string::iterator& it,
+                                  const std::string::iterator& end)
+    {
+        ++it;
+        if (it == end)
+            throw_parse_exception("Invalid escape sequence");
+        char value;
+        if (*it == 'b')
+        {
+            value = '\b';
+        }
+        else if (*it == 't')
+        {
+            value = '\t';
+        }
+        else if (*it == 'n')
+        {
+            value = '\n';
+        }
+        else if (*it == 'f')
+        {
+            value = '\f';
+        }
+        else if (*it == 'r')
+        {
+            value = '\r';
+        }
+        else if (*it == '"')
+        {
+            value = '"';
+        }
+        else if (*it == '\\')
+        {
+            value = '\\';
+        }
+        else if (*it == 'u' || *it == 'U')
+        {
+            return parse_unicode(it, end);
+        }
+        else
+        {
+            throw_parse_exception("Invalid escape sequence");
+        }
+        ++it;
+        return std::string(1, value);
+    }
+
+    std::string parse_unicode(std::string::iterator& it,
+                              const std::string::iterator& end)
+    {
+        bool large = *it++ == 'U';
+        auto codepoint = parse_hex(it, end, large ? 0x10000000 : 0x1000);
+
+        if ((codepoint > 0xd7ff && codepoint < 0xe000) || codepoint > 0x10ffff)
+        {
+            throw_parse_exception(
+                "Unicode escape sequence is not a Unicode scalar value");
+        }
+
+        std::string result;
+        // See Table 3-6 of the Unicode standard
+        if (codepoint <= 0x7f)
+        {
+            // 1-byte codepoints: 00000000 0xxxxxxx
+            // repr: 0xxxxxxx
+            result += static_cast<char>(codepoint & 0x7f);
+        }
+        else if (codepoint <= 0x7ff)
+        {
+            // 2-byte codepoints: 00000yyy yyxxxxxx
+            // repr: 110yyyyy 10xxxxxx
+            //
+            // 0x1f = 00011111
+            // 0xc0 = 11000000
+            //
+            result += static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f));
+            //
+            // 0x80 = 10000000
+            // 0x3f = 00111111
+            //
+            result += static_cast<char>(0x80 | (codepoint & 0x3f));
+        }
+        else if (codepoint <= 0xffff)
+        {
+            // 3-byte codepoints: zzzzyyyy yyxxxxxx
+            // repr: 1110zzzz 10yyyyyy 10xxxxxx
+            //
+            // 0xe0 = 11100000
+            // 0x0f = 00001111
+            //
+            result += static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f));
+            result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x1f));
+            result += static_cast<char>(0x80 | (codepoint & 0x3f));
+        }
+        else
+        {
+            // 4-byte codepoints: 000uuuuu zzzzyyyy yyxxxxxx
+            // repr: 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+            //
+            // 0xf0 = 11110000
+            // 0x07 = 00000111
+            //
+            result += static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07));
+            result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
+            result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
+            result += static_cast<char>(0x80 | (codepoint & 0x3f));
+        }
+        return result;
+    }
+
+    uint32_t parse_hex(std::string::iterator& it,
+                       const std::string::iterator& end, uint32_t place)
+    {
+        uint32_t value = 0;
+        while (place > 0)
+        {
+            if (it == end)
+                throw_parse_exception("Unexpected end of unicode sequence");
+
+            if (!is_hex(*it))
+                throw_parse_exception("Invalid unicode escape sequence");
+
+            value += place * hex_to_digit(*it++);
+            place /= 16;
+        }
+        return value;
+    }
+
+    bool is_hex(char c)
+    {
+        return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+    }
+
+    uint32_t hex_to_digit(char c)
+    {
+        if (is_number(c))
+            return static_cast<uint32_t>(c - '0');
+        return 10 + static_cast<uint32_t>(
+                        c - ((c >= 'a' && c <= 'f') ? 'a' : 'A'));
+    }
+
+    std::shared_ptr<base> parse_number(std::string::iterator& it,
+                                       const std::string::iterator& end)
+    {
+        auto check_it = it;
+        auto check_end = find_end_of_number(it, end);
+
+        auto eat_sign = [&]() {
+            if (check_it != end && (*check_it == '-' || *check_it == '+'))
+                ++check_it;
+        };
+
+        eat_sign();
+
+        auto eat_numbers = [&]() {
+            auto beg = check_it;
+            while (check_it != end && is_number(*check_it))
+            {
+                ++check_it;
+                if (check_it != end && *check_it == '_')
+                {
+                    ++check_it;
+                    if (check_it == end || !is_number(*check_it))
+                        throw_parse_exception("Malformed number");
+                }
+            }
+
+            if (check_it == beg)
+                throw_parse_exception("Malformed number");
+        };
+
+        auto check_no_leading_zero = [&]() {
+            if (check_it != end && *check_it == '0' && check_it + 1 != check_end
+                && check_it[1] != '.')
+            {
+                throw_parse_exception("Numbers may not have leading zeros");
+            }
+        };
+
+        check_no_leading_zero();
+        eat_numbers();
+
+        if (check_it != end
+            && (*check_it == '.' || *check_it == 'e' || *check_it == 'E'))
+        {
+            bool is_exp = *check_it == 'e' || *check_it == 'E';
+
+            ++check_it;
+            if (check_it == end)
+                throw_parse_exception("Floats must have trailing digits");
+
+            auto eat_exp = [&]() {
+                eat_sign();
+                check_no_leading_zero();
+                eat_numbers();
+            };
+
+            if (is_exp)
+                eat_exp();
+            else
+                eat_numbers();
+
+            if (!is_exp && check_it != end
+                && (*check_it == 'e' || *check_it == 'E'))
+            {
+                ++check_it;
+                eat_exp();
+            }
+
+            return parse_float(it, check_it);
+        }
+        else
+        {
+            return parse_int(it, check_it);
+        }
+    }
+
+    std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it,
+                                              const std::string::iterator& end)
+    {
+        std::string v{it, end};
+        v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
+        it = end;
+        try
+        {
+            return make_value<int64_t>(std::stoll(v));
+        }
+        catch (const std::invalid_argument& ex)
+        {
+            throw_parse_exception("Malformed number (invalid argument: "
+                                  + std::string{ex.what()} + ")");
+        }
+        catch (const std::out_of_range& ex)
+        {
+            throw_parse_exception("Malformed number (out of range: "
+                                  + std::string{ex.what()} + ")");
+        }
+    }
+
+    std::shared_ptr<value<double>> parse_float(std::string::iterator& it,
+                                               const std::string::iterator& end)
+    {
+        std::string v{it, end};
+        v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
+        it = end;
+        try
+        {
+            return make_value<double>(std::stod(v));
+        }
+        catch (const std::invalid_argument& ex)
+        {
+            throw_parse_exception("Malformed number (invalid argument: "
+                                  + std::string{ex.what()} + ")");
+        }
+        catch (const std::out_of_range& ex)
+        {
+            throw_parse_exception("Malformed number (out of range: "
+                                  + std::string{ex.what()} + ")");
+        }
+    }
+
+    std::shared_ptr<value<bool>> parse_bool(std::string::iterator& it,
+                                            const std::string::iterator& end)
+    {
+        auto eat = make_consumer(it, end, [this]() {
+            throw_parse_exception("Attempted to parse invalid boolean value");
+        });
+
+        if (*it == 't')
+        {
+            eat("true");
+            return make_value<bool>(true);
+        }
+        else if (*it == 'f')
+        {
+            eat("false");
+            return make_value<bool>(false);
+        }
+
+        eat.error();
+        return nullptr;
+    }
+
+    std::string::iterator find_end_of_number(std::string::iterator it,
+                                             std::string::iterator end)
+    {
+        return std::find_if(it, end, [](char c) {
+            return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E'
+                   && c != '-' && c != '+';
+        });
+    }
+
+    std::string::iterator find_end_of_date(std::string::iterator it,
+                                           std::string::iterator end)
+    {
+        return std::find_if(it, end, [](char c) {
+            return !is_number(c) && c != 'T' && c != 'Z' && c != ':' && c != '-'
+                   && c != '+' && c != '.';
+        });
+    }
+
+    std::string::iterator find_end_of_time(std::string::iterator it,
+                                           std::string::iterator end)
+    {
+        return std::find_if(it, end, [](char c) {
+            return !is_number(c) && c != ':' && c != '.';
+        });
+    }
+
+    local_time read_time(std::string::iterator& it,
+                         const std::string::iterator& end)
+    {
+        auto time_end = find_end_of_time(it, end);
+
+        auto eat = make_consumer(
+            it, time_end, [&]() { throw_parse_exception("Malformed time"); });
+
+        local_time ltime;
+
+        ltime.hour = eat.eat_digits(2);
+        eat(':');
+        ltime.minute = eat.eat_digits(2);
+        eat(':');
+        ltime.second = eat.eat_digits(2);
+
+        int power = 100000;
+        if (it != time_end && *it == '.')
+        {
+            ++it;
+            while (it != time_end && is_number(*it))
+            {
+                ltime.microsecond += power * (*it++ - '0');
+                power /= 10;
+            }
+        }
+
+        if (it != time_end)
+            throw_parse_exception("Malformed time");
+
+        return ltime;
+    }
+
+    std::shared_ptr<value<local_time>>
+    parse_time(std::string::iterator& it, const std::string::iterator& end)
+    {
+        return make_value(read_time(it, end));
+    }
+
+    std::shared_ptr<base> parse_date(std::string::iterator& it,
+                                     const std::string::iterator& end)
+    {
+        auto date_end = find_end_of_date(it, end);
+
+        auto eat = make_consumer(
+            it, date_end, [&]() { throw_parse_exception("Malformed date"); });
+
+        local_date ldate;
+        ldate.year = eat.eat_digits(4);
+        eat('-');
+        ldate.month = eat.eat_digits(2);
+        eat('-');
+        ldate.day = eat.eat_digits(2);
+
+        if (it == date_end)
+            return make_value(ldate);
+
+        eat('T');
+
+        local_datetime ldt;
+        static_cast<local_date&>(ldt) = ldate;
+        static_cast<local_time&>(ldt) = read_time(it, date_end);
+
+        if (it == date_end)
+            return make_value(ldt);
+
+        offset_datetime dt;
+        static_cast<local_datetime&>(dt) = ldt;
+
+        int hoff = 0;
+        int moff = 0;
+        if (*it == '+' || *it == '-')
+        {
+            auto plus = *it == '+';
+            ++it;
+
+            hoff = eat.eat_digits(2);
+            dt.hour_offset = (plus) ? hoff : -hoff;
+            eat(':');
+            moff = eat.eat_digits(2);
+            dt.minute_offset = (plus) ? moff : -moff;
+        }
+        else if (*it == 'Z')
+        {
+            ++it;
+        }
+
+        if (it != date_end)
+            throw_parse_exception("Malformed date");
+
+        return make_value(dt);
+    }
+
+    std::shared_ptr<base> parse_array(std::string::iterator& it,
+                                      std::string::iterator& end)
+    {
+        // this gets ugly because of the "homogeneity" restriction:
+        // arrays can either be of only one type, or contain arrays
+        // (each of those arrays could be of different types, though)
+        //
+        // because of the latter portion, we don't really have a choice
+        // but to represent them as arrays of base values...
+        ++it;
+
+        // ugh---have to read the first value to determine array type...
+        skip_whitespace_and_comments(it, end);
+
+        // edge case---empty array
+        if (*it == ']')
+        {
+            ++it;
+            return make_array();
+        }
+
+        auto val_end = std::find_if(
+            it, end, [](char c) { return c == ',' || c == ']' || c == '#'; });
+        parse_type type = determine_value_type(it, val_end);
+        switch (type)
+        {
+            case parse_type::STRING:
+                return parse_value_array<std::string>(it, end);
+            case parse_type::LOCAL_TIME:
+                return parse_value_array<local_time>(it, end);
+            case parse_type::LOCAL_DATE:
+                return parse_value_array<local_date>(it, end);
+            case parse_type::LOCAL_DATETIME:
+                return parse_value_array<local_datetime>(it, end);
+            case parse_type::OFFSET_DATETIME:
+                return parse_value_array<offset_datetime>(it, end);
+            case parse_type::INT:
+                return parse_value_array<int64_t>(it, end);
+            case parse_type::FLOAT:
+                return parse_value_array<double>(it, end);
+            case parse_type::BOOL:
+                return parse_value_array<bool>(it, end);
+            case parse_type::ARRAY:
+                return parse_object_array<array>(&parser::parse_array, '[', it,
+                                                 end);
+            case parse_type::INLINE_TABLE:
+                return parse_object_array<table_array>(
+                    &parser::parse_inline_table, '{', it, end);
+            default:
+                throw_parse_exception("Unable to parse array");
+        }
+    }
+
+    template <class Value>
+    std::shared_ptr<array> parse_value_array(std::string::iterator& it,
+                                             std::string::iterator& end)
+    {
+        auto arr = make_array();
+        while (it != end && *it != ']')
+        {
+            auto value = parse_value(it, end);
+            if (auto v = value->as<Value>())
+                arr->get().push_back(value);
+            else
+                throw_parse_exception("Arrays must be heterogeneous");
+            skip_whitespace_and_comments(it, end);
+            if (*it != ',')
+                break;
+            ++it;
+            skip_whitespace_and_comments(it, end);
+        }
+        if (it != end)
+            ++it;
+        return arr;
+    }
+
+    template <class Object, class Function>
+    std::shared_ptr<Object> parse_object_array(Function&& fun, char delim,
+                                               std::string::iterator& it,
+                                               std::string::iterator& end)
+    {
+        auto arr = make_element<Object>();
+
+        while (it != end && *it != ']')
+        {
+            if (*it != delim)
+                throw_parse_exception("Unexpected character in array");
+
+            arr->get().push_back(((*this).*fun)(it, end));
+            skip_whitespace_and_comments(it, end);
+
+            if (*it != ',')
+                break;
+
+            ++it;
+            skip_whitespace_and_comments(it, end);
+        }
+
+        if (it == end || *it != ']')
+            throw_parse_exception("Unterminated array");
+
+        ++it;
+        return arr;
+    }
+
+    std::shared_ptr<table> parse_inline_table(std::string::iterator& it,
+                                              std::string::iterator& end)
+    {
+        auto tbl = make_table();
+        do
+        {
+            ++it;
+            if (it == end)
+                throw_parse_exception("Unterminated inline table");
+
+            consume_whitespace(it, end);
+            parse_key_value(it, end, tbl.get());
+            consume_whitespace(it, end);
+        } while (*it == ',');
+
+        if (it == end || *it != '}')
+            throw_parse_exception("Unterminated inline table");
+
+        ++it;
+        consume_whitespace(it, end);
+
+        return tbl;
+    }
+
+    void skip_whitespace_and_comments(std::string::iterator& start,
+                                      std::string::iterator& end)
+    {
+        consume_whitespace(start, end);
+        while (start == end || *start == '#')
+        {
+            if (!detail::getline(input_, line_))
+                throw_parse_exception("Unclosed array");
+            line_number_++;
+            start = line_.begin();
+            end = line_.end();
+            consume_whitespace(start, end);
+        }
+    }
+
+    void consume_whitespace(std::string::iterator& it,
+                            const std::string::iterator& end)
+    {
+        while (it != end && (*it == ' ' || *it == '\t'))
+            ++it;
+    }
+
+    void consume_backwards_whitespace(std::string::iterator& back,
+                                      const std::string::iterator& front)
+    {
+        while (back != front && (*back == ' ' || *back == '\t'))
+            --back;
+    }
+
+    void eol_or_comment(const std::string::iterator& it,
+                        const std::string::iterator& end)
+    {
+        if (it != end && *it != '#')
+            throw_parse_exception("Unidentified trailing character '"
+                                  + std::string{*it}
+                                  + "'---did you forget a '#'?");
+    }
+
+    bool is_time(const std::string::iterator& it,
+                 const std::string::iterator& end)
+    {
+        auto time_end = find_end_of_time(it, end);
+        auto len = std::distance(it, time_end);
+
+        if (len < 8)
+            return false;
+
+        if (it[2] != ':' || it[5] != ':')
+            return false;
+
+        if (len > 8)
+            return it[8] == '.' && len > 9;
+
+        return true;
+    }
+
+    option<parse_type> date_type(const std::string::iterator& it,
+                                 const std::string::iterator& end)
+    {
+        auto date_end = find_end_of_date(it, end);
+        auto len = std::distance(it, date_end);
+
+        if (len < 10)
+            return {};
+
+        if (it[4] != '-' || it[7] != '-')
+            return {};
+
+        if (len >= 19 && it[10] == 'T' && is_time(it + 11, date_end))
+        {
+            // datetime type
+            auto time_end = find_end_of_time(it + 11, date_end);
+            if (time_end == date_end)
+                return {parse_type::LOCAL_DATETIME};
+            else
+                return {parse_type::OFFSET_DATETIME};
+        }
+        else if (len == 10)
+        {
+            // just a regular date
+            return {parse_type::LOCAL_DATE};
+        }
+
+        return {};
+    }
+
+    std::istream& input_;
+    std::string line_;
+    std::size_t line_number_ = 0;
+};
+
+/**
+ * Utility function to parse a file as a TOML file. Returns the root table.
+ * Throws a parse_exception if the file cannot be opened.
+ */
+inline std::shared_ptr<table> parse_file(const std::string& filename)
+{
+#if defined(BOOST_NOWIDE_FSTREAM_INCLUDED_HPP)
+    boost::nowide::ifstream file{filename.c_str()};
+#elif defined(NOWIDE_FSTREAM_INCLUDED_HPP)
+    nowide::ifstream file{filename.c_str()};
+#else
+    std::ifstream file{filename};
+#endif
+    if (!file.is_open())
+        throw parse_exception{filename + " could not be opened for parsing"};
+    parser p{file};
+    return p.parse();
+}
+
+template <class... Ts>
+struct value_accept;
+
+template <>
+struct value_accept<>
+{
+    template <class Visitor, class... Args>
+    static void accept(const base&, Visitor&&, Args&&...)
+    {
+        // nothing
+    }
+};
+
+template <class T, class... Ts>
+struct value_accept<T, Ts...>
+{
+    template <class Visitor, class... Args>
+    static void accept(const base& b, Visitor&& visitor, Args&&... args)
+    {
+        if (auto v = b.as<T>())
+        {
+            visitor.visit(*v, std::forward<Args>(args)...);
+        }
+        else
+        {
+            value_accept<Ts...>::accept(b, std::forward<Visitor>(visitor),
+                                        std::forward<Args>(args)...);
+        }
+    }
+};
+
+/**
+ * base implementation of accept() that calls visitor.visit() on the concrete
+ * class.
+ */
+template <class Visitor, class... Args>
+void base::accept(Visitor&& visitor, Args&&... args) const
+{
+    if (is_value())
+    {
+        using value_acceptor
+            = value_accept<std::string, int64_t, double, bool, local_date,
+                           local_time, local_datetime, offset_datetime>;
+        value_acceptor::accept(*this, std::forward<Visitor>(visitor),
+                               std::forward<Args>(args)...);
+    }
+    else if (is_table())
+    {
+        visitor.visit(static_cast<const table&>(*this),
+                      std::forward<Args>(args)...);
+    }
+    else if (is_array())
+    {
+        visitor.visit(static_cast<const array&>(*this),
+                      std::forward<Args>(args)...);
+    }
+    else if (is_table_array())
+    {
+        visitor.visit(static_cast<const table_array&>(*this),
+                      std::forward<Args>(args)...);
+    }
+}
+
+/**
+ * Writer that can be passed to accept() functions of cpptoml objects and
+ * will output valid TOML to a stream.
+ */
+class toml_writer
+{
+  public:
+    /**
+     * Construct a toml_writer that will write to the given stream
+     */
+    toml_writer(std::ostream& s, const std::string& indent_space = "\t")
+        : stream_(s), indent_(indent_space), has_naked_endline_(false)
+    {
+        // nothing
+    }
+
+  public:
+    /**
+     * Output a base value of the TOML tree.
+     */
+    template <class T>
+    void visit(const value<T>& v, bool = false)
+    {
+        write(v);
+    }
+
+    /**
+     * Output a table element of the TOML tree
+     */
+    void visit(const table& t, bool in_array = false)
+    {
+        write_table_header(in_array);
+        std::vector<std::string> values;
+        std::vector<std::string> tables;
+
+        for (const auto& i : t)
+        {
+            if (i.second->is_table() || i.second->is_table_array())
+            {
+                tables.push_back(i.first);
+            }
+            else
+            {
+                values.push_back(i.first);
+            }
+        }
+
+        for (unsigned int i = 0; i < values.size(); ++i)
+        {
+            path_.push_back(values[i]);
+
+            if (i > 0)
+                endline();
+
+            write_table_item_header(*t.get(values[i]));
+            t.get(values[i])->accept(*this, false);
+            path_.pop_back();
+        }
+
+        for (unsigned int i = 0; i < tables.size(); ++i)
+        {
+            path_.push_back(tables[i]);
+
+            if (values.size() > 0 || i > 0)
+                endline();
+
+            write_table_item_header(*t.get(tables[i]));
+            t.get(tables[i])->accept(*this, false);
+            path_.pop_back();
+        }
+
+        endline();
+    }
+
+    /**
+     * Output an array element of the TOML tree
+     */
+    void visit(const array& a, bool = false)
+    {
+        write("[");
+
+        for (unsigned int i = 0; i < a.get().size(); ++i)
+        {
+            if (i > 0)
+                write(", ");
+
+            if (a.get()[i]->is_array())
+            {
+                a.get()[i]->as_array()->accept(*this, true);
+            }
+            else
+            {
+                a.get()[i]->accept(*this, true);
+            }
+        }
+
+        write("]");
+    }
+
+    /**
+     * Output a table_array element of the TOML tree
+     */
+    void visit(const table_array& t, bool = false)
+    {
+        for (unsigned int j = 0; j < t.get().size(); ++j)
+        {
+            if (j > 0)
+                endline();
+
+            t.get()[j]->accept(*this, true);
+        }
+
+        endline();
+    }
+
+    /**
+     * Escape a string for output.
+     */
+    static std::string escape_string(const std::string& str)
+    {
+        std::string res;
+        for (auto it = str.begin(); it != str.end(); ++it)
+        {
+            if (*it == '\b')
+            {
+                res += "\\b";
+            }
+            else if (*it == '\t')
+            {
+                res += "\\t";
+            }
+            else if (*it == '\n')
+            {
+                res += "\\n";
+            }
+            else if (*it == '\f')
+            {
+                res += "\\f";
+            }
+            else if (*it == '\r')
+            {
+                res += "\\r";
+            }
+            else if (*it == '"')
+            {
+                res += "\\\"";
+            }
+            else if (*it == '\\')
+            {
+                res += "\\\\";
+            }
+            else if (*it >= 0x0000 && *it <= 0x001f)
+            {
+                res += "\\u";
+                std::stringstream ss;
+                ss << std::hex << static_cast<uint32_t>(*it);
+                res += ss.str();
+            }
+            else
+            {
+                res += *it;
+            }
+        }
+        return res;
+    }
+
+  protected:
+    /**
+     * Write out a string.
+     */
+    void write(const value<std::string>& v)
+    {
+        write("\"");
+        write(escape_string(v.get()));
+        write("\"");
+    }
+
+    /**
+     * Write out a double.
+     */
+    void write(const value<double>& v)
+    {
+        std::ios::fmtflags flags{stream_.flags()};
+
+        stream_ << std::showpoint;
+        write(v.get());
+
+        stream_.flags(flags);
+    }
+
+    /**
+     * Write out an integer, local_date, local_time, local_datetime, or
+     * offset_datetime.
+     */
+    template <class T>
+    typename std::enable_if<is_one_of<T, int64_t, local_date, local_time,
+                                      local_datetime,
+                                      offset_datetime>::value>::type
+    write(const value<T>& v)
+    {
+        write(v.get());
+    }
+
+    /**
+     * Write out a boolean.
+     */
+    void write(const value<bool>& v)
+    {
+        write((v.get() ? "true" : "false"));
+    }
+
+    /**
+     * Write out the header of a table.
+     */
+    void write_table_header(bool in_array = false)
+    {
+        if (!path_.empty())
+        {
+            indent();
+
+            write("[");
+
+            if (in_array)
+            {
+                write("[");
+            }
+
+            for (unsigned int i = 0; i < path_.size(); ++i)
+            {
+                if (i > 0)
+                {
+                    write(".");
+                }
+
+                if (path_[i].find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
+                                               "fghijklmnopqrstuvwxyz0123456789"
+                                               "_-")
+                    == std::string::npos)
+                {
+                    write(path_[i]);
+                }
+                else
+                {
+                    write("\"");
+                    write(escape_string(path_[i]));
+                    write("\"");
+                }
+            }
+
+            if (in_array)
+            {
+                write("]");
+            }
+
+            write("]");
+            endline();
+        }
+    }
+
+    /**
+     * Write out the identifier for an item in a table.
+     */
+    void write_table_item_header(const base& b)
+    {
+        if (!b.is_table() && !b.is_table_array())
+        {
+            indent();
+
+            if (path_.back().find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
+                                               "fghijklmnopqrstuvwxyz0123456789"
+                                               "_-")
+                == std::string::npos)
+            {
+                write(path_.back());
+            }
+            else
+            {
+                write("\"");
+                write(escape_string(path_.back()));
+                write("\"");
+            }
+
+            write(" = ");
+        }
+    }
+
+  private:
+    /**
+     * Indent the proper number of tabs given the size of
+     * the path.
+     */
+    void indent()
+    {
+        for (std::size_t i = 1; i < path_.size(); ++i)
+            write(indent_);
+    }
+
+    /**
+     * Write a value out to the stream.
+     */
+    template <class T>
+    void write(const T& v)
+    {
+        stream_ << v;
+        has_naked_endline_ = false;
+    }
+
+    /**
+     * Write an endline out to the stream
+     */
+    void endline()
+    {
+        if (!has_naked_endline_)
+        {
+            stream_ << "\n";
+            has_naked_endline_ = true;
+        }
+    }
+
+  private:
+    std::ostream& stream_;
+    const std::string indent_;
+    std::vector<std::string> path_;
+    bool has_naked_endline_;
+};
+
+inline std::ostream& operator<<(std::ostream& stream, const base& b)
+{
+    toml_writer writer{stream};
+    b.accept(writer);
+    return stream;
+}
+
+template <class T>
+std::ostream& operator<<(std::ostream& stream, const value<T>& v)
+{
+    toml_writer writer{stream};
+    v.accept(writer);
+    return stream;
+}
+
+inline std::ostream& operator<<(std::ostream& stream, const table& t)
+{
+    toml_writer writer{stream};
+    t.accept(writer);
+    return stream;
+}
+
+inline std::ostream& operator<<(std::ostream& stream, const table_array& t)
+{
+    toml_writer writer{stream};
+    t.accept(writer);
+    return stream;
+}
+
+inline std::ostream& operator<<(std::ostream& stream, const array& a)
+{
+    toml_writer writer{stream};
+    a.accept(writer);
+    return stream;
+}
+}
+#endif
diff --git a/package/hvac/bin/runxdg b/package/hvac/bin/runxdg
new file mode 100755 (executable)
index 0000000..dac903c
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/runxdg $@
diff --git a/package/hvac/config.xml b/package/hvac/config.xml
new file mode 100644 (file)
index 0000000..d6722bd
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="hvac" version="0.1">
+  <name>HVAC</name>
+  <icon src="icon.svg"/>
+  <content src="bin/runxdg" type="application/vnd.agl.native"/>
+  <description>Launcher for XDG application on AGL HomeScreen 2017</description>
+  <author>Panasonic Corporation</author>
+  <license>MIT</license>
+  <feature name="urn:AGL:widget:required-api">
+    <param name="homescreen" value="ws" />
+    <param name="windowmanager" value="ws" />
+  </feature>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+    <param name="http://tizen.org/privilege/internal/dbus" value="required" />
+  </feature>
+</widget>
diff --git a/package/hvac/icon.svg b/package/hvac/icon.svg
new file mode 100644 (file)
index 0000000..91661a7
--- /dev/null
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:i="&amp;ns_ai;"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 320 320"
+   style="enable-background:new 0 0 320 320;"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="icon.svg"><metadata
+     id="metadata1292"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+     id="defs1290" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1464"
+     id="namedview1288"
+     showgrid="false"
+     inkscape:zoom="0.7375"
+     inkscape:cx="-572.20339"
+     inkscape:cy="160"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2" /><style
+     type="text/css"
+     id="style4">
+       .st0{display:none;}
+       .st1{display:inline;}
+       .st2{opacity:0.4;fill:url(#SVGID_1_);}
+       .st3{fill:url(#SVGID_2_);}
+       .st4{fill:#FFFFFF;}
+       .st5{font-family:'Roboto-Regular';}
+       .st6{font-size:25px;}
+       .st7{letter-spacing:6;}
+       .st8{fill:url(#SVGID_3_);}
+       .st9{fill:url(#SVGID_4_);}
+       .st10{fill:url(#SVGID_5_);}
+       .st11{fill:url(#SVGID_6_);}
+       .st12{fill:url(#SVGID_7_);}
+       .st13{fill:url(#SVGID_8_);}
+       .st14{fill:url(#SVGID_9_);}
+       .st15{fill:url(#SVGID_10_);}
+       .st16{fill:url(#SVGID_11_);}
+       .st17{fill:url(#SVGID_12_);}
+       .st18{fill:url(#SVGID_13_);}
+       .st19{fill:url(#SVGID_14_);}
+       .st20{fill:url(#SVGID_15_);}
+       .st21{fill:url(#SVGID_16_);}
+       .st22{fill:url(#SVGID_17_);}
+       .st23{fill:url(#SVGID_18_);}
+       .st24{opacity:0.29;}
+       .st25{fill:url(#SVGID_19_);}
+       .st26{fill:url(#SVGID_20_);}
+       .st27{fill:url(#SVGID_21_);}
+       .st28{fill:url(#SVGID_22_);}
+       .st29{fill:url(#SVGID_23_);}
+       .st30{fill:url(#SVGID_24_);}
+       .st31{fill:url(#SVGID_25_);}
+       .st32{fill:url(#SVGID_26_);}
+       .st33{fill:url(#SVGID_27_);}
+       .st34{fill:url(#SVGID_28_);}
+       .st35{fill:url(#SVGID_29_);}
+       .st36{fill:url(#SVGID_30_);}
+       .st37{fill:url(#SVGID_31_);}
+       .st38{fill:url(#SVGID_32_);}
+       .st39{fill:url(#SVGID_33_);}
+       .st40{fill:url(#SVGID_34_);}
+       .st41{fill:url(#SVGID_35_);}
+       .st42{fill:url(#SVGID_36_);}
+       .st43{opacity:0.4;fill:url(#SVGID_37_);}
+       .st44{fill:url(#SVGID_38_);}
+       .st45{fill:url(#SVGID_39_);}
+       .st46{fill:url(#SVGID_40_);}
+       .st47{fill:url(#SVGID_41_);}
+       .st48{fill:url(#SVGID_42_);}
+       .st49{fill:url(#SVGID_43_);}
+       .st50{fill:url(#SVGID_44_);}
+       .st51{display:inline;opacity:0.29;}
+       .st52{display:inline;fill:url(#SVGID_45_);}
+       .st53{display:inline;fill:url(#SVGID_46_);}
+       .st54{display:inline;fill:#FFFFFF;}
+       .st55{display:inline;fill:url(#SVGID_47_);}
+       .st56{display:inline;fill:url(#SVGID_48_);}
+       .st57{display:inline;fill:url(#SVGID_49_);}
+       .st58{display:inline;fill:url(#SVGID_50_);}
+       .st59{display:inline;fill:url(#SVGID_51_);}
+       .st60{display:inline;fill:url(#SVGID_52_);}
+       .st61{opacity:0.4;fill:url(#SVGID_53_);}
+       .st62{fill:url(#SVGID_54_);}
+       .st63{fill:url(#SVGID_55_);}
+       .st64{fill:url(#SVGID_56_);}
+       .st65{fill:url(#SVGID_57_);}
+       .st66{fill:url(#SVGID_58_);}
+       .st67{opacity:0.4;fill:url(#SVGID_59_);}
+       .st68{fill:url(#SVGID_60_);}
+       .st69{fill:url(#SVGID_61_);}
+       .st70{fill:url(#SVGID_62_);}
+       .st71{fill:url(#SVGID_63_);}
+       .st72{fill:url(#SVGID_64_);}
+       .st73{fill:url(#SVGID_65_);}
+       .st74{fill:url(#SVGID_66_);}
+       .st75{fill:url(#SVGID_67_);}
+       .st76{fill:url(#SVGID_68_);}
+       .st77{fill:url(#SVGID_69_);}
+       .st78{fill:url(#SVGID_70_);}
+       .st79{fill:url(#SVGID_71_);}
+       .st80{fill:url(#SVGID_72_);}
+       .st81{fill:url(#SVGID_73_);}
+       .st82{fill:url(#SVGID_74_);}
+       .st83{fill:url(#SVGID_75_);}
+       .st84{fill:url(#SVGID_76_);}
+       .st85{fill:url(#SVGID_77_);}
+       .st86{fill:url(#SVGID_78_);}
+       .st87{fill:url(#SVGID_79_);}
+       .st88{fill:url(#SVGID_80_);}
+       .st89{fill:url(#SVGID_81_);}
+       .st90{fill:url(#SVGID_82_);}
+       .st91{fill:url(#SVGID_83_);}
+       .st92{fill:url(#SVGID_84_);}
+       .st93{fill:url(#SVGID_85_);}
+       .st94{fill:url(#SVGID_86_);}
+       .st95{opacity:0.4;fill:url(#SVGID_87_);}
+       .st96{fill:url(#SVGID_88_);}
+       .st97{fill:url(#SVGID_89_);}
+       .st98{fill:url(#SVGID_90_);}
+       .st99{fill:url(#SVGID_91_);}
+       .st100{fill:url(#SVGID_92_);}
+       .st101{fill:url(#SVGID_93_);}
+       .st102{fill:url(#SVGID_94_);}
+       .st103{opacity:0.4;fill:url(#SVGID_95_);}
+       .st104{fill:url(#SVGID_96_);}
+       .st105{fill:url(#SVGID_97_);}
+       .st106{fill:url(#SVGID_98_);}
+       .st107{fill:url(#SVGID_99_);}
+       .st108{fill:url(#SVGID_100_);}
+       .st109{fill:url(#SVGID_101_);}
+       .st110{display:inline;fill:url(#SVGID_102_);}
+       .st111{display:inline;fill:url(#SVGID_103_);}
+       .st112{fill:url(#SVGID_104_);}
+       .st113{fill:url(#SVGID_105_);}
+       .st114{fill:url(#SVGID_106_);}
+       .st115{fill:url(#SVGID_107_);}
+       .st116{fill:url(#SVGID_108_);}
+       .st117{opacity:0.4;fill:url(#SVGID_109_);}
+       .st118{fill:url(#SVGID_110_);}
+       .st119{fill:url(#SVGID_111_);}
+       .st120{fill:url(#SVGID_112_);}
+       .st121{fill:url(#SVGID_113_);}
+       .st122{fill:url(#SVGID_114_);}
+       .st123{opacity:0.4;fill:url(#SVGID_115_);}
+       .st124{fill:url(#SVGID_116_);}
+       .st125{fill:url(#SVGID_117_);}
+       .st126{fill:url(#SVGID_118_);}
+       .st127{display:inline;fill:url(#SVGID_119_);}
+       .st128{display:inline;fill:url(#SVGID_120_);}
+       .st129{fill:url(#SVGID_121_);}
+       .st130{fill:url(#SVGID_122_);}
+</style><switch
+     id="switch6"><g
+       i:extraneous="self"
+       id="g8"><g
+         id="Multimedia_Inactive_copy"><circle
+           class="st24"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle884" /><linearGradient
+           id="SVGID_91_"
+           gradientUnits="userSpaceOnUse"
+           x1="115.9317"
+           y1="254.1836"
+           x2="256.3852"
+           y2="-133.5267"><stop
+             offset="0"
+             style="stop-color:#8BC53F"
+             id="stop887" /><stop
+             offset="2.015080e-02"
+             style="stop-color:#7CCB56;stop-opacity:0.9678"
+             id="stop889" /><stop
+             offset="6.089833e-02"
+             style="stop-color:#62D67D;stop-opacity:0.9028"
+             id="stop891" /><stop
+             offset="0.1057"
+             style="stop-color:#4BDFA0;stop-opacity:0.8312"
+             id="stop893" /><stop
+             offset="0.1543"
+             style="stop-color:#38E7BE;stop-opacity:0.7537"
+             id="stop895" /><stop
+             offset="0.2077"
+             style="stop-color:#28EED6;stop-opacity:0.6684"
+             id="stop897" /><stop
+             offset="0.2681"
+             style="stop-color:#1CF3E8;stop-opacity:0.572"
+             id="stop899" /><stop
+             offset="0.3394"
+             style="stop-color:#13F6F5;stop-opacity:0.4581"
+             id="stop901" /><stop
+             offset="0.4323"
+             style="stop-color:#0EF8FD;stop-opacity:0.3098"
+             id="stop903" /><stop
+             offset="0.6264"
+             style="stop-color:#0DF9FF;stop-opacity:0"
+             id="stop905" /></linearGradient><circle
+           class="st99"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle907" /><linearGradient
+           id="SVGID_92_"
+           gradientUnits="userSpaceOnUse"
+           x1="4.0481"
+           y1="287.9492"
+           x2="320.4859"
+           y2="-15.4029"
+           gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop
+             offset="0"
+             style="stop-color:#59FF7F"
+             id="stop910" /><stop
+             offset="1"
+             style="stop-color:#6BFBFF"
+             id="stop912" /></linearGradient><path
+           class="st100"
+           d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5     c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2     c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9     c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z"
+           id="path914" /><g
+           id="g916"><text
+             transform="matrix(1 0 0 1 53.5841 284.7119)"
+             class="st4 st5 st6 st7"
+             id="text918">MULTIMEDIA</text>
+<linearGradient
+             id="SVGID_93_"
+             gradientUnits="userSpaceOnUse"
+             x1="140.5445"
+             y1="202.2363"
+             x2="186.8444"
+             y2="68.7049"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop921" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop923" /></linearGradient><path
+             class="st101"
+             d="M114.5,190.9c-6.4,0-12-2.6-14.8-7.5c-2.9-4.9-5.4-14.5,9.6-23.2c4.8-2.8,17.1-3.9,20.8-4l0.1,3.6      c-4.6,0.1-15.5,1.4-19.1,3.5c-9.4,5.4-12.1,11.5-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2c6.6-3.8,10.6-10.5,10.7-17.9l-0.1-0.7V95.4      l71.9-14.2l0.1,71.3c0,6.7-3.3,16.4-12.5,21.8c-11.1,6.4-24.1,4.8-28.9-3.5c-2.9-4.9-5.4-14.5,9.6-23.2      c4.4-2.5,14.4-3.8,18.8-3.9l0.1,3.6c-4.2,0.1-13.5,1.4-17.1,3.5c-6.4,3.7-13.1,9.9-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2      c7.9-4.5,10.7-12.8,10.7-18.5l-0.1-0.8V85.6l-64.7,12.7v66.8l0.1,0.7c0,8.7-4.7,16.6-12.5,21.1      C123.9,189.6,119,190.9,114.5,190.9z"
+             id="path925" /><linearGradient
+             id="SVGID_94_"
+             gradientUnits="userSpaceOnUse"
+             x1="145.3286"
+             y1="203.8951"
+             x2="191.6285"
+             y2="70.3637"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop928" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop930" /></linearGradient><polygon
+             class="st102"
+             points="155.6,123.3 154.8,119.8 195.5,110.2 196.3,113.7     "
+             id="polygon932" /></g></g></g></switch></svg>
\ No newline at end of file
diff --git a/package/hvac/runxdg.toml b/package/hvac/runxdg.toml
new file mode 100644 (file)
index 0000000..243db32
--- /dev/null
@@ -0,0 +1,22 @@
+[application]
+# role: identifier for WindowManager (used in layers.json)
+# e.g. role = "WebBrowser"
+role = "HVAC"
+
+# launch by "POSIX"(fork/exec), "AFM_DBUS"(afm via dbus),  "AFM_WEBSOCKET"(afm via websockt)
+method = "POSIX"
+
+# path: path to the executable
+# e.g.
+#   path = "/usr/bin/chromium"
+path = "/usr/bin/weston-simple-egl"
+
+# params: arguments of the excecutable 
+# e.g.
+#   params = [
+#     "--mus",
+#     "--no-sandbox",
+#     '--window-size="1080,1488"',
+#     "--ozone-platform=wayland",
+#     "<URL>"
+#   ]
diff --git a/package/navi/bin/runxdg b/package/navi/bin/runxdg
new file mode 100755 (executable)
index 0000000..dac903c
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/runxdg $@
diff --git a/package/navi/config.xml b/package/navi/config.xml
new file mode 100644 (file)
index 0000000..97c921b
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="navigation" version="0.1">
+  <name>Navigation</name>
+  <icon src="icon.svg"/>
+  <content src="bin/runxdg" type="application/vnd.agl.native"/>
+  <description>Launcher for XDG application on AGL HomeScreen 2017</description>
+  <author>Panasonic Corporation</author>
+  <license>MIT</license>
+  <feature name="urn:AGL:widget:required-api">
+    <param name="homescreen" value="ws" />
+    <param name="windowmanager" value="ws" />
+  </feature>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+    <param name="http://tizen.org/privilege/internal/dbus" value="required" />
+  </feature>
+</widget>
diff --git a/package/navi/icon.svg b/package/navi/icon.svg
new file mode 100644 (file)
index 0000000..91661a7
--- /dev/null
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:i="&amp;ns_ai;"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 320 320"
+   style="enable-background:new 0 0 320 320;"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="icon.svg"><metadata
+     id="metadata1292"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+     id="defs1290" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1464"
+     id="namedview1288"
+     showgrid="false"
+     inkscape:zoom="0.7375"
+     inkscape:cx="-572.20339"
+     inkscape:cy="160"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2" /><style
+     type="text/css"
+     id="style4">
+       .st0{display:none;}
+       .st1{display:inline;}
+       .st2{opacity:0.4;fill:url(#SVGID_1_);}
+       .st3{fill:url(#SVGID_2_);}
+       .st4{fill:#FFFFFF;}
+       .st5{font-family:'Roboto-Regular';}
+       .st6{font-size:25px;}
+       .st7{letter-spacing:6;}
+       .st8{fill:url(#SVGID_3_);}
+       .st9{fill:url(#SVGID_4_);}
+       .st10{fill:url(#SVGID_5_);}
+       .st11{fill:url(#SVGID_6_);}
+       .st12{fill:url(#SVGID_7_);}
+       .st13{fill:url(#SVGID_8_);}
+       .st14{fill:url(#SVGID_9_);}
+       .st15{fill:url(#SVGID_10_);}
+       .st16{fill:url(#SVGID_11_);}
+       .st17{fill:url(#SVGID_12_);}
+       .st18{fill:url(#SVGID_13_);}
+       .st19{fill:url(#SVGID_14_);}
+       .st20{fill:url(#SVGID_15_);}
+       .st21{fill:url(#SVGID_16_);}
+       .st22{fill:url(#SVGID_17_);}
+       .st23{fill:url(#SVGID_18_);}
+       .st24{opacity:0.29;}
+       .st25{fill:url(#SVGID_19_);}
+       .st26{fill:url(#SVGID_20_);}
+       .st27{fill:url(#SVGID_21_);}
+       .st28{fill:url(#SVGID_22_);}
+       .st29{fill:url(#SVGID_23_);}
+       .st30{fill:url(#SVGID_24_);}
+       .st31{fill:url(#SVGID_25_);}
+       .st32{fill:url(#SVGID_26_);}
+       .st33{fill:url(#SVGID_27_);}
+       .st34{fill:url(#SVGID_28_);}
+       .st35{fill:url(#SVGID_29_);}
+       .st36{fill:url(#SVGID_30_);}
+       .st37{fill:url(#SVGID_31_);}
+       .st38{fill:url(#SVGID_32_);}
+       .st39{fill:url(#SVGID_33_);}
+       .st40{fill:url(#SVGID_34_);}
+       .st41{fill:url(#SVGID_35_);}
+       .st42{fill:url(#SVGID_36_);}
+       .st43{opacity:0.4;fill:url(#SVGID_37_);}
+       .st44{fill:url(#SVGID_38_);}
+       .st45{fill:url(#SVGID_39_);}
+       .st46{fill:url(#SVGID_40_);}
+       .st47{fill:url(#SVGID_41_);}
+       .st48{fill:url(#SVGID_42_);}
+       .st49{fill:url(#SVGID_43_);}
+       .st50{fill:url(#SVGID_44_);}
+       .st51{display:inline;opacity:0.29;}
+       .st52{display:inline;fill:url(#SVGID_45_);}
+       .st53{display:inline;fill:url(#SVGID_46_);}
+       .st54{display:inline;fill:#FFFFFF;}
+       .st55{display:inline;fill:url(#SVGID_47_);}
+       .st56{display:inline;fill:url(#SVGID_48_);}
+       .st57{display:inline;fill:url(#SVGID_49_);}
+       .st58{display:inline;fill:url(#SVGID_50_);}
+       .st59{display:inline;fill:url(#SVGID_51_);}
+       .st60{display:inline;fill:url(#SVGID_52_);}
+       .st61{opacity:0.4;fill:url(#SVGID_53_);}
+       .st62{fill:url(#SVGID_54_);}
+       .st63{fill:url(#SVGID_55_);}
+       .st64{fill:url(#SVGID_56_);}
+       .st65{fill:url(#SVGID_57_);}
+       .st66{fill:url(#SVGID_58_);}
+       .st67{opacity:0.4;fill:url(#SVGID_59_);}
+       .st68{fill:url(#SVGID_60_);}
+       .st69{fill:url(#SVGID_61_);}
+       .st70{fill:url(#SVGID_62_);}
+       .st71{fill:url(#SVGID_63_);}
+       .st72{fill:url(#SVGID_64_);}
+       .st73{fill:url(#SVGID_65_);}
+       .st74{fill:url(#SVGID_66_);}
+       .st75{fill:url(#SVGID_67_);}
+       .st76{fill:url(#SVGID_68_);}
+       .st77{fill:url(#SVGID_69_);}
+       .st78{fill:url(#SVGID_70_);}
+       .st79{fill:url(#SVGID_71_);}
+       .st80{fill:url(#SVGID_72_);}
+       .st81{fill:url(#SVGID_73_);}
+       .st82{fill:url(#SVGID_74_);}
+       .st83{fill:url(#SVGID_75_);}
+       .st84{fill:url(#SVGID_76_);}
+       .st85{fill:url(#SVGID_77_);}
+       .st86{fill:url(#SVGID_78_);}
+       .st87{fill:url(#SVGID_79_);}
+       .st88{fill:url(#SVGID_80_);}
+       .st89{fill:url(#SVGID_81_);}
+       .st90{fill:url(#SVGID_82_);}
+       .st91{fill:url(#SVGID_83_);}
+       .st92{fill:url(#SVGID_84_);}
+       .st93{fill:url(#SVGID_85_);}
+       .st94{fill:url(#SVGID_86_);}
+       .st95{opacity:0.4;fill:url(#SVGID_87_);}
+       .st96{fill:url(#SVGID_88_);}
+       .st97{fill:url(#SVGID_89_);}
+       .st98{fill:url(#SVGID_90_);}
+       .st99{fill:url(#SVGID_91_);}
+       .st100{fill:url(#SVGID_92_);}
+       .st101{fill:url(#SVGID_93_);}
+       .st102{fill:url(#SVGID_94_);}
+       .st103{opacity:0.4;fill:url(#SVGID_95_);}
+       .st104{fill:url(#SVGID_96_);}
+       .st105{fill:url(#SVGID_97_);}
+       .st106{fill:url(#SVGID_98_);}
+       .st107{fill:url(#SVGID_99_);}
+       .st108{fill:url(#SVGID_100_);}
+       .st109{fill:url(#SVGID_101_);}
+       .st110{display:inline;fill:url(#SVGID_102_);}
+       .st111{display:inline;fill:url(#SVGID_103_);}
+       .st112{fill:url(#SVGID_104_);}
+       .st113{fill:url(#SVGID_105_);}
+       .st114{fill:url(#SVGID_106_);}
+       .st115{fill:url(#SVGID_107_);}
+       .st116{fill:url(#SVGID_108_);}
+       .st117{opacity:0.4;fill:url(#SVGID_109_);}
+       .st118{fill:url(#SVGID_110_);}
+       .st119{fill:url(#SVGID_111_);}
+       .st120{fill:url(#SVGID_112_);}
+       .st121{fill:url(#SVGID_113_);}
+       .st122{fill:url(#SVGID_114_);}
+       .st123{opacity:0.4;fill:url(#SVGID_115_);}
+       .st124{fill:url(#SVGID_116_);}
+       .st125{fill:url(#SVGID_117_);}
+       .st126{fill:url(#SVGID_118_);}
+       .st127{display:inline;fill:url(#SVGID_119_);}
+       .st128{display:inline;fill:url(#SVGID_120_);}
+       .st129{fill:url(#SVGID_121_);}
+       .st130{fill:url(#SVGID_122_);}
+</style><switch
+     id="switch6"><g
+       i:extraneous="self"
+       id="g8"><g
+         id="Multimedia_Inactive_copy"><circle
+           class="st24"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle884" /><linearGradient
+           id="SVGID_91_"
+           gradientUnits="userSpaceOnUse"
+           x1="115.9317"
+           y1="254.1836"
+           x2="256.3852"
+           y2="-133.5267"><stop
+             offset="0"
+             style="stop-color:#8BC53F"
+             id="stop887" /><stop
+             offset="2.015080e-02"
+             style="stop-color:#7CCB56;stop-opacity:0.9678"
+             id="stop889" /><stop
+             offset="6.089833e-02"
+             style="stop-color:#62D67D;stop-opacity:0.9028"
+             id="stop891" /><stop
+             offset="0.1057"
+             style="stop-color:#4BDFA0;stop-opacity:0.8312"
+             id="stop893" /><stop
+             offset="0.1543"
+             style="stop-color:#38E7BE;stop-opacity:0.7537"
+             id="stop895" /><stop
+             offset="0.2077"
+             style="stop-color:#28EED6;stop-opacity:0.6684"
+             id="stop897" /><stop
+             offset="0.2681"
+             style="stop-color:#1CF3E8;stop-opacity:0.572"
+             id="stop899" /><stop
+             offset="0.3394"
+             style="stop-color:#13F6F5;stop-opacity:0.4581"
+             id="stop901" /><stop
+             offset="0.4323"
+             style="stop-color:#0EF8FD;stop-opacity:0.3098"
+             id="stop903" /><stop
+             offset="0.6264"
+             style="stop-color:#0DF9FF;stop-opacity:0"
+             id="stop905" /></linearGradient><circle
+           class="st99"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle907" /><linearGradient
+           id="SVGID_92_"
+           gradientUnits="userSpaceOnUse"
+           x1="4.0481"
+           y1="287.9492"
+           x2="320.4859"
+           y2="-15.4029"
+           gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop
+             offset="0"
+             style="stop-color:#59FF7F"
+             id="stop910" /><stop
+             offset="1"
+             style="stop-color:#6BFBFF"
+             id="stop912" /></linearGradient><path
+           class="st100"
+           d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5     c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2     c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9     c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z"
+           id="path914" /><g
+           id="g916"><text
+             transform="matrix(1 0 0 1 53.5841 284.7119)"
+             class="st4 st5 st6 st7"
+             id="text918">MULTIMEDIA</text>
+<linearGradient
+             id="SVGID_93_"
+             gradientUnits="userSpaceOnUse"
+             x1="140.5445"
+             y1="202.2363"
+             x2="186.8444"
+             y2="68.7049"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop921" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop923" /></linearGradient><path
+             class="st101"
+             d="M114.5,190.9c-6.4,0-12-2.6-14.8-7.5c-2.9-4.9-5.4-14.5,9.6-23.2c4.8-2.8,17.1-3.9,20.8-4l0.1,3.6      c-4.6,0.1-15.5,1.4-19.1,3.5c-9.4,5.4-12.1,11.5-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2c6.6-3.8,10.6-10.5,10.7-17.9l-0.1-0.7V95.4      l71.9-14.2l0.1,71.3c0,6.7-3.3,16.4-12.5,21.8c-11.1,6.4-24.1,4.8-28.9-3.5c-2.9-4.9-5.4-14.5,9.6-23.2      c4.4-2.5,14.4-3.8,18.8-3.9l0.1,3.6c-4.2,0.1-13.5,1.4-17.1,3.5c-6.4,3.7-13.1,9.9-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2      c7.9-4.5,10.7-12.8,10.7-18.5l-0.1-0.8V85.6l-64.7,12.7v66.8l0.1,0.7c0,8.7-4.7,16.6-12.5,21.1      C123.9,189.6,119,190.9,114.5,190.9z"
+             id="path925" /><linearGradient
+             id="SVGID_94_"
+             gradientUnits="userSpaceOnUse"
+             x1="145.3286"
+             y1="203.8951"
+             x2="191.6285"
+             y2="70.3637"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop928" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop930" /></linearGradient><polygon
+             class="st102"
+             points="155.6,123.3 154.8,119.8 195.5,110.2 196.3,113.7     "
+             id="polygon932" /></g></g></g></switch></svg>
\ No newline at end of file
diff --git a/package/navi/runxdg.toml b/package/navi/runxdg.toml
new file mode 100644 (file)
index 0000000..9af35ac
--- /dev/null
@@ -0,0 +1,22 @@
+[application]
+# role: identifier for WindowManager (used in layers.json)
+# e.g. role = "WebBrowser"
+role = "Navigation"
+
+# launch by "POSIX"(fork/exec), "AFM_DBUS"(afm via dbus),  "AFM_WEBSOCKET"(afm via websockt)
+method = "POSIX"
+
+# path: path to the executable
+# e.g.
+#   path = "/usr/bin/chromium"
+path = "/usr/bin/weston-simple-egl"
+
+# params: arguments of the excecutable 
+# e.g.
+#   params = [
+#     "--mus",
+#     "--no-sandbox",
+#     '--window-size="1080,1488"',
+#     "--ozone-platform=wayland",
+#     "<URL>"
+#   ]
diff --git a/package/simple-egl/bin/runxdg b/package/simple-egl/bin/runxdg
new file mode 100755 (executable)
index 0000000..dac903c
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/runxdg $@
diff --git a/package/simple-egl/config.xml b/package/simple-egl/config.xml
new file mode 100644 (file)
index 0000000..dbf701a
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="runxdg" version="0.1">
+  <name>SimpleEGL</name>
+  <icon src="icon.svg"/>
+  <content src="bin/runxdg" type="application/vnd.agl.native"/>
+  <description>Launcher for XDG application on AGL HomeScreen 2017</description>
+  <author>Panasonic Corporation</author>
+  <license>MIT</license>
+  <feature name="urn:AGL:widget:required-api">
+    <param name="homescreen" value="ws" />
+    <param name="windowmanager" value="ws" />
+  </feature>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+    <param name="http://tizen.org/privilege/internal/dbus" value="required" />
+  </feature>
+</widget>
diff --git a/package/simple-egl/icon.svg b/package/simple-egl/icon.svg
new file mode 100644 (file)
index 0000000..91661a7
--- /dev/null
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:i="&amp;ns_ai;"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 320 320"
+   style="enable-background:new 0 0 320 320;"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="icon.svg"><metadata
+     id="metadata1292"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+     id="defs1290" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1464"
+     id="namedview1288"
+     showgrid="false"
+     inkscape:zoom="0.7375"
+     inkscape:cx="-572.20339"
+     inkscape:cy="160"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2" /><style
+     type="text/css"
+     id="style4">
+       .st0{display:none;}
+       .st1{display:inline;}
+       .st2{opacity:0.4;fill:url(#SVGID_1_);}
+       .st3{fill:url(#SVGID_2_);}
+       .st4{fill:#FFFFFF;}
+       .st5{font-family:'Roboto-Regular';}
+       .st6{font-size:25px;}
+       .st7{letter-spacing:6;}
+       .st8{fill:url(#SVGID_3_);}
+       .st9{fill:url(#SVGID_4_);}
+       .st10{fill:url(#SVGID_5_);}
+       .st11{fill:url(#SVGID_6_);}
+       .st12{fill:url(#SVGID_7_);}
+       .st13{fill:url(#SVGID_8_);}
+       .st14{fill:url(#SVGID_9_);}
+       .st15{fill:url(#SVGID_10_);}
+       .st16{fill:url(#SVGID_11_);}
+       .st17{fill:url(#SVGID_12_);}
+       .st18{fill:url(#SVGID_13_);}
+       .st19{fill:url(#SVGID_14_);}
+       .st20{fill:url(#SVGID_15_);}
+       .st21{fill:url(#SVGID_16_);}
+       .st22{fill:url(#SVGID_17_);}
+       .st23{fill:url(#SVGID_18_);}
+       .st24{opacity:0.29;}
+       .st25{fill:url(#SVGID_19_);}
+       .st26{fill:url(#SVGID_20_);}
+       .st27{fill:url(#SVGID_21_);}
+       .st28{fill:url(#SVGID_22_);}
+       .st29{fill:url(#SVGID_23_);}
+       .st30{fill:url(#SVGID_24_);}
+       .st31{fill:url(#SVGID_25_);}
+       .st32{fill:url(#SVGID_26_);}
+       .st33{fill:url(#SVGID_27_);}
+       .st34{fill:url(#SVGID_28_);}
+       .st35{fill:url(#SVGID_29_);}
+       .st36{fill:url(#SVGID_30_);}
+       .st37{fill:url(#SVGID_31_);}
+       .st38{fill:url(#SVGID_32_);}
+       .st39{fill:url(#SVGID_33_);}
+       .st40{fill:url(#SVGID_34_);}
+       .st41{fill:url(#SVGID_35_);}
+       .st42{fill:url(#SVGID_36_);}
+       .st43{opacity:0.4;fill:url(#SVGID_37_);}
+       .st44{fill:url(#SVGID_38_);}
+       .st45{fill:url(#SVGID_39_);}
+       .st46{fill:url(#SVGID_40_);}
+       .st47{fill:url(#SVGID_41_);}
+       .st48{fill:url(#SVGID_42_);}
+       .st49{fill:url(#SVGID_43_);}
+       .st50{fill:url(#SVGID_44_);}
+       .st51{display:inline;opacity:0.29;}
+       .st52{display:inline;fill:url(#SVGID_45_);}
+       .st53{display:inline;fill:url(#SVGID_46_);}
+       .st54{display:inline;fill:#FFFFFF;}
+       .st55{display:inline;fill:url(#SVGID_47_);}
+       .st56{display:inline;fill:url(#SVGID_48_);}
+       .st57{display:inline;fill:url(#SVGID_49_);}
+       .st58{display:inline;fill:url(#SVGID_50_);}
+       .st59{display:inline;fill:url(#SVGID_51_);}
+       .st60{display:inline;fill:url(#SVGID_52_);}
+       .st61{opacity:0.4;fill:url(#SVGID_53_);}
+       .st62{fill:url(#SVGID_54_);}
+       .st63{fill:url(#SVGID_55_);}
+       .st64{fill:url(#SVGID_56_);}
+       .st65{fill:url(#SVGID_57_);}
+       .st66{fill:url(#SVGID_58_);}
+       .st67{opacity:0.4;fill:url(#SVGID_59_);}
+       .st68{fill:url(#SVGID_60_);}
+       .st69{fill:url(#SVGID_61_);}
+       .st70{fill:url(#SVGID_62_);}
+       .st71{fill:url(#SVGID_63_);}
+       .st72{fill:url(#SVGID_64_);}
+       .st73{fill:url(#SVGID_65_);}
+       .st74{fill:url(#SVGID_66_);}
+       .st75{fill:url(#SVGID_67_);}
+       .st76{fill:url(#SVGID_68_);}
+       .st77{fill:url(#SVGID_69_);}
+       .st78{fill:url(#SVGID_70_);}
+       .st79{fill:url(#SVGID_71_);}
+       .st80{fill:url(#SVGID_72_);}
+       .st81{fill:url(#SVGID_73_);}
+       .st82{fill:url(#SVGID_74_);}
+       .st83{fill:url(#SVGID_75_);}
+       .st84{fill:url(#SVGID_76_);}
+       .st85{fill:url(#SVGID_77_);}
+       .st86{fill:url(#SVGID_78_);}
+       .st87{fill:url(#SVGID_79_);}
+       .st88{fill:url(#SVGID_80_);}
+       .st89{fill:url(#SVGID_81_);}
+       .st90{fill:url(#SVGID_82_);}
+       .st91{fill:url(#SVGID_83_);}
+       .st92{fill:url(#SVGID_84_);}
+       .st93{fill:url(#SVGID_85_);}
+       .st94{fill:url(#SVGID_86_);}
+       .st95{opacity:0.4;fill:url(#SVGID_87_);}
+       .st96{fill:url(#SVGID_88_);}
+       .st97{fill:url(#SVGID_89_);}
+       .st98{fill:url(#SVGID_90_);}
+       .st99{fill:url(#SVGID_91_);}
+       .st100{fill:url(#SVGID_92_);}
+       .st101{fill:url(#SVGID_93_);}
+       .st102{fill:url(#SVGID_94_);}
+       .st103{opacity:0.4;fill:url(#SVGID_95_);}
+       .st104{fill:url(#SVGID_96_);}
+       .st105{fill:url(#SVGID_97_);}
+       .st106{fill:url(#SVGID_98_);}
+       .st107{fill:url(#SVGID_99_);}
+       .st108{fill:url(#SVGID_100_);}
+       .st109{fill:url(#SVGID_101_);}
+       .st110{display:inline;fill:url(#SVGID_102_);}
+       .st111{display:inline;fill:url(#SVGID_103_);}
+       .st112{fill:url(#SVGID_104_);}
+       .st113{fill:url(#SVGID_105_);}
+       .st114{fill:url(#SVGID_106_);}
+       .st115{fill:url(#SVGID_107_);}
+       .st116{fill:url(#SVGID_108_);}
+       .st117{opacity:0.4;fill:url(#SVGID_109_);}
+       .st118{fill:url(#SVGID_110_);}
+       .st119{fill:url(#SVGID_111_);}
+       .st120{fill:url(#SVGID_112_);}
+       .st121{fill:url(#SVGID_113_);}
+       .st122{fill:url(#SVGID_114_);}
+       .st123{opacity:0.4;fill:url(#SVGID_115_);}
+       .st124{fill:url(#SVGID_116_);}
+       .st125{fill:url(#SVGID_117_);}
+       .st126{fill:url(#SVGID_118_);}
+       .st127{display:inline;fill:url(#SVGID_119_);}
+       .st128{display:inline;fill:url(#SVGID_120_);}
+       .st129{fill:url(#SVGID_121_);}
+       .st130{fill:url(#SVGID_122_);}
+</style><switch
+     id="switch6"><g
+       i:extraneous="self"
+       id="g8"><g
+         id="Multimedia_Inactive_copy"><circle
+           class="st24"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle884" /><linearGradient
+           id="SVGID_91_"
+           gradientUnits="userSpaceOnUse"
+           x1="115.9317"
+           y1="254.1836"
+           x2="256.3852"
+           y2="-133.5267"><stop
+             offset="0"
+             style="stop-color:#8BC53F"
+             id="stop887" /><stop
+             offset="2.015080e-02"
+             style="stop-color:#7CCB56;stop-opacity:0.9678"
+             id="stop889" /><stop
+             offset="6.089833e-02"
+             style="stop-color:#62D67D;stop-opacity:0.9028"
+             id="stop891" /><stop
+             offset="0.1057"
+             style="stop-color:#4BDFA0;stop-opacity:0.8312"
+             id="stop893" /><stop
+             offset="0.1543"
+             style="stop-color:#38E7BE;stop-opacity:0.7537"
+             id="stop895" /><stop
+             offset="0.2077"
+             style="stop-color:#28EED6;stop-opacity:0.6684"
+             id="stop897" /><stop
+             offset="0.2681"
+             style="stop-color:#1CF3E8;stop-opacity:0.572"
+             id="stop899" /><stop
+             offset="0.3394"
+             style="stop-color:#13F6F5;stop-opacity:0.4581"
+             id="stop901" /><stop
+             offset="0.4323"
+             style="stop-color:#0EF8FD;stop-opacity:0.3098"
+             id="stop903" /><stop
+             offset="0.6264"
+             style="stop-color:#0DF9FF;stop-opacity:0"
+             id="stop905" /></linearGradient><circle
+           class="st99"
+           cx="159.7"
+           cy="133.4"
+           r="101.9"
+           id="circle907" /><linearGradient
+           id="SVGID_92_"
+           gradientUnits="userSpaceOnUse"
+           x1="4.0481"
+           y1="287.9492"
+           x2="320.4859"
+           y2="-15.4029"
+           gradientTransform="matrix(1 5.464556e-03 -5.464556e-03 1 -2.0192 -3.0212)"><stop
+             offset="0"
+             style="stop-color:#59FF7F"
+             id="stop910" /><stop
+             offset="1"
+             style="stop-color:#6BFBFF"
+             id="stop912" /></linearGradient><path
+           class="st100"
+           d="M160,238.8c-0.2,0-0.4,0-0.6,0c-58-0.3-104.9-47.7-104.6-105.7C55.2,75.3,102.3,28.5,160,28.5     c0.2,0,0.4,0,0.6,0c58,0.3,104.9,47.7,104.6,105.7l0,0C264.8,192,217.7,238.8,160,238.8z M160,32.2     c-55.7,0-101.2,45.2-101.5,100.9c-0.3,55.9,45,101.7,100.9,102c0.2,0,0.4,0,0.6,0c55.7,0,101.2-45.2,101.5-100.9     c0.3-55.9-45-101.7-100.9-102C160.4,32.2,160.2,32.2,160,32.2z"
+           id="path914" /><g
+           id="g916"><text
+             transform="matrix(1 0 0 1 53.5841 284.7119)"
+             class="st4 st5 st6 st7"
+             id="text918">MULTIMEDIA</text>
+<linearGradient
+             id="SVGID_93_"
+             gradientUnits="userSpaceOnUse"
+             x1="140.5445"
+             y1="202.2363"
+             x2="186.8444"
+             y2="68.7049"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop921" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop923" /></linearGradient><path
+             class="st101"
+             d="M114.5,190.9c-6.4,0-12-2.6-14.8-7.5c-2.9-4.9-5.4-14.5,9.6-23.2c4.8-2.8,17.1-3.9,20.8-4l0.1,3.6      c-4.6,0.1-15.5,1.4-19.1,3.5c-9.4,5.4-12.1,11.5-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2c6.6-3.8,10.6-10.5,10.7-17.9l-0.1-0.7V95.4      l71.9-14.2l0.1,71.3c0,6.7-3.3,16.4-12.5,21.8c-11.1,6.4-24.1,4.8-28.9-3.5c-2.9-4.9-5.4-14.5,9.6-23.2      c4.4-2.5,14.4-3.8,18.8-3.9l0.1,3.6c-4.2,0.1-13.5,1.4-17.1,3.5c-6.4,3.7-13.1,9.9-8.3,18.3c3.8,6.6,14.6,7.6,24,2.2      c7.9-4.5,10.7-12.8,10.7-18.5l-0.1-0.8V85.6l-64.7,12.7v66.8l0.1,0.7c0,8.7-4.7,16.6-12.5,21.1      C123.9,189.6,119,190.9,114.5,190.9z"
+             id="path925" /><linearGradient
+             id="SVGID_94_"
+             gradientUnits="userSpaceOnUse"
+             x1="145.3286"
+             y1="203.8951"
+             x2="191.6285"
+             y2="70.3637"><stop
+               offset="0"
+               style="stop-color:#59FF7F"
+               id="stop928" /><stop
+               offset="1"
+               style="stop-color:#6BFBFF"
+               id="stop930" /></linearGradient><polygon
+             class="st102"
+             points="155.6,123.3 154.8,119.8 195.5,110.2 196.3,113.7     "
+             id="polygon932" /></g></g></g></switch></svg>
\ No newline at end of file
diff --git a/package/simple-egl/runxdg.toml b/package/simple-egl/runxdg.toml
new file mode 100644 (file)
index 0000000..1729766
--- /dev/null
@@ -0,0 +1,22 @@
+[application]
+# role: identifier for WindowManager (used in layers.json)
+# e.g. role = "WebBrowser"
+role = "SimpleEGL"
+
+# launch by "POSIX"(fork/exec), "AFM_DBUS"(afm via dbus),  "AFM_WEBSOCKET"(afm via websockt)
+method = "POSIX"
+
+# path: path to the executable
+# e.g.
+#   path = "/usr/bin/chromium"
+path = "/usr/bin/weston-simple-egl"
+
+# params: arguments of the excecutable 
+# e.g.
+#   params = [
+#     "--mus",
+#     "--no-sandbox",
+#     '--window-size="1080,1488"',
+#     "--ozone-platform=wayland",
+#     "<URL>"
+#   ]
diff --git a/src/runxdg.cpp b/src/runxdg.cpp
new file mode 100644 (file)
index 0000000..5553ff5
--- /dev/null
@@ -0,0 +1,621 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <cstdio>
+
+#include "cpptoml/cpptoml.h"
+
+#include "runxdg.hpp"
+
+#define RUNXDG_CONFIG "runxdg.toml"
+
+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)) {
+      setup_surface(id);
+    }
+    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<RunXDG*>(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 = [](json_object* object) {
+    AGL_DEBUG("Got Event_Active");
+  };
+
+  std::function< void(json_object*) > h_inactive = [](json_object* object) {
+    AGL_DEBUG("Got Event_Inactive");
+  };
+
+  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");
+    json_object* obj = json_object_new_object();
+    json_object_object_add(obj, this->m_wm->kKeyDrawingName,
+                           json_object_new_string(this->m_role.c_str()));
+    this->m_wm->endDraw(obj);
+  };
+
+  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) {
+    json_object *val;
+
+    if (json_object_object_get_ex(object, "application_name", &val)) {
+      const char *name = json_object_get_string(val);
+
+      AGL_DEBUG("Event_TapShortcut <%s>", name);
+
+      if (strcmp(name, this->m_role.c_str()) == 0) {
+        // check app exist and re-launch if needed
+        AGL_DEBUG("Activesurface %s ", this->m_role.c_str());
+
+        json_object *obj = json_object_new_object();
+        json_object_object_add(obj, this->m_wm->kKeyDrawingName,
+                               json_object_new_string(this->m_role.c_str()));
+        json_object_object_add(obj, this->m_wm->kKeyDrawingArea,
+                               json_object_new_string("normal.full"));
+
+        this->m_wm->activateSurface(obj);
+      }
+    }
+  };
+  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<std::string>("role"));
+  m_path = *(app->get_as<std::string>("path"));
+  if (m_role.empty() || m_path.empty()) {
+    AGL_FATAL("No name or path defined in config");
+  }
+
+  std::string method = *(app->get_as<std::string>("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<std::string>("params");
+  for (const auto& param : *params)
+  {
+    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 (int id)
+{
+  std::string sid = std::to_string(id);
+
+  // This surface is mine, register pair app_name and ivi id.
+  json_object *obj = json_object_new_object();
+  json_object_object_add(obj, m_wm->kKeyDrawingName,
+                         json_object_new_string(m_role.c_str()));
+  json_object_object_add(obj, m_wm->kKeyIviId,
+                         json_object_new_string(sid.c_str()));
+
+  AGL_DEBUG("requestSurfaceXDG(%s,%s)", m_role.c_str(), sid.c_str());
+  m_wm->requestSurfaceXDG(obj);
+
+  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;
+
+    json_object *obj = json_object_new_object();
+    json_object_object_add(obj, m_wm->kKeyDrawingName,
+                           json_object_new_string(m_role.c_str()));
+    json_object_object_add(obj, m_wm->kKeyDrawingArea,
+                           json_object_new_string("normal.full"));
+    m_wm->activateSurface(obj);
+  }
+}
+
+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;
+
+  // 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());
+        setup_surface(id);
+      }
+    }
+  }
+  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;
+}
diff --git a/src/runxdg.hpp b/src/runxdg.hpp
new file mode 100644 (file)
index 0000000..ce0c016
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+#ifndef RUNXDG_HPP
+#define RUNXDG_HPP
+
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+
+#include <gio/gio.h>
+
+#include <ilm/ilm_control.h>
+
+#include <libwindowmanager.h>
+#include <libhomescreen.hpp>
+
+#define AGL_FATAL(fmt, ...) fatal("ERROR: " fmt "\n", ##__VA_ARGS__)
+#define AGL_WARN(fmt, ...) warn("WARNING: " fmt "\n", ##__VA_ARGS__)
+#define AGL_DEBUG(fmt, ...) debug("DEBUG: " fmt "\n", ##__VA_ARGS__)
+#define AGL_TRACE(file,line) debug("%s:%d\n", file,line);
+
+void fatal (const char* format, ...);
+void warn (const char* format, ...);
+void debug (const char* format, ...);
+
+class ILMControl
+{
+  public:
+    ILMControl(notificationFunc callback, void *user_data) {
+        ilm_init();
+        ilm_registerNotification(callback, user_data);
+    }
+
+    ~ILMControl(void) {
+        ilm_unregisterNotification();
+        ilm_destroy();
+        AGL_DEBUG("ilm_destory().\n");
+    }
+};
+
+class Launcher
+{
+  public:
+    virtual void register_surfpid(pid_t surf_pid) = 0;
+    virtual void unregister_surfpid(pid_t surf_pid) = 0;
+    virtual pid_t find_surfpid_by_rid(pid_t app_pid) = 0;
+
+    virtual int launch(std::string& name) = 0;
+    virtual void loop(volatile sig_atomic_t& e_flag) = 0;
+
+    int m_rid = 0;
+};
+
+class POSIXLauncher : public Launcher
+{
+  private:
+    std::vector<pid_t> m_pid_v;
+
+  public:
+    std::vector<std::string> m_args_v;
+
+    void register_surfpid(pid_t surf_pid);
+    void unregister_surfpid(pid_t surf_pid);
+    pid_t find_surfpid_by_rid(pid_t rid);
+
+    int launch(std::string& name);
+    void loop(volatile sig_atomic_t& e_flag);
+};
+
+class AFMLauncher : public Launcher
+{
+  private:
+    std::map<int, int> m_pgids;  // pair of <afm:rid, ivi:pid>
+
+  public:
+    void register_surfpid(pid_t surf_pid);
+    void unregister_surfpid(pid_t surf_pid);
+    pid_t find_surfpid_by_rid(pid_t app_pid);
+};
+
+class AFMDBusLauncher : public AFMLauncher
+{
+  public:
+    int launch(std::string& name);
+    void loop(volatile sig_atomic_t& e_flag) {
+      while (!(e_flag)) { sleep(60*60*24); } }
+
+  private:
+    int get_dbus_message_bus(GBusType bus_type, GDBusConnection* &conn);
+
+    const char* DBUS_SERVICE   = "org.AGL.afm.user";
+    const char* DBUS_PATH      = "/org/AGL/afm/user";
+    const char* DBUS_INTERFACE = "org.AGL.afm.user";
+};
+
+class AFMWebSocketLauncher : public AFMLauncher
+{
+  // not implemented yet
+  public:
+    int launch(std::string& name) { return 0; }
+    void loop(volatile sig_atomic_t& e_flag) {
+      while (!(e_flag)) { sleep(60*60*24); } }
+};
+
+class RunXDG
+{
+  public:
+    RunXDG(int port, const char* token, const char* id);
+
+    void start(void);
+    void notify_ivi_control_cb(ilmObjectType object, t_ilm_uint id,
+                               t_ilm_bool created);
+    static void notify_ivi_control_cb_static (ilmObjectType object,
+                                              t_ilm_uint id,
+                                              t_ilm_bool created,
+                                              void *user_data);
+  private:
+    std::string m_role;
+    std::string m_path;
+
+    std::string m_id;
+
+    int m_port;
+    std::string m_token;
+
+    Launcher *m_launcher;
+
+    LibWindowmanager *m_wm;
+    LibHomeScreen *m_hs;
+    ILMControl *m_ic;
+
+    std::map<int, int> m_surfaces;  // pair of <afm:rid, ivi:id>
+
+    bool m_pending_create = false;
+
+    int init_wm(void);
+    int init_hs(void);
+
+    int parse_config(const char *file);
+
+    void setup_surface(int id);
+};
+
+#endif  // RUNXDG_HPP