6

Is it possible for a c application using libwayland-client.so to get the name of the compositor / display server it opened a connection to (e.g. KWin, Sway, ...)? I fail to find it in the docs.

For reference, in X11 this is possible using XProps specified by EWMH: _NET_SUPPORTING_WM_CHECK to get the window id of the window manager and then using _NET_WM_NAME.

Im fine with anything giving me a way to identify it, for example a pretty name, the process name, the pid or similar.

Current solution is to detect which socket file wayland will be using (${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY:-wayland-0}), detecting which process are listening on it and picking the one which is most probably the compositor (similar to what neofetch does in bash). But since i need to open a connection anyway, and this method is very bug prone, i think you can see why i want to have a cleaner solution.

  • I am cranking up an answer but I'd like some more info. After you have gotten the socket via `wm="$(ps -p "${tmp_pid}" -ho comm=)` what exactly is the output you expect? – Fra93 Jun 21 '22 at 13:53
  • I looked into the Wayland API, and I couldn't find anything in it that would identify a compositor by name. – AlexApps99 Jun 21 '22 at 22:32
  • `lsof -t "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY:-wayland-0}"` gives two pids on my system, `kwin_wayland_wrapper` and `Xwayland`. Note, that the compositor itself, `kwin_wayland`, is not even in it. `ps` fails because of the multiple pids. The problem is, i still would need a list of hardcoded known compositor names, to check which of those processes is the compositor. – Linus Dierheimer Jun 22 '22 at 08:05
  • If the usescase is not clear, its for a neofetch like [app](https://github.com/LinusDierheimer/fastfetch), which simply should reliable show, which wayland compositor the current user is running. – Linus Dierheimer Jun 22 '22 at 08:11
  • Are you using `wl_display_connect(NULL)` to connect and from that you want to find the compositor / display server you opened a connection to? – Ted Lyngmo Jun 22 '22 at 21:29
  • @TedLyngmo Yes exactly. Although i would be fine with a method that doesn't need a connection too. – Linus Dierheimer Jun 23 '22 at 10:19
  • Did you check if you can bind to a global registry object, after the connection, that has the compositor name in it? By looking at the API I found this https://wayland-book.com/surfaces/compositor.html but I don't have a system to experiment if `wl_compositor struct` has a field that contains the name. – Fra93 Jun 23 '22 at 10:51
  • [This example](https://wayland-book.com/registry/binding.html) to dump the server globals looks pretty interesting. – Ted Lyngmo Jun 23 '22 at 11:14
  • I am not sure what you guys mean that `struct wl_compositor` contains a name. It is a forward declaration of a struct never specified, so you can only use a pointer to it to do api requests. If you mean the `interface` argument of `registry_handle_global`, it will always be `"wl_compositor"`. The `name` argument is named a bit unfortunate, because it actually is a unique internal id, which can change between runs. – Linus Dierheimer Jun 23 '22 at 13:12
  • @LinusDierheimer I don't think anyway said that it contains the name. Fra93 said he/she couldn't check if it does. I also looked and, as you say, it's just an opaque type - a pointer to a global that you get from `wl_registry_bind` for example. Did you run the example I linked to? I don't have any possibility to do it myself. I would also look at [Weston](https://github.com/wayland-project/weston), which is the reference implementation of a Wayland compositor. – Ted Lyngmo Jun 23 '22 at 13:43
  • @TedLyngmo The example you linked just outputs the names of the interfaces the compositor is exposing, e.g. `"wl_output"` or `"wl_compositor"` – Linus Dierheimer Jun 23 '22 at 13:54
  • @LinusDierheimer I see. Did you dig into Weston? I was able to compile it with my own debug print outs and run it but I have an X11 server so I couldn't follow the wayland-path in the program. It seems to be able to present the name its connected to though. – Ted Lyngmo Jun 23 '22 at 14:10
  • @TedLyngmo i did, but i didn't find any suiting protocol. – Linus Dierheimer Jun 23 '22 at 14:35

1 Answers1

3

Requirements:

  • determine the PID of the peer compositor process for a display connection on the client side
  • must run under Linux
  • optionally determines the process name

Since this is not directly supported by the API, you can

  • get the file descriptor of the display context (wl_display_get_fd)
  • use the file descriptor to read the associated PID of the peer process (getsockopt with the SO_PEERCRED option, see e.g. this nice SO answer)
  • finally, you can get the process name by reading /proc/<pid>/comm.

You could also retrieve the process command line if you need more information.

However, the output of the following test program would look like this under Ubuntu 22.04 LTS:

pid: 1733, process name: gnome-shell

Self-contained Example in C

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <sys/socket.h>
#include <errno.h>

#define PROCESS_NAME_MAX_LENGTH 1024

static pid_t pid_from_fd(int fd) {
    struct ucred ucred;
    socklen_t len = sizeof(struct ucred);
    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
        perror("getsockopt failed");
        exit(-1);
    }
    return ucred.pid;
}

static char *process_name_from_pid(const pid_t pid) {
    char *name = malloc(PROCESS_NAME_MAX_LENGTH);
    if (!name) {
        perror("malloc failed");
        exit(-1);
    }
    char proc_buf[64];
    sprintf(proc_buf, "/proc/%d/comm", pid);
    FILE *fp;
    if ((fp = fopen(proc_buf, "r")) == NULL) {
        fprintf(stderr, "opening '%s' failed: %s\n", proc_buf, strerror(errno));
        exit(-1);
    }
    if (fgets(name, PROCESS_NAME_MAX_LENGTH, fp) == NULL) {
        fprintf(stderr, "reading '%s' failed\n", proc_buf);
        exit(-1);
    }
    name[strcspn(name, "\n")] = 0;
    fclose(fp);
    return name;
}

int main(void) {
    struct wl_display *display = wl_display_connect(NULL);
    if (display == NULL) {
        fprintf(stderr, "can't connect to display\n");
        exit(-1);
    }
    int fd = wl_display_get_fd(display);
    pid_t pid = pid_from_fd(fd);
    char *process_name = process_name_from_pid(pid);
    printf("pid: %d, process name: %s\n", pid, process_name);
    free(process_name);
    wl_display_disconnect(display);
    return 0;
}
Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47