June 11, 2013

Getting into the Harmattan Share menu: The missing guide

Meego-Harmattan has this Share menu that’s a deceptively simple idea that lets apps talk to a remote service, or to another app. For example, you can ‘Share’ an image from the Gallery app to a remote service (say Flickr), or to another app in the phone (say Mail).

There is not much documentation about either ways of getting into the Share menu. The little official stuff that I could find are these:

  1. Share to a remote service using the web-upload plugins: http://gitorious.org/meego-sharing-framework/pages/Tutorials
  2. Share to anything else using method plugins: http://gitorious.org/meego-sharing-framework/share-ui/trees/master/method-plugins

The method plugins infrastructure seems to be the lower-level API of the two, offering only the integration with the share menu. The web-upload plugins infrastructure is more specialized for the specific case of uploading to remote services, and is most probably making use of the method plugins infrastructure internally.

This article is primarily concerned with only the method plugins infrastructure. It’s based on what I learnt looking at the reference code mentioned below under ‘References’, and based on my experience writing the Share-menu integration for Notekeeper.

What happens when you hit ‘Share’?

First, a quick look at how this sharing thing is architected.

When you hit ‘Share’, from say the Gallery, the Gallery app runs /usr/bin/share-ui as a separate process (probably done through the ShareUiInterface API) and tells it what needs to be shared (it could be an image, a PDF, a bunch of files, or even a web-URL).

This share-ui app first shows a sharing screen with a thumbnail of the image (or approriate icon if it’s not an image). Then it dynamically loads all Qt plugins installed at /usr/lib/share-ui/plugins. Each of these share-ui plugins can offer multiple items to be added to the Share menu – each item being a ‘sharing method’. The share-ui app would ask each sharing method of each plugin whether it wants to figure in the share menu for this particular image (or file/files/URL/whatever). Only those methods that say “Yes” at this point will be shown in the Share menu.

When the user actually selects something in the share menu (like Mail, or Flickr), the share-ui tells the appropriate sharing method that something has been shared to it. It’s up to the method to do whatever it wants at that point. Once the method signals that it’s done, the share-ui app quits.

<guesswork>

Interestingly, the web-upload plugins framework itself seems to be written using this infrastructure. There’s a libwebupload.so under /usr/lib/share-ui/plugins, which, I think, creates ‘sharing method’ objects based on the xml files under /usr/share/accounts/services, and when the user selects the method, defers the actual uploading to the appropriate plugin from /usr/lib/webupload/plugins.

</guesswork>

Writing a share-ui plugin

To create a share-ui plugin, we need to create a .lib file that can be placed at /usr/lib/share-ui/plugins, and can be loaded as a Qt plugin, and can offer the interface that the share-ui app expects to see.

We’re now going to try and write a simple share-ui plugin that copies the path to the image (or file or URL) shared to it to the system clipboard. It’s not going to be of much use by itself, but it would serve as a good example to illustrate how to write a share-ui plugin.

Let’s start by writing a .pro file:

# copy-share-ui-plugin.pro

TEMPLATE = lib
TARGET = copy-share-ui-plugin

CONFIG += share-ui-plugin

HEADERS = copyshareuiplugin.h   copyshareuimethod.h
SOURCES = copyshareuiplugin.cpp copyshareuimethod.cpp

We need the CONFIG += share-ui-plugin because we’re building this on top of the share-ui framework.

The CopyShareUiPlugin class should derive from ShareUI::PluginBase and have a public method called methods() which would return the ‘sharing methods’ that the plugin offers. Our plugin would offer just a single sharing method, the CopyShareUiMethod.

// copyshareuiplugin.h

#include <ShareUI/PluginBase>
#include <ShareUI/MethodBase>

class CopyShareUIPlugin : public ShareUI::PluginBase
{
    Q_OBJECT
public:
    explicit CopyShareUIPlugin(QObject *parent = 0)
        : ShareUI::PluginBase(parent) { }
    virtual ~CopyShareUIPlugin() { }

    QList<ShareUI::MethodBase*> methods(QObject *parent = 0);
};


// copyshareuiplugin.cpp

#include "copyshareuiplugin.h"
#include "copyshareuimethod.h"
#include <QtPlugin>

QList<ShareUI::MethodBase*> CopyShareUIPlugin::methods(
                                            QObject *parent)
{
    QList<ShareUI::MethodBase*> methods;
    methods << new CopyShareUIMethod(parent);
    return methods;
}

// Make this class available as a Qt Plugin
Q_EXPORT_PLUGIN2(copy-share-ui-plugin, CopyShareUIPlugin)

Now, onto the sharing method. The CopyShareUIMethod should derive from ShareUI::MethodBase and should implement four virtual methods, and two slots.

// copyshareuimethod.h

#include <ShareUI/MethodBase>

class CopyShareUIMethod : public ShareUI::MethodBase
{
    Q_OBJECT
public:
    explicit CopyShareUIMethod(QObject *parent = 0);
    virtual ~CopyShareUIMethod();

    virtual QString id();
    virtual QString title();
    virtual QString subtitle();
    virtual QString icon();

public slots:
    void currentItems(const ShareUI::ItemContainer *items);
    void selected(const ShareUI::ItemContainer *items);
};

The id() method should return a unique id identifying the sharing method. Ideally, this should be in the reverse domain name notation, like “com.meego.email” for the Email sharing method.

The return values for title(), subtitle() and icon() decide how the item appears in the Sharing menu.

// copyshareuimethod.cpp

#include "copyshareuimethod.h"

CopyShareUIMethod::CopyShareUIMethod(QObject *parent)
  : ShareUI::MethodBase(parent) {
}

CopyShareUIMethod::~CopyShareUIMethod()
{
}

QString CopyShareUIMethod::id()
{
    return "com.example.copy";
}

QString CopyShareUIMethod::title()
{
    return "Copy path";
}

QString CopyShareUIMethod::subtitle()
{
    return "Copy path to clipboard";
}

QString CopyShareUIMethod::icon()
{
    return "icon-m-content-document";
}

The share-ui app would call the currentItems() slot to check whether we want to figure in the Share menu. The argument to that method is a list of items being shared. If we want this sharing method to appear in the Share menu for these items, the currentItems() method should emit visible(true), else should emit visible(false). For now, we always emit true.

// copyshareuimethod.cpp

void CopyShareUIMethod::currentItems(
                          const ShareUI::ItemContainer * items)
{
    Q_UNUSED(items);
    emit visible(true); // declared in ShareUI::MethodBase
}

The share-ui app would call the selected() slot to tell us that the user tapped on our item in the share menu. We would be implementing the copy-to-clipboard part here. If we’re successful, we’re supposed to emit done(), if not, we can emit selectedFailed(“Error message”). The done() signal indicates that the sharing is complete, so on seeing that, the share-ui app would terminate.

// copyshareuimethod.cpp

void CopyShareUIMethod::selected(const ShareUI::ItemContainer * items)
{
    Q_UNUSED(items);
    // TODO: Copy path to clipboard
    emit done(); // declared in ShareUI::MethodBase
}

With just this, we should already be able to see our ‘Copy path’ item in the Share menu. To build this, open the .pro file in Qt Creator. Take care to include the Harmattan build target. It’s better you refuse when Qt Creator offers to add debian packaging files to this project – it sometimes messes things up.

If you build (‘Build’, not ‘Run’) the project for Harmattan in release mode, you should get a compiled libcopy-share-ui-plugin.so.1.0.0 file.

Copy that to /usr/lib/share-ui/plugins/libcopy-share-ui-plugin.so in your N9 or N950 (I’m assuming you have enabled developer mode and know how to get to a root prompt). If you so prefer, you can automate this copy-file-to-phone part by adding the following to your .pro file:

target.path = /usr/lib/share-ui/plugins
INSTALLS += target

With that, if you have your N9 / N950 configured for development with Qt Creator, you can say ‘Deploy project’ and the library file should get installed at the right path in the phone.

Once the .so file is installed in the phone, open the Share menu from any app in the phone (like Gallery or Documents). You should see ‘Copy path’ in the Share menu. Tapping it should make the Share menu disappear. So far, so good.

copy-share-menu-1-basic

Accessing what is being shared

Next, let’s do the actual write-to-clipboard part.

For that, we’ll need to access the list of items being shared. We should be able to get that from the ShareUI::ItemContainer pointer being passed to us. The best way to figure out how to access
the items is to refer to the header files under ShareUI/.

// copyshareuimethod.cpp

#include <ShareUI/ItemContainer>
#include <ShareUI/FileItem>
#include <ShareUI/DataUriItem>
#include <MDataUri>
#include <QClipboard>
#include <QApplication>

void CopyShareUIMethod::selected(const ShareUI::ItemContainer * items)
{
    QString path("");
    if (items->count() == 1) { // can handle only one shared item
        ShareUI::SharedItem item = items->getItem(0);
        ShareUI::FileItem *fileItem = 
                            ShareUI::FileItem::toFileItem(item);
        ShareUI::DataUriItem *dataUriItem = 
                            ShareUI::DataUriItem::toDataUriItem(item);
        if (fileItem) { // it's a file
            path = fileItem->filePath();
        } else if (dataUriItem) { // it's a URL
            path = dataUriItem->dataUri().textData();
        }
    }
    QApplication::clipboard()->setText(path);
    // Give some time for the setText() call to talk to the X Server
    // and then emit done() to quit the share-ui app
    QTimer::singleShot(500, this, SIGNAL(done()));
}

Now, the plugin actually does what we set out to do.

Hiding ourselves from the Share menu

Since the plugin works only if there’s just one item to share, it would be nice to not even show up when multiple items are being shared. We can do that by appropriately emitting visible() in the currentItems() method.

void CopyShareUIMethod::currentItems(
                          const ShareUI::ItemContainer * items)
{
    // This sharing method is valid only if
    // there's just one item to share
    emit visible(items->count() == 1);
}

Custom icon

So far, we’re using a standard Harmattan icon. What if we want a custom icon to be shown in the Share menu instead? We need a 64×64 png like the Harmattan content icons. Let’s pick a 64×64 Qt logo image and copy it to our project folder.

We should have it installed at some place in the phone. So, we should tell Qt Creator to include this icon in the package and deployed when we say ‘Deploy’.

# copy-share-ui-plugin.pro

icon.path = /usr/share/icons/hicolor/64x64/apps/
icon.files = copy-shareui-qtlogo64.png
INSTALLS += icon

And then, should return this as the icon in icon().

// copyshareuimethod.cpp

QString CopyShareUIMethod::icon()
{
    return "/usr/share/icons/hicolor/64x64/apps/copy-shareui-qtlogo64.png";
}

Surprise, surprise. It doesn’t work. Only a red square shows up as the icon for the ‘Copy path’ item in the share menu.

copy-share-menu-2-bad-icon

To understand why this happens, we need to know how the share-ui application loads icons. The share-ui app, like any Meego Touch app, loads graphics resources through the theme server. The theme server however doesn’t have our icon loaded, which is why it’s returning a red square.

We could place our image file in the theme directory (like /usr/share/themes/blanco/meegotouch/icons), but even with that, it would need the theme server to be restarted for it to be shown correctly.

Instead, a good way to solve this problem is to explicitly tell the theme server to index the folder where we placed our custom icon in the phone. A call to MTheme::addPixmapDirectory() will do that for us. After that, we only need to return the basename of the file.

// copyshareuimethod.cpp

#include <MTheme>

QString CopyShareUIMethod::icon()
{
    if (!m_isPixmapDirAdded) { // need to add it just once
        QString shareIconPath = "/usr/share/icons/hicolor/64x64/apps";
        MTheme::addPixmapDirectory(shareIconPath);
        m_isPixmapDirAdded = true;
    }
    return "copy-shareui-qtlogo64";
}

We use a member variable called m_isPixmapDirAdded to make sure we make the addPixmapDirectory() call only once per process.

To make it compile, we need to add meegotouch to the config:

# copy-share-ui-plugin.pro

CONFIG += meegotouch

That should do the trick. Build and Deploy, and you should be able to see the custom icon in the share menu.

copy-share-menu-3-custom-icon

Working code

The complete working code discussed in this post is available at: https://github.com/roop/copy-share-ui-plugin

Each step is committed separately, so you can browse the commits in line with the steps in this guide.

What’s next

Next week, I’ll talk specifically about integrating your application with the Share menu. Like, for example, how Notekeeper for N9 integrates with the Share menu.

References:

  1. Meego Sharing Framework examples: https://gitorious.org/meego-sharing-framework/share-ui/trees/master/method-plugins
  2. CmdShare by Tuomas Kulve https://projects.developer.nokia.com/cmdshare/browser

One Response to “Getting into the Harmattan Share menu: The missing guide”

  1. Debayan says:

    Cool stuff guys!

Leave a comment

(email will not be published)