Develop and Configure

Version added 20-Jun-2018| Modified 15-Oct-2018

To create a native app, you must write the source code and prepare the required configuration files.

For easier understanding, the process to create a native app is explained using the example of an app named com.example.app.nativeqt that has the following features:

  • Displays a "Hello, Native Qt Application" message on screen.

  • Calls com.webos.service.applicationmanager/registerNativeApp method.

  • Prints log on /var/log/messages file when it is first launched.

  • Prints log with the updated parameter and status whenever the app is relaunched.

The directory structure of com.example.app.nativeqt must be as follows:

com.example.app.nativeqt
├── main.cpp
├── ServiceRequest.h
├── ServiceRequest.cpp
├── appinfo.json
├── icon.png
├── com.example.app.nativeqt.pro
└── README.md

Prerequisites

Before you begin developing the native app, you must:

  • Build and flash the webOS OSE image. For detailed information, see Building webOS OSE and Flashing webOS OSE.

  • Create a project directory (com.example.app.nativeqt) for the sample native app.

$ mkdir com.example.app.nativeqt

Source Code

First, define the functionality of the native app on the source code.

ServiceRequest.h

Write a class that can register to luna-service2 and call registerNativeApp() method of System and Application Manager (SAM). This header file has the function declaration.

  • Create and update the file : ServiceRequest.h

    • Directory : com.example.app.nativeqt

#ifndef SERVICEREQUEST_H
#define SERVICEREQUEST_H
 
#include <glib.h>
#include <string>
#include <luna-service2/lunaservice.h>
#include <pbnjson.hpp>
#include <PmLog.h>
 
class ServiceRequest
{
public:
    ServiceRequest(std::string appId);
    virtual ~ServiceRequest();
    LSHandle* getHandle() const { return m_serviceHandle; }
    void registerNativeApp();
 
protected:
    LSHandle* acquireHandle();
    void clearHandle();
 
private:
    GMainLoop* m_mainLoop;
    LSHandle* m_serviceHandle;
    std::string m_appId;
};
#endif

ServiceRequest.cpp

Define SeviceRequest class methods. In addition, add methods related to PmLog and PbnJson to use conveniently.

  • Create and update the file : ServiceRequest.cpp

    • Directory : com.example.app.nativeqt

#include "ServiceRequest.h"
 
static PmLogContext getPmLogContext()
{
    static PmLogContext s_context = 0;
    if (0 == s_context)
    {
        PmLogGetContext("NativeQtApp", &s_context);
    }
    return s_context;
}
 
static pbnjson::JValue convertStringToJson(const char *rawData)
{
    pbnjson::JInput input(rawData);
    pbnjson::JSchema schema = pbnjson::JSchemaFragment("{}");
    pbnjson::JDomParser parser;
    if (!parser.parse(input, schema))
    {
        return pbnjson::JValue();
    }
    return parser.getDom();
}
 
static std::string convertJsonToString(const pbnjson::JValue json)
{
    return pbnjson::JGenerator::serialize(json, pbnjson::JSchemaFragment("{}"));
}
 
ServiceRequest::ServiceRequest(std::string appId)
    : m_mainLoop(g_main_loop_new(nullptr, false))
    , m_serviceHandle(nullptr)
{
    m_appId = appId;
    m_serviceHandle = acquireHandle();
}
 
ServiceRequest::~ServiceRequest()
{
    clearHandle();
    if (m_mainLoop)
    {
        g_main_loop_quit(m_mainLoop); // optional!
        g_main_loop_unref(m_mainLoop);
        m_mainLoop = nullptr;
    }
}
 
LSHandle* ServiceRequest::acquireHandle()
{
    LSError lserror;
    LSErrorInit(&lserror);
 
    LSHandle* handle = nullptr;
    if (!LSRegister(m_appId.c_str(), &handle, &lserror))
    {
        LSErrorPrint(&lserror, stderr);
        return nullptr;       
    }
     
    if (!LSGmainAttach(handle, m_mainLoop, &lserror))
    {
        LSErrorPrint(&lserror, stderr);
        return nullptr;
    }
    return handle;
}
 
void ServiceRequest::clearHandle()
{
    LSError lserror;
    LSErrorInit(&lserror);
    if (m_serviceHandle)
    {
        LSUnregister(m_serviceHandle, &lserror);
        m_serviceHandle = nullptr;
    }
}
 
static bool registerNativeAppCallback(LSHandle* sh, LSMessage* msg, void* ctx)
{
    PmLogInfo(getPmLogContext(), "REGISTER_CALLBACK", 1, PMLOGJSON("payload", LSMessageGetPayload(msg)),  " ");
     
    pbnjson::JValue response = convertStringToJson(LSMessageGetPayload(msg));
    bool successCallback = response["returnValue"].asBool();
    if (successCallback)
    {
        std::string message = response["message"].asString();
        PmLogInfo(getPmLogContext(), "REGISTER_CALLBACK", 1, PMLOGKS("message", message.c_str()),  " ");
 
        if (message == "registered")
        {           
            // first launch..
        }
        else if (message == "relaunch")
        {
            if (response.hasKey("parameters"))
            {
                pbnjson::JValue launchParams = response["parameters"];
                PmLogInfo(getPmLogContext(), "REGISTER_CALLBACK", 1,
                            PMLOGJSON("parameters", convertJsonToString(launchParams).c_str()),  " ");
                 
                // process launch params..
            }
        }
    }
    else
    {
        PmLogError(getPmLogContext(), "REGISTER_CALLBACK", 0, "RegisterNativeApp Callback Error" );
        // error handling..
    }
    return true;
}
 
void ServiceRequest::registerNativeApp()
{
    PmLogInfo(getPmLogContext(), "APP_REGISTER", 0, "Register Native app");
    LSError lserror;
    LSErrorInit(&lserror);
    LSHandle* handle = getHandle();
    if (!handle) {
        PmLogError(getPmLogContext(), "APP_REGISTER", 0, "LSHandle is NULL" );
    }
 
    if (!LSCall(handle,
                "luna://com.webos.service.applicationmanager/registerNativeApp",
                "{}",
                registerNativeAppCallback,
                NULL,
                NULL,
                &lserror))
    {
        LSErrorPrint(&lserror, stderr);
    }
}

A brief explanation of the above file:

  • Line(3~11) : A function that calls PmLogGetContext() in PmLog library to print log on /var/log/messages. For more details about pmlog, see Using Pmlog in C/C++.

  • Line(13~28) : Create pbnjson utility functions, which convert String to Json and Json to String based on pbnjson library. pbnjson is a JSON engine, implemented as a pair of libraries with APIs for easier C and C++ abstraction.

  • Line(30~47) : Define constructor and destructor of ServiceRequest Class.

  • Line(49~78) : Define functions to register and unregister "com.example.app.nativeqt" to and from luna-service. For more details about luna-service functions, see the Native services section.

  • Line(80~112) : Implement the callback function of registerNativeApp.

    • Line(91~94) : When the app first calls the method,  "message" value is "registered" in response.

    • Line(95~105) : When the app is already running and SAM's launch() method is called, the value of "message" comes up as "relaunch". If the user gives a parameter of "params" when calling launch(), the app can be delivered with the value of "parameters".

  • Line(115~135) : Call registerNativeApp() method of SAM.

main.cpp

For the sample native app (com.example.app.nativeqt), you must:

  • Create and update the file : main.cpp

    • Directory : com.example.app.nativeqt

#include "ServiceRequest.h"
#include <QApplication>
#include <QLabel>
#include <QDesktopWidget>
#include <QWindow>
#include <qpa/qplatformnativeinterface.h>
 
int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QRect rec = QApplication::desktop()->screenGeometry();
 
    QLabel *label = new QLabel("Hello, Native Qt Application!!");
    label->setFixedSize(rec.width(), rec.height());
    label->setStyleSheet("background-color:yellow;");
    label->setAlignment(Qt::AlignCenter);
 
    QFont font = label->font();
    font.setPointSize(50);
    label->setFont(font);
    label->show();
 
    ServiceRequest s_request("com.example.app.nativeqt");
    s_request.registerNativeApp();
 
    QWidget *widget = label->window();
    QWindow *window = widget->windowHandle();
 
    QApplication::platformNativeInterface()->setWindowProperty(window->handle(), QStringLiteral("appId"), QStringLiteral("com.example.app.nativeqt"));
 
    return app.exec();
}

A brief explanation of the above file:

  • Line(1) : Include "ServiceRequest.h" header file which has methods that can call services based on luna-service2.

  • Line(2~6) : Include Qt header files.

  • Line(8~21) : Create QApplication and QLabel. Set label's position, font-size and style. After that, display the label.

  • Line(23~24) : Declare ServiceRequest object and call registerNativeApp() method.

  • Line(26~29) : Use QWindow::Handle() to get QPlatformWindow from QLabel. Set "appId" to the window.

  • Line(31) : Enters the main event loop and waits until exit() is called, then returns the value that was set to exit(): 0 if exit() is called via quit().

For detailed information on Qt, see Qt Documentation.

README.md

This file provides general information of the native app. For the sample native app (com.example.app.nativeqt), you should:

  • Create and update the file : README.md

    • Directory : com.example.app.nativeqt

If the REAMD.md file is missing, a build error occurs.
Make sure the 'Summary' subsection is a single line. Even a blank space before the 'Description' section is considered a part of the summary and can cause the build to fail. 
Here is a snippet of the README.md file that will not cause a build error. Any whitespace character in the red-colored line would cause a build error.

Summary
-------
native app sample
(no blank space)
Description
-----------
Summary
-------
native app sample 

Description
----------- 

native app sample

How to Build on Linux
---------------------

## Dependencies

Below are the tools and libraries (and their minimum versions) required to build sample program:

* qmake

## Building

    $ cd build-webos
    $ source oe-init-build-env
    $ bitbake com.example.app.nativeqt 

Copyright and License Information
=================================
Unless otherwise specified, all content, including all source code files and
documentation files in this repository are:

Copyright (c) 2018 LG Electronics, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache-2.0

Configuration Files

This section describes how to prepare the configuration files required to build and test the native app.

appinfo.json

Apps are required to have metadata before they can be packaged. This metadata is stored in a file called appinfo.json, which is used by the webOS device to identify the app, its icon, and other information that is needed to launch the app. For the sample native app (com.example.app.nativeqt), you must:

  • Create and update the file : appinfo.json

    • Directory : com.example.app.nativeqt

{
    "id": "com.example.app.nativeqt",
    "version": "0.1.0",
    "vendor": "My Company",
    "type": "native",
    "main": "com.example.app.nativeqt",
    "title": "Native qt App",
    "icon": "icon.png"
}

A brief explanation of the above file:

  • Line(2) : The ID for the app.

  • Line(5) : The type of the native app.

  • Line(6) : The executable file name.

  • Line(7) : The title to be shown on the Home Launcher app.

  • Line(8) : The icon to be shown on the Home Launcher app. Make sure the icon file is available in the project root directory. You can use your own icon.png (80*80) file.

For more details, see appinfo.json.

qmake Project File

This file specifies the application name and the qmake template to be used for generating the project, as well as the source, header, and UI files included in the project.

  • Create and update the file : com.example.app.nativeqt.pro

    • Directory : com.example.app.nativeqt

TARGET = com.example.app.nativeqt
 
CONFIG += qt
QT += widgets gui-private
 
CONFIG += link_pkgconfig
PKGCONFIG += luna-service2 glib-2.0 pbnjson_cpp PmLogLib
 
SOURCES += ServiceRequest.cpp main.cpp
HEADERS += ServiceRequest.h
 
 
INSTALL_APPDIR = $${WEBOS_INSTALL_WEBOS_APPLICATIONSDIR}/com.example.app.nativeqt
 
target.path = $${INSTALL_APPDIR}
 
icon.path = $${INSTALL_APPDIR}
icon.files = icon.png
 
appinfo.path = $${INSTALL_APPDIR}
appinfo.files = appinfo.json
 
INSTALLS += target icon appinfo

A brief explanation of the above file:

  • Line(1) : Set TARGET name.

  • Line(3) : The CONFIG variable is a special variable that 'qmake' uses when generating a Makefile. qt is added to the list of existing values contained in CONFIG.

  • Line(4) : Link against the Qt Widgets Module. Add 'gui-private' to use private GUI include directories.

  • Line(6~7) : 'qmake' can configure the build process to take advantage of external libraries that are supported by pkg-config, such as the luna-servic2, glib, pbnjson, and PmLog libraries.

  • Line(9) : A list of source code files to be used when building the project.

  • Line(10) : A list of filenames of header (.h) files used when building the project.

  • Line(12) : Set installed directory on target board. INSTALL_APPDIR would be /usr/palm/applications/com.example.app.nativeqt on the target.

  • Line(14~20) : *.files specifies a path in project directory and *.path specifies the path to the file system to be installed on the target.

  • Line(22) : Add targets, icons, and appinfo files from the INSTALLS list.

For more details, see qmake Project Files.

Except as noted, this content is licensed under Creative Commons Attribution 4.0 and sample code is licensed under Apache License 2.0.