Wednesday, December 30, 2009

Adding custom Javascript bindings to WebKIT

This post will show you how to add custom objects / functions which are implemented in C but used in a Javascript that's part of a HTML document that WebKIT renders. We will add a class "myclass" to the Javascript engine instance and it will have one static function named "mymethod()" that returns a single string.

First you need to install all the needed dependencies, assuming you are using Ubuntu or other Debian based system run the following command as root:
  • apt-get install libwebkit-1.0.1 libwebkit-dev
The following very simple HTML page will be used to test that the Javascript binding to the native C function works properly.

simple.html
<html>
<body>
<h1>String in html</h1>

<script type="text/javascript">
document.write("<h1>String from JS:");
document.write(myclass.mymethod());
document.write("</h1>");
</script>

</body>
</html>

C implementation of myclass.mymethod()

mymethod() will be implemented as a static function on the myclass object. The JavaScript framework used by WebKIT can be used with an API that's documented on Apple's pages. The important thing for this simple class is the JSClassDefinition and JSStaticFunction struct both have to be filled out with information about our class, including its static function(s) (mymethod), initialize/constructor callback, finalize/destructor callback and so on. To add the class to the Javascript engine we need something called the JSGlobalContextRef, how you get it depends on the flavor of WebKIT you are using. In this tutorial I will show how to do it with the GTK port of WebKIT. Connect a handler to the window-object-cleared signal which is sent when a new page is loaded (found this out after a lot of googling, reference). In the callback you connect you can call webkit_web_frame_get_global_context(), it will return the JSGlobalContextRef you need.

Enough talking, here is the actual source code for the example,


#include <gtk/gtk.h>
#include <webkit/webkit.h>
#include <JavaScriptCore/JavaScript.h>

static void myclass_init_cb(JSContextRef ctx, JSObjectRef object)
{
// ...
}

static void myclass_finalize_cb(JSObjectRef object)
{
// ...
}

static JSValueRef myclass_mymethod(JSContextRef context,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception)
{
JSStringRef string = JSStringCreateWithUTF8CString("mystring");
return JSValueMakeString(context, string);
}

static const JSStaticFunction class_staticfuncs[] =
{
{ "mymethod", myclass_mymethod, kJSPropertyAttributeReadOnly },
{ NULL, NULL, 0 }
};

static const JSClassDefinition class_def =
{
0,
kJSClassAttributeNone,
"TestClass",
NULL,

NULL,
class_staticfuncs,

myclass_init_cb,
myclass_finalize_cb,

NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};

static void addJSClasses(JSGlobalContextRef context)
{
JSClassRef classDef = JSClassCreate(&class_def);
JSObjectRef classObj = JSObjectMake(context, classDef, context);
JSObjectRef globalObj = JSContextGetGlobalObject(context);
JSStringRef str = JSStringCreateWithUTF8CString("myclass");
JSObjectSetProperty(context, globalObj, str, classObj,
kJSPropertyAttributeNone, NULL);
}

static void window_object_cleared_cb(WebKitWebView *web_view,
WebKitWebFrame *frame,
gpointer context,
gpointer arg3,
gpointer user_data)

{
JSGlobalContextRef jsContext = webkit_web_frame_get_global_context(frame);
addJSClasses(jsContext);
}

static GtkWidget* main_window;
static WebKitWebView* web_view;

static void destroy_cb(GtkWidget* widget, gpointer data)
{
gtk_main_quit ();
}

static GtkWidget* create_browser()
{
GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (web_view));

g_signal_connect (G_OBJECT (web_view), "window-object-cleared", G_CALLBACK(window_object_cleared_cb), web_view);

return scrolled_window;
}

static GtkWidget* create_window()
{
GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), 500, 500);
g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
return window;
}

int main (int argc, char* argv[])
{
gtk_init (&argc, &argv);
if (!g_thread_supported())
g_thread_init (NULL);

GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), create_browser (), TRUE, TRUE, 0);

main_window = create_window();
gtk_container_add(GTK_CONTAINER (main_window), vbox);

gchar* uri = (gchar*) "file://simple.html";
webkit_web_view_open(web_view, uri);

gtk_widget_grab_focus (GTK_WIDGET (web_view));
gtk_widget_show_all (main_window);
gtk_main ();

return 0;
}

Compile & Test

Finally, to compile and test the example code run the following commands:
  • gcc test.c -o test `pkg-config --cflags --libs webkit-1.0`
  • ./test

Wednesday, December 16, 2009

Clutter tutorials

For some time I've been planning to look into Clutter more seriously, it looks like a really nice framework to build a UI engine on top of. Today I found the following two articles that gives a decent introduction to how the API works,

This post goes through the basics of Clutter's C API. It covers how to create an initial empty window, how to render simple rectangles (with textures) and finally how to animate and scale them.
http://tuxradar.com/content/clutter-beginners-tutorial

This one mentions on how it's possible to use gjs to create JavaScript bindings which gives you an easier to use development environment (no memory management and other C "features").
http://townx.org/blog/elliot/introduction-sorts-javascript-desktop-application-development-gjs-and-clutter

Monday, December 14, 2009

Creating a web service client using gsoap.

In this post I'll continue my posts related to creating web services (Part 1, Part 2), as have been shown it's easy to setup and create the server part of the service using Java, Axis2 and Tomcat. However you do not always have access to a very hungry and memory intensive Java stack so this post will cover how you can create the client in native C code. For this I'll be using gSOAP. If you have the web service up and running as describe previously you should be able to access it's WSDL document at http://localhost:8080/axis2/services/ExampleService?wsdl. The WSDL document describes the service in detail and can be used to generate the needed code to access the service. First you start off by installing the package:
  • apt-get install gsoap
Now you can use the wsdl2h tool to generate C bindings (it can also be used to generate C++ bindings, just drop the -c argument)
  • wsdl2h -c -o mywebservice.h http://localhost:8080/axis2/services/ExampleService?wsdl
  • soapcpp2 -C -c mywebservice.h -I/usr/include/gsoap
The following very simple code segment shows how to call the remote service, both the getSomeValue() and setSomeValue() function. It can be compiled by doing:
  • gcc -I/usr/include/gsoap myclient.c -o myclient.o soapC.c soapClient.c /usr/include/gsoap/stdsoap2.c
myclient.c
#include "soapH.h"
#include "stdio.h"
#include "MyWebService.nsmap"

int main(int argc, char *argv[])
{
struct soap *soap = soap_new();
struct _ns1__getSomeValueResponse response;
struct _ns1__setSomeValue value;

*value.args0 = 1.0f;

if (soap_send___ns2__setSomeValue(soap, NULL, NULL, &value) == SOAP_OK)
printf("okay");
else
soap_print_fault(soap, stderr); // display the SOAP fault on the stderr stream

if (soap_call___ns2__getSomeValue(soap, NULL, NULL, &response) == SOAP_OK)
printf("value: %f\n", *response.return_);
else // an error occurred
soap_print_fault(soap, stderr); // display the SOAP fault on the stderr stream
}