Skip to content

How to add a action on key pressed

Thibault Guittet edited this page Aug 8, 2014 · 3 revisions

This is a small tutorial on how to add an action in the program. We are gonna take the disconnect action as a model.

Disconnection take place in CONTEXT_HOME (the technology view). You can disconnect a technology from his service by pressing 'd' on the appropriate item.

The engine side

Let's start at the bottom, the dbus method call.

All dbus method calls use dbus_method_call implemented in dbus_helpers.c. According to the documentation of connman available in dir/services-api.txt of the git repository, the Disconnect method is at interface net.connman.Service and path /net/connman/service/service_dbus_name of the dbus service ('service_dbus_name' is a valid service name).

So it's quite easy, we must have a (valid) service dbus name, the only obligation of function signature is the int imposed as return value. So let's take the following signature:

int __cmd_disconnect(const char *serv_dbus_name);

The call to dbus_method_call is somehow polluted by the number of arguments but it's not rocket science. To fill this call you need to ask yourself if the data (here serv_dbus_name) to call the method with has to be freed when the dbus method call return. If not, use call_return_list for callback. If you need to free the user_data use call_return_list_free and set the user_data parameter accordingly. The rest of parameters are deductible, thus we get the following method call:

int __cmd_disconnect(const char *serv_dbus_name)
{
        return dbus_method_call(connection, key_connman_service,
                        serv_dbus_name, "net.connman.Service", "Disconnect",
                        call_return_list, NULL, NULL, NULL);
}

What we really did calling this is "execute the disconnect method on the service I gave you, then go notify call_return_list".

Now that the first brick is in place, let's add the support for this command in engine.c.

In engine the situation is different, because you ignore if a technology <-> service connection is in progress, you need to take the technology and look for the matching service. This is one of the purpose of get_services_matching_tech_type.

Engine functions directly affected by query data have all the same signature, so this is what our function look like:

static int disconnect_technology(struct json_object *jobj);

jobj is the json object representing arbitrary data in the query. We don't need much data in this action, only the technology name. Finally, the data will be something like:

{ "technology": "/net/connman/technology/tech_name" }

The verifications to perform on the data at the function level are simple:

  • do the technology exists ?
  • is the technology connected ? (you can't disconnect a technology that isn't connected, can you ?)

The first verification is an effect of get_services_matching_tech_type, the second is up to us. Thus our function have to do the following:

  1. extract data from jobj
  2. check if the technology is connected
  3. search for the corresponding service
  4. pass the service dbus name to the command we made in commands.c

I won't put the code for this function because it's quite long but rather simple, check engine.c for the implementation.

Last thing we have to do in the engine is to add the function to the "query functions". This translate in adding a line in cmd_table[].

Start by defining a command name in keys.c and add the function name. The rest need some explication. If the data you carry in the message is simple enough, you may be able to translate the data structure directly in plain json/regex. If this is your case, set trusted_is_json_string to true and set your string in the union. In the contrary, if your data structure is far to complex for plain json, set trusted_is_json_string to false and fill the union with a empty string. The fact that your data structure can't be set in plain json force you to do two additional steps:

  1. define your data structure directly in a json object in json_regex.c as a extern variable
  2. define the above variable in engine.c
  3. add a filter in init_cmd_table() to initialize the command table with your json_object

The engine can now be queried for disconnection!

The client side

The first thing to do on this side is a function we can use to query the engine. We have to get a dbus technology name to trigger the disconnection and we are going to use the one from an item in the home page. For convenience, our function will get a user pointer data for parameter. This function does pretty simple things:

  • create the json object for the query
  • send the query and check for errors

Thus we get this code:

static void disconnect_of_service(struct userptr_data *data)
{
        struct json_object *cmd, *tmp;

        cmd = json_object_new_object();
        tmp = json_object_new_object();

        json_object_object_add(cmd, key_command,
                        json_object_new_string(key_engine_disconnect));
        json_object_object_add(tmp, key_technology,
                        json_object_new_string(data->dbus_name));
        json_object_object_add(cmd, key_command_data, tmp);

        if (engine_query(cmd) == -EINVAL)
                report_error();
}

report_error() is a way to create an error window with a preformed message.

This leaves us with the user interaction triggering the function. As aforementioned, I want to execute the disconnect action in the CONTEXT_HOME context, so let's add input filtering in exec_action_context_home(). 'd' key seems reasonable for a disconnect action so let's use this.

When the 'd' key is pressed, we have to do the following:

  • get the current item of the technology menu
  • execute the disconnect function we just wrote with the user pointer of the current item

The resulting code is quite simple:

case 100: // 'd'
      item = current_item(my_menu);
      disconnect_of_service(item_userptr(item));
      print_info_in_footer(false, "Disconnecting...");
      break;

Finally, don't forget to mention that this action is available to the user by adding a message in exec_action_context_home() of ncurses_utils.c.

Not that hard is it?

We now have a disconnect action!

Note that we could have made it easier by disconnecting the service in the CONTEXT_SERVICE_CONFIG context. But I liked the idea of disconnect in the technology view better. Moreover, reacting to a key in the service configuration view is a bit different: you would have to use a function key due to the presence of fields. This variant can totally work but is less ergonomic, isn't it ?.

Now that you can add actions, don't hesitate to do so, you can always ask me for help. Happy hacking 😄 !

Clone this wiki locally