Protonet App for webOS: Creating a webOS socket

Von Protonet Team. Veröffentlicht 29. August 2010.

So we’re building this thing we call Protonet we all use it and we have fun using it. But when my brother isn’t sitting at his laptop, he’s virtually unreachable. After building a rudimentary iPhone client, I figured that if I can chat in the train on my iPhone, then why couldn’t my brother, Ali, on his Palm Pre? So, I set out to make a client for his phone.

To understand why it’s necessary to do the things described in this blog post you need to understand how the palm SDK works.
When Palm called the thing webOS they didn’t call it that way because thought it might sound cool, they called it that way because essentially every application that ran on the pre in the beginning is actually a web application. Subsequently as with most web applications initially you could only write your palm pre applications in HTML/Js/css. In webOS terms this is called the MOJO toolkit. The result is that while it makes it very simple to write applications for the Pre it’s not possible to open sockets and do other things.

The first step to overcome this was to allow creating SDL applications for games and similar applications. That still doesn’t solve our socket issue for normal applications though. So the second step is the so called Hybrid application. Think of it as a web application that has a daemon process loaded on start which is written in C or C++.

The thing about these daemons is that they actually run in the SDL event loop. The first question would be why didn’t you just use SDL_net? SDL_net doesn’t support asynchronous operation and it doesn’t exactly have the most stable history in the world. You would have to use net2, which wraps SDL_net into its own thread. I also did not want to use libevent, partly because it adds a big library as dependency and partly because it actually IS an event loop.

Here’s a quick overview of what how it works:

  1. You create the web application
  2. You create a c plugin
  3. You tell the web application to load the c plugin as an object, that will allow you to access the registered functions in javascript using
    $(‘myPlugin’).whateverFunction
  4. You communicate between plugin and webapp using callbacks you register on each side

So, let’s get started! I’m going to start with the Javascript/HTML side and then explain the C side and I’ll show you how to glue them together along the way. The source-code for this project is available on http://github.com/protonet/webos-socket

If you already have some webOS experience, you might probably want to skip section 1.

Section 1: The basics

When you launch your application you are pretty much in the app-assistant.js that’s where we’re going to tell it to load our socket scene. Scenes are the viewports in webOS terms.

this.stageController.pushScene("socket", new socket());

The HTML for this view is in “mojo/app/views/socket/socket-scene.html”. In there we create a Textfield for the server ip address, a button to connect and a place to output some data to:

 

We then need to instantiate the mojo elements in the views controller, which is the “app/controllers/socket-assistant.js” as defined in the sources.json file.

this.controller.setupWidget('ConnectBtn',
  this.attributes = {
  },
  this.model = {
    label: 'Connect',
    disabled: false
  }
);
this.controller.setupWidget("ipField",
  this.attributes = {
    hintText: $L("ip address"),
    multiline: false,
    enterSubmits: true,
    focus: true
  },
  this.model = {
    value: "192.168.1.136",
    disabled: false
});

That should be pretty self explanatory. Here’s a link to the textfield reference and the button reference.

We then tell the application to listen for taps on the connect button:

Mojo.Event.listen(this.controller.get("ConnectBtn"),Mojo.Event.tap, this.handleConnect);

Section 2: The hybrid glue

First of all you will need to add

"plug-ins": true

to your appinfo.json. If you do not do this, the plugin application/daemon will not start no matter what. And it won’t throw any exceptions either. It will just silently fail.

Once you’ve done that you tell your scene to load the plugin and you create a remote control that is usable from your javascript code.


Again pretty self explanatory. socket_plugin is the name of the executable you package with your application. socketPlugin will become your remote control. So if you use $(‘socketPlugin’) you can access methods that were declared within the plugin. So how do you use this? You put the above code in the top of the socket-scene.html. We already have a callback for a tap on the connect button defined in previous section. Let’s fill it with life:

  handleConnect: function(event)
  {
    $('socketPlugin').openSocket($('ipField').mojo.getValue(), 2000);
  }

The above code will actually execute a function that is registered inside your C plugin and pass it the value of our ip address text field and the port number 2000.

We also create a callback function for the plugin to use whenever it has data available:

$('socketPlugin').didReceiveData = this.didReceiveData.bind(this);

And we bind it to the following function:

  didReceiveData: function(a)
  {
    $('status').innerHTML = String(a);
  },

Section 3: The actual plugin

The plugin will have it’s own appinfo in which you will specify how much memory consumption is allowed and what kind it is. In this case the file is called socket_plugin_appinfo.json

{
    "type": "hybrid",
    "requiredMemory": 5
}

The basic structure is going to look like this:

initialize_SDL();
initialize_PDL();

RegisterYourJSFunctions();

do {
    ProcessSDLEventLoop();
} while (Event.type != SDL_QUIT);

cleanupBeforeQuit();

Functions that should be callable from your mojo/JS code will always be declared in the following way:

PDL_bool openSocket(PDL_JSParameters *params)

Then you need to actually register these to make them available:

PDL_RegisterJSHandler("openSocket", openSocket);

Here’s what we do:

  1. JS calls our openSocket function
  2. We process the parameters, since this isn’t strictly typed. Keep in mind that the whole example is very basic and there’s virtually no exception handling. I’m basically considering a best case scenario.

PDL has it’s own helper functions to handle parameter parsing.

if (PDL_GetNumJSParams(params) < 2 )
{
    PDL_JSException(params, "You must provide the ip address and port");
    return PDL_TRUE;
}
const char *ip_address = PDL_GetJSParamString(params, 0);
int port               = PDL_GetJSParamInt(params, 1);

PDL_JSException will actually throw an exception that you will not see in the application itself but may very well see using the
palm-log application.

Let’s take a look at how we call the javascript callback for receiving data works:

const char *params[1];
params[0] = theData;
PDL_Err mjErr = PDL_CallJS("didReceiveData", params, 1);
if ( mjErr != PDL_NOERROR )
{
    printf("error: %sn", PDL_GetError());
}

We will just call the callback defined in the socket-assistant.js with one parameter which is a string. Again this is self explanatory if you’ve seen it once.

Section 4: Building a basic asynchronous socket

The problem to solve was the following:

How do you create a non blocking socket that works with the SDL event loop without too much effort?

My solution was using SIGIO. Basic explanation:

  1. You create a socket
  2. You register a signal handler for SIGIO
  3. You make the socket asynchronous and let the signal handler do the rest

What happens is this:

  1. You connect to the server and you don’t need to worry about the SDL_event loop
  2. Once data is available for reading the signal handler kicks in. We proccess the data and may send it to our didReceiveData JS callback.

So what do you do when you want to handle multiple socket connections at once? Well you use sa_sigaction and set a proper context. Either that or you wrap your socket connections into threads and instead of registering your signal handler to the current pid with getpid, you register them to the threads gettid. I haven’t tried the latter though.

The result looks as follows:

if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    printf("socket() failed");
    return -1;
}

/* Set up the server address structure */
memset(&sockaddr, 0, sizeof(sockaddr));       /* Zero out structure */
sockaddr.sin_family = AF_INET;                /* Internet family */
sockaddr.sin_addr.s_addr = inet_addr(ip_address); /* Any incoming interface */
sockaddr.sin_port = htons(port);              /* Port */

/* Bind to the local address */
if (connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) < 0){
    printf("connect() failed");
    return -1;
}

/* Set signal handler for SIGIO */
handler.sa_sigaction = SIGIOHandler;
/* Create mask that mask all signals */
if (sigfillset(&handler.sa_mask) < 0){
    printf("sigfillset() failed");
    return -1;
}
/* No flags */
handler.sa_flags = 0;

if (sigaction(SIGIO|SIGHUP, &handler, 0) < 0){
    printf("sigaction() failed for SIGIO");
    return -1;
}

/* We must own the socket to receive the SIGIO message */
if (fcntl(sock, F_SETOWN, getpid()) < 0){
    printf("Unable to set process owner to us");
    return -1;
}

/* Arrange for nonblocking I/O and SIGIO delivery */
if (fcntl(sock, F_SETFL, O_NONBLOCK | FASYNC) < 0){
    printf("Unable to put client sock into non-blocking/async mode");
    return -1;
}

Section 5: Debugging/Running etc.

You can build and run the example using:

mac/package-it.sh
palm-install com.protonet.sockettest_1.0.0_all.ipk
palm-launch com.protonet.sockettest

To see the Javascript exceptions you can then do:

palm-log -f com.protonet.sockettest

However since I couldn’t figure out how to see the printf log messages. I wrote my log messages to the syslog
You might just want to ssh to the palm pre once it’s hooked up your USB and then tail the syslog.

ssh -l 10022 root@localhost

HTH Reza Jelveh

FAQ

Q. Why isn’t the plugin launching?

A. Make sure you have

echo "filemode.755=socket_plugin" > $STAGING_DIR/package.properties

somewhere in your packaging script.

also make sure you have

"plug-ins": true

in your appinfo.json

Q. How would I package my socket in a thread?

A. You need to replace getpid with gettid

Q. The application doesn’t work in the emulator

A. You cannot test hybrid applications in the emulator, yet. The emulator is i386 but the libraries provided by the Palm toolchain are arm. That might change in the future but for now your stuck with getting a real webOS device.