Skip to content
Thibault Guittet edited this page Aug 25, 2014 · 5 revisions

This software have a client-server architecture. All the dbus command execution is done by the engine. All the graphical tasks are done by the client. The engine and the client have a kind of communication channel: the client query the engine which respond using a callback. All communication between the engine and the client are done using c json objects (not plain json). Those communication rest on json object formatted messages described in engine.c (keys.c is used for harmony across engine <-> client messages). Finally, because the base of the project is asynchronous (connmand use dbus after all) the project relies on callback mechanisms.

The engine

The engine won't ask connman dbus service for everything, only operations require a dbus exchange. A cache in the engine allow further query validation and faster response time to basic operations like "get all available technologies". This cache is updated by connman dbus service signals.

What a engine do (and where):

  • handle queries:
    1. check if those are valid using regex based validation (engine.c and json_utils.c)
    2. dispatch the query to the appropriate command (engine.c, commands.c and dbus_helpers.c)
    3. notify the client that the query have been sent
    4. receive the response (dbus message) for the command in a callback, translate it in json object and send the response to the client (engine.c and dbus_json.c)
  • handle signals from connman dbus:
    1. receive the (dbus message) signal in a callback, translate it to json object, update his "cache" and forward the signal to the client (engine.c and dbus_json.c)
  • handle agent dbus registering/unregistering
  • handle agent requests and errors:
    1. keep a cache of agent requests and errors, providing a simple way to pilot the agent (engine.c, agent.c and dbus_json.c)

The client

It's where all the (inter)action takes place. As said before, a unique callback is used to get engine responses. The client is designed in multiple pieces:

  • renderers.c Translate json objects to ncurses elements
  • popup.c Pretty explicit, used only by the agent for requests and errors
  • special_win.c Special ncurses windows for errors (not agent related) and help
  • loop.c Custom implementation of a main loop, listen for dbus messages and stdin input. All dbus messages/replies respond on a engine callback
  • main.c This is the brain of this program. It dispatch everything coming from the engine, query it and execute actions on user input

The client use a state based system to know what to do next, how to get back... I called it "context" and it corresponds to the various views you can have.

The current implementation use 4 contexts:

  • CONTEXT_HOME think of it as the first and main page of this program
  • CONTEXT_SERVICE_CONFIG expose the settings of a connected service
  • CONTEXT_SERVICE_CONFIG_STANDALONE expose the settings of a non connected service
  • CONTEXT_SERVICES the list of services you can connect to

Transitions between those contexts are achieved by exec_back() and exec_action() in main.c. Also a exec_refresh() to refresh the current context.

A user can't choose to see the list of services or the service settings, this depends if the technology is connected or not. For now, 2 similar network cards result in a single technology, e.g. 2 wifi interfaces -> 1 technology.

stdin events priority

User input triggers an event for the main loop, which launch ncurses_action() in main.c. From there, basic priority rules apply ('->' mean 'redirect execution flow'):

  1. help window -> win_driver()
  2. error window -> win_driver()
  3. 'Escape'/'Esc' key -> exec_back()
  4. 'F5' -> exec_refresh()
  5. 'F1' -> print the help window
  6. popup -> popup_driver()
  7. switch on context -> appropriate context driver

Refreshing quirks

I tried to build a generic system to refresh the current context, but every rule has his exceptions.

Refresh actually mean re-query the engine with the same request I did to get in this context.

This refresh action is triggered by 'F5' and signals. The exception is in CONTEXT_SERVICES: imagine an area crawling with wifi access points: the network list would change so fast, you couldn't select the service you want without incredible reflexes. Thus 'F6' allow you to re-scan wifi networks. Because having to manually ask for refresh after a scan action isn't practical, a kind of beacon will force refresh. The problem with connect actions is similar to the re-scan one. This beacon is set in commands.c and is present on the dbus method response. So, a bypass of the exception is performed and the refresh action takes place.

Refresh actions had a bad habit: your cursor would move to the first item/field. To overcome this annoying behavior repos_cursor() was introduced in main.c. The way it works is quite simple: if you want to put your cursor back where it were, remember where you were and move the cursor back. To do this I use user pointers in fields and items. A user pointer is a handy feature allowing you to put a pointer to data in fields and items (of forms and menus). This pointer is then grabbed in the context structure on a refresh action. This works nicely with items because they hold a unique information, a connman dbus name pointing to a service/technology. The situation is less ideal in forms, we don't have a unique thing to hold onto so we have to create it. Therefore, cursor reposition on forms isn't perfect and might fail: put the cursor on the first field, nothing harmful...

Clone this wiki locally