Startup notification protocol
===

This document specifies a mechanism allowing a desktop environment to
track application startup, to provide user feedback and other
features. 

Version: 0.1
Revised: October 20, 2002
Authors: Lubos Lunak, Havoc Pennington

Terms
===

Launch:   a "startup event" such as opening a new window, opening a new
          application, or adding a panel applet. Note that the 
          launch may or may not involve creating a new UNIX process.
Launcher: code which starts up a launch
Launchee: code which is started up by the launcher

X Messages
===

"X messages" are a mechanism for sending text strings between X
clients.

To send a string as an X message, a client does the following:

 - Creates an X window to be used to identify the message
   uniquely. This window need not be mapped, and may be
   a child of any window.

 - Interns atoms type_atom and type_atom_begin indicating
   the type of message.

 - Decides on a target_xwindow; this is the root window
   for "broadcast" messages, or a specific client's window.

 - Send a series of client messages to the target X window, where each
   client message contains a portion of the string. The client
   messages should have the window field set to the X window
   identifying the message, the format field set to 8, and
   the message_type field set to the type of message, type_atom_begin
   for the first client message and type_atom for any following client
   messages.

   The last byte used in the last client message must be nul, and no
   intermediate bytes may be nul. The nul byte identifies
   the end of the message.

   Client messages must be sent to the chosen target_xwindow, with the
   event mask PropertyChangeMask. 

   (FIXME this is a bad choice of mask, as we get a bunch of annoying
   PropertyNotify events; but which mask would be better?)

   Attachment "A" contains example code.

 - Destroys the unique X window used to identify the message. 
   The window can be destroyed immediately, it is only used 
   for its window ID.

Implementations may impose a maximum length of message they are
willing to accept. Typically this length will be reasonably low, 
perhaps 4K of data.

Key-value strings
===

The specific strings sent during startup notification encode a message
type, followed by a list of key-value pairs. Here is an example:

  new: NAME="Hello World" PID=252

A string listing key-value pairs works as follows:

 - the entire string must be valid UTF-8. Invalid strings should be
   discarded as corrupt, as accepting bad data leads to
   interoperability problems. (Learn from web browsers.)

   Although the string is UTF-8, parsing is specified in terms of
   bytes not characters in the below specification.

 - all bytes up to the first ':' byte indicate the type of the
   message. If the message contains no ':' byte it should be discarded
   as corrupt.

 - To find the start of a key, the ':' byte delimiting the message
   type must be skipped. Any space (' ') bytes following it must also
   be skipped. (Other kinds of whitespace must not be skipped.)  The
   first non-' ' byte after the ':' byte is the start of the first
   key.

 - All bytes until the next '=' byte form the name of the 
   key. The '=' byte should be discarded, as it delimits the 
   key from the value.

 - Parsing of the value begins with the byte immediately following the
   '=' byte.  The value is parsed
   as follows.

   There are two dimensions, "escaped" and "quoted", creating 4
   states:

     - escaped = TRUE quoted = TRUE
         . the current byte is appended literally, and the escaped
           flag is set to FALSE

     - escaped = TRUE quoted = FALSE
         . the current byte is appended literally, and the escaped
           flag is set to FALSE

     - escaped = FALSE quoted = FALSE
         . if the current byte is a double quote '"' it is 
           discarded, and the quoted flag is set to TRUE
         . if the current byte is a backslash '\', it is 
           discarded, and the escaped flag is set to TRUE
         . if the current byte is a space ' ' byte or nul byte,
           the end of the value has been reached
         . any other byte, INCLUDING tabs, newlines, etc., must be
           appended literally.

     - escaped = FALSE quoted = TRUE:
         . if the current byte is a double quote '"'
           it is discarded, and the quoted flag is 
           set to FALSE
         . if the current byte is a backslash '\'
           it is discarded, and the escaped flag 
           is set to TRUE
         . otherwise the current byte is appended literally

   If a nul byte is seen in a state other than escaped = FALSE 
   quoted = FALSE, it is an error, and the message should be discarded
   as corrupt.

   Note that the escaping here is simpler than either C string literal
   escaping, or shell quoting. Unlike C string literals, "\n" means
   "the letter n," not "newline"; unlike quoted shell strings, "\e"
   means "the letter e," not "backslash followed by the letter e."

   Note that an empty string can be represented by simply not
   including a value before the first whitespace, as in FOO:
      FOO= NAME=Hello
   or by empty quotes as in BAR:
      BAR="" NAME=Hello

 - Once the end of the value has been reached, any space (' ') bytes
   should be skipped. The first non-' ' byte is the first byte of the
   next key.

Note that keys are case-sensitive, Foo and FOO are different keys.


Startup notification
===

The startup notification protocol involves sending X messages with the
message_type atom _NET_STARTUP_INFO_BEGIN/_NET_STARTUP_INFO to the
root window.  In multihead setups, the messages should go to the root
window of the X screen where the launchee application is being
launched.

As a general convention, any key-value pairs in startup notification
messages that aren't understood by a given client should be ignored by
that client. Also, any keys or message types not documented here must
be prefixed by the two bytes "X-" as in "X-myproperty" or
"X-mymessage".

All messages in the startup notification protocol refer to a "startup
sequence"; a "startup sequence" reflects a single launch event.

Here are the message types ("message types" here means the type at the
beginning of the message string, not the type of the X message):
 
  new:    message indicating that a new startup sequence has been
          initiated. The key-value pairs in this message indicate the
          properties of the startup sequence. If this startup sequence
          already exists, "new:" message is equivalent to "change:"
          (i.e. the values are updated instead of creating a new
          startup sequence).
          

  change: message changing a startup sequence. Clients should update
          information about the matching startup sequence to the newly
          provides information. "change" messages contain
          a subset of the keys allowed in a "new" message. Not all
          attributes of the startup sequence are allowed to change
          over time.
          
          "change:" messages not be taken as a "new:" message,
          i.e. they should not cause any feedback or similar.
          
          If a client has not seen a "new:" message for the same sequence,
          then it should remember the information for later use in case
          a "new:" message comes later. "change:" messages without
          a following "new:" message can be discarded after a reasonable
          timeout (>= 1 minute). Rationale: The application
          may want to send certain information like timestamp or description
          first, before handling control to library code deciding
          if there should be actually any feedback for the launch. 


  remove: message ending a startup sequence. Once this message
          has been seen for a given sequence, any further 
          messages referring to the sequence should be ignored.
          
All messages must include these keys:

  ID
 
          uniquely identifies a startup sequence; should be some globally 
          unique string (for example, hostname+pid+"_TIME"+current time).
          The string should be in the form of <unique>_TIME<timestamp>,
          where the timestamp is the X server timestamp of the user
          action that caused the launch. See the TIMESTAMP key
          for details.

The following keys are required in a "new" message and may be included
in either a "new" or a "changed" message:

  NAME    

          some human-readable name of the item being started; 
          for example, "Control Center" or "Untitled Document";
          this name should be localized.

  SCREEN

          the X screen number the startup sequence is on

The following keys may be provided optionally in either a "new" or a
"changed" message:

  BIN     
         
          name of the executable being started, argv[0]
          
  ICON    
  
          a string to be interpreted exactly as the "Icon" field 
          in desktop entries is interpreted.

  LAUNCHED_BY
 
          identification of the top-level window in which the launch was initiated.
          The value is the X window ID of the window.

  DESKTOP 

          the desktop on which the application should appear, 
          counting from 0, as in _NET_WM_DESKTOP. However, 
          this value should never override a _NET_WM_DESKTOP
          property set on window that's being mapped.
          This desktop is relative to the screen provided by 
          the SCREEN key.

  TIMESTAMP

          X server timestamp of the user action that caused this
          launch. For example window manager that doesn't allow
          stealing of focus by newly mapped windows while the user
          works in an application can use this timestamp for windows
          that have matching _NET_STARTUP_ID property if they don't
          have any _NET_WM_USER_TIME property set or if it's older.
          See the description of _NET_WM_USER_TIME in the WM spec
          for details.
          
          This key is obsoleted by including the timestamp directly
          in the ID field.

  DESCRIPTION   

          a short description suitable for display in a dialog that
          indicates what's happening. For example "Opening document
          Foo" or "Launching KWord" - the description should be in
          "foo-ing whatever" format, describing the current status.

  WMCLASS

          a string to match against the "resource name" or "resource
          class" hints. If this key is present, the launchee will most
          likely not send a "remove" message on its own. If the
          desktop environment detects a toplevel window mapped with
          this name or class, it should send a "remove" message for 
          the startup sequence. Note that the class hint is in
          Latin-1, so the value of this key must be converted to 
          Latin-1 before strcmp'ing it with the window class/name. 
          (Though in all known cases only ASCII is involved so it 
          doesn't matter.)
          
  SILENT
          
          a boolean (1/0) value. When set to 1, there should be
          no visual feedback. This can be used to suspend
          the visual feedback temporarily, e.g. when
          application shows a dialog during its startup before
          mapping the main window. Another use is for launch
          sequences for applications that are neither compliant
          nor their WMClass is known, but which should preferably
          have their window mapped on the desktop specified by
          the value of DESKTOP.

  APPLICATION_ID

          When launching an application using a .desktop file from
          the normal application paths (see desktop file specification),
          this should be basename of the .desktop file.
          For example: "foo.desktop".

          When launching a .desktop file NOT in the paths, this should
          be an absolute path to the .desktop file.

Some details of the startup sequence:

 - "new" and "change" messages are sent by the launcher code

 - the launchee code is responsible for sending a "remove" 
   message to end the launch sequence, unless the WMCLASS 
   key was set.

 - the "new" message must be the first message. Other message 
   types should be ignored by all clients unless those clients
   have seen a "new" message with the same ID.

 - "change" messages can be sent at any time between "new" and
   "remove"

  
Communicating from a launcher process to a launchee process
===

To communicate the startup sequence information from a launcher
process to a launchee process, when possible an environment variable
should be used:

 DESKTOP_STARTUP_ID
   value of the "ID" field in the "new" message

It is suggested to unset this environment variable in the launchee
as soon as it's read, to avoid possible reuse by some process started
later by launchee.
Mechanisms other than the environment variable may be used as well, as
long as they are reliable. The environment variable is only used when
the launchee code is in a process started by the launcher code; if
they are in the same process the environment variable may not be
relevant.

Desktop entry spec extensions
===

StartupNotify=BOOLEAN

  If true, it is KNOWN that the application will send a "remove"
  message when started with the DESKTOP_LAUNCH_ID environment variable
  set.
  If false, it is KNOWN that the application does not work
  with startup notification at all (does not shown any window, breaks
  even when using StartupWMClass, etc.).
  If absent, a reasonable handling is up to implementations (assuming false,
  using StartupWMClass, etc.).

StartupWMClass=STRING

  If specified, it is KNOWN that the application will map at least one
  window with the given string as its WM class or WM name hint.

EWMH spec extensions
===

_NET_STARTUP_ID, UTF8_STRING

  The ID used for the startup sequence for the window. If set 
  on a group leader window, applies to all application windows
  in that group that do not set their own _NET_STARTUP_ID.
  
  If a new value for the property is set, the window manager
  should update the window's status accordingly (update its virtual
  desktop, etc.).


Launchee failures
===

The launcher process is responsible for detecting launchee failures
such as a crash and should end the launch sequence in such case.
In case launchee fails to end the launch sequence, clients should
treat the launch sequence as ended withing a reasonable time.


A. Sample code to send X message
===

This code omits creation/destruction of "xwindow" which is the unique
identifier window for the message. It should be created just before
this code and destroyed just after.
   
       XEvent xevent;
       const char *src;
       const char *src_end;
       char *dest;
       char *dest_end;

       xevent.xclient.type = ClientMessage;
       xevent.xclient.message_type = type_atom_begin;
       xevent.xclient.display = xdisplay;
       xevent.xclient.window = xwindow;
       xevent.xclient.format = 8;

       src = message;
       src_end = message + strlen (message) + 1; /* +1 to include nul byte */

       while (src != src_end)
         {
           dest = &xevent.xclient.data.b[0];
           dest_end = dest + 20;

           if (src == message)
             {
               *dest = '\0';
               ++dest;
             }

           while (dest != dest_end &&
                  src != src_end)
             {
               *dest = *src;
               ++dest;
               ++src;
             }

           XSendEvent (xdisplay,
                       target_xwindow,
                       False,
                       PropertyChangeMask,
                       xevent);

           xevent.xclient.message_type = type_atom_begin;
         }

Change history
===

Changes since 0.1:

- TIMESTAMP field is obsoleted by including the timestamp directly in the ID.
- data from "change:" messages should be used even if they precede the matching "new:" message
- added LAUNCHED_BY
- Explicitly mention that updating the _NET_STARTUP_ID property on a window should trigger
  window manager's actions for the new startup notification again.
