Saturday, September 24, 2011

Crazy Chickens game available on N9/N950 Harmattan

Recently I ported my Crazy Chickens game for Harmattan / Maemo6. Its now available on Ovi Store.

Find out more information about this game here. Following are few snaps of game from Harmattan version.






Saturday, September 10, 2011

Accessing Amazon Web service from Qt

Currently I am trying to port my Audiobook reader application to Harmattan. I decided to improve its UI and as a change I decided to show book cover page in background.

Initially I thought to use taglib to fetch album art from audio file, but then I decided to download cover art from internet as some audio file might not contain album art information.

To download cover art from internet, I decided to use Amazon web service. Initially I struggled to successfully communicate with AWS.

AWS required to send signature with every request you send and there is certain procedure to create that signature. Creating signature with Qt is quite easy, but still I was facing problem with retrieving data from AWS because I did not read QUrl documentation carefully.

While generating QUrl, it expect human redable URL string with no percentage encoding. But I was trying to generate QUrl from already encoded parameters.

And as a result I was getting such error message.
"Value 2011-09-11T05%3A18%3A42 for parameter Timestamp is invalid. Reason: Must be in ISO8601 format".

Double encoding was causing above problem.

I am posting my code here, in hope that will be helpful to someone.

First all to communicate with AWS you required to have AWS access key id and Secret Access key. You can get it from here.

Here is AWS doc that explain how to generate signature. And here utility which you can use to verify if you generated correct signature.

Now we are ready for implementation. Following is my code.

Here in below code, I am trying to create AWS request to get all image for mentioned book. In request I am also specifying SignatureMetod to HmacSHA1, by default AWS use HmacSHA256 signature method for comparison.

const char AWS_KEY[] = "YOU ACCESS KEY";
const char AWS_PASS[] = "YOUR SECRET KEY";
const char END_POINT[] = "http://ecs.amazonaws.com/onca/xml";

AwsAlbumArtHelper::AwsAlbumArtHelper(QObject *parent) :
    QObject(parent)
{
    _netManager = new QNetworkAccessManager(this);    
}

QByteArray AwsAlbumArtHelper::getTimeStamp()
{
    QDateTime dateTime = QDateTime::currentDateTimeUtc();
    return dateTime.toString(Qt::ISODate).toUtf8();
}

void AwsAlbumArtHelper::downloadAlbumArt(const QString& bookname)
{
    _bookName = bookname;
    QMap< QString,QString > queryItems;
    queryItems["AWSAccessKeyId"] = AWS_KEY;
    queryItems["ResponseGroup"] = "Images";
    queryItems["Keywords"] = QUrl::toPercentEncoding(bookname);
    queryItems["Operation"] = "ItemSearch";
    queryItems["SearchIndex"] = "Books";
    queryItems["Service"] = "AWSECommerceService";
    queryItems["SignatureMethod"] = "HmacSHA1";
    queryItems["Timestamp"] = QUrl::toPercentEncoding(getTimeStamp());
    queryItems["Signature"] = createSignature(queryItems);

    QUrl downloadUrl = createUrl(queryItems);
    QNetworkRequest request(downloadUrl);

    disconnect(_netManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(imageDownloaded(QNetworkReply*)));
    connect(_netManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(xmlDownloaded(QNetworkReply*)));
    _netManager->get(request);
}
Following code create signature from query parameters. I used code pasted here for signing string with hmacSha1.
QByteArray AwsAlbumArtHelper::createSignature(const QMap< QString,QString >& queryItems)
{
    QUrl url(END_POINT);
    QString stringToSign = "GET\n";
    stringToSign.append(url.host() + "\n");
    stringToSign.append(url.path() + "\n");

    QList<qstring> keys = queryItems.keys();
    for( int i=0; i < keys.count() ; ++i ) {
        stringToSign.append(keys[i]+"="+queryItems[keys[i]]);
        if( i != keys.count() -1  ) {
            stringToSign.append("&");
        }
    }
    QString signature = hmacSha1(AWS_PASS,stringToSign.toUtf8());
    return QUrl::toPercentEncoding(signature);
}

QUrl AwsAlbumArtHelper::createUrl( const QMap< QString,QString >& queryItems )
{
    QUrl url(END_POINT);
    QMapIterator<QString, QString> it(queryItems);
    while (it.hasNext()) {            
        it.next();
        //everything is already URL encoded
        url.addEncodedQueryItem(it.key().toUtf8(), it.value().toUtf8());
    }
    return url;
}
Now we are ready to submit request using downloadAlbumArt method with required book name and AWS should return xml data with various image link. We need to parse xml and use mentioned link to download image.

Sunday, September 4, 2011

Porting Qt app to Harmattan

As I received Nokia N9 Dev kit from Qt Ambassador program, I started working on porting my existing Maemo 5 application to Maemo 6 (Harmattan/Meego) platform.

It seems from documentation that preferred way to create Meego application is by using QML and com.meego components. In order to make my Qt and QML application to work correctly on meego I needed to make quite a few changes.

First thing I noticed is that, Qt Widget are available on harmattan but that just for backword compatiblity and dose not provide native look and feel for Harmattan platform. That mean Qt Widget library are kind of useless on harmattan. We need to use meego touch library or meego QML component.

Some of my application were QWidget based or some were Graphics View based. I ported most of application to use QML and If i can not port to use QML then I  used QDeclarativeItem to load that component to QML.

For some of my application I was locking orientation, using "WA_Maemo5PortraitOrientation" flag in view.setAttribute(Qt::WA_Maemo5PortraitOrientation, true); API call, But on Harmattan I need to use following code in QML page.
 Page{
        orientationLock: PageOrientation.LockPortrait
    }
To make application full screen, I need to disable statusbar and toolbar from PageStackWindow. I used following code

PageStackWindow {
    showStatusBar: false
    showToolBar: false
}
In one of my application I was using GraphicsView framework, I loaded graphics view to QML using QDeclarativeItem and QGraphicsProxyWidget. Visit following link for more info. 

But after GraphicsView loaded to QML, view was displaying some white area that initially I was not able to remove. After a white I realized it was frame for graphics view. I removed it by using setFrameStyle API.
setFrameStyle(QFrame::NoFrame);
Desktop file also needs to be changed to locate icon correctly and to launch only single instance of application.
[Desktop Entry]
Encoding=UTF-8
Version=0.1
Name=appname
GenericName=app name
Comment=some comment
Exec=/usr/bin/single-instance /usr/bin/appname
Terminal=false
Type=Application
Icon=/usr/share/icons/hicolor/64x64/apps/icon.png
Also in control file, to make your application user application and not system component, we need to add following flag.
Maemo-Flags: visible
I used following in my project file to transfer icon file to proper location.
unix  {
    #VARIABLES
    isEmpty(PREFIX) {
        PREFIX = /usr
    }
    BINDIR = $$PREFIX/bin
    DATADIR =$$PREFIX/share

    DEFINES += DATADIR=\\\"$$DATADIR\\\" PKGDATADIR=\\\"$$PKGDATADIR\\\"

    INSTALLS += target desktop icon64

    target.path =$$BINDIR

    desktop.path = $$DATADIR/applications
    desktop.files += ./image/$${TARGET}.desktop

    icon64.path = /usr/share/icons/hicolor/64x64/apps/
    icon64.files += ./image/$${TARGET}.png
}
I also faced segmentation fault while testing Webview with emulator, but on device it works fine.

That's all, most of my experience was smooth while porting and did not struggled much.