More Than You Ever Wanted To Know About XSMP
While I (DanWinship) was hacking on SessionManagement, I learned a lot about XSMP. And then I kept forgetting the things I'd learned, so I started writing them down. And then I started rewriting the notes in a format suitable for other people to read. And then I decided no one would really care, so I never did anything with it.
But anyway, some other people have been hacking on ../NewGnomeSession lately, or talking about XSMP in other contexts, and I've found myself needing to share bits of this lore, so I figured I'd should make this available somewhere.
The notes are organized as a commentary on the XSMP spec (specifically, the version identified as "X Version 11, Release 6.7") and are divided into three categories:
- Clarifications
- Non-obvious aspects of the spec. Notably:
- Information that is in the spec, but difficult to understand (because it's written badly, or merely implied by various other sections rather than being explicit)
- Information that is not in the spec, but is apparent once you've spent enough time thinking about it
Information that may or may not be in the spec, but is generally agreed by implementors to be what the authors meant to say
- Information about stuff libSM does behind your back that you might not realize.
- Observations
- Information about what various existing XSMP client libraries and XSMP server implementations do. (As of early 2007.)
- IMHOs
- My opinions about what implementations should do in places where the spec is ambiguous or (IMHO) wrong. You may disagree.
In other words, the Clarifications are things you need to know in order to write a conforming implementation, the Observations are things you need to know in order to write an interoperable implementation, and the IMHOs are things you need to know to write a good (IMHO) implementation. (To the extent that that's even possible...)
RegisterClientReply (p. 4)
If the client didn't supply a previous-ID field to the RegisterClient message, the SM must send a SaveYourself message with type = Local, shutdown = False, interact-style = None, and fast = False immediately after the RegisterClientReply. The client should respond to this like any other SaveYourself message.
- Observation
Some clients ignore the last sentence and handle the initial SaveYourself specially for various reasons (eg, setting the required properties, but not signalling the application to save its state, since that would slow down startup).
- Clarification
SmcOpenConnection() automatically retries connecting with a NULL previousId if the server rejects the passed-in previousId. So clients that want to distinguish the "initial SaveYourself" from other SaveYourselfs should expect an "initial SaveYourself" if either (a) they pass a NULL previousId to SmcOpenConnection(), or (b) they pass a non-NULL previousId to SmcOpenConnection(), but the returned clientIdRet is different.
SaveYourself (pp. 4-5)
If interact-style is None, the client must not interact with the user while saving state. If the interact-style is Errors, the client may interact with the user only if an error condition arises. If interact-style is Any, then the client may interact with the user for any purpose.
- Clarification
libSM automatically enforces these semantics on the server side; the server's interact_request callback will only be invoked if the client makes a legitimate InteractRequest. Illegal InteractRequests automatically get a BadState error in response.
- Observation
No known server implementation treats Any and Errors differently. Many clients also ignore the distinction (and in fact, many will request interaction even if they got an interact-style of "None", and then not notice if they get a BadState error.)
When a client receives SaveYourself and has not yet responded SaveYourselfDone to a previous SaveYourself, it must send a SaveYourselfDone and may then begin responding as appropriate to the newly received SaveYourself.
- Clarification
- There are two ways this can happen:
If a shutdown SaveYourself is occurring, and then the server sends a ShutdownCancelled to the client, but then starts a new SaveYourself before the client has responded to the ShutdownCancelled with a SaveYourselfDone.
If the client is doing a single-client SaveYourself (eg, the "initial SaveYourself"), and then another client requests an all-client SaveYourself. (It's not clear whether or not the spec actually intends to allow this case.)
- IMHO
Servers shouldn't do this to clients; they should wait until all clients are in the idle state before starting a new SaveYourself.
The type field specifies the type of information that should be saved: Global, Local, or Both.
- Observation
Almost all GnomeClient-based apps ignore the type field, and either always do a Local SaveYourself or always do a Both SaveYourself. (QApplication and EggSMClient split the SaveYourself callback into two separate signals, avoiding this confusion.)
The Local type indicates that the application must update the properties to reflect its current state, send a SaveYourselfDone and continue. Specifically it should save enough information to restore the state as seen by the user of this client. It should not affect the state as seen by other users.
- Clarification
In other words, the application should checkpoint its state so that it can be resumed later, but it shouldn't change its state in any user-visible way; the application state immediately after resuming should be exactly the same as it was immediately before saving.
The Global type indicates that the user wants the client to commit all of its data to permanent, globally-accessible storage.
- Clarification
- In other words, if the user has open files with unsaved changes, save those changes.
If a word processor was sent a SaveYourself with a type of Local, it could create a temporary file that included the current contents of the file, the location of the cursor, and other aspects of the current editing session. It would then update its RestartCommand property with enough information to find the temporary file, and its DiscardCommand with enough information to remove it.
- Clarification
This is actually much trickier than the spec suggests; eg, someone else might edit the file on disk before the user logs back in again, or alternatively, the session manager might be planning to resume from that saved state more than once in the future (with the user actually saving the file at some point in between). In either case, it's not clear what state of the file the application should present the user with when it is resumed. (SaveYourselfs with save-type=Both don't have this problem, because the user is forced to either commit or discard the unsaved changes before the state is saved.)
- IMHO
Local SaveYourselfs are bad, m'kay?
If a word processor was sent a SaveYourself with a type of Global, it would simply save the currently edited file.
- IMHO
- The last part should say "it would ask the user whether or not to save the currently edited file". Applications shouldn't simply make irrevocable changes to user files without asking the user first.
- IMHO
To ensure that clients do not save files without asking the user first, session managers shouldn't send out a Global or Both SaveYourself with interact-style None, and likewise clients shouldn't send a SaveYourselfRequest with those parameters.
If the client stores any local state in a file or similar 'external' storage, it must create a distinct copy in response to each SaveYourself message.
- Observation
Many GnomeClient-based apps get this wrong, and save their state either globally or per-client-id instead. (However, most servers don't support keeping multiple saved states, so it doesn't usually end up causing problems.) QApplication and EggSMClient do the right thing here automatically.
The shutdown field specifies whether the system is being shut down.... The client must save and then then must prevent interaction until it receives a SaveComplete, Die, or a ShutdownCancelled, because anything the user does after the save will be lost.
- Clarification
The part about preventing interaction is only meant to apply when the shutdown field is True. (This is implied by its position near the discussion of the shutdown field, and by the phrase "anything the user does after the save will be lost", which wouldn't be true in the non-shutdown case. The SMlib documentation also agrees with this interpretation.)
- Observation
- Many clients don't actually block interaction like this.
- IMHO
- Session managers should do their best to prevent clients from receiving input during the logout process (other than when the clients have requested interaction).
The fast field specifies whether or not the client should save its state as quickly as possible.
- Observation
No client is known to make use of the "fast" flag when saving its state. (However, see SaveYourselfRequest below.)
- IMHO
Clients should ignore the "fast" flag.
SaveYourselfPhase2 (p. 5)
- Observation
- In practice, trying to have the window manager remember and preserve state relating to windows has never worked well. If clients do not restore windows in exactly the same way on resume as they were on logout, then the window manager will likely end up applying its saved state to the wrong windows. Also, in many cases you want an application to remember its window state between runs, not just when resuming from a saved session, so clients are likely to have to have code dealing with this anyway.
- IMHO
- Clients should try to preserve their window state themselves. (However, window managers should try to preserve window state information too, in case the clients don't.)
Note that it is currently not possible for apps to reliably save all of their window state on their own. This would require EWMH updates.
SaveYourselfRequest (p. 6)
When the SM receives this request it may generate a SaveYourself message in response and it may leave the fields intact.
- Clarification
Note the "may"s. Clients can't assume that a session manager will act on a SaveYourselfRequest in exactly the way they requested it (or even that it will act on it at all--xsm ignores all SaveYourselfRequests; saves can only be initiated its own UI).
- Observation
Session managers generally don't let the client decide whether or not to save the session when calling SaveYourselfRequest; they use a combination of preferences/settings and logout-dialog buttons to decide. In theory then, the session manager should just ignore the passed-in save-type, and set the save-type of the SaveYourself messages to SmSaveGlobal or SmSaveBoth depending on whether it's just shutting down, or also saving the state. This is what ksmserver does, but old-gnome-session and xfce-session are both buggy:
old-gnome-session passes the save-type from the request to the SaveYourself, except that if the requested save-type is SmSaveGlobal, and then the user chooses to save the session, the save-type is changed to SmSaveBoth. The reverse is not true though; if the requested save-type is SmSaveBoth, that will be passed to the clients even if the session is not actually being saved.
xfce-session only sends SaveYourselfs at all if the user chooses to save the session (in which case it sends the SaveYourselfs with the requested save-type); if the session is not being saved, it just skips over the SaveYourself phase entirely and goes directly to Die. (Technically, that violates the spec, although it violates it in a way that no client can detect, so it doesn't really matter.)
if you pass SmSaveGlobal to xfce-session, and the user chooses to save their state, then correctly-written clients will not save their state.
if you pass SmSaveBoth to old-gnome-session, and the user chooses not to save their state, then correctly-written clients will save their state (though gnome-session will then delete the saved state because it knows that it doesn't want it).
- IMHO
The "correct" way for a client to initiate an ordinary logout is to send a SaveYourselfRequest with SmSaveGlobal for save-type, SmInteractStyleAny for interact-style, True for shutdown and global, and False for fast. However, with current session managers, best results will be achieved by passing SmSaveBoth instead of SmSaveGlobal. (This may cause clients to needlessly save their state under old-gnome-session, but it will clean up after them, and also, most GnomeClient-based apps would needlessly save their state even if you did pass SmSaveGlobal.)
- Observation
gnome-session, ksmserver, and xfce-session all interpret "fast" in a shutdown SaveYourselfRequest to mean "don't show a confirmation dialog, just log out". (GNOME and XFCE propagate the "fast" flag from the SaveYourselfRequest to the SaveYourself; KDE does not.)
- IMHO
The above interpretation of "fast" should be considered canonical.
If global is set to False, then the resulting SaveYourself should be sent to the application that sent the SaveYourselfRequest.
- Observation
The spec allows a SaveYourselfRequest with global=False and shutdown=True, but doesn't define what that would mean. GNOME, KDE, and XFCE all interpret it differently.
- Observation
global=False and shutdown=False SaveYourselfRequests are well-defined ("checkpoint the state of just this client"), but not fully supported. In particular, ksmserver sends the SaveYourself and SaveComplete messages out back-to-back rather than doing the whole state machine properly, which would mess up clients that handle the SaveYourself message asynchronously.
- IMHO
Clients shouldn't send global=False SaveYourselfRequests.
SaveYourselfDone (p. 7)
If the SaveYourself operation was successful, then the client should set the success field to True; otherwise the client should set it to False.
- Observation
gnome-session and xfce-session ignore the success field. ksmserver used to consider success=False to be equivalent to cancel-shutdown=True in InteractDone, but later disabled this behavior for better compatibility with GNOME apps, because libgnomeui used to set success=False if the application didn't implement the "save_yourself" signal. (This was later fixed in libgnomeui, but apparently ksmserver never reverted back to its previous behavior.)
- IMHO
Clients shouldn't return False to a shutdown SaveYourself, unless they're hoping to cancel the shutdown by doing so.
ConnectionClosed (p. 8)
The reason field specifies why the client is resigning from the session... It is the responsibility of the SM to display this reason to the user.
- Observation
- None of the known session managers do this.
- IMHO
- This field is dead. Ignore it.
SetProperties (p. 8)
Sets the specified properties to the specified values... Some properties have predefined semantics.
- Clarification
Although applications are allowed to call SetProperties and DeleteProperties at any time, the changes don't seem to be guaranteed to have any effect on the session manager's behavior until an SmSaveLocal or SmSaveBoth SaveYourself occurs. In particular, clients that change DiscardCommand while not in a SaveYourself are likely to end up leaking saved states.
Errors (p. 9)
Any message received out-of-sequence will generate a BadState error message.
- Clarification
libSM automatically generates BadState errors for most out-of-sequence messages. However, it does not generate an error for a SaveYourselfRequest received while a SaveYourself is already in progress.
- Clarification
Because of the asynchronous way messages are processed, it is possible for a client to send a SaveYourselfRequest to the server which the server doesn't see until after it sends an unrelated SaveYourself to the client. (Eg, if the client sends a SaveYourselfRequest immediately after receiving RegisterClientReply from the server, but the server follows the RegisterClientReply with a SaveYourself.) From the server's perspective it will look like the client illegally tried to start a nested SaveYourself, even though in reality the client was in the "idle" state when it sent the SaveYourselfRequest.
- Clarification
Clients can't reliably send a SaveYourselfRequest immediately after connecting to the server: if you want to have a client connect and then immediately initiate a logout, you have to figure out whether or not the server is going to send you a SaveYourself (as per the clarification about SmcOpenConnection in the section on RegisterClientReply above), and then if so, process that SaveYourself fully before sending the SaveYourselfRequest.
- Observation
gnome-session queues "out-of-sequence" SaveYourselfRequests to run later (but removes them from the queue if the client disconnects). ksmserver processes out-of-sequence SaveYourselfRequests immediately (potentially violating the spec). xfce-session closes the connection if it receives an out-of-sequence SaveYourself. (xsm ignores SaveYourselfRequest.)
- IMHO
The server shouldn't consider "out-of-sequence" SaveYourselfRequests to be errors, but should either postpone or discard them (depending on whether or not the new request is still relevant after the current SaveYourself).
- Clarification
As with the above SaveYourself/SaveYourselfRequest example, it is possible for an InteractRequest and a ShutdownCancelled to cross on the wire. libSM does not detect this case, so servers must check for it themselves (and simply ignore the InteractRequest if they have already cancelled the save).
Client State Diagram (pp. 9-10)
save-yourself: ... if shutdown mode: send SaveYourselfDone -> save-yourself-done otherwise: send SaveYourselfDone -> idle
- Clarification
This rule (and the identical rule in the "phase2" state) contradicts three other sections of the spec (as well as the SMlib docs) and is therefore presumably a bug; the client always goes into save-yourself-done after sending a SaveYourselfDone, regardless of whether it is shutting down or not. (The three contradicting sections are the description of the SaveYourselfDone message [which says the client must wait for a response, with no reference to shutdown vs. non-shutdown], the Server State Diagram [which shows that from the server's POV, the client doesn't become idle again in the non-shutdown case until the server sends it a SaveComplete], and the description of the save-yourself-done state [which allows for receiving a SaveComplete message, which would never happen during a shutdown SaveYourself].)
Session Manager State Diagram (pp. 11-12)
- Clarification
The diagram mixes up the global session manager state and the per-client state. In particular, it ignores the existence of single-client SaveYourselfs. (Eg, it seems to suggest that if the server sends an "initial SaveYourself" to a newly-connected client, and then a different client sends a SaveYourselfRequest, then the server must respond with a BadState error.)
Predefined Properties (pp. 16-18)
The required properties must be set each time a client connects with the SM. The properties must be set after the client sends RegisterClient and before the client sends SaveYourselfDone. Otherwise, the behavior of the session manager is not defined.
- Clarification
All clients should set the required properties after registering with the session manager. Resumed clients won't receive a SaveYourself, and so of course shouldn't send a SaveYourselfDone.
- IMHO
Although a resumed client must set a RestartCommand when reconnecting, it's not well defined what the session manager does with this state (which, in theory, ought to be identical to the state that the SM just resumed from). To be safe, the client shouldn't write any new state information to disk or set a new DiscardCommand (since the session manager might end up leaking it), and it shouldn't reuse its previous DiscardCommand (since the session manager might not notice that it's the same, and so might end up running it and accidentally discarding the previous saved state.)
Clients may set, get, and delete nonstandard properties.
- Observation
- xfce-session discards any properties that it doesn't recognize, but this isn't much of a problem since clients generally treat properties as write-only anyway.
CloneCommand - This is like the RestartCommand except it restarts a copy of the application. The only difference is that the application doesn't supply its client id at register time.
- Observation
Many clients actually set CloneCommand to a command that will start a clean copy of the app, rather than a clone whose "only difference" is a new client id. Qt/KDE apps don't set CloneCommand at all. This generally doesn't matter, since xsm is the only known session manager that makes use of the CloneCommand field, and no one uses xsm.
- IMHO
Session managers should ignore CloneCommand.
CurrentDirectory - On POSIX-based systems specifies the value of the current directory that needs to be set up prior to starting the program
- Observation
- ksmserver does not do this.
- IMHO
Clients shouldn't depend on CurrentDirectory being restored; if they need to restore their working directory, they should store it as part of their saved state and restore it manually.
DiscardCommand - ...
- Clarification
- Clients can never know for sure whether or not the SM will reuse a saved state, and so the SM must be responsible for cleaning up saved states when they are no longer useful:
When a client deletes or changes the value of its DiscardCommand property during a SaveYourself, and the state associated with the old DiscardCommand will never be resumed, the SM should run the old DiscardCommand.
When the SM shuts down, if the most-recently-saved state will never be resumed, it should run the DiscardCommands associated with that state.
If the SM explicitly deletes a saved session, it should run the DiscardCommands associated with that session.
After resuming a saved state, if the SM will never resume that state again, it should run the state's DiscardCommands (although it shouldn't run the DiscardCommand for a client until the client either saves a new state, or exits, since the SM doesn't know for sure when the client is done reading the data from the old saved state.)
Session managers that don't support resuming saved states at all can just run the DiscardCommand right away any time it is set.
- IMHO
Session managers should check if a client is reusing its previous DiscardCommand when saving a new state, and shouldn't discard the "old" state in that case. Session managers should also notice changes to DiscardCommand made during SmSaveGlobal SaveYourselfs, since many clients don't interpret save-type correctly.
- Observation
xfce-session only runs DiscardCommands for clients that fail to resume; all other saved states are leaked. gnome-session also leaks states in some cases. (There is nothing clients can do about this, and they shouldn't try.)
If [DiscardCommand] is not specified, the SM will assume that all of the client's state is encoded in the RestartCommand
- IMHO
This presumably ought to have said "all of the client's state is encoded in the RestartCommand, CurrentDirectory, and Environment", but given that some SMs ignore CurrentDirectory and Environment, it should be considered correct as written.
Environment - On POSIX based systems, this will be of type LISTofARRAY8 where the ARRAY8s alternate between environment variable name and environment variable value.
- Observation
The spec doesn't say what the SM is supposed to do with this field, although it seems obvious that the intent must have been that it should be used to set up the environment when running the RestartCommand and CloneCommand (and in fact, that's what xsm does). KDE and XFCE ignore this property.
- IMHO
As with CurrentDirectory, clients can't rely on this working and should save and restore their environment by hand if they need to.
Program - The name of the program that is running. On POSIX systems this should be the first parameter passed to execve and should be of type ARRAY8.
- Clarification
Program seems to be intended as a user-visible identifier for the client and nothing else. Session managers can't assume that Program necessarily corresponds to anything in particular, and can't assume that different applications will necessarily have different Program values.
- IMHO
Clients that run under an interpreter (eg, python, mono) should make sure that Program is set to the application script/assembly/etc, not the interpreter binary.
RestartCommand - The restart command contains a command that when delivered to the host that the client is running on (determined from the connection), will cause the client to restart in its current state.
- Observation
- Most session managers don't support starting clients on remote hosts.
- IMHO
Session managers shouldn't send Local or Both SaveYourselfs to clients on remote hosts if they don't support restarting them (since they presumably won't be able to run a remote DiscardCommand either). ("Local" SaveYourselfs should just be ignored. "Both" SaveYourselfs should be converted to "Global".)
ResignCommand - A client that sets the RestartStyleHint to RestartAnyway uses this property to specify a command that undoes the effect of the client and removes any saved state.
- Observation
old-gnome-session ran this when the user used the Sessions capplet to remove a no-longer-running RestartAnyway client from the active session (which is correct). No other SM appears to use it ever.
- IMHO
Clients shouldn't use ResignCommand.
RestartStyleHint - If the RestartStyleHint property is present, it will contain the style of restarting the client prefers...
- Observation
ksmserver treats RestartAnyway and RestartImmediately clients as though they were RestartIfRunning. xfce-session treats RestartNever clients as though they were RestartIfRunning!
- IMHO
Don't use RestartAnyway; XDG Autostart is a better solution for that class of application.
ShutdownCommand - This command is executed at shutdown time to clean up after a client that is no longer running but retained its state by setting RestartStyleHint to RestartAnyway.
- Observation
old-gnome-session ra this command at shutdown time for all clients, not just no-longer-running RestartAnyway clients. No other SM appear to use it.
- IMHO
Clients shouldn't use ShutdownCommand.
UserID - Specifies the user's ID. On POSIX-based systems this will contain the the user's name
- IMHO
Session managers should notice when UserID is not the logged-in user's username (eg, when the client's UserID is "root", and the logged-in user is not root), and shouldn't restart the client as the logged-in user in that case. (They could either try to restart it as the specified user, or just fail to restart it.)
- Observation
- Only ksmserver does this.