Skip to content
Paul Ecoffet edited this page Aug 27, 2013 · 20 revisions

AweRem is a plugin-based remote controller for your computer. Remotes can easily be added or removed. If you want to provide a new remote for an application, you can easily create a plugin for that.

Plugins are written in HTML5/CSS/Javascript (With additional tools such as jQuery Mobile, 960 grid and nativeDroid) for the UI and in Python 2 for the server-side application.

The template of a plugin

Plugin data is stored in folder with the name of the plugin. So as to be activated, the plugin should be in the modules folder of awerem-computer.

plugin_name
 ├ __init__.py
 ├ plugin_name.arm
 ├ plugin_name.py
 ├ ui.html
 ╰ your_own_files

This hierarchy is subject to change, because all the plugin info and code is accessible from anyone. It's likely that every UI related files will be moved in a ui folder.

  • __init__.py is used by Yapsy to know if there is Python code (and perhaps a plugin) in this folder, without it, your plugin wouldn't be loaded
  • plugin_name.arm is a file with a INI format that contains information about your plugin
  • plugin_name.py is the plugin which would be loaded by Yapsy. It must contains a AweremPlugin class.
  • ui.html is the file that will be displayed in the AweRem phone application. It contains the UI of your plugin.

Here is a standard .arm file:

[Core]

Name = plugin_name
Module = name_of_py_file

[Documentation]
Author = Your Name
Version = 0.1
Description = This plugin is awesome

Creating the UI

The UI displayed in the Android application is written with HTML5, CSS and Javascript. The Android application will look for the ui.html file inside your plugin folder. This file will be parsed with some additional information such as the location of jQuery Mobile files and the Awerem Javascript API. The template could be found at awerem/ressources/ui.tpl.html

Here is the boilerplate of a ui.html file:

<!DOCTYPE html>
<html>
    <head>
        <!-- The Theme you choose for your remote, between dark and light -->
        <link rel="stylesheet" type="text/css" href="/resources/css/jquerymobile.nativedroid.light.css" id="jQMnDTheme"/>
        <!-- The color you choose for your remote, between red, blue, purple, orange and yellow -->
        <link rel="stylesheet" type="text/css" href="/resources/css/jquerymobile.nativedroid.color.red.css" id="jQMnDColor"/>
    </head>
    <body>
        <div data-role="page" data-theme="b">
            <div data-role="content">
            <!-- Your content -->
            </div>
        </div>
        <script type="text/javascript">
            <!-- Your script -->
        </script>
    </body>
</html>

Communicating with the server

So as to communicate with the server, you may use the awerem object and its sendAction and sendActionAsync methods.

awerem.sendAction is a blocking method and will return its value directly. Since it's a blocking method, you shouldn't want to use it and prefer awerem.sendActionAsync instead. It takes as arguments the name of the function you want to call, and its arguments. These arguments can only be a string, a boolean, an integer or a float. For instance:

var tabindex = awerem.sendAction("switchTab", //The name of the function to call remotely
        -1, "foo", 0.45); //The arguments of the function

awerem.sendActionAsync is like awerem.sendAction but the function will be called asynchronously. It takes the same arguments as awerem.sendAction, except that if the last argument of the function call is a javascript function, then it will be used as a callback when the remote method would return its result.

Examples:

awerem.sendActionAsync("moveMouse", //The remote method name
        -45, -75); //The arguments of the function
                   //Since the last argument is not a JS function,
                   // the result of this method would be dismissed

var curVolume = 0;
awerem.sendActionAsync("getData", //The remote method name
        "volume", "left", //The arguments of the function
        function(volume) {curVolume = volume}); //Since the last argument is a JS function,
                                                //When getVolume returns, the callback
                                                //will be triggered with the result
                                                //of "getVolume" as argument

Creating the server-side application

Your server-side application must contains a AweremPlugin class in your module_name.py. This class must override some methods. Here is the boilerplate code for your module_name.py file:

#!/bin/python

from modules.aweremplugin import AweRemPlugin, AweRemHandler


class MyPluginHandler(AweRemHandler):

    def __init__(self, myplugin):
        self.myplugin = myplugin

    def out_myfunc(self):
        """
        Print "Triggered"
        """
        print("Triggered")
        return True  # You must not return None


class MyPlugin(AweRemPlugin):
    """
    MyPlugin is awesome
    """
    def __init__(self):
        self.handler = None
        self.handler = None

    def activate(self):
        self.handler = MyPluginHandler(self)
        self.info = {"title": "My Plugin", "category": "contextual",
                     "priority": 0}

    def getHandler(self):
        return self.handler

    def getInfo(self):
        return self.info

So as to understand this code, let's first focus on the AweremPlugin subclass, then we will focus on the AweRemHandler one.

The plugin class

First, the AweRemPlugin subclass must override the activate, getInfo and getHandler methods (The two last methods name are subject to change so as to respect PEP-8).

The activate method

The activate method is called by Yapsy once the instance of your plugin is activated. Your plugin could be loaded but might not be activated. Thus, if you must do any memory or CPU intensive action, di ut in the activate method, and not in the __init__ one (Generally speaking, you should avoid any foreground cpu or memory intensive action).

The getInfo method

The getInfo method must return a dict containing various informations:

  • title: The title displayed to the user
  • category: "contextual" (music player, browser, ...) or "utils" (mouse, gamepad, keyboard)
  • priority:
    • 0 - The app the module controls has the focus
    • 1 - The app the module controls is launched
    • 2 - The app the module controls is system-wide
    • -1 - The app the module controls is not launched (won't be displayed)

The title or the priority of your app is subject to change. For instance, the title of the linux remote might change depending of the distribution the user is running, or the priority of the firefox remote might change depending of the application is running or not. So as to get the update of your info taken into account in the navigation drawer in your Android AweRem application, you must call self.pollmanager.updateNavigationDrawer() in your code.

For instance, if you want to update the priority of your remote depending if the app it tracks is running or not, do:

from modules.aweremplugin import AweremPlugin

class MyPlugin(AweremPlugin):

    ...

    def state_change(self, running):
        if running:
            self.info["priority"] = 1
        else:
            self.info["priority"] = -1
        self.pollmanager.updateNavigationDrawer()

    def getInfo(self):
       return self.info

    ...

The getHandler method

The getHandler must return an AweremHandler instance. For more information about AweremHandler, go to the AweremHandler section.

The getIcon method

You may want to override the getIcon(dpi) method. This method is called to get the path of your icon depending of the dpi of the phone. By default, getIcon will seek for 'res/icons/icon_'+dpi'+'.png'. Yet, you may want your icon to be personalized. For instance, the linux remote's icon can follow the distribution's icon of the host.

Track the lifecycle of the app you controls

Depending of the remote you are coding, you might want to track the lifecycle of your application. So as to do so, you can import ProcessesManagerSingleton from processesmanager. An example is worth a thousand words:

from modules.aweremplugin import AweRemPlugin
from processesmanager import ProcessesManagerSingleton

...

class MyPlugin(AweRemPlugin):
    """
        My plugin
    """

    def activate(self):
        self.info = {"title": "MyRegex remote", "category": "contextual",
                     "priority": -1}
        ProcessesManagerSingleton.get().addCallback(r".*myregex.*", self.callback)

    def callback(self, running):
        if running:
            self.info["priority"] = 1
            self.prepare_communication_stuff()
        else:
            self.info["priority"] = -1
            self.free_communication_stuff()
        self.pollmanager.updateNavigationDrawer()

    ...

You have the warranty that:

  • If your application was running before your callback is added, the callback will be called as soon as possible
  • Your callback will be triggered only if the state of the application changed, thus you can perform some slow computation without too much remorse.

The list of app currently running is only updated every few seconds. If you want to get the name of your app for the system, you can try to find it on linux with the top or ps -ef commands in a shell.