diff options
Diffstat (limited to 'backend/session')
| -rw-r--r-- | backend/session/direct-freebsd.c | 268 | ||||
| -rw-r--r-- | backend/session/direct-ipc.c | 269 | ||||
| -rw-r--r-- | backend/session/direct.c | 278 | ||||
| -rw-r--r-- | backend/session/logind.c | 562 | ||||
| -rw-r--r-- | backend/session/session.c | 359 | 
5 files changed, 1736 insertions, 0 deletions
diff --git a/backend/session/direct-freebsd.c b/backend/session/direct-freebsd.c new file mode 100644 index 00000000..342d0d4e --- /dev/null +++ b/backend/session/direct-freebsd.c @@ -0,0 +1,268 @@ +#include <assert.h> +#include <dev/evdev/input.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +const struct session_impl session_direct; + +struct direct_session { +	struct wlr_session base; +	int tty_fd; +	int old_tty; +	int old_kbmode; +	int sock; +	pid_t child; + +	struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( +		struct wlr_session *base) { +	assert(base->impl == &session_direct); +	return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { +	struct direct_session *session = direct_session_from_session(base); + +	int fd = direct_ipc_open(session->sock, path); +	if (fd < 0) { +		wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), +			fd == -EINVAL ? "; is another display server running?" : ""); +		return fd; +	} + +	return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { +	struct direct_session *session = direct_session_from_session(base); + +	int ev; +	struct drm_version dv = {0}; +	if (ioctl(fd, DRM_IOCTL_VERSION, &dv) == 0) { +		direct_ipc_dropmaster(session->sock, fd); +	} else if (ioctl(fd, EVIOCGVERSION, &ev) == 0) { +		ioctl(fd, EVIOCREVOKE, 0); +	} + +	close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { +	struct direct_session *session = direct_session_from_session(base); + +	// Only seat0 has VTs associated with it +	if (strcmp(session->base.seat, "seat0") != 0) { +		return true; +	} + +	return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { +	struct direct_session *session = direct_session_from_session(base); + +	if (strcmp(session->base.seat, "seat0") == 0) { +		struct vt_mode mode = { +			.mode = VT_AUTO, +		}; + +		errno = 0; + +		ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); +		ioctl(session->tty_fd, KDSETMODE, KD_TEXT); +		ioctl(session->tty_fd, VT_SETMODE, &mode); + +		ioctl(session->tty_fd, VT_ACTIVATE, session->old_tty); + +		if (errno) { +			wlr_log(WLR_ERROR, "Failed to restore tty"); +		} + +		wl_event_source_remove(session->vt_source); +		close(session->tty_fd); +	} + +	direct_ipc_finish(session->sock, session->child); +	close(session->sock); + +	free(session); +} + +static int vt_handler(int signo, void *data) { +	struct direct_session *session = data; +	struct drm_version dv = {0}; +	struct wlr_device *dev; + +	if (session->base.active) { +		session->base.active = false; +		wlr_signal_emit_safe(&session->base.session_signal, session); + +		wl_list_for_each(dev, &session->base.devices, link) { +			if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { +				direct_ipc_dropmaster(session->sock, dev->fd); +			} +		} + +		ioctl(session->tty_fd, VT_RELDISP, 1); +	} else { +		ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + +		wl_list_for_each(dev, &session->base.devices, link) { +			if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { +				direct_ipc_setmaster(session->sock, dev->fd); +			} +		} + +		session->base.active = true; +		wlr_signal_emit_safe(&session->base.session_signal, session); +	} + +	return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { +	int fd = -1, tty = -1, tty0_fd = -1, old_tty = 1; +	if ((tty0_fd = open("/dev/ttyv0", O_RDWR | O_CLOEXEC)) < 0) { +		wlr_log_errno(WLR_ERROR, "Could not open /dev/ttyv0 to find a free vt"); +		goto error; +	} +	if (ioctl(tty0_fd, VT_GETACTIVE, &old_tty) != 0) { +		wlr_log_errno(WLR_ERROR, "Could not get active vt"); +		goto error; +	} +	if (ioctl(tty0_fd, VT_OPENQRY, &tty) != 0) { +		wlr_log_errno(WLR_ERROR, "Could not find a free vt"); +		goto error; +	} +	close(tty0_fd); +	char tty_path[64]; +	snprintf(tty_path, sizeof(tty_path), "/dev/ttyv%d", tty - 1); +	wlr_log(WLR_INFO, "Using tty %s", tty_path); +	fd = open(tty_path, O_RDWR | O_NOCTTY | O_CLOEXEC); + +	if (fd == -1) { +		wlr_log_errno(WLR_ERROR, "Cannot open tty"); +		return false; +	} + +	ioctl(fd, VT_ACTIVATE, tty); +	ioctl(fd, VT_WAITACTIVE, tty); + +	int old_kbmode; +	if (ioctl(fd, KDGKBMODE, &old_kbmode)) { +		wlr_log_errno(WLR_ERROR, "Failed to read tty %d keyboard mode", tty); +		goto error; +	} + +	if (ioctl(fd, KDSKBMODE, K_CODE)) { +		wlr_log_errno(WLR_ERROR, +			"Failed to set keyboard mode K_CODE on tty %d", tty); +		goto error; +	} + +	if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { +		wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty %d", tty); +		goto error; +	} + +	struct vt_mode mode = { +		.mode = VT_PROCESS, +		.relsig = SIGUSR2, +		.acqsig = SIGUSR2, +		.frsig = SIGIO, // has to be set +	}; + +	if (ioctl(fd, VT_SETMODE, &mode) < 0) { +		wlr_log(WLR_ERROR, "Failed to take control of tty %d", tty); +		goto error; +	} + +	struct wl_event_loop *loop = wl_display_get_event_loop(display); +	session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, +		vt_handler, session); +	if (!session->vt_source) { +		goto error; +	} + +	session->base.vtnr = tty; +	session->tty_fd = fd; +	session->old_tty = old_tty; +	session->old_kbmode = old_kbmode; + +	return true; + +error: +	// In case we could not get the last active one, drop back to tty 1, +	// better than hanging in a useless blank console. Otherwise activate the +	// last active. +	ioctl(fd, VT_ACTIVATE, old_tty); +	close(fd); +	return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { +	struct direct_session *session = calloc(1, sizeof(*session)); +	if (!session) { +		wlr_log_errno(WLR_ERROR, "Allocation failed"); +		return NULL; +	} + +	session->sock = direct_ipc_init(&session->child); +	if (session->sock == -1) { +		goto error_session; +	} + +	const char *seat = getenv("XDG_SEAT"); +	if (!seat) { +		seat = "seat0"; +	} + +	if (strcmp(seat, "seat0") == 0) { +		if (!setup_tty(session, disp)) { +			goto error_ipc; +		} +	} else { +		session->base.vtnr = 0; +		session->tty_fd = -1; +	} + +	wlr_log(WLR_INFO, "Successfully loaded direct session"); + +	snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); +	session->base.impl = &session_direct; +	return &session->base; + +error_ipc: +	direct_ipc_finish(session->sock, session->child); +	close(session->sock); +error_session: +	free(session); +	return NULL; +} + +const struct session_impl session_direct = { +	.create = direct_session_create, +	.destroy = direct_session_destroy, +	.open = direct_session_open, +	.close = direct_session_close, +	.change_vt = direct_change_vt, +}; diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c new file mode 100644 index 00000000..2b9634da --- /dev/null +++ b/backend/session/direct-ipc.c @@ -0,0 +1,269 @@ +#define _POSIX_C_SOURCE 200809L +#ifdef __FreeBSD__ +#define __BSD_VISIBLE 1 +#include <dev/evdev/input.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#ifdef __linux__ +#include <sys/sysmacros.h> +#include <linux/major.h> +#endif +#include "backend/session/direct-ipc.h" + +enum { DRM_MAJOR = 226 }; + +#if WLR_HAS_LIBCAP +#include <sys/capability.h> + +static bool have_permissions(void) { +	cap_t cap = cap_get_proc(); +	cap_flag_value_t val; + +	if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) { +		wlr_log(WLR_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master"); +		cap_free(cap); +		return false; +	} + +	cap_free(cap); +	return true; +} +#else +static bool have_permissions(void) { +#ifdef __linux__ +	if (geteuid() != 0) { +		wlr_log(WLR_ERROR, "Do not have root privileges; cannot become DRM master"); +		return false; +	} +#endif +	return true; +} +#endif + +static void send_msg(int sock, int fd, void *buf, size_t buf_len) { +	char control[CMSG_SPACE(sizeof(fd))] = {0}; +	struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; +	struct msghdr msghdr = {0}; + +	if (buf) { +		msghdr.msg_iov = &iovec; +		msghdr.msg_iovlen = 1; +	} + +	if (fd >= 0) { +		msghdr.msg_control = &control; +		msghdr.msg_controllen = sizeof(control); + +		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); +		*cmsg = (struct cmsghdr) { +			.cmsg_level = SOL_SOCKET, +			.cmsg_type = SCM_RIGHTS, +			.cmsg_len = CMSG_LEN(sizeof(fd)), +		}; +		memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); +	} + +	ssize_t ret; +	do { +		ret = sendmsg(sock, &msghdr, 0); +	} while (ret < 0 && errno == EINTR); +} + +static ssize_t recv_msg(int sock, int *fd_out, void *buf, size_t buf_len) { +	char control[CMSG_SPACE(sizeof(*fd_out))] = {0}; +	struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; +	struct msghdr msghdr = {0}; + +	if (buf) { +		msghdr.msg_iov = &iovec; +		msghdr.msg_iovlen = 1; +	} + +	if (fd_out) { +		msghdr.msg_control = &control; +		msghdr.msg_controllen = sizeof(control); +	} + +	ssize_t ret; +	do { +		ret = recvmsg(sock, &msghdr, MSG_CMSG_CLOEXEC); +	} while (ret < 0 && errno == EINTR); + +	if (fd_out) { +		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); +		if (cmsg) { +			memcpy(fd_out, CMSG_DATA(cmsg), sizeof(*fd_out)); +		} else { +			*fd_out = -1; +		} +	} + +	return ret; +} + +enum msg_type { +	MSG_OPEN, +	MSG_SETMASTER, +	MSG_DROPMASTER, +	MSG_END, +}; + +struct msg { +	enum msg_type type; +	char path[256]; +}; + +static void communicate(int sock) { +	struct msg msg; +	int drm_fd = -1; +	bool running = true; + +	while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) { +		switch (msg.type) { +		case MSG_OPEN: +			errno = 0; + +			// These are the same flags that logind opens files with +			int fd = open(msg.path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); +			int ret = errno; +			if (fd == -1) { +				goto error; +			} + +#ifndef __FreeBSD__ +			struct stat st; +			if (fstat(fd, &st) < 0) { +				ret = errno; +				goto error; +			} + +			uint32_t maj = major(st.st_rdev); +			if (maj != INPUT_MAJOR && maj != DRM_MAJOR) { +				ret = ENOTSUP; +				goto error; +			} + +			if (maj == DRM_MAJOR && drmSetMaster(fd)) { +				ret = errno; +			} +#else +			int ev; +			struct drm_version dv = {0}; +			if (ioctl(fd, EVIOCGVERSION, &ev) == -1 && +					ioctl(fd, DRM_IOCTL_VERSION, &dv) == -1) { +				ret = ENOTSUP; +				goto error; +			} + +			if (dv.version_major != 0 && drmSetMaster(fd)) { +				ret = errno; +			} +#endif + +error: +			send_msg(sock, ret ? -1 : fd, &ret, sizeof(ret)); +			if (fd >= 0) { +				close(fd); +			} + +			break; + +		case MSG_SETMASTER: +			drmSetMaster(drm_fd); +			close(drm_fd); +			send_msg(sock, -1, NULL, 0); +			break; + +		case MSG_DROPMASTER: +			drmDropMaster(drm_fd); +			close(drm_fd); +			send_msg(sock, -1, NULL, 0); +			break; + +		case MSG_END: +			running = false; +			send_msg(sock, -1, NULL, 0); +			break; +		} +	} + +	close(sock); +} + +int direct_ipc_open(int sock, const char *path) { +	struct msg msg = { .type = MSG_OPEN }; +	snprintf(msg.path, sizeof(msg.path), "%s", path); + +	send_msg(sock, -1, &msg, sizeof(msg)); + +	int fd, err, ret; +	int retry = 0; +	do { +		ret = recv_msg(sock, &fd, &err, sizeof(err)); +	} while (ret == 0 && retry++ < 3); + +	return err ? -err : fd; +} + +void direct_ipc_setmaster(int sock, int fd) { +	struct msg msg = { .type = MSG_SETMASTER }; + +	send_msg(sock, fd, &msg, sizeof(msg)); +	recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_dropmaster(int sock, int fd) { +	struct msg msg = { .type = MSG_DROPMASTER }; + +	send_msg(sock, fd, &msg, sizeof(msg)); +	recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_finish(int sock, pid_t pid) { +	struct msg msg = { .type = MSG_END }; + +	send_msg(sock, -1, &msg, sizeof(msg)); +	recv_msg(sock, NULL, NULL, 0); + +	waitpid(pid, NULL, 0); +} + +int direct_ipc_init(pid_t *pid_out) { +	if (!have_permissions()) { +		return -1; +	} + +	int sock[2]; +	if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) < 0) { +		wlr_log_errno(WLR_ERROR, "Failed to create socket pair"); +		return -1; +	} + +	pid_t pid = fork(); +	if (pid < 0) { +		wlr_log_errno(WLR_ERROR, "Fork failed"); +		close(sock[0]); +		close(sock[1]); +		return -1; +	} else if (pid == 0) { +		close(sock[0]); +		communicate(sock[1]); +		_Exit(0); +	} + +	close(sock[1]); +	*pid_out = pid; +	return sock[0]; +} diff --git a/backend/session/direct.c b/backend/session/direct.c new file mode 100644 index 00000000..0912cd58 --- /dev/null +++ b/backend/session/direct.c @@ -0,0 +1,278 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <linux/kd.h> +#include <linux/major.h> +#include <linux/vt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_direct; + +struct direct_session { +	struct wlr_session base; +	int tty_fd; +	int old_kbmode; +	int sock; +	pid_t child; + +	struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( +		struct wlr_session *base) { +	assert(base->impl == &session_direct); +	return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { +	struct direct_session *session = direct_session_from_session(base); + +	int fd = direct_ipc_open(session->sock, path); +	if (fd < 0) { +		wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), +			fd == -EINVAL ? "; is another display server running?" : ""); +		return fd; +	} + +	struct stat st; +	if (fstat(fd, &st) < 0) { +		close(fd); +		return -errno; +	} + +	if (major(st.st_rdev) == DRM_MAJOR) { +		direct_ipc_setmaster(session->sock, fd); +	} + +	return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { +	struct direct_session *session = direct_session_from_session(base); + +	struct stat st; +	if (fstat(fd, &st) < 0) { +		wlr_log_errno(WLR_ERROR, "Stat failed"); +		close(fd); +		return; +	} + +	if (major(st.st_rdev) == DRM_MAJOR) { +		direct_ipc_dropmaster(session->sock, fd); +	} else if (major(st.st_rdev) == INPUT_MAJOR) { +		ioctl(fd, EVIOCREVOKE, 0); +	} + +	close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { +	struct direct_session *session = direct_session_from_session(base); + +	// Only seat0 has VTs associated with it +	if (strcmp(session->base.seat, "seat0") != 0) { +		return true; +	} + +	return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { +	struct direct_session *session = direct_session_from_session(base); + +	if (strcmp(session->base.seat, "seat0") == 0) { +		struct vt_mode mode = { +			.mode = VT_AUTO, +		}; +		errno = 0; + +		ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); +		ioctl(session->tty_fd, KDSETMODE, KD_TEXT); +		ioctl(session->tty_fd, VT_SETMODE, &mode); + +		if (errno) { +			wlr_log(WLR_ERROR, "Failed to restore tty"); +		} + +		wl_event_source_remove(session->vt_source); +		close(session->tty_fd); +	} + +	direct_ipc_finish(session->sock, session->child); +	close(session->sock); + +	free(session); +} + +static int vt_handler(int signo, void *data) { +	struct direct_session *session = data; + +	if (session->base.active) { +		session->base.active = false; +		wlr_signal_emit_safe(&session->base.session_signal, session); + +		struct wlr_device *dev; +		wl_list_for_each(dev, &session->base.devices, link) { +			if (major(dev->dev) == DRM_MAJOR) { +				direct_ipc_dropmaster(session->sock, +					dev->fd); +			} +		} + +		ioctl(session->tty_fd, VT_RELDISP, 1); +	} else { +		ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + +		struct wlr_device *dev; +		wl_list_for_each(dev, &session->base.devices, link) { +			if (major(dev->dev) == DRM_MAJOR) { +				direct_ipc_setmaster(session->sock, +					dev->fd); +			} +		} + +		session->base.active = true; +		wlr_signal_emit_safe(&session->base.session_signal, session); +	} + +	return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { +	int fd = open("/dev/tty", O_RDWR); +	if (fd == -1) { +		wlr_log_errno(WLR_ERROR, "Cannot open /dev/tty"); +		return false; +	} + +	struct vt_stat vt_stat; +	if (ioctl(fd, VT_GETSTATE, &vt_stat)) { +		wlr_log_errno(WLR_ERROR, "Could not get current tty number"); +		goto error; +	} + +	int tty = vt_stat.v_active; +	int ret, kd_mode, old_kbmode; + +	ret = ioctl(fd, KDGETMODE, &kd_mode); +	if (ret) { +		wlr_log_errno(WLR_ERROR, "Failed to get tty mode"); +		goto error; +	} + +	if (kd_mode != KD_TEXT) { +		wlr_log(WLR_ERROR, +			"tty already in graphics mode; is another display server running?"); +		goto error; +	} + +	ioctl(fd, VT_ACTIVATE, tty); +	ioctl(fd, VT_WAITACTIVE, tty); + +	if (ioctl(fd, KDGKBMODE, &old_kbmode)) { +		wlr_log_errno(WLR_ERROR, "Failed to read keyboard mode"); +		goto error; +	} + +	if (ioctl(fd, KDSKBMODE, K_OFF)) { +		wlr_log_errno(WLR_ERROR, "Failed to set keyboard mode"); +		goto error; +	} + +	if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { +		wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty"); +		goto error; +	} + +	struct vt_mode mode = { +		.mode = VT_PROCESS, +		.relsig = SIGUSR2, +		.acqsig = SIGUSR2, +	}; + +	if (ioctl(fd, VT_SETMODE, &mode) < 0) { +		wlr_log(WLR_ERROR, "Failed to take control of tty"); +		goto error; +	} + +	struct wl_event_loop *loop = wl_display_get_event_loop(display); +	session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, +		vt_handler, session); +	if (!session->vt_source) { +		goto error; +	} + +	session->base.vtnr = tty; +	session->tty_fd = fd; +	session->old_kbmode = old_kbmode; + +	return true; + +error: +	close(fd); +	return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { +	struct direct_session *session = calloc(1, sizeof(*session)); +	if (!session) { +		wlr_log_errno(WLR_ERROR, "Allocation failed"); +		return NULL; +	} + +	session->sock = direct_ipc_init(&session->child); +	if (session->sock == -1) { +		goto error_session; +	} + +	const char *seat = getenv("XDG_SEAT"); +	if (!seat) { +		seat = "seat0"; +	} + +	if (strcmp(seat, "seat0") == 0) { +		if (!setup_tty(session, disp)) { +			goto error_ipc; +		} +	} else { +		session->base.vtnr = 0; +		session->tty_fd = -1; +	} + +	snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); +	session->base.impl = &session_direct; + +	wlr_log(WLR_INFO, "Successfully loaded direct session"); +	return &session->base; + +error_ipc: +	direct_ipc_finish(session->sock, session->child); +	close(session->sock); +error_session: +	free(session); +	return NULL; +} + +const struct session_impl session_direct = { +	.create = direct_session_create, +	.destroy = direct_session_destroy, +	.open = direct_session_open, +	.close = direct_session_close, +	.change_vt = direct_change_vt, +}; diff --git a/backend/session/logind.c b/backend/session/logind.c new file mode 100644 index 00000000..9a1383ce --- /dev/null +++ b/backend/session/logind.c @@ -0,0 +1,562 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +#if WLR_HAS_SYSTEMD +	#include <systemd/sd-bus.h> +	#include <systemd/sd-login.h> +#elif WLR_HAS_ELOGIND +	#include <elogind/sd-bus.h> +	#include <elogind/sd-login.h> +#endif + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_logind; + +struct logind_session { +	struct wlr_session base; + +	sd_bus *bus; +	struct wl_event_source *event; + +	char *id; +	char *path; + +	// specifies whether a drm device was taken +	// if so, the session will be (de)activated with the drm fd, +	// otherwise with the dbus PropertiesChanged on "active" signal +	bool has_drm; +}; + +static struct logind_session *logind_session_from_session( +		struct wlr_session *base) { +	assert(base->impl == &session_logind); +	return (struct logind_session *)base; +} + +static int logind_take_device(struct wlr_session *base, const char *path) { +	struct logind_session *session = logind_session_from_session(base); + +	int fd = -1; +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	struct stat st; +	if (stat(path, &st) < 0) { +		wlr_log(WLR_ERROR, "Failed to stat '%s'", path); +		return -1; +	} + +	if (major(st.st_rdev) == DRM_MAJOR) { +		session->has_drm = true; +	} + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		session->path, "org.freedesktop.login1.Session", "TakeDevice", +		&error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to take device '%s': %s", path, +			error.message); +		goto out; +	} + +	int paused = 0; +	ret = sd_bus_message_read(msg, "hb", &fd, &paused); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to parse D-Bus response for '%s': %s", +			path, strerror(-ret)); +		goto out; +	} + +	// The original fd seems to be closed when the message is freed +	// so we just clone it. +	fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); +	if (fd < 0) { +		wlr_log(WLR_ERROR, "Failed to clone file descriptor for '%s': %s", +			path, strerror(errno)); +		goto out; +	} + +out: +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +	return fd; +} + +static void logind_release_device(struct wlr_session *base, int fd) { +	struct logind_session *session = logind_session_from_session(base); + +	struct stat st; +	if (fstat(fd, &st) < 0) { +		wlr_log(WLR_ERROR, "Failed to stat device '%d': %s", fd, +			strerror(errno)); +		return; +	} + +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; +	int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		session->path, "org.freedesktop.login1.Session", "ReleaseDevice", +		&error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to release device '%d': %s", fd, +			error.message); +	} + +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +	close(fd); +} + +static bool logind_change_vt(struct wlr_session *base, unsigned vt) { +	struct logind_session *session = logind_session_from_session(base); + +	// Only seat0 has VTs associated with it +	if (strcmp(session->base.seat, "seat0") != 0) { +		return true; +	} + +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		"/org/freedesktop/login1/seat/self", "org.freedesktop.login1.Seat", "SwitchTo", +		&error, &msg, "u", (uint32_t)vt); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to change to vt '%d'", vt); +	} + +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +	return ret >= 0; +} + +static bool find_session_path(struct logind_session *session) { +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +			"/org/freedesktop/login1", "org.freedesktop.login1.Manager", +			"GetSession", &error, &msg, "s", session->id); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to get session path: %s", error.message); +		goto out; +	} + +	const char *path; +	ret = sd_bus_message_read(msg, "o", &path); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Could not parse session path: %s", error.message); +		goto out; +	} +	session->path = strdup(path); + +out: +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); + +	return ret >= 0; +} + +static bool session_activate(struct logind_session *session) { +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		session->path, "org.freedesktop.login1.Session", "Activate", +		&error, &msg, ""); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to activate session: %s", error.message); +	} + +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +	return ret >= 0; +} + +static bool take_control(struct logind_session *session) { +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		session->path, "org.freedesktop.login1.Session", "TakeControl", +		&error, &msg, "b", false); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to take control of session: %s", +			error.message); +	} + +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +	return ret >= 0; +} + +static void release_control(struct logind_session *session) { +	int ret; +	sd_bus_message *msg = NULL; +	sd_bus_error error = SD_BUS_ERROR_NULL; + +	ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +		session->path, "org.freedesktop.login1.Session", "ReleaseControl", +		&error, &msg, ""); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to release control of session: %s", +			error.message); +	} + +	sd_bus_error_free(&error); +	sd_bus_message_unref(msg); +} + +static void logind_session_destroy(struct wlr_session *base) { +	struct logind_session *session = logind_session_from_session(base); + +	release_control(session); + +	wl_event_source_remove(session->event); +	sd_bus_unref(session->bus); +	free(session->id); +	free(session->path); +	free(session); +} + +static int session_removed(sd_bus_message *msg, void *userdata, +		sd_bus_error *ret_error) { +	wlr_log(WLR_INFO, "SessionRemoved signal received"); +	return 0; +} + +static struct wlr_device *find_device(struct wlr_session *session, +		dev_t devnum) { +	struct wlr_device *dev; + +	wl_list_for_each(dev, &session->devices, link) { +		if (dev->dev == devnum) { +			return dev; +		} +	} + +	wlr_log(WLR_ERROR, "Tried to use dev_t %lu not opened by session", +		(unsigned long)devnum); +	assert(0); +} + +static int pause_device(sd_bus_message *msg, void *userdata, +		sd_bus_error *ret_error) { +	struct logind_session *session = userdata; +	int ret; + +	uint32_t major, minor; +	const char *type; +	ret = sd_bus_message_read(msg, "uus", &major, &minor, &type); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to parse D-Bus response for PauseDevice: %s", +			strerror(-ret)); +		goto error; +	} + +	if (major == DRM_MAJOR) { +		assert(session->has_drm); +		session->base.active = false; +		wlr_signal_emit_safe(&session->base.session_signal, session); +	} + +	if (strcmp(type, "pause") == 0) { +		ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", +			session->path, "org.freedesktop.login1.Session", "PauseDeviceComplete", +			ret_error, &msg, "uu", major, minor); +		if (ret < 0) { +			wlr_log(WLR_ERROR, "Failed to send PauseDeviceComplete signal: %s", +				strerror(-ret)); +		} +	} + +error: +	return 0; +} + +static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { +	struct logind_session *session = userdata; +	int ret; + +	int fd; +	uint32_t major, minor; +	ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to parse D-Bus response for ResumeDevice: %s", +			strerror(-ret)); +		goto error; +	} + +	if (major == DRM_MAJOR) { +		struct wlr_device *dev = find_device(&session->base, makedev(major, minor)); +		dup2(fd, dev->fd); + +		if (!session->base.active) { +			session->base.active = true; +			wlr_signal_emit_safe(&session->base.session_signal, session); +		} +	} + +error: +	return 0; +} + +static int properties_changed(sd_bus_message *msg, void *userdata, +		sd_bus_error *ret_error) { +	struct logind_session *session = userdata; +	int ret = 0; + +	// if we have a drm fd we don't depend on this +	if (session->has_drm) { +		return 0; +	} + +	// PropertiesChanged arg 1: interface +	const char *interface; +	ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path +	if (ret < 0) { +		goto error; +	} + +	if (strcmp(interface, "org.freedesktop.login1.Session") != 0) { +		// not interesting for us; ignore +		wlr_log(WLR_DEBUG, "ignoring PropertiesChanged from %s", interface); +		return 0; +	} + +	// PropertiesChanged arg 2: changed properties with values +	ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); +	if (ret < 0) { +		goto error; +	} + +	const char *s; +	while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { +		ret = sd_bus_message_read_basic(msg, 's', &s); +		if (ret < 0) { +			goto error; +		} + +		if (strcmp(s, "Active") == 0) { +			int ret; +			ret = sd_bus_message_enter_container(msg, 'v', "b"); +			if (ret < 0) { +				goto error; +			} + +			bool active; +			ret = sd_bus_message_read_basic(msg, 'b', &active); +			if (ret < 0) { +				goto error; +			} + +			if (session->base.active != active) { +				session->base.active = active; +				wlr_signal_emit_safe(&session->base.session_signal, session); +			} +			return 0; +		} else { +			sd_bus_message_skip(msg, "{sv}"); +		} + +		ret = sd_bus_message_exit_container(msg); +		if (ret < 0) { +			goto error; +		} +	} + +	if (ret < 0) { +		goto error; +	} + +	ret = sd_bus_message_exit_container(msg); +	if (ret < 0) { +		goto error; +	} + +	// PropertiesChanged arg 3: changed properties without values +	sd_bus_message_enter_container(msg, 'a', "s"); +	while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) { +		if (strcmp(s, "Active") == 0) { +			sd_bus_error error = SD_BUS_ERROR_NULL; +			bool active; +			ret = sd_bus_get_property_trivial(session->bus, +				"org.freedesktop.login1", session->path, +				"org.freedesktop.login1.Session", "Active", &error, +				'b', &active); +			if (ret < 0) { +				wlr_log(WLR_ERROR, "Failed to get 'Active' property: %s", +					error.message); +				return 0; +			} + +			if (session->base.active != active) { +				session->base.active = active; +				wlr_signal_emit_safe(&session->base.session_signal, session); +			} +			return 0; +		} +	} + +	if (ret < 0) { +		goto error; +	} + +	return 0; + +error: +	wlr_log(WLR_ERROR, "Failed to parse D-Bus PropertiesChanged: %s", +		strerror(-ret)); +	return 0; +} + +static bool add_signal_matches(struct logind_session *session) { +	int ret; + +	char str[256]; +	const char *fmt = "type='signal'," +		"sender='org.freedesktop.login1'," +		"interface='org.freedesktop.login1.%s'," +		"member='%s'," +		"path='%s'"; + +	snprintf(str, sizeof(str), fmt, "Manager", "SessionRemoved", +		"/org/freedesktop/login1"); +	ret = sd_bus_add_match(session->bus, NULL, str, session_removed, session); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); +		return false; +	} + +	snprintf(str, sizeof(str), fmt, "Session", "PauseDevice", session->path); +	ret = sd_bus_add_match(session->bus, NULL, str, pause_device, session); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); +		return false; +	} + +	snprintf(str, sizeof(str), fmt, "Session", "ResumeDevice", session->path); +	ret = sd_bus_add_match(session->bus, NULL, str, resume_device, session); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); +		return false; +	} + +	ret = sd_bus_match_signal(session->bus, NULL, "org.freedesktop.login1", +		session->path, "org.freedesktop.DBus.Properties", "PropertiesChanged", +		properties_changed, session); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); +		return false; +	} + +	return true; +} + +static int dbus_event(int fd, uint32_t mask, void *data) { +	sd_bus *bus = data; +	while (sd_bus_process(bus, NULL) > 0) { +		// Do nothing. +	} +	return 1; +} + +static struct wlr_session *logind_session_create(struct wl_display *disp) { +	int ret; +	struct logind_session *session = calloc(1, sizeof(*session)); +	if (!session) { +		wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); +		return NULL; +	} + +	ret = sd_pid_get_session(getpid(), &session->id); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to get session id: %s", strerror(-ret)); +		goto error; +	} + +	char *seat; +	ret = sd_session_get_seat(session->id, &seat); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to get seat id: %s", strerror(-ret)); +		goto error; +	} +	snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + +	if (strcmp(seat, "seat0") == 0) { +		ret = sd_session_get_vt(session->id, &session->base.vtnr); +		if (ret < 0) { +			wlr_log(WLR_ERROR, "Session not running in virtual terminal"); +			goto error; +		} +	} +	free(seat); + +	ret = sd_bus_default_system(&session->bus); +	if (ret < 0) { +		wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", strerror(-ret)); +		goto error; +	} + +	if (!find_session_path(session)) { +		sd_bus_unref(session->bus); +		goto error; +	} + +	if (!add_signal_matches(session)) { +		goto error_bus; +	} + +	struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); +	session->event = wl_event_loop_add_fd(event_loop, sd_bus_get_fd(session->bus), +		WL_EVENT_READABLE, dbus_event, session->bus); + +	if (!session_activate(session)) { +		goto error_bus; +	} + +	if (!take_control(session)) { +		goto error_bus; +	} + +	wlr_log(WLR_INFO, "Successfully loaded logind session"); + +	session->base.impl = &session_logind; +	return &session->base; + +error_bus: +	sd_bus_unref(session->bus); +	free(session->path); + +error: +	free(session->id); +	return NULL; +} + +const struct session_impl session_logind = { +	.create = logind_session_create, +	.destroy = logind_session_destroy, +	.open = logind_take_device, +	.close = logind_release_device, +	.change_vt = logind_change_vt, +}; diff --git a/backend/session/session.c b/backend/session/session.c new file mode 100644 index 00000000..96cde65c --- /dev/null +++ b/backend/session/session.c @@ -0,0 +1,359 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libudev.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "util/signal.h" + +extern const struct session_impl session_logind; +extern const struct session_impl session_direct; + +static const struct session_impl *impls[] = { +#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND +	&session_logind, +#endif +	&session_direct, +	NULL, +}; + +static int udev_event(int fd, uint32_t mask, void *data) { +	struct wlr_session *session = data; + +	struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); +	if (!udev_dev) { +		return 1; +	} + +	const char *action = udev_device_get_action(udev_dev); + +	wlr_log(WLR_DEBUG, "udev event for %s (%s)", +		udev_device_get_sysname(udev_dev), action); + +	if (!action || strcmp(action, "change") != 0) { +		goto out; +	} + +	dev_t devnum = udev_device_get_devnum(udev_dev); +	struct wlr_device *dev; + +	wl_list_for_each(dev, &session->devices, link) { +		if (dev->dev == devnum) { +			wlr_signal_emit_safe(&dev->signal, session); +			break; +		} +	} + +out: +	udev_device_unref(udev_dev); +	return 1; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { +	struct wlr_session *session = +		wl_container_of(listener, session, display_destroy); +	wlr_session_destroy(session); +} + +struct wlr_session *wlr_session_create(struct wl_display *disp) { +	struct wlr_session *session = NULL; + +	const char *env_wlr_session = getenv("WLR_SESSION"); +	if (env_wlr_session) { +		if (!strcmp(env_wlr_session, "logind") || !strcmp(env_wlr_session, "systemd")) { +		#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND +			session = session_logind.create(disp); +		#else +			wlr_log(WLR_ERROR, "wlroots is not compiled with logind support"); +		#endif +		} else if (!strcmp(env_wlr_session, "direct")) { +			session = session_direct.create(disp); +		} else { +			wlr_log(WLR_ERROR, "WLR_SESSION has an invalid value: %s", env_wlr_session); +		} +	} else { +		const struct session_impl **iter; +		for (iter = impls; !session && *iter; ++iter) { +			session = (*iter)->create(disp); +		} +	} + +	if (!session) { +		wlr_log(WLR_ERROR, "Failed to load session backend"); +		return NULL; +	} + +	session->active = true; +	wl_signal_init(&session->session_signal); +	wl_signal_init(&session->events.destroy); +	wl_list_init(&session->devices); + +	session->udev = udev_new(); +	if (!session->udev) { +		wlr_log_errno(WLR_ERROR, "Failed to create udev context"); +		goto error_session; +	} + +	session->mon = udev_monitor_new_from_netlink(session->udev, "udev"); +	if (!session->mon) { +		wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); +		goto error_udev; +	} + +	udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL); +	udev_monitor_enable_receiving(session->mon); + +	struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); +	int fd = udev_monitor_get_fd(session->mon); + +	session->udev_event = wl_event_loop_add_fd(event_loop, fd, +		WL_EVENT_READABLE, udev_event, session); +	if (!session->udev_event) { +		wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); +		goto error_mon; +	} + +	session->display_destroy.notify = handle_display_destroy; +	wl_display_add_destroy_listener(disp, &session->display_destroy); + +	return session; + +error_mon: +	udev_monitor_unref(session->mon); +error_udev: +	udev_unref(session->udev); +error_session: +	session->impl->destroy(session); +	return NULL; +} + +void wlr_session_destroy(struct wlr_session *session) { +	if (!session) { +		return; +	} + +	wlr_signal_emit_safe(&session->events.destroy, session); +	wl_list_remove(&session->display_destroy.link); + +	wl_event_source_remove(session->udev_event); +	udev_monitor_unref(session->mon); +	udev_unref(session->udev); + +	session->impl->destroy(session); +} + +int wlr_session_open_file(struct wlr_session *session, const char *path) { +	int fd = session->impl->open(session, path); +	if (fd < 0) { +		return fd; +	} + +	struct wlr_device *dev = malloc(sizeof(*dev)); +	if (!dev) { +		wlr_log_errno(WLR_ERROR, "Allocation failed"); +		goto error; +	} + +	struct stat st; +	if (fstat(fd, &st) < 0) { +		wlr_log_errno(WLR_ERROR, "Stat failed"); +		goto error; +	} + +	dev->fd = fd; +	dev->dev = st.st_rdev; +	wl_signal_init(&dev->signal); +	wl_list_insert(&session->devices, &dev->link); + +	return fd; + +error: +	free(dev); +	return fd; +} + +static struct wlr_device *find_device(struct wlr_session *session, int fd) { +	struct wlr_device *dev; + +	wl_list_for_each(dev, &session->devices, link) { +		if (dev->fd == fd) { +			return dev; +		} +	} + +	wlr_log(WLR_ERROR, "Tried to use fd %d not opened by session", fd); +	assert(0); +} + +void wlr_session_close_file(struct wlr_session *session, int fd) { +	struct wlr_device *dev = find_device(session, fd); + +	session->impl->close(session, fd); +	wl_list_remove(&dev->link); +	free(dev); +} + +void wlr_session_signal_add(struct wlr_session *session, int fd, +		struct wl_listener *listener) { +	struct wlr_device *dev = find_device(session, fd); + +	wl_signal_add(&dev->signal, listener); +} + +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { +	if (!session) { +		return false; +	} + +	return session->impl->change_vt(session, vt); +} + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static int open_if_kms(struct wlr_session *restrict session, const char *restrict path) { +	int fd; + +	if (!path) { +		return -1; +	} + +	fd = wlr_session_open_file(session, path); +	if (fd < 0) { +		return -1; +	} + +	drmModeRes *res = drmModeGetResources(fd); +	if (!res) { +		goto out_fd; +	} + +	drmModeFreeResources(res); +	return fd; + +out_fd: +	wlr_session_close_file(session, fd); +	return -1; +} + +static size_t explicit_find_gpus(struct wlr_session *session, +		size_t ret_len, int ret[static ret_len], const char *str) { +	char *gpus = strdup(str); +	if (!gpus) { +		wlr_log_errno(WLR_ERROR, "Allocation failed"); +		return 0; +	} + +	size_t i = 0; +	char *save; +	char *ptr = strtok_r(gpus, ":", &save); +	do { +		if (i >= ret_len) { +			break; +		} + +		ret[i] = open_if_kms(session, ptr); +		if (ret[i] < 0) { +			wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr); +		} else { +			++i; +		} +	} while ((ptr = strtok_r(NULL, ":", &save))); + +	free(gpus); +	return i; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +size_t wlr_session_find_gpus(struct wlr_session *session, +		size_t ret_len, int *ret) { +	const char *explicit = getenv("WLR_DRM_DEVICES"); +	if (explicit) { +		return explicit_find_gpus(session, ret_len, ret, explicit); +	} + +#ifdef __FreeBSD__ +	// XXX: libudev-devd does not return any GPUs (yet?) +	return explicit_find_gpus(session, ret_len, ret, "/dev/drm/0"); +#else + +	struct udev_enumerate *en = udev_enumerate_new(session->udev); +	if (!en) { +		wlr_log(WLR_ERROR, "Failed to create udev enumeration"); +		return -1; +	} + +	udev_enumerate_add_match_subsystem(en, "drm"); +	udev_enumerate_add_match_sysname(en, "card[0-9]*"); +	udev_enumerate_scan_devices(en); + +	struct udev_list_entry *entry; +	size_t i = 0; + +	udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { +		if (i == ret_len) { +			break; +		} + +		bool is_boot_vga = false; + +		const char *path = udev_list_entry_get_name(entry); +		struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); +		if (!dev) { +			continue; +		} + +		const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); +		if (!seat) { +			seat = "seat0"; +		} +		if (session->seat[0] && strcmp(session->seat, seat) != 0) { +			udev_device_unref(dev); +			continue; +		} + +		// This is owned by 'dev', so we don't need to free it +		struct udev_device *pci = +			udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + +		if (pci) { +			const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); +			if (id && strcmp(id, "1") == 0) { +				is_boot_vga = true; +			} +		} + +		int fd = open_if_kms(session, udev_device_get_devnode(dev)); +		if (fd < 0) { +			udev_device_unref(dev); +			continue; +		} + +		udev_device_unref(dev); + +		ret[i] = fd; +		if (is_boot_vga) { +			int tmp = ret[0]; +			ret[0] = ret[i]; +			ret[i] = tmp; +		} + +		++i; +	} + +	udev_enumerate_unref(en); + +	return i; +#endif +}  | 
