#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session/direct-ipc.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 int direct_session_open(struct wlr_session *base, const char *path) { struct direct_session *session = wl_container_of(base, session, base); int fd = direct_ipc_open(session->sock, path); if (fd < 0) { wlr_log(L_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) { session->base.drm_fd = fd; } return fd; } static void direct_session_close(struct wlr_session *base, int fd) { struct direct_session *session = wl_container_of(base, session, base); struct stat st; if (fstat(fd, &st) < 0) { wlr_log_errno(L_ERROR, "Stat failed"); close(fd); return; } if (major(st.st_rdev) == DRM_MAJOR) { direct_ipc_dropmaster(session->sock, session->base.drm_fd); session->base.drm_fd = -1; } 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 = wl_container_of(base, session, base); return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; } static void direct_session_finish(struct wlr_session *base) { struct direct_session *session = wl_container_of(base, session, base); 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(L_ERROR, "Failed to restore tty"); } direct_ipc_finish(session->sock, session->child); close(session->sock); wl_event_source_remove(session->vt_source); close(session->tty_fd); free(session); } static int vt_handler(int signo, void *data) { struct direct_session *session = data; if (session->base.active) { session->base.active = false; wl_signal_emit(&session->base.session_signal, session); direct_ipc_dropmaster(session->sock, session->base.drm_fd); ioctl(session->tty_fd, VT_RELDISP, 1); } else { ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); direct_ipc_setmaster(session->sock, session->base.drm_fd); session->base.active = true; wl_signal_emit(&session->base.session_signal, session); } return 1; } static bool setup_tty(struct direct_session *session, struct wl_display *display) { int fd = dup(STDIN_FILENO); if (fd == -1) { wlr_log_errno(L_ERROR, "Cannot open tty"); return false; } struct stat st; if (fstat(fd, &st) == -1 || major(st.st_rdev) != TTY_MAJOR || minor(st.st_rdev) == 0) { wlr_log(L_ERROR, "Not running from a virtual terminal"); goto error; } int tty = minor(st.st_rdev); int ret, kd_mode, old_kbmode; ret = ioctl(fd, KDGETMODE, &kd_mode); if (ret) { wlr_log_errno(L_ERROR, "Failed to get tty mode"); goto error; } if (kd_mode != KD_TEXT) { wlr_log(L_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(L_ERROR, "Failed to read keyboard mode"); goto error; } if (ioctl(fd, KDSKBMODE, K_OFF)) { wlr_log_errno(L_ERROR, "Failed to set keyboard mode"); goto error; } if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { wlr_log_errno(L_ERROR, "Failed to set graphics mode on tty"); goto error; } struct vt_mode mode = { .mode = VT_PROCESS, .relsig = SIGUSR1, .acqsig = SIGUSR1, }; if (ioctl(fd, VT_SETMODE, &mode) < 0) { wlr_log(L_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, SIGUSR1, 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_start(struct wl_display *disp) { struct direct_session *session = calloc(1, sizeof(*session)); if (!session) { wlr_log_errno(L_ERROR, "Allocation failed"); return NULL; } session->sock = direct_ipc_start(&session->child); if (session->sock == -1) { goto error_session; } if (!setup_tty(session, disp)) { goto error_ipc; } // XXX: Is it okay to trust the environment like this? const char *seat = getenv("XDG_SEAT"); if (!seat) { seat = "seat0"; } wlr_log(L_INFO, "Successfully loaded direct session"); snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); session->base.drm_fd = -1; session->base.impl = &session_direct; session->base.active = true; wl_signal_init(&session->base.session_signal); 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 = { .start = direct_session_start, .finish = direct_session_finish, .open = direct_session_open, .close = direct_session_close, .change_vt = direct_change_vt, };