Saturday, April 28, 2012

Showing numerical virtual keyboard in QML

Getting the virtual keyboard to show in numerical input mode is not hard, but perhaps poorly documented.

Here's an example code how to do it:

TextField {
    inputMethodHints: Qt.ImhDigitsOnly
    validator: RegExpValidator { regExp: /\d*/ }
}

The above code allows unlimited amount of numbers to be entered, yet allows for an empty field.

If you want the field to have at least one to three numbers entered, something like this can be used:

TextField {
    inputMethodHints: Qt.ImhDigitsOnly
    validator: RegExpValidator { regExp: /\d{1,3}/ }
}

If the field is empty, you'll notice its border is red - as a mark that it is invalid input. This won't prevent the user to leave the field empty though. It's application specific how to handle such a case: whether to display a warning, an error, or preventing leaving the page.

The basic operation is nevertheless this:

  • inputMethodHints instructs the virtual keyboard to open in numerical mode only.
  • validator is used to match the key presses to the regular expression pattern, so that unwanted input can not be entered to the field. As a practical example, the numerical keyboard on harmattan allows entering characters such as '+', '-' and '.'. Validator can be used to disallow those, as has been done in above examples.

Saturday, February 4, 2012

Creating application with background daemon with QtCreator

This posting outlines how to create a program to run in the background (daemon) in MeeGo Harmattan with QtCreator. The resulting installation file is a single .deb that can be submitted to Nokia Store. The daemon will be started after installing the application, and will be started upon each reboot of the device.

The tutorial assumes you want to create a QML application that can be used to control the background daemon, and the daemon itself. The name of the app will be exampleapp.

Quick outline

This is a quick outline of what has to be done, with details in later chapters:
  • Create in QtCreator a subdirs project.
  • Inside the subdirs project create a console application project for the daemon and a Qt Quick Application project for the UI application.
  • Create a configuration file that controls launching of the daemon.
  • Create aegis manifest file to set credentials for daemon.
  • Create debian post-installation and before removal scripts to start the daemon after installation, and stopping the daemon before uninstallation.

Creating the basic project structure in QtCreator

The applications submitted to Nokia store should be contained in single .deb file. To have an application that contains two binaries - the daemon and the application providing the graphical user interface - we need two different projects. We can have both a single .deb file and two different projects by creating a subdirs project.

The final project will have a file structure like this:

- exampleapp/
  - exampleapp/
    - exampleapp*.png
    - exampleapp_harmattan.desktop
    - exampleapp.pro
    - main.cpp
    - qml/
  exampleappd/
    - exampleappd.conf
    - exampleappd.pro
    - main.cpp
  qtc_packaging/
    - debian_harmattan/
      - manifest.aegis
      - ... + various other files

Select File - New File or Project - Other Project - Subdirs project. Give it a name, in this example it's named exampleapp. Select the compilation targets, of course at least including Harmattan. Then click Finish & Add Subproject. As you've selected Harmattan as a target, QtCreator then asks if it should include various debian packaging related files to the project. Click Yes.

Now QtCreator offers to create the first of the subprojects. We can start by creating the QML project first, although the order does not matter. Choose from the dialog Qt Quick project - Qt Quick Application. As its name give the same name as the main subdirs project's name, in this example the name will be exampleapp. Select as "Qt Quick Application Type" Qt Quick Components for Meego/Harmattan. In Target Setup dialog make sure at least Harmattan is selected.

After the graphical QML based project is created, it's time to create the daemon project. Right click on Projects view on the subdirs project and select New subproject. Choose as project template Other Project - Qt Console Application. Give it a name, in this example I enter exampleappd (postscript 'd' is a common unix convention for daemon processes). Again on Target Setup make sure Harmattan is selected.

After this you have two projects, one for the graphical user interface, and one for the daemon running in the background. You do what you do in each of them, such as communicating with each other via dbus (which is out of scope for this blog posting).

Creating daemon control file

In order for the daemon to be started automatically on device reboots, a .conf file that is installed under
/etc/init/apps
is needed. The configuration files in there are processed by Upstart (Google it for more information). Here's a bare bones version of it for the example. Create a file exampleapp/exampleappd/exampleappd.conf

description "ExampleApp daemon startup script"
author "harmarto@somewherecom"
stop on core_shutdown
console output
respawn
respawn limit 3 300
normal exit 0
# This tries executing the daemon as 'user', this is
# what's almost always wanted, but you also need
# aegis.manifest file for that to work (shown later).
exec /usr/bin/aegis-exec -s -u user /opt/exampleapp/bin/exampleappd

For more options in the configuration file and some explanations on the values used see these resources:

You need to add the file to to exampleapp/exampleappd/exampleappd.pro so that it will be installed to a place where Upstart finds it (you can add this to the end of the file):
 
daemonconf.path = /etc/init/apps
daemonconf.files = exampleapp.conf
INSTALLS += daemonconf 
As it is preferable that all your binaries are bundled in the same location (/opt/exampleapp/bin/), you need to also add this to exampleappd.pro:
target.path = /opt/exampleapp/bin
INSTALLS += target

Aegis manifest

On Harmattan you will almost always want to run the daemon as user, not as a root. On Harmattan the credentials your application requests and receives from the security platform are far more important than the traditional unix root/user division. As Upstart doesn't run as 'user', you will need to request credentials for your daemon to switch to running as user. QtCreator should already have created an empty file at exampleapp/qtc_packaging/debian_harmattan/manifest.aegis. Put the following into it to get your daemon running as user:
<aegis>
  <request>
    <credential name="UID::user" />
    <credential name="GID::users" />
    <for path="/opt/exampleapp/bin/exampleappd"/>
  </request>
</aegis>

Starting the daemon after installation

If you want the daemon to start after user installs your application, you need to add a script that is executed after successfull installation.

Create following file at exampleapp/qtc_packaging/debian_harmattan/postinst

#!/bin/sh
set -e

case "$1" in
    configure)

    echo "Starting exampleappd ..."
    [[ -e /etc/init/apps/exampleappd.conf ]] && initctl start apps/exampleappd
    ;;

    abort-upgrade|abort-remove|abort-deconfigure)
    ;;

    *)
        echo "postinst called with unknown argument \`$1'" >&2
        exit 1
    ;;
esac

# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.

#DEBHELPER#

exit 0
the initstl start command is an Upstart command to execute the commands in your exampleappd.conf file.

Stopping the daemon after uninstalling

Even more important is to ensure that your daemon is stopped if user uninstalls your application. In a similar way to to running script after installing, you can instruct the system to run a script before uninstalling to stop the daemon.

Create following file exampleapp/qtc_packaging/debian_harmattan/prerm

#!/bin/sh

set -e

case "$1" in
    purge|remove|upgrade)
        initctl stop apps/exampleappd || true
        # Just in case Upstart could not stop the daemon, kill it    
        killall exampleappd || true        
    ;;
    
    failed-upgrade|abort-install|abort-upgrade|disappear)
    ;;

    *)
        echo "postrm called with unknown argument \`$1'" >&2
        exit 1
    ;;
esac

#DEBHELPER#

exit 0

Running from QtCreator

You can run the resulting project from QtCreator, but it is not guaranteed that your daemon starts before the user interface application. That can be slight annoyance depending on the project. Nevertheless, when you run the application with Harmattan target, the files are installed on your device. You can then, if needed, close the user interface application, start the daemon yourself from command line by running /opt/bin/exampleapp/bin/exampleappd, and then start the UI from the application grid.

Caveats

The daemon application must be pure console application. In other words, no graphics should be displayed by it. Trying to create any graphics context will most likely just result in abrupt termination of your daemon. There are simple ways to have daemons that can display graphics and QML, but that's a different topic if there's interest.

Thanks to

The postinst/prerm scripts are lifted pretty much straight from sandst1's MyMoves sources. Many thanks to his great application, as well as for sharing the sources.

Sunday, January 29, 2012

Closing virtual keyboard when pressing enter/return in QML

The stock apps on N9/N950 close the virtual keyboard on single line text edit when pressing the enter/return key on the virtual keyboard. This allows easily to see the content of the whole page without trying to aim at some area in the page that is not used for input.

Unfortunately, when you create UIs with QML this is not the default behavior. With some help from IRC #harmattan channel and some googling, this is how I got it to work:

TextField {
    Keys.onReturnPressed: {
        platformCloseSoftwareInputPanel()
    }
}

Now the virtual keyboard is closed. But there is still one issue. The keyboard is closed, but the text input still retains focus. If you try clicking on the text field again to start typing, nothing happens. You will have to click somewhere else, so that it loses focus, and then click again the text field. The stock applications make the input field lose focus when the virtual keyboard is closed.

This was a little more hackish to implement. TextField contains a TextInput, which has focus property. But unfortunately TextField does not contain an alias for that property, so you can't just simply set focus = false for the TextField. This kind of rather ugly workaround does the job:

TextField {
    Keys.onReturnPressed: {
        platformCloseSoftwareInputPanel()
        dummy.focus = true
    }
}
// This is just a dummy invisible item that takes the focus when virtual keyboard is closed
Item { id: dummy }