Back to systemd

Multi-Seat on Linux

systemd, versions 30 and newer, includes support for keeping track of user sessions and seats. This adds full hotplug multi-seat support to Linux. Here's a quick overview on how it works.

If you are interested in the actual APIs for multi-seat support, please have a look at the logind Bus API and sd-login(7). If you are planning to write (or port) a multi-seat aware display manager, please have a look at Writing Display Managers.

Definition of Terms

  • A seat consists of all hardware devices assigned to a specific workplace. It consists of at least one graphics device, and usually also includes keyboard, mouse. It can also include video cameras, sound cards and more. Seats are identified by seat names, which are short strings (<= 64chars), that start with the four characters "seat" followed by at least one more character from the range a-zA-Z0-9, "_" and "-". They are suitable for inclusion in file names. Seat names may or may not be stable and may be reused if a seat becomes available again.
  • A session is defined by the time a user is logged in until he logs out. A session is bound to one or no seats (the latter for 'virtual' ssh logins). Multiple sessions can be attached to the same seat, but only one of them can be active, the others are in the background. A session is identified by a short string. systemd ensures that audit sessions are identical to systemd sessions, and uses the audit session id as session id in systemd (if auditing is enabled). The session identifier too shall be considered a short string (<= 64chars) consisting only of a-zA-Z0-9, "_" and "-", suitable for inclusion in a file name. Session IDs are unique on the local machine and are never reused as long as the machine is online.
  • A user (the way we know it on Unix) corresponds to the person using a computer. A single user can have opened multiple sessions at the same time. A user is identified by a numeric user id (UID) or a user name (string).
  • A multi-session system allows multiple user sessions on the same seat at the same time. Linux+systemd qualifies.
  • A multi-seat system allows multiple independent seats that can be individually and simultaneously used by different users. Linux+systemd qualifies.

All hardware devices that are eligible to being assigned to a seat, are assigned to one. A device can be assigned to only one seat at a time. If a device is not assigned to any particular other seat it is implicitly assigned to the special default seat called "seat0".

Note that hardware like printers, hard disks or network cards is generally not assigned to a specific seat. They are available to all seats equally. (Well, with one exception: USB sticks can be assigned to a seat.)

"seat0" always exists.

udev Rules

Assignment of hardware devices to seats is managed inside the udev database, via settings on the devices:

  • Tag "seat": if set this device is eligible to be assigned to a seat. This tag is set for graphics devices, mice, keyboards, video cards, sound cards and more. Note that some devices like sound cards consist of multiple subdevices (i.e. a PCM for input and another one for output). This tag will be set only for the originating device, not for the individual subdevices. A UI for configuring assignment of devices to seats should enumerate and subscribe to all devices with this tag set and show them in the UI. Note that USB hubs can be assigned to a seat as well, in which case all (current and future) devices plugged into it will also be assigned to the same seat (unless they are explicitly assigned to another seat).
  • Tag "master-of-seat": if set this device is enough for a seat to be considered existent. This tag is usually set for the framebuffer device of graphics cards. A seat hence consists of an arbitrary number of devices marked with the "seat" tag, but (at least) one of these devices needs to be tagged with "master-of-seat" before the seat is actually considered to be around.
  • Property "ID_SEAT": if set specifies the name of the seat a specific device is assigned to. If not set the device is assigned to seat0 (!). Also, to speed up enumeration of hardware belonging to a specific seat the seat is also set as tag on the device. i.e. if the property "ID_SEAT=seat-waldo" is set for a device the tag "seat-waldo" will be set as well. Note (and this is important!) that if a device is assigned to seat0, it will usually not carry such a tag and you need to enumerate all devices and check the "ID_SEAT" property manually. Again, if a device is assigned to seat0 this is visible on the device in two ways: with a property ID_SEAT=seat0 and with no property ID_SEAT set for it at all.
  • Property "ID_AUTOSEAT": if set to "1" then this device automatically generates a new seat and independent seat, which is named after the path of the device. This is set for specialized USB hubs like the Plugable devices, which when plugged in should create a hotplug seat without further configuration.
  • Property "ID_FOR_SEAT": when creating additional (manual) seats starting from a graphics device this is a good choice to name the seat after. It is created from the path of the device. This is useful in UIs for configuring seats: as soon as you create a new seat from a graphics device, read this property and prefix it with "seat-" and use it as name for the seat

A seat exists only and exclusively because a properly tagged device with the right ID_SEAT property exists. Besides udev rules there is no persistent data about seats stored on disk.

Note that logind manages ACLs on a number of device classes, to allow user code to access the device nodes attached to a seat as long as the user has an active session on it. This is mostly transparent to applications. As mentioned above, for certain user software it might be a good idea to watch whether they can access device nodes instead of thinking about seats.

Uses

  • If you are writing a display server (like X11) and you have not been told a seat to run on, imply that you are running for seat0. Now, to find graphics cards and input devices for your X server enumerate all devices, and filter those out that are not assigned to your seat. For this, use the ID_SEAT property and the seatXXX tag. And don't forget that devices assigned to the special seat0 seat do not have the tag or the property ID_SEAT set. (see below for a code example for this)
  • If you are writing a display manager (like gdm), then enumerate all seats via logind's D-Bus interface, and subscribe to new seats being created. Then, spawn an X server and login screen on each seat showing up. (For details, see Writing Display Managers)
  • If you are writing a bootup splash tool (like Plymouth), then ignore all seat information completely and make use of all input devices and graphics cards as they become available. Stop using them as soon as the first X server starts up.
  • If you are writing user-level software interfacing directly with kernel drivers (like PulseAudio), consider ignoring seat information completely, and make available to the user all devices he/she can access. A device that a user cannot access is a device that is not assigned to any of the seats he is currently active on. In order to track changes when sessions come and go (and are activated/deactivated) consider watching the devices nodes with inotify so that you are notified when the access mode changes. This scheme works only if your software runs under the user ID of the user himself!
  • If you are writing a UI to configure seats and assign specific hardware to them, enumerate all hardware with the tag "seat", and read the ID_SEAT property from them to determine seat assignments. When changing assignments, use the AttachDevice() D-Bus call of the logind service which takes a seat name and a number of sysfs paths to the devices. You can also use this call to create a new seat, simply by assigning a graphics device to a previously unused seat name. Use ID_FOR_SEAT as a starting point for a new seat name.

This is only a very basic introduction, for details see logind Bus API and sd-login(7). If you have questions ping Lennart.

Examples

You may use loginctl to list and introspect the seats on your system. Here's an example on a system with two seats:

$ loginctl list-seats
SEAT
seat0
seat-usb-pci-0000_00_1d_0-usb-0_1_2

$ loginctl seat-status seat0
seat0
        Sessions: *1
         Devices:
                  ├ /sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input2
                  │ (input:input2) "Power Button"
                  ├ /sys/devices/LNXSYSTM:00/device:00/PNP0A08:00/LNXVIDEO:00/input/input8
                  │ (input:input8) "Video Bus"
                  ├ /sys/devices/LNXSYSTM:00/device:00/PNP0C0D:00/input/input0
                  │ (input:input0) "Lid Switch"
                  ├ /sys/devices/LNXSYSTM:00/device:00/PNP0C0E:00/input/input1
                  │ (input:input1) "Sleep Button"
                  ├ /sys/devices/pci0000:00/0000:00:02.0/drm/card0
                  │ (drm:card0)
                  ├ /sys/devices/pci0000:00/0000:00:02.0/graphics/fb0
                  │ (graphics:fb0) "inteldrmfb"
                  ├ /sys/devices/pci0000:00/0000:00:1a.0/usb1
                  │ (usb:usb1)
                  │ └ /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1
                  │   (usb:1-1)
                  │   ├ /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.6/1-1.6:1.0/input/input6
                  │   │ (input:input6) "Integrated Camera"
                  │   └ /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.6/1-1.6:1.0/video4linux/video0
                  │     (video4linux:video0) "Integrated Camera"
                  ├ /sys/devices/pci0000:00/0000:00:1b.0/sound/card0
                  │ (sound:card0) "Intel"
                  ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2
                  │ (usb:usb2)
                  │ └ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1
                  │   (usb:2-1)
                  │   ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0/input/input5
                  │   │ (input:input5) "N-Trig Touchscreen"
                  │   └ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.1/input/input9
                  │     (input:input9) "N-Trig Touchscreen"
                  ├ /sys/devices/platform/i8042/serio0/input/input3
                  │ (input:input3) "AT Translated Set 2 keyboard"
                  ├ /sys/devices/platform/i8042/serio1/input/input4
                  │ (input:input4) "SynPS/2 Synaptics TouchPad"
                  ├ /sys/devices/platform/i8042/serio1/serio2/input/input10
                  │ (input:input10) "TPPS/2 IBM TrackPoint"
                  ├ /sys/devices/platform/thinkpad_acpi/input/input7
                  │ (input:input7) "ThinkPad Extra Buttons"
                  └ /sys/devices/platform/thinkpad_acpi/sound/card29
                    (sound:card29) "ThinkPadEC"

$ loginctl seat-status seat-usb-pci-0000_00_1d_0-usb-0_1_2
seat-usb-pci-0000_00_1d_0-usb-0_1_2
         Devices:
                  └ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2
                    (usb:2-1.2)
                    ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.1/graphics/fb1
                    │ (graphics:fb1) "udlfb"
                    ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.0/sound/card1
                    │ (sound:card1) "Device"
                    ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.3/input/input11
                    │ (input:input11) "C-Media Electronics Inc. USB Multimedia Audio Device"
                    ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.5/2-1.2.5:1.0/input/input12
                    │ (input:input12) "EL USB Keyboard "
                    ├ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.5/2-1.2.5:1.1/input/input13
                    │ (input:input13) "EL USB Keyboard "
                    └ /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.6/2-1.2.6:1.0/input/input14
                      (input:input14) "Logitech USB-PS/2 Optical Mouse"

You may use "loginctl attach" to assign hardware to a specific seat.

Here's a short code example (no error checking, so don't use this as is) for enumerating all input devices on a specific seat. A display server might use something like this:

int enum_input_on_seat(const char *seat) {
        struct udev *udev;
        struct udev_enumerate *enumerate;
        struct udev_list_entry *item, *first;

        if (!seat)                                                        /* ← Here's the beef */
                seat = "seat0";                                           /* ← Here's the beef */

        udev = udev_new();

        enumerate = udev_enumerate_new(udev);
        udev_enumerate_add_match_subsystem(enumerate, "input");

        if (strcmp(seat, "seat0") != 0)                                   /* ← Here's the beef */
                udev_enumerate_add_match_tag(enumerate, seat);            /* ← Here's the beef */

        udev_enumerate_scan_devices(enumerate);

        first = udev_enumerate_get_list_entry(e);
        udev_list_entry_foreach(item, first) {
                struct udev_device *d;
                const char *d_seat;

                d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));

                d_seat = udev_device_get_property_value(d, "ID_SEAT");    /* ← Here's the beef */
                if (!d_seat)                                              /* ← Here's the beef */
                        d_seat = "seat0";                                 /* ← Here's the beef */

                if (strcmp(seat, d_seat) == 0) {                          /* ← Here's the beef */

                        /* Found a device, yihaaa! */
                        printf("Found device %s\n", udev_device_new_from_syspath(d));

                        do_something_with_our_input_device(d);
                }

                udev_device_unref(d);
        }

        udev_enumerate_unref(enumerate);
        udev_unref(udev);
}

Multi-Seat X

Note that even though XOrg gained, in version 1.12 of xserver, a new -seat switch to make use of multi-seat information it does so only for input devices, not for displays. To work around this systemd includes a tiny wrapper binary in /lib/systemd/systemd-multi-seat-x which emulates the right behaviour by creating a throw-away X configuration file which does the right thing. If you are implementing a multi-seat display manager you probably want to use this binary instead of the real X binary for now. As soon as XOrg upstream gains proper multi-seat support also for displays this binary will be removed from systemd, hence write your DM to fallback to the real X server if systemd's wrapper is not found.