Posts Tagged ‘adobe air’

Trilite, application design experiment with XULRunner and .NET

Goodbye Adobe Air

Despite positive first impressions with Adobe Air, I began avoiding it a while ago for a few reason:

  • Air’s focus was very much geared towards Flash development, not HTML/CSS/JS, and I had no interest in Flash development
  • A lot of interesting web technologies never manifested within Air (SVG, WebGL) and it looked doubtful that Adobe cared to add anything that might challenge Flash
  • Linux support was dropped – a platform dropping OS support is not a good sign
  • Native interaction support, a feature in Air 2 which I was excited about, didn’t impress me in its implementation and it was very much geared towards Flash/ActionScript development
  • Adobe’s push for Air became more a platform for mobile development rather than desktop development, to the extent that desktop development was pushed far into the background

With all of the negatives above, coupled with the propriety, vendor lock-in, nature of Air, I really didn’t feel like using it for development of anything.

XULRunner / XPCOM

I was still optimistic and interested in web technologies (HTML/CSS/JS) for layout and styling in desktop applications. As I stated previous:

Looking into cross-platform GUI frameworks, I’ve played around with WinForms (cross platform with Mono), Qt, Gtk, and wxWidgets. I’ve been disappointed to various degrees with all of them. It hit me that the most flexible and powerful cross-platform layout and styling framework out there is the HTML/CSS combo. It’s not perfect (e.g. floats, vertical centering) but it’s pretty damn good.

I stated looking at other solutions. I didn’t want to relive my experiences dealing with compiling webkit (though I was, and still am, tempted to play around with chromiumembedded), so I went with XULRunner, which allows for bootstrapping XUL-based applications (e.g. Firefox). XUL is not HTML, and in actuality provides markup to design a UI with native controls, but one control provided is the iframe control which renders and interprets HTML, CSS, and Javascript.

Having XUL for the application interface, you could write a desktop application with the application logic in Javascript, but you’re bound by the same limitations as a web application. XPCOM is a solution to these limitations and allows for interactions with the host system for things such as reading files, running another process, etc. That said, I wasn’t excited to learn XPCOM – it seemed convoluted and I didn’t want to waste time doing a deep-dive into yet another framework and being tied down to its limitations. I figured if I could write the application logic in another executable and have it communicate with the UI via a lightweight (very lightweight) XPCOM-based component, that would be ideal. In terms of simplicity and availability, sockets seemed like the go-to solution for Inter-process communication between XPCOM and anything else.

The Socket Bridge

So I came around to the idea of a Socket Bridge (I had originally played around with it in an Adobe Air project and was able to apply it here as well). Within the XUL application, Javascript-based XPCOM code would implement the Socket Bridge client and the executable handling the application logic would implement the Socket Bridge server, and the 2 could communicate easily.

SocketBridge

An HTTP server could take the place of the SocketBridge server, but I felt that was overkill, less flexible, and added an additional layer of complexity as the server then needed to be connected to the application code.

Trilite

As a proof-of-concept, I began working on Trilite, a simple HTTP profiling tool, with the application logic done in C#, that would send a number of HTTP requests to a server, capture the time it took to get a response, and calculate some simple stats about the results. A pretty simple application but something pretty handy for optimization work.

I’m pretty happy with the results thus far, particularly with regards to having a consistent, stable, and cross-platform UI.

You can find the current code in the Trilite repository.

There is no bootstrap to launch the Socket Bridge server and XULRunner app, they need to be executed manually for the application to launch:

  • Launch the server by running /trilite.public/app-server/trilite/trilite/bin/Debug/trilite.exe
  • Launch XULRunner, in /trilite.public/xulrunner, with the application.ini file in the root directory. For Windows, you can also run the trilite shortcut in the root directory.

A few screenshots under Windows:

Trilite

Trilite

Trilite

And here’s Trilite running under Ubuntu:

Trilite on Ubuntu

This post should, hopefully, provide a top-level overview of the application architecture. I’ll be writing more about XUL, XPCOM, the Socket Bridge, and Trilite in subsequent posts, providing more details and code.

Launching a Mono/.NET exe from Adobe Air

I’m working on some bridging code between Adobe Air and Mono/.NET stuff. The first challenge in the process was figuring out how the launch the .exe from Adobe Air. In Windows you can just launch the .exe file (assuming the .NET Framework is installed), but for other system you need to pass it as an argument to the mono executable. The JavaScript code is shown below.

var proc = new air.NativeProcess();
if
(air.Capabilities.os.toLowerCase().indexOf("win") > -1) {
    
var file = air.File.applicationDirectory;
    file = file.resolvePath(
"air.SocketBridgeServer.exe");
                            
    
var nativeProcessStartupInfo = new air.NativeProcessStartupInfo();
    nativeProcessStartupInfo.executable = file;

    proc.start(nativeProcessStartupInfo);
}
else {
    
var mono = new air.File();
    mono = mono.resolvePath(
"/usr/bin/mono");
                            
    
var exeFile = air.File.applicationDirectory;
    exeFile = exeFile.resolvePath(
"air.SocketBridgeServer.exe");
                            
    
var args = new air.Vector["<String>"];
    args.push(exeFile.nativePath);
                            
    
var nativeProcessStartupInfo = new air.NativeProcessStartupInfo();
    nativeProcessStartupInfo.executable = mono;
    nativeProcessStartupInfo.arguments = args;
    proc.start(nativeProcessStartupInfo);
}

This handles Windows, OS X, and should handle most distros of Linux; the location of the mono executable is the tricky part, Adobe Air doesn’t read the path environment variable, so the exact location of mono must be specified. mono is usually in /usr/bin, but custom distros, installations, etc. could put it elsewhere.

poly2path

I’m working on a little SVG project using Raphaël. Unfortunately, Illustrator exports polygons in its SVG output which is not supported by Raphaël (only paths are supported). So I wrote an app to convert the SVG polygon string to an SVG path string.

Please upgrade your Flash Player This is the content that would be shown if the user does not have Flash Player 9.0.115 or higher installed.

the conversion…

poly2path conversion

Note that you only input the points data from the polygon (from the points attribute), not the entire polygon element. The result is the path string for the d attribute of the path element.

The conversion is very simple and based upon the fact that a polygon is a path starting with an absolute moveto, linetos to each of the points, and a closepath (this bug report [yes, a bug report!] was pretty helpful).

I actually wanted to render the output, but I was disappointed to discover that Adobe Air doesn’t currently support SVG.

The main reason for not including it was runtime size concerns (adding it would have increased the runtime size by 15 to 20 percent). Initially, the main pain-points regarding AIR were the size of the runtime, integration with the operating system and native APIs, support for the <canvas> tag and new CSS properties, and JavaScript performance. These priorities, coupled with a trend toward reduced interest in SVG graphics, led to SVG support not being included in the current version of Adobe AIR.

JavaScript MD5 hash for air.FileStream

This is the MD5 file hash used in progTools.

You can copy and paste the code below or get it via. bitbucket.

The code builds upon Paul Johnston’s MD5 implementation and you’ll need to include his code first.

<!-- Paul Johnston's MD5 implementation -->
<script type="text/javascript" src="tools/md5/md5.js"></script>

<!-- Additional MD5 functions for working on air.FileStream; requires Paj's md5.js -->
<script type="text/javascript" src="tools/md5/md5_file.js"></script>

core_md5_ex() is a modified/hacked version of the original core_md5() to allow for progressively processing chunks of data, instead of doing it all in one go.

// modified version of Paul Johnston's MD5 implementation
function core_md5_ex(x, len, abcd_start, append_padding, total_len)
{
/* append padding */
if (append_padding) {
    x[len >> 5] |= 0x80 << ((len) % 32);
    x[(((len + 64) >>> 9) << 4) + 14] = total_len*8;
}

var a = abcd_start[0];
var b = abcd_start[1];
var c = abcd_start[2];
var d = abcd_start[3];

for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;

a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);

a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}

return Array(a, b, c, d);
}

What’s changed is:

  • We pass in the starting values (which is the current hash on the data thus far, minus the necessary padding)
  • We use a variable to determine whether or not to append padding to the input; this is only done when processing the last block on data read from the stream.

hex_md5_stream() is the function which processes the stream: reading in chunks, transforming the bytes in inBytes into an array 4-byte WORDs, and calling core_md5_ex() with the necessary data.

function hex_md5_stream(inStream)
{
    
var abcd_start = new Array();
    abcd_start.push(1732584193);
    abcd_start.push(-271733879);
    abcd_start.push(-1732584194);
    abcd_start.push(271733878);
    
    
var inBytes = new air.ByteArray();
    
var appendPaddingToBlock = false;
    
var totalLen = 0;
    
    
while (inStream.bytesAvailable > 0)
    {
        inStream.readBytes(inBytes, 0, Math.min(8192, inStream.bytesAvailable));        
        
if(inBytes.length < 8192)
        {
            appendPaddingToBlock =
true;
        }
        
        totalLen += inBytes.length;
        
        bin =
new Array();
        
for(var i = 0; i < inBytes.length * 8; i+=8)
            bin[i>>5] |= (inBytes[i>>3] & 0xff) << (i%32);
        
        abcd_start = core_md5_ex(bin, inBytes.length*8, abcd_start, appendPaddingToBlock, totalLen);
        inBytes.clear();
    }
    
    
return binl2hex(abcd_start);    
}

Here’s the top-level view of how it works:

var inStream = new air.FileStream();
                
var file = new air.File();
file.url =
"file:///" + filename;                

inStream.open(file, air.FileMode.READ);
var md5result = hex_md5_stream(inStream);                 
inStream.close();             

progTools and Adobe Air

I made a little app to get my feet wet with Adobe Air. progTools just packages together a few common functions I find myself using frequently. You can get it my clicking the install badge (one of the very cool aspects of Adobe Air) below.

Please upgrade your Flash Player This is the content that would be shown if the user does not have Flash Player 9.0.115 or higher installed.

(h/t to Peter Elst for the AIR Badge WordPress plugin)

What’s offered:

  • Conversion to/from a Unix timestamp
  • MD5 hash on a string
  • MD5 hash on a file
  • SHA1 hash on a string

progTools 1.2

Not too impressive, and only the MD5 file hash really utilizes a desktop feature of the Air framework, but it is somewhat useful and, at least in my case, I won’t end up going to Paj’s Home to use the javascript md5 implementation demo quite as often. Note, Paj’s MD5 library was used and I slightly modified core_md5() for the file hash to deal with hashing successive blocks. I’ll post the code soon.

I initially dismissed Air, back when it was Apollo, as I didn’t see the value in having yet another proprietary framework which didn’t really offer much beyond what was capable within a browser, aside from local file access. A few additions to the framework and a few realizations on my part have shifted my views:

  • Air supports HTML/CSS for layout and styling. Looking into cross-platform GUI frameworks, I’ve played around with WinForms (cross platform with Mono), Qt, Gtk, and wxWidgets. I’ve been disappointed to various degrees with all of them. It hit me that the most flexible and powerful cross-platform layout and styling framework out there is the HTML/CSS combo. It’s not perfect (e.g. floats, vertical centering) but it’s pretty damn good.
  • Support in Air 2 for sockets and interaction with native applications. This vastly opens the field for the types of applications possible with Air.
  • Market support from Adobe. The Air Marketplace is perhaps not too impressive, but it’s a major step in the right direction for desktop apps. Both Microsoft and Apple have their own stores planned, but with the success of such catalogs on smartphones for years now, why did it take so long to figure it out?
  • Install badges. They’re cool and important as they provide a bridge between the web and the desktop. Odd, but it seems Adobe more-so than Microsoft or Apple seems to understand the web-desktop relationship. Again, why is Adobe, a company that was fairly divorced from the desktop application space, the first to figure out that this was something important or at least the first to actually build it.

Now it’s not all sunshine and roses. Making an HTML/AJAX app in Air brings up a problem every AJAX developer has likely faced at some point. Javascript is slow… very slow. JavaScriptCore/Nitro, V8, Chakra, Tracemonkey… it doesn’t really matter (though performance improvements are being made), once your volume of data grows you’ll cringe at how slow things become. Coming from C++, C#, or even PHP, it’s painful to witness. In progTools a file only a few megabytes large will noticeably stall the application (I didn’t do the call asynchronously, but that’s besides the point). ActionScript is perhaps better and interop to a native executable could also alleviate the issue, but ultimately I’d simply like a faster JavaScript engine.

A second issue, relevant but not specific to Adobe Air, is code signing; you’ll notice the scary warning when installing progTools. Code signing is bullshit. Expensive bullshit. Yet, every platform developer is requiring it due to some misguided attempt at security. If you want to install progTools, the chain of trust is between me » this web server » you. Sticking a certificate authority in this chain is nonsense – a typical user will not know the CA and cannot establish any level of trust with some random, corporate CA.

Coding signing simply punishes small developers and establishes a new industry to leech from our wallets. In addition, as this user on StackOverflow asserts, it may well hamper the success of Air:

When you visit a site that lets you download an AIR app, it pops up big red screaming warnings about the imminent trashing of your computer, the theft of your identity and a life of torment[1]. Unless, of course, all the bedroom programmers decide to cough up the ongoing cost of certification.

User encouragement FAIL. Hobby developer encouragement FAIL. Technophobe terrorficiation avoidance FAIL.

I love AIR, but I don’t know what they were thinking with the installer. Laywers’ office moved closer to the developers’ over at HQ or something?

Anyways, I’m done ranting. I’ll eventually suck it up and get a certificate as I’m powerless to do anything else.

As for Air, I’ve just scratched the surface, but I’m impressed.

oh, and if you’d like to see something added to progTools, just let me know.