X11 TV application communication protocols
This page briefly describes the two protocols which allow communication between nxtvepg and X11 TV applications to provide EPG information or remote control.
This documented is intended for software developers. Readers should have experience with programming X clients at Xlib level, i.e. they should at least be familiar with the concept of X event handling and what windows and atoms represent at this level. This document is not intended to be a formal specification.
This protocol is based on remote control mechanisms implemented in Xawtv. They allow to capture the currently tuned station and station changes and to send generic remote commands to xawtv. Since the protocol predates nxtvepg there's no support for EPG specifically.
Communication is based on 3 X atoms which all must be placed in the TV application's toplevel window (i.e. the topmost one below the window manager's decorative frames)
Contains the name of the application (e.g. "Xawtv") When searching for a peer, nxtvepg examines every toplevel window for this atom and compares the value with a list of known application names. The list has to be extended for each new TV application.
Describes the TV station currently tuned by the TV application. Contains 3 strings, each terminated by zero bytes: TV frequency in kHz, channel name, station name. This atom must be updated by the TV application upon every channel change, since nxtvepg uses property change events to monitor for channel changes.
This atom is used to pass commands from remote applications to Xawtv. The command name and parameters are passed as zero-separated strings. The following commands are used by nxtvepg:
Command Description setstation ... Tune the TV network with the given name setstation back Toggle back to the previously viewed TV station setstation next Switch to the next station in the TV app's channel table setstation prev Switch to the previous station in the TV app's channel table capture on Enable TV video display capture off Disable or pause TV video display volume mute Toggle audio mute on/off message ... Display the given message (e.g. in the window title bar or inside the TV video display)
To invoke a command it's simply written into the atom by means of XChangeProperty(), which triggers a property change event in the TV application. There's no confirmation sent back to nxtvepg to indicate if the command was sucessfully executed.
When nxtvepg is started it first scans all existing toplevel windows for known WM_CLASS names. If one is found, the window ID is stored. If not, nxtvepg registers for sub-structure notify events on the root window and checks WM_CLASS on all newly created toplevel windows. (This procedure is the main weakness of this protocol, since both the initial scan and the monitoring cause an unnecessary load on the X server. Note that not only application main windows are toplevel windows, but also every dialog, popup menu or even tool-tip.)While nxtvepg is "connected" to a TV app (i.e. a valid window ID is known) it monitors for property change events on the STATION atom. After every change it reads the station name from the atom and matches it against the internal network name list. If a match is found, or if the station can be identified by means of VPS/PDC capture, EPG info is sent back to the TV application. (Note by default nxtvepg does not use the "message" command to display EPG information since it doesn't look very nice in xawtv. Instead it generates a small popup window by itself and just positions it close to the TV application's window.) While nxtvepg is "connected", the user also has the possibility to remote control the TV application. Most importantly he/she can generate "setstation" commands simply by double-clicking a programme entry. nxtvepg learns about termination of the TV application by receiving a destroy event for the TV app's toplevel window. Note the TV app has no means to know if nxtvepg is connected.
The second communication protocol supported by nxtvepg (starting with version 2.8.0) is derived from the Selection Manager protocol specified in the Inter-Client Communication Conventions Manual, version 2. Note that despite the protocol's name it's not limited to the exchange of copy/paste text buffers. See also freedesktop.org for other examples how the protocol is used (e.g in the systray protocol)
This protocol is in some ways similar to the Xawtv protocol, since it also uses atoms for message transport and - at least for now - the same messages are exchanged. The main differences are firstly the use of the X11 selection mechanism to simplify and speed up the search for peers, and secondly that the connection is bidirectional and generally more robust.
Initially, each peer must create an X window which is only used to manage the selection. An atom is placed on this window which identifies the peer either as an EPG or a TV application. Other atoms which are used for data transfer and explained in the next chapter are also placed here.
The application then can search for a compatible peer. This is done by a single call of the XGetSelectionOwner() function. If sucessful, the function returns the peer's selection window ID. If not, the client listens for client messages on the root window, to receive broadcast messages which are sent when a new manager starts (see next paragraph.)
To be able to receive messages from peers, an application must register as selection manager with function XSetSelectionOwner(). Only then others can find it with XGetSelectionOwner(). nxtvepg always registers as manager, but for TV applications it's optional. Applications which only want to query EPG infos need not become manager. However it's required to become manager if a TV app also wants to allow remote control (e.g. changing channels from inside nxtvepg)
Multiple instances can be active on both sides: if you start nxtvepg several times (possibly on different hosts, but with the same -tvdisplay) only the first one will serve EPG queries and follow station changes of TV apps. The other instances still can send remote commands to the TV apps though. Another nxtvepg instance will automatically take over management if the previous manager is terminated. If multiple TV apps are active, they should sort out among themselves which one is manager.
Communication follows a query/response model: the client first assigns request parameters to one atom, then sends a selection request message to the peer's manager window. After processing the request, the peer assigns a possible result value to another atom and replies with a selection notify event. Note that all atoms are placed on the client's selection window. This way multiple client requests can be processed in parallel. The extra selection request/notify messages are used (in contrary to property change events in Xawtv) to support large data transfers in multiple chunks. Theoretically a client can request the entire EPG database.
Important: nxtvepg's implementation as described above slightly differs from the ICCC specification. In the spec the same atom is used for parameters and response, so clients have to wait for a response before they can send the next query. To simplify station change handling nxtvepg departs from that, i.e. allows overlapping requests. Only the last request to the same target (see next chapter) will be answered for each client.
The following is a list of the atoms used for data exchange. Arguments are zero-separated strings.
Identifier for nxtvepg's selection window, i.e. used by the TV application during connection establishment in search for the nxtvepg client. It's automatically set on the selection window during initialisation. The value is a zero separated list of: protocol version (NETAIP/1.0 for the current version), application name, application version, feature bits (currently always 0). The application is intended to be displayed to the user (e.g. is display of the connection status) but not evaluated by nxtvepg.
Identifier for the TV application's selection window, i.e. used by nxtvepg in search for TV clients. The value is the same as in the previous atom.
Used to send "setstation" messages to an EPG peer which signal a channel change and request a reply of EPG information for the new channel. Applications should not manipulate this property's value directly, but use function Xiccc_SendQuery instead. The property is assigned channel information and parameters for the EPG request in form of zero-separated strings which contain:
- Protocol identifier (always NETAIP/1.0)
- Channel name (e.g. "ARD"),
- Frequency in kHz,
- Channel name (e.g. "SE10"),
- VPS/PDC channel ID (CNI, e.g. 0xD92 for German network "Kabel1"),
- Requested EPG format (always "TAB" currently),
- Count of requested EPG programmes,
- update flag
When the update flag is set, unsolicited selection notification are sent by nxtvepg when new programmes start on the channel, or if another TV applications changes the channel.
This atom is used to receive replies to the above setstation message, i.e. EPG information. Basically the client could use any other atom for this, this specific name is just what's used by the code in xiccc. In contrary to Xawtv the client should not use property change events for notification if incoming events, but instead wait for selection notify events. (This is required because the remote peer may write the property's data in several chunks.)
Used to send all other kinds of messages between peers. It's always a number zero-separated strings (note the last string should be terminated with a single zero.) The first element is the name of the command, the rest is parameters and depends on the type of command. Default commands which should be supported by all TV apps are the same as for _XAWTV_REMOTE. Actually, the content of this property is identical to Xawtv.
Used to receive replies to remote commands. The content depends on the request, but peers should always send at least a status code back.
The following is a short description of the reference implementation in source module epgui/xiccc.c Note it's not intended as a library, you may have to slightly adapt the function interfaces to your requirements.
Most functions are asynchronous. For example, when sending a message to a peer, the reply is not yet available when the function returns. Instead you must install an event handler which will be invoked when a reply arrives, or other relevant events occur.
Since the protocol is based on Xlib directly, you need to install an event handler which receives all X events, called "low-level" in the following. The xiccc module will filter out protocol events and pass everything else (keyboard input events etc.) on to your toolkit library. Events which should be handled by the application (e.g. incoming messages) are signaled by means of a bitfield and message queues. The application should normally not process them directly in the X event handler since it might interfer with your toolkit's event processing. Instead a "high-level" handler should be triggered which is then invoked as a regular toolkit event.
In addition, event processing in the "high-level" handler requires to install at least temporarily an X11 error handler which ignores "Bad Window" errors which might be caused when accessing resources of remote peers which have been been terminated.
A typical protocol client will look like this (not showing application specific functionality):
- During application startup phase
- Install a handler which receives all X input events
- Install a "high-level" event handler which can be triggered by the low-level handler.
- Xiccc_Initialize(&xiccc, dpy, TRUE, pIdArgv, idLen);
- Xiccc_ClaimManagement(&xiccc, FALSE);
note these functions may already raise events,
hence check event bits and trigger high-level handler if necessary
- main task event loop
- In the X input event handler: pass all events to Xiccc_XEvent()
Check for high-level events in the bit mask and trigger the high-level handler, if neccessary
- In the high-level handler, first call Xiccc_HandleInternalEvent()
Process application events, as necessary (e.g. display EPG info on-screen; see list below)
- Report TV station changes to EPG app by sending SETSTATION messages
- In the X input event handler: pass all events to Xiccc_XEvent()
- During application shutdown phase
The following events may be generated by xiccc and should be handled by the application in the "high-level" event handler.
|XICCC_NEW_PEER||A new peer was found or a new manager has taken over management.|
|XICCC_LOST_PEER||The remote manager has terminated or given up management.|
|XICCC_GOT_MGMT||A previous request to take over management with Xiccc_ClaimManagement() was sucessful.|
|XICCC_LOST_MGMT||Another application has taken over management.|
|XICCC_SETSTATION_REQ||A message has arrived on the SETSTATION target. It can be retrieved from the respective message queue.|
|XICCC_SETSTATION_REPLY||A response to a previous SETSTATION message has been received.|
|XICCC_REMOTE_REQ||A message has arrived on the REMOTE target. It can be retrieved from the respective message queue.|
|XICCC_REMOTE_REPLY||A response to a previous REMOTE message has been received.|
To include the header file xiccc.h you need to first include the following headers:
#include <X11/Xlib.h> #include <X11/Xatom.h>
The following is an overview of all functions offered by xiccc.c
|Function||Arguments & description|
|bool||Xiccc_Initialize||( XICCC_STATE * pXi, Display * dpy, bool isEpg, const char * pIdArgv, uint idLen )|
|Called once during start-up to initialize the module state and allocate static resources (e.g. create the atoms)|
|void||Xiccc_Destroy||( XICCC_STATE * pXi )|
|Called during shutdown to free resources (e.g. destroy the selection window)|
|bool||Xiccc_ClaimManagement||( XICCC_STATE * pXi, bool force )|
|Start procedure to become owner of the selection (i.e. manager) For TV applications use of this function is optional, unless you want the to be able to receive remote commands sent by the peer. An XICCC_GOT_MGMT event will be generated when the request was successful. The request may fail when there already is a manager, unless force is set to TRUE.|
|bool||Xiccc_ReleaseManagement||( XICCC_STATE * pXi )|
|Start procedure to voluntarily release ownership. An XICCC_LOST_MGMT event will be generated when the release is complete.|
|bool||Xiccc_SearchPeer||( XICCC_STATE * pXi )|
|This function is used to establish a connection to a remote peer. A XICCC_NEW_PEER event will be raised as soon as a peer is found, which may be immediately when the function returns. Hence the application should check the events bits after calling this function and possible invoke the "high-level" event handler or otherwise handle the event, if required. The caller should install an X error handler to catch possible Bad window errors.|
|bool||Xiccc_XEvent||( XEvent *eventPtr, XICCC_STATE * pXi, bool * pNeedHandler )|
|This function should be called for every incoming X event and filters out events for the own selection window or that of a connected peer and protocol messages on the root window. The result value is TRUE if the event was processed internally. If an event occured which is of interest for the application, boolean*pNeedHandler is set to TRUE and the respective bit is set in the events member in the state struct. The application should then trigger it's "high-level" event handler to process the event and clear bits if processed events.|
|void||Xiccc_HandleInternalEvent||( XICCC_STATE * pXi )|
|This function should be called first inside the "high-level" event handler. It may generate additional application level events.|
|bool||Xiccc_SendQuery||( XICCC_STATE * pXi, const char * pCmd, sint cmdLen, Atom target, Atom property )|
|Send the given message to a connected peer on the given target atom. The caller should install an X error handler to catch possible Bad window errors. The result will be written to the given property atom. When the reply arrives, an event will be raised.|
|bool||Xiccc_SplitArgv||( Display * dpy, Window wid, Atom property, char *** ppArgv, uint * pArgc )|
|This is a helper function which can be used to split a received message into separate strings.|
|bool||Xiccc_SendReply||( XICCC_STATE * pXi, const char * pStr, int strLen, XICCC_EV_QUEUE * pReq, Atom target )|
|This function sends a reply message to the peer. specified by the given message queue element (i.e. a selection notification is sent) The caller should install an X error handler to catch possible Bad window errors.|
|bool||Xiccc_SendNullReply||( XICCC_STATE * pXi, XICCC_EV_QUEUE * pReq, Atom target )|
|This function is used when a severe error occurs during processing of an incoming message (e.g. parameters could not be parsed) The caller should install an X error handler to catch possible Bad window errors.|
|void||Xiccc_BuildArgv||( char ** ppBuild, uint * pArgLen, ... )|
|This function combines the given strings into a single zero-separated string. The caller must free *ppBuild afterwards.|
|bool||Xiccc_ParseMsgSetstation||( Display * dpy, XICCC_EV_QUEUE * pReq, Atom target )|
|Helper function only intended for nxtvepg to parse SETSTATION messages.|
|void||Xiccc_QueueUnlinkEvent||( XICCC_EV_QUEUE ** pHead, XICCC_EV_QUEUE * pReq )|
|Unlinks a specific message from the queue; memory must be freed or event queued in other queue by caller|
|void||Xiccc_QueueRemoveRequest||( XICCC_EV_QUEUE ** pHead, Window requestor )|
|Removes and frees all messages of a given peer from the queue.|
|void||Xiccc_QueueAddEvent||( XICCC_EV_QUEUE ** pHead, XICCC_EV_QUEUE * pNew )|
|Add a message to the queue; new events are inserted at the head of the queue|
|void||Xiccc_QueueFree||( XICCC_EV_QUEUE ** pHead )|
|Removes all messages in a queue.|