Search code examples
clinuxwayland

Wayland client get compositor name


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.


Solution

  • 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;
    }