Follow a desktop session end to end

Build a versioned evidence map across a graphical desktop session.

15 min readChapter 145 of 168Intermediate → advanced
In this chapter

Before you start#

Review PAM and accounts, logind and sessions, Wayland surfaces, application activation, and PipeWire objects. This chapter combines independently pinned systemd v256 and GLib 2.82.2 sources with the separately versioned Wayland and audio material in those lessons. The components were not built or tested together here. The incident below is synthetic.

Learning goals#

Separate session, user manager, application, compositor and audio objects; follow two real source branches at handoff boundaries; track a buffer and file descriptor through an asynchronous example; and propose observations that distinguish competing causes without capturing a desktop.

Source walkthrough#

A session crosses asynchronous boundaries
A session crosses asynchronous boundaries1. Login and user manager to Application: Launch or activate application. 2. Application to Compositor: Create surface and commit buffer. 3. Compositor to Application (return): Configure and release events.Login and user managerApplicationCompositorLaunch or activateapplicationCreate surface andcommit bufferConfigure and releaseevents
  1. Login and user managerApplication

    Launch or activate application

  2. ApplicationCompositor

    Create surface and commit buffer

  3. CompositorApplication

    Return. Configure and release events

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Login and user manager → Application: Launch or activate application
  2. Application → Compositor: Create surface and commit buffer
  3. Compositor → Application (return): Configure and release events
Conceptual model. A hypothetical prepared session, not a captured desktop trace.

A desktop session is a collection of processes communicating through several protocols, not a single call stack. As one conditional example, a login program invokes PAM for authentication and account/session policy, logind tracks a session and seat where configured, a user manager starts selected services, and a compositor accepts application connections. A desktop entry or D-Bus activation can start or contact an application. The application creates a Wayland surface and submits buffers; the compositor eventually handles output. Optional audio can follow a separate PipeWire graph. Which login manager, compositor, bus and service manager are present is a deployment choice. For this chapter's model, assume an explicitly prepared session using a Wayland compositor; do not infer that a particular login manager launches Weston by default.

The first excerpt is from systemd v256 src/login/logind-session.c, session_start, whose file notice states LGPL-2.1-or-later. The surrounding assertions and scope setup are omitted:

c
        if (!s->user)
                return -ESTALE;

        if (s->stopping)
                return -EINVAL;

        if (s->started)
                return 0;

A session without its user reference is stale; one already stopping cannot follow this start path; an already started session returns without repeating it. After these checks, the source calls user_start, starts the session scope and sets up an idle timer, checking each return. It later marks the session started, saves state and sends signals. A successful earlier user-start call does not mean every later scope or timer step succeeded. A logind session object, a PAM transaction, a systemd unit and a compositor process have different owners and lifetimes.

The second excerpt is from GLib 2.82.2 gio/gapplication.c, g_application_register, licensed LGPL-2.1-or-later. It follows the implementation registration call; surrounding allocation and notification are omitted:

c
      if (application->priv->impl == NULL)
        return FALSE;

      application->priv->is_remote = application->priv->remote_actions != NULL;
      application->priv->is_registered = TRUE;

A failed implementation registration returns false before the registered flag is set. If registration succeeds, the code identifies whether remote actions exist and records registration. In the surrounding function a local primary instance emits startup; a remote instance has a different role. A D-Bus name, a process and an application object are not interchangeable identifiers. A successful registration does not mean a surface has been configured, a frame committed or audio connected. See the separate desktop-entry launch implementation for one launch path.

From authentication to a graphical application#

PAM modules form a configured stack called by an application; PAM does not itself define one mandatory login manager or compositor. Authentication, account checks, credential establishment and session setup are related but distinct decisions. A successful password check does not prove a graphical session can allocate a seat, access a display device or start a user service. A failed module can return a status and trigger cleanup of resources already acquired. Logs and configuration can contain sensitive information, so this exercise does not inspect a real PAM stack or account.

A logind session associates a user, session identifier and possibly a seat with a lifecycle. Its session scope and the per-user manager are separate units of management. A user can have more than one session and can have user services whose lifetime does not exactly match one graphical window. The compositor needs an appropriate display and input route, but not every deployment uses the same device-access mechanism. The Linux kernel supplies process, file descriptor, device and namespace mechanisms; userland processes implement session policy and protocols on top of them.

An application may be started directly or activated over D-Bus. A desktop file names an executable and metadata; in an activation path, the bus name and service definition determine how an appropriate process is contacted or started. GApplication can represent a primary instance and route actions to it from another invocation. An application ID is not a PID or a socket fd. The receiving application must process events in its main context; registration can succeed while its UI thread later stalls. Name ownership can disappear when the owning connection closes, and the client must handle failed activation or disconnection.

Wayland's compositor connection is normally a Unix-domain socket. A client has proxy objects for display, registry, surface and other protocol objects; the compositor has corresponding server-side state. These protocol objects are not kernel display objects or Windows-style handles. The client sends requests and receives asynchronous events. A disconnect destroys the usefulness of the connection and requires cleanup of client-side state; it does not automatically prove the user session or machine stopped. A single application can fail while the compositor remains healthy.

A synthetic incident#

Assume this invented sequence, measured from a hypothetical application launch. At 0 ms the session and compositor are already running in the prepared environment. At 10 ms an application activation request is sent. At 22 ms the primary application registers successfully. At 30 ms it creates a Wayland surface; at 40 ms it receives configure serial 17. At 43 ms it acknowledges that serial. At 50 ms it commits buffer B, backed by a shared-memory file descriptor. At 150 ms the user still sees no new content. No presentation event or buffer-release event has been supplied in this evidence. These values are teaching data, not a captured log or a guarantee of real protocol timing.

Hypothesis A is that the compositor never accepted or processed the buffer commit. Hypothesis B is that the compositor processed it but the application did not repaint useful content, the surface was obscured, output was unavailable, or the observation point missed presentation. A third possibility is that the client committed a buffer with dimensions or content inconsistent with its own intended frame. The supplied facts establish neither the compositor's internal state nor what pixels were visible. Registration at 22 ms cannot resolve a frame-presentation question because it belongs to an earlier and different boundary.

The stages below are ordered by protocol dependency. The presentation stage was not observed in this incident, so it is shown as unknown.

Handoffs before a visible frame
  1. Session checks

    logind session_start rejects a stale or stopping session; a started one returns success without repeating startup.

  2. Registration

    g_application_register sets is_registered only after the implementation registers.

  3. Configure and ack

    The compositor sends a configure serial; the client acknowledges it.

  4. Commit

    The client attaches a shared-memory buffer and commits the surface.

  5. Presentation

    Compositing and visibility are separate stages, and no event was supplied here.

Conceptual model. Conditional stage order for the synthetic incident, citing systemd v256 and GLib 2.82.2 for the first two checks; the last stage was not observed and no session was run.

A controlled test would use a self-authored client in a separately prepared disposable graphical environment and record its configure, acknowledgement, buffer content checksum, commit and release observations, plus compositor-side acceptance where available. Compare a known simple solid-color buffer with the suspect buffer while keeping size, scale and surface state fixed. If the control frame is accepted and presented but the suspect frame is not, investigate client buffer construction or state. If neither reaches compositor-side acceptance, investigate the connection and protocol errors. A release event demonstrates that the compositor no longer needs a buffer under that protocol; it is not by itself a timestamp proving the user saw a particular frame.

The test must not capture another user's screen, input or clipboard. It should use only the prepared environment, close its own connections and buffers, stop only its own processes, and discard the environment afterward. If the compositor exits, record the disconnect and preserve the distinction between an application failure and a session-wide failure. This test is a design, not something run here. The safe exercise below needs no graphical environment at all.

Buffer and descriptor ownership#

A shared-memory buffer can be represented by a file descriptor and metadata describing size, stride, offset and pixel format. Passing a descriptor over a Unix socket transfers a reference to an underlying open file description or related kernel object; the receiver gets its own descriptor number. The integer can differ in each process. Closing the sender's descriptor does not necessarily invalidate a reference already received by the compositor, while closing it before a successful transfer can prevent the receiver from obtaining the resource. Neither a descriptor number nor a pathname alone proves buffer contents or ownership.

The kernel side of descriptor passing moves file references onto the message rather than copying the buffer. In net/unix/af_unix.c, v6.12, SPDX GPL-2.0-or-later (the receive path and the rest of the file are omitted):

c
static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
{
	if (too_many_unix_fds(current))
		return -ETOOMANYREFS;

	UNIXCB(skb).fp = scm->fp;
	scm->fp = NULL;

	if (unix_prepare_fpl(UNIXCB(skb).fp))
		return -ENOMEM;

	return 0;
}

The guard for too many in-flight descriptors returns before the move, and the sender's pending list pointer is cleared once the list has moved. The receiver's descriptor numbers are installed later, in a path this excerpt does not show, so the excerpt does not say when the receiver can use the descriptor or what a failed receive does.

Descriptor numbers and kernel references
Descriptor numbers and kernel referencesSender reference: Open file held until the sender closes it → Kernel open file: One object shared by the references (refers to). Receiver fd number: Integer in the receiver, with its own lifetime → Kernel open file: One object shared by the references (refers to; closed separately). Sender fd number: Integer valid only in the sender process → Shared memory pages: Contents; reuse is governed by the protocol (mapped by the sender)refers torefers to; closedseparatelymapped by the senderSender fd numberInteger valid only in thesender processSender referenceOpen file held until thesender closes itReceiver fd numberInteger in the receiver,with its own lifetimeKernel open fileOne object shared by thereferencesShared memory pagesContents; reuse is governedby the protocol
Sender referenceOpen file held until the sender closes it
Kernel open fileOne object shared by the references
Receiver fd numberInteger in the receiver, with its own lifetime
Kernel open fileOne object shared by the references
Sender fd numberInteger valid only in the sender process
Shared memory pagesContents; reuse is governed by the protocol
Read the text version
  • Sender reference: Open file held until the sender closes it → Kernel open file: One object shared by the references (refers to)
  • Receiver fd number: Integer in the receiver, with its own lifetime → Kernel open file: One object shared by the references (refers to; closed separately)
  • Sender fd number: Integer valid only in the sender process → Shared memory pages: Contents; reuse is governed by the protocol (mapped by the sender)
Conceptual model. Teaching model of descriptor passing on Linux v6.12; the descriptor numbers and the buffer are invented, not a captured process.

In the synthetic case, let the client create a file-backed region of sufficient size and send a descriptor as part of creating buffer B. The client retains whatever mapping and references it needs, and the compositor obtains its own reference when descriptor transfer succeeds. After the commit, the client must not modify or reuse the buffer in a way forbidden by the applicable protocol and synchronization contract while the compositor may still use it. A later release permits the relevant reuse under that contract. If the connection fails before the descriptor is received, the sender must close its own temporary references; if it fails afterward, both processes' cleanup paths matter. An fd is an ownership reference, not the frame itself.

For a synthetic 800 by 600 buffer with four bytes per pixel and no extra row padding, the minimum data size is 1,920,000 bytes. A stride larger than 3,200 bytes changes the required allocation. A client that computes only width times height and ignores stride can supply an undersized backing region. Format, scale, buffer transformation and damage must also be consistent with the actual protocol and negotiated features. These are hypotheses to test against the specified client and compositor, not claims that the invented incident had any of these defects.

The host kernel implements descriptor transfer, mappings, scheduling and device interfaces, while the compositor decides how to composite and present content. A Wayland commit is a protocol request, not a direct guarantee that a DRM atomic commit succeeded. Depending on the compositor and backend, GPU work, display synchronization and output configuration add more stages. The Wayland surface lesson describes its versioned protocol baseline; this chapter does not assert that its examples ran with the separately pinned systemd and GLib versions.

Libraries differ in whether they take a descriptor or duplicate it. In gio/gunixfdlist.c, GLib 2.82.2, LGPL-2.1-or-later per its header (the rest of the file is omitted):

c
gint
g_unix_fd_list_append (GUnixFDList  *list,
                       gint          fd,
                       GError      **error)
{
  gint new_fd;

  g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), -1);
  g_return_val_if_fail (fd >= 0, -1);
  g_return_val_if_fail (error == NULL || *error == NULL, -1);

  if ((new_fd = dup_close_on_exec_fd (fd, error)) < 0)
    return -1;

The function duplicates the caller's descriptor with dup_close_on_exec_fd, and the comment above it says the list closes its copy when it is finalized. The caller keeps its own descriptor and must close it. The excerpt does not show finalization or the path after a failed append, so it does not establish that a failed call leaves no reference behind.

Optional audio is a separate graph#

If the application also plays a notification, it can connect to an audio server and negotiate stream format and buffers independently of the Wayland surface. PipeWire nodes, ports and links describe a media graph; policy components may establish routes. An application might show a window but have no audio route, or play audio while its surface is not visible. A successful visual commit does not prove that a media graph is connected, and audio playback does not establish that a frame was displayed.

An audio stream's format, quantum, buffering and device clock affect latency. A denied connection, missing device or unavailable session manager must be reported at that boundary rather than blamed on the compositor without evidence. This discussion is an architectural bridge to earlier audio chapters, not a claim that a real PipeWire node or hardware device was inspected. No microphone, audio output, screen, keyboard or clipboard is accessed in this chapter's lab.

Designing a discriminating report#

Use a timeline with columns for time, process, object, operation, result and evidence source. Mark a value as synthetic, source-derived or observed. For the example, application registration is a source-modeled success at 22 ms; configure serial 17 and the fd transfer are hypothetical protocol events; visible presentation is unknown. Do not insert a compositor success message simply because a client logged a commit. A timestamp from another clock domain may need conversion before it can be compared, and a delayed log line can be emitted after the event it describes.

Resource cleanup is part of the report. The client owns its socket and file descriptors until they are closed or transferred; protocol proxies and mappings have their own lifecycle. On an activation failure there may be no client surface to destroy. On a compositor disconnect, the application must stop using invalid protocol objects and release local resources. A pending callback or worker must not access a freed buffer after teardown. Process exit closes descriptors, but it does not undo arbitrary writes to persistent files or establish that a remote service processed every request.

A resolution claim should name a change and a counterexample. If an undersized buffer is the hypothesis, a corrected stride calculation should make the controlled client pass while a deliberately insufficient size produces a clear failure in the disposable environment. If no runtime test is performed, the best deliverable is a source-based explanation and a test plan, not a claim of a fixed desktop. Different compositor versions and graphics backends can have different behavior; a successful single test cannot certify all drivers or session configurations.

Practice#

This bounded own-process model calculates the supplied buffer size and labels the missing observation. It creates no window, connects to no session bus and reads no desktop state:

sh
python3 - <<'PY'
width, height, bytes_per_pixel = 800, 600, 4
stride = width * bytes_per_pixel
print('stride:', stride, 'minimum bytes:', stride * height)
print('commit supplied:', True, 'presentation supplied:', False)
PY

Expected values are stride 3,200 and size 1,920,000 bytes; presentation is false because no such evidence was supplied. As a counterexample, increase the stride to 3,328: the allocation must account for 1,996,800 bytes, not the tightly packed size. This is arithmetic, not a Wayland or display test. If Python is unavailable, calculate the two products by hand.

For the source-only alternative, use the complete synthetic timeline and the two source excerpts. Produce a table with session-start checks, registration outcome, configure serial, commit, fd owners and unknown presentation. State two competing hypotheses and one observation that would falsify each. Explain cleanup for a disconnect before and after fd transfer. No host desktop, user manager, audio device or compositor connection is required.

Knowledge check#

  1. Why does successful application registration not prove a visible frame?
  2. What do the two error checks at the beginning of session_start establish?
  3. What changes when a file descriptor is transferred to another process?
  4. Why does a buffer release not alone prove that the user saw its contents?
  5. What evidence would distinguish a bad client buffer from failure before compositor acceptance?

Answers#

  1. Registration establishes application-side registration state. Surface configuration, buffer commit, compositing and output presentation are later asynchronous stages with independent failures.
  2. A missing user reference returns stale and a stopping session is rejected. An already started session returns success without repeating the later startup sequence; subsequent operations can still fail on the normal path.
  3. The receiver obtains a descriptor referencing the transferred kernel object, with its own descriptor number and lifetime. It does not merely receive the sender's integer as a globally meaningful handle.
  4. Release concerns when the compositor no longer needs the buffer under the applicable contract. Presentation, visibility and user observation are different facts.
  5. Compare a controlled known buffer with the suspect one, record protocol and compositor acceptance and inspect size, stride and content. A common failure before acceptance favors a different boundary than a failure unique to one buffer.

Checkpoint#

Submit the incident timeline, fd ownership and cleanup table, two hypotheses and a falsifiable control plan. Pass when source facts, synthetic evidence and unknown runtime behavior are labeled separately. Revisit the handoff boundaries and retry if registration or commit is treated as proof of presentation. This is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.