#include <errno.h> #include <fcntl.h> #include <libudev.h> #include <linux/input.h> #include <linux/memfd.h> #include <linux/vt.h> #include <signal.h> #include <stdarg.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <systemd/sd-bus.h> #include <systemd/sd-event.h> #include <systemd/sd-login.h> #include <unistd.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <shell.h> #include <keymap.h> #define STB_IMAGE_WRITE_IMPLEMENTATION #include <stb_image_write.h> #define array_size(a) (sizeof(a) / sizeof((a)[0])) #define BYTES_PER_PIXEL 4 enum { DISPLAY_CONTROLLER_CHOSEN = 1, DISPLAY_HAS_FRAME_BUFFERS = 2, DISPLAY_SAVED = 4, }; enum { NUM_WORKSPACES = 4, NUM_WINDOWS_PER_WORKSPACE = 3, }; struct display_buffer { void *mem; uint64_t size; uint32_t width; uint32_t height; uint32_t stride; uint32_t handle; uint32_t fb; }; struct display { int fd; struct display_buffer buffers[2]; unsigned int back_buffer_index:1; unsigned int back_buffer_is_locked:1; unsigned int is_draw_needed:1; uint32_t state; drmModeModeInfo mode; uint32_t conn; uint32_t crtc; drmModeCrtc *saved_crtc; }; struct window { uint32_t x; uint32_t y; uint32_t width; uint32_t height; unsigned int front_buffer:1; void *mem; int socket_fd; pid_t pid; }; struct workspace { struct window windows[NUM_WINDOWS_PER_WORKSPACE]; unsigned focus; }; struct keyboard { int fd; uint32_t major; uint32_t minor; uint8_t key_bits[SHELL_KEY_BITS_SIZE]; }; struct root { bool is_active; struct workspace workspaces[NUM_WORKSPACES]; unsigned focus; sd_bus *bus; sd_event *event; struct keyboard keyboard; struct display display; }; _Noreturn static void die(const char *m) { fprintf(stderr, "%s\n", m); exit(1); } static void * xmalloc(size_t size) { if (size == 0) die("Memory allocation error."); void *x = malloc(size); if (x == NULL) die("Memory allocation failed."); return x; } static void display_init(struct display *disp) { disp->state = 0; disp->back_buffer_index = 1; disp->back_buffer_is_locked = 0; disp->is_draw_needed = 0; } static struct display_buffer * display_front_buffer(struct display *disp) { return &disp->buffers[1 - disp->back_buffer_index]; } static struct display_buffer * display_back_buffer(struct display *disp) { return &disp->buffers[disp->back_buffer_index]; } static void display_connect(struct display *disp, int fd) { disp->fd = fd; // We need "dumb buffer" support so fail if the device doesn't have that. { uint64_t has_dumb; int r = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb); if (r < 0 || !has_dumb) die("Failed to connect to the display."); } // We choose an arbitrary connector that's currently active, a mode to use // on it (determines resolution), and a controller (CRTC) that's // compatible with one of the connector's encoders. { drmModeRes *res = drmModeGetResources(disp->fd); if (!res) die("Failed to connect to display."); bool did_choose_crtc = false; for (int i = 0; i < res->count_connectors; i++) { drmModeConnector *conn = drmModeGetConnector(disp->fd, res->connectors[i]); if (!conn) continue; if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) { drmModeModeInfo *mode; { int j = conn->count_modes - 1; while (j > 0 && !(conn->modes[j].type & DRM_MODE_TYPE_PREFERRED)) j--; mode = &conn->modes[j]; } for (int j = 0; j < conn->count_encoders; j++) { drmModeEncoder *enc = drmModeGetEncoder(disp->fd, conn->encoders[j]); if (!enc) continue; for (int k = 0; k < res->count_crtcs; k++) { if (enc->possible_crtcs & (1 << k)) { disp->crtc = res->crtcs[k]; disp->conn = conn->connector_id; disp->mode = *mode; did_choose_crtc = true; break; } } drmModeFreeEncoder(enc); if (did_choose_crtc) break; } } drmModeFreeConnector(conn); if (did_choose_crtc) break; } drmModeFreeResources(res); if (!did_choose_crtc) die("Failed to connect to display."); } disp->state |= DISPLAY_CONTROLLER_CHOSEN; // Allocate and mmap the two full-screen pixel buffers. for (int i = 0; i < 2; i++) { struct display_buffer *buffer = &disp->buffers[i]; buffer->width = disp->mode.hdisplay; buffer->height = disp->mode.vdisplay; struct drm_mode_create_dumb ioctl_create = { .width = buffer->width, .height = buffer->height, .bpp = 32, }; int r = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &ioctl_create); if (r < 0) die("Failed to connect to display."); buffer->stride = ioctl_create.pitch; buffer->size = ioctl_create.size; buffer->handle = ioctl_create.handle; r = drmModeAddFB(fd, buffer->width, buffer->height, 24, 32, buffer->stride, buffer->handle, &buffer->fb); if (r) die("Failed to connect to display."); struct drm_mode_map_dumb ioctl_map = { .handle = buffer->handle, }; r = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &ioctl_map); if (r) die("Failed to connect to display."); buffer->mem = mmap(0, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, ioctl_map.offset); if (buffer->mem == MAP_FAILED) die("Failed to connect to display."); } disp->state |= DISPLAY_HAS_FRAME_BUFFERS; // Save the current state for the CRTC we are about to take over. We will // restore this state when we disconnect from the display. // // Take over the connector and CRTC that we selected above. { struct display_buffer *front_buffer = display_front_buffer(disp); disp->saved_crtc = drmModeGetCrtc(disp->fd, disp->crtc); int r = drmModeSetCrtc(disp->fd, disp->crtc, front_buffer->fb, 0, 0, &disp->conn, 1, &disp->mode); if (r) die("Failed to connect to display."); } disp->state |= DISPLAY_SAVED; } static void display_disconnect(struct display *disp) { if (disp->state & DISPLAY_SAVED) { drmModeSetCrtc( disp->fd, disp->saved_crtc->crtc_id, disp->saved_crtc->buffer_id, disp->saved_crtc->x, disp->saved_crtc->y, &disp->conn, 1, &disp->saved_crtc->mode); drmModeFreeCrtc(disp->saved_crtc); disp->state &= ~DISPLAY_SAVED; } if (disp->state & DISPLAY_HAS_FRAME_BUFFERS) { for (int i = 0; i < 2; i++) { struct display_buffer *buffer = &disp->buffers[i]; munmap(buffer->mem, buffer->size); drmModeRmFB(disp->fd, buffer->fb); drmIoctl(disp->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &(struct drm_mode_destroy_dumb){ .handle = buffer->handle, }); } disp->state &= ~DISPLAY_HAS_FRAME_BUFFERS; } } static void set_bit(uint8_t *bits, unsigned i) { bits[i / 8] |= (1 << (i % 8)); } static void clear_bit(uint8_t *bits, unsigned i) { bits[i / 8] &= ~(1 << (i % 8)); } static int fetch_bit(const uint8_t *bits, unsigned i) { return (bits[i / 8] & (1 << (i % 8))) ? 1 : 0; } struct keyboard * keyboard_new(void) { struct keyboard *keyboard = xmalloc(sizeof(*keyboard)); keyboard->fd = -1; return keyboard; } int keyboard_connect(struct keyboard *keyboard, int fd, unsigned int major, unsigned int minor) { keyboard->fd = fd; keyboard->major = major; keyboard->minor = minor; int r = ioctl(fd, EVIOCGKEY(SHELL_KEY_BITS_SIZE), keyboard->key_bits); if (r == -1) die("Failed to read keyboard state."); return 0; } static void blit(void *dest, uint32_t dest_width, uint32_t x, uint32_t y, void *source, uint32_t source_width, uint32_t source_height) { for (uint32_t i = 0; i < source_height; i++) { memmove(dest + BYTES_PER_PIXEL * (dest_width * (y + i) + x), source + BYTES_PER_PIXEL * source_width * i, BYTES_PER_PIXEL * source_width); } } static uint32_t encode_color_xrgb(uint8_t r, uint8_t g, uint8_t b) { union { uint32_t u; uint8_t bytes[4]; } x = { .bytes = { b, g, r, 0 }, }; return x.u; } static void clear_buffer(void *buffer, uint32_t width, uint32_t height) { uint32_t color = encode_color_xrgb(0xff, 0xff, 0xff); uint32_t *pixel = buffer; for (unsigned int j = 0; j < width; j++) for (unsigned int k = 0; k < height; k++) *pixel++ = color; } static void draw_frame(void *mem, uint32_t width, struct window *window, bool has_focus) { int x0 = window->x - 5; int y0 = window->y - 5; int x1 = window->x + window->width + 4; int y1 = window->y + window->height + 4; uint32_t color = has_focus ? encode_color_xrgb(0x48, 0x48, 0x48) : encode_color_xrgb(0xcc, 0xcc, 0xcc); uint32_t *pixels = mem; for (int x = x0, i = width * y0 + x0; x <= x1; x++, i++) pixels[i] = color; for (int x = x0, i = width * y1 + x0; x <= x1; x++, i++) pixels[i] = color; for (int y = y0, i = width * y0 + x0; y <= y1; y++, i += width) pixels[i] = color; for (int y = y0, i = width * y0 + x1; y <= y1; y++, i += width) pixels[i] = color; } static void draw_window(void *back_buffer, uint32_t disp_width, struct window *window) { if (window->pid == -1) return; void *buffer = window->mem + (window->front_buffer * BYTES_PER_PIXEL * window->width * window->height); blit(back_buffer, disp_width, window->x, window->y, buffer, window->width, window->height); } static void draw(struct root *root) { struct display *disp = &root->display; if (disp->back_buffer_is_locked || !root->is_active) { disp->is_draw_needed = 1; return; } struct display_buffer *back_buffer = display_back_buffer(disp); uint32_t disp_width = back_buffer->width; uint32_t disp_height = back_buffer->height; clear_buffer(back_buffer->mem, disp_width, disp_height); struct workspace *workspace = &root->workspaces[root->focus]; for (int i = 0; i < NUM_WINDOWS_PER_WORKSPACE; i++) { struct window *window = &workspace->windows[i]; draw_frame(back_buffer->mem, disp_width, window, i == workspace->focus); draw_window(back_buffer->mem, disp_width, window); } int r = drmModePageFlip(disp->fd, disp->crtc, back_buffer->fb, DRM_MODE_PAGE_FLIP_EVENT, root); if (r) fprintf(stderr, "Failed to flip back buffer onto display."); disp->back_buffer_index = !disp->back_buffer_index; disp->back_buffer_is_locked = 1; disp->is_draw_needed = 0; } static void root_init(struct root *root, sd_bus *bus, sd_event *event, uint32_t disp_width, uint32_t disp_height) { root->is_active = true; root->bus = bus; root->event = event; root->focus = 0; for (int i = 0; i < NUM_WORKSPACES; i++) { struct workspace *workspace = &root->workspaces[i]; workspace->focus = 0; for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) { struct window *window = &workspace->windows[j]; switch (j) { case 0: window->x = 20; window->y = 20; window->width = (disp_width / 2) - 30; window->height = disp_height - 40; break; case 1: window->x = (disp_width / 2) + 10; window->y = 20; window->width = (disp_width / 2) - 30; window->height = (disp_height / 2) - 30; break; case 2: window->x = (disp_width / 2) + 10; window->y = (disp_height / 2) + 10; window->width = (disp_width / 2) - 30; window->height = (disp_height / 2) - 30; break; default: die("Failed to initialize workspaces."); break; } window->front_buffer = 0; window->mem = NULL; window->socket_fd = -1; window->pid = -1; } } } static struct window * root_lookup_window_by_fd(struct root *root, int fd) { for (int i = 0; i < NUM_WORKSPACES; i++) { struct workspace *workspace = &root->workspaces[i]; for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) { struct window *window = &workspace->windows[j]; if (window->socket_fd == fd) return window; } } return NULL; } static void send_keyboard_detached(struct window *window); static void send_keyboard_attached(struct window *window, uint8_t *key_bits); static void root_set_workspace_focus(struct root *root, int focus) { if (root->focus == focus) return; struct workspace *workspace; struct window *window; workspace = &root->workspaces[root->focus]; window = &workspace->windows[workspace->focus]; send_keyboard_detached(window); root->focus = focus; workspace = &root->workspaces[root->focus]; window = &workspace->windows[workspace->focus]; send_keyboard_attached(window, root->keyboard.key_bits); draw(root); } static void choose_keyboard(uint32_t *major, uint32_t *minor) { struct udev *udev = udev_new(); if (udev == NULL) die("Failed to choose keyboard."); struct udev_enumerate *enumerate = udev_enumerate_new(udev); if (enumerate == NULL) die("Failed to choose keyboard."); int r; r = udev_enumerate_add_match_property(enumerate, "ID_INPUT_KEYBOARD", "1"); if (r < 0) die("Failed to choose keyboard."); r = udev_enumerate_scan_devices(enumerate); if (r < 0) die("Failed to choose keyboard."); struct udev_list_entry *first_entry = udev_enumerate_get_list_entry(enumerate); if (first_entry == NULL) die("Failed to choose keyboard."); struct udev_list_entry *entry; *major = 0; *minor = 0; udev_list_entry_foreach(entry, first_entry) { const char *name = udev_list_entry_get_name(entry); if (name == NULL) continue; struct udev_device *device = udev_device_new_from_syspath(udev, name); if (device == NULL) continue; dev_t dev = udev_device_get_devnum(device); if (major(dev) == 0 && minor(dev) == 0) { udev_device_unref(device); continue; } *major = major(dev); *minor = minor(dev); udev_device_unref(device); break; } udev_enumerate_unref(enumerate); udev_unref(udev); if (*major == 0 && *minor == 0) die("Failed to choose keyboard."); } static void switch_to_vt(int n) { int fd = open("/dev/tty", O_RDWR); if (fd == -1) die("Failed to switch VT."); int r; r = ioctl(fd, VT_ACTIVATE, n); if (r == -1) die("Failed to switch VT."); r = ioctl(fd, VT_WAITACTIVE, n); if (r == -1) die("Failed to switch VT."); close(fd); } static int sigchld_handler(sd_event_source *source, const struct signalfd_siginfo *siginfo, void *data) { struct root *root = data; (void)source; (void)siginfo; for (;;) { int status; pid_t pid; pid = waitpid(-1, &status, WUNTRACED|WCONTINUED|WNOHANG); if (pid == 0) break; if (pid == -1) { if (errno == ECHILD) break; die("Failed to handle SIGCHLD."); } if (WIFEXITED(status)) { fprintf(stderr, "Child exited with status %d.\n", (int)WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { fprintf(stderr, "Child was terminated by signal %d.\n", (int)WTERMSIG(status)); } else if (WIFSTOPPED(status)) { fprintf(stderr, "Child was stopped.\n"); continue; } else if (WIFCONTINUED(status)) { fprintf(stderr, "Child was continued.\n"); continue; } for (int i = 0; i < NUM_WORKSPACES; i++) { struct workspace *workspace = &root->workspaces[i]; for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) { struct window *window = &workspace->windows[j]; if (window->pid == pid) { window->front_buffer = 0; size_t mem_size = 2 * BYTES_PER_PIXEL * window->width * window->height; int r = munmap(window->mem, mem_size); if (r == -1) die("Failed to release draw buffers."); window->mem = NULL; close(window->socket_fd); window->socket_fd = -1; window->pid = -1; } } } } draw(root); return 0; } static void send_welcome(struct window *window) { struct shell_message_welcome message = { .class = SHELL_MESSAGE_WELCOME, .width = window->width, .height = window->height, }; ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. /* if (r == -1 && errno == EPIPE) { kill(window->pid, SIGKILL); return; } */ if (r != sizeof(message)) { //die("Failed to communicate with client process."); perror("Failed to send welcome message to child process"); return; } } static void send_keyboard_event(struct window *window, struct input_event *event) { if (window->pid == -1) return; struct shell_message_keyboard_event message = { .class = SHELL_MESSAGE_KEYBOARD_EVENT, .code = event->code, .value = event->value, }; ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. /* if (r == -1 && (errno == EPIPE || errno == EBADF)) { kill(window->pid, SIGKILL); return; } */ if (r != sizeof(message)) { //die("Failed to communicate with client process."); perror("Failed to send keyboard event message to child process"); //exit(1); return; } } static void send_reset_completed(struct window *window) { struct shell_message_reset_completed message = { .class = SHELL_MESSAGE_RESET_COMPLETED, }; ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. /* if (r == -1 && (errno == EPIPE || errno == EBADF)) { kill(window->pid, SIGKILL); return; } */ if (r != sizeof(message)) { //die("Failed to communicate with client process."); perror("Failed to send buffer unlocked message to child process"); //exit(1); return; } } static void send_buffer_unlocked(struct window *window) { if (window->pid == -1) return; struct shell_message_buffer_unlocked message = { .class = SHELL_MESSAGE_BUFFER_UNLOCKED, }; ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. /* if (r == -1 && (errno == EPIPE || errno == EBADF)) { kill(window->pid, SIGKILL); return; } */ if (r != sizeof(message)) { //die("Failed to communicate with client process."); perror("Failed to send buffer unlocked message to child process"); //exit(1); return; } } static void send_keyboard_detached(struct window *window) { if (window->pid == -1) return; struct shell_message_keyboard_detached message = { .class = SHELL_MESSAGE_KEYBOARD_DETACHED, }; ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. /* if (r == -1 && (errno == EPIPE || errno == EBADF)) { kill(window->pid, SIGKILL); return; } */ if (r != sizeof(message)) { perror("Failed to send keyboard detached message to child process"); return; } } static void send_keyboard_attached(struct window *window, uint8_t *key_bits) { if (window->pid == -1) return; struct shell_message_keyboard_attached message = { .class = SHELL_MESSAGE_KEYBOARD_ATTACHED, }; memmove(message.key_bits, key_bits, SHELL_KEY_BITS_SIZE); ssize_t r = write(window->socket_fd, &message, sizeof(message)); // TODO Rethink this. if (r != sizeof(message)) { perror("Failed to send keyboard attached message to child process"); return; } } static int child_message_event_handler(sd_event_source *source, int fd, uint32_t events, void *data) { int drop(void) { sd_event_source_unref(source); return 0; } // TODO What kinds of errors (EPOLLERR) can we get? How should they be // handled? if (events & EPOLLERR) return drop(); if (events & EPOLLHUP) return drop(); if (events != EPOLLIN) die("Unexpected condition on socket."); static _Alignas(max_align_t) char packet[SHELL_PACKET_SIZE_MAX]; size_t packet_size; for (;;) { ssize_t r = read(fd, packet, SHELL_PACKET_SIZE_MAX); if (r == -1 && errno == EINTR) continue; if (r == -1) return drop(); packet_size = r; break; } struct root *root = data; struct window *window = root_lookup_window_by_fd(root, fd); if (window == NULL) return drop(); // TODO The following error should not be fatal. We need a general way to // deal with protocol errors. if (packet_size < sizeof(uint16_t)) die("Protocol error on socket."); uint16_t class = *(uint16_t *)packet; switch (class) { case SHELL_MESSAGE_RESET: if (window->front_buffer != 0) { size_t buffer_size = BYTES_PER_PIXEL * window->width * window->height; memmove(window->mem, window->mem + buffer_size, buffer_size); window->front_buffer = 0; } send_reset_completed(window); send_welcome(window); break; case SHELL_MESSAGE_SHOW: window->front_buffer = !window->front_buffer; send_buffer_unlocked(window); draw(root); break; default: fprintf(stderr, "Unexpected message class received from client."); break; } return 0; } static void enable_cloexec(int fd) { const char *error_message = "Failed to enable CLOEXEC on file."; int r = fcntl(fd, F_GETFD); if (r == -1) die(error_message); int flags = r; flags |= FD_CLOEXEC; r = fcntl(fd, F_SETFD, flags); if (r == -1) die(error_message); } static void disable_nonblock(int fd) { const char *error_message = "Failed to disable NONBLOCK on file."; int r = fcntl(fd, F_GETFL); if (r == -1) die(error_message); int flags = r; flags &= ~O_NONBLOCK; r = fcntl(fd, F_SETFL, flags); if (r == -1) die(error_message); } static void launch_client(struct root *root, const char *path, const char *arg0) { struct workspace *workspace = &root->workspaces[root->focus]; struct window *window = &workspace->windows[workspace->focus]; if (window->pid != -1) return; int r; size_t mem_size = 2 * BYTES_PER_PIXEL * window->width * window->height; char filename[64]; r = snprintf(filename, sizeof(filename), "buffer_%u_%u", root->focus, workspace->focus); if (r >= sizeof(filename)) die("Failed to create draw buffers for child process."); int mem_fd = syscall(__NR_memfd_create, filename, 0); if (mem_fd == -1) die("Failed to create draw buffers for child process."); do { r = ftruncate(mem_fd, mem_size); } while (r == -1 && errno == EINTR); if (r == -1) die("Failed to create draw buffers for child process."); void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0); if (mem == MAP_FAILED) die("Failed to create draw buffers for child process."); window->mem = mem; int socket_fds[2]; r = socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_NONBLOCK, 0, socket_fds); if (r == -1) die("Failed to create socket for child process."); window->socket_fd = socket_fds[0]; send_welcome(window); // TODO This is weird. We pass the event_source parameter only to trigger // a certain behavior; otherwise, the library allocates an event_source // object that it deallocates when the event loop is deallocated. We have // no use for the actual object here but we want to be able to deallocate // it when the child terminates. sd_event_source *event_source; r = sd_event_add_io(root->event, &event_source, window->socket_fd, EPOLLIN, child_message_event_handler, root); if (r < 0) die("Failed to add child message event handler."); clear_buffer(window->mem, window->width, window->height); pid_t pid = fork(); switch (pid) { case -1: die("Failed to fork child process."); break; case 0: { sigset_t sigset; int r; const char error_message[] = "Failed to initialize signal handling for child process."; r = sigemptyset(&sigset); if (r == -1) die(error_message); r = sigprocmask(SIG_SETMASK, &sigset, NULL); if (r == -1) die(error_message); signal(SIGPIPE, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); } close(socket_fds[0]); if (socket_fds[1] != 3) { // TODO We close fd 3 here without any guarantee that mem_fd != 3. // How to fix this problem? do { r = dup2(socket_fds[1], 3); } while (r == -1 && errno == EINTR); if (r == -1) die("Failed while launching child process."); close(socket_fds[1]); } if (mem_fd != 4) { do { r = dup2(mem_fd, 4); } while (r == -1 && errno == EINTR); if (r == -1) die("Failed while launching child process."); close(mem_fd); } execlp(path, arg0, (char *)NULL); die("Failed to exec child process."); break; default: window->pid = pid; close(mem_fd); close(socket_fds[1]); enable_cloexec(socket_fds[0]); break; } } static void on_key_release(sd_event_source *source, int code) { struct root *root = sd_event_source_get_userdata(source); struct keyboard *keyboard = &root->keyboard; if (code >= 0 && code <= KEY_MAX) clear_bit(keyboard->key_bits, (unsigned)code); } static void on_key_press(sd_event_source *source, int code) { int r; struct root *root = sd_event_source_get_userdata(source); sd_event *event = sd_event_source_get_event(source); struct keyboard *keyboard = &root->keyboard; if (code >= 0 && code <= KEY_MAX) set_bit(keyboard->key_bits, (unsigned)code); if (!fetch_bit(keyboard->key_bits, KEY_LEFTMETA) && !fetch_bit(keyboard->key_bits, KEY_RIGHTMETA)) return; switch (code) { case KEY_ESC: r = sd_event_exit(event, 0); if (r < 0) die("Failed to handle keyboard event."); return; case KEY_TAB: { struct workspace *workspace = &root->workspaces[root->focus]; struct window *window = &workspace->windows[workspace->focus]; uint8_t key_bits[SHELL_KEY_BITS_SIZE]; memmove(key_bits, keyboard->key_bits, SHELL_KEY_BITS_SIZE); clear_bit(key_bits, KEY_LEFTMETA); clear_bit(key_bits, KEY_RIGHTMETA); clear_bit(key_bits, KEY_TAB); send_keyboard_detached(window); workspace->focus = (workspace->focus + 1) % NUM_WINDOWS_PER_WORKSPACE; window = &workspace->windows[workspace->focus]; send_keyboard_attached(window, key_bits); draw(root); } return; case KEY_1: root_set_workspace_focus(root, 0); return; case KEY_2: root_set_workspace_focus(root, 1); return; case KEY_3: root_set_workspace_focus(root, 2); return; case KEY_4: root_set_workspace_focus(root, 3); return; case KEY_F1: switch_to_vt(1); return; case KEY_F2: switch_to_vt(2); return; case KEY_F3: switch_to_vt(3); return; case KEY_F4: switch_to_vt(4); return; } switch (keymap_lookup(code)) { case 'g': launch_client(root, "./germ", "germ"); return; case 'l': launch_client(root, "./logo", "logo"); return; case 't': launch_client(root, "./term", "term"); return; case 's': { struct display_buffer *buf = display_front_buffer(&root->display); int w = buf->width; int h = buf->height; int stride = w; uint8_t *color = buf->mem; uint8_t *gray = malloc(w * h); if (gray == NULL) return; for (int i = 0; i < w * h; i++) gray[i] = color[4 * i]; stbi_write_png("screenshot.png", w, h, 1, gray, stride); free(gray); } return; } } static int bus_event_handler(sd_event_source *source, int fd, uint32_t revents, void *data) { struct root *root = data; int r; for (;;) { r = sd_bus_process(root->bus, NULL); if (r < 0) die("Failed to process input from bus."); if (r == 0) break; } return 0; } static void page_flip_handler(int device, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct root *root = data; struct display *disp = &root->display; disp->back_buffer_is_locked = 0; if (disp->is_draw_needed) draw(root); } static int display_event_handler(sd_event_source *source, int fd, uint32_t revents, void *data) { drmEventContext event_context = { .version = DRM_EVENT_CONTEXT_VERSION, .page_flip_handler = page_flip_handler, }; int r; do { r = drmHandleEvent(fd, &event_context); } while (r == -1 && errno == EINTR); if (r) fprintf(stderr, "Failed to flip back buffer onto display."); return 0; } static int keyboard_event_handler(sd_event_source *source, int fd, uint32_t revents, void *data) { struct root *root = data; struct keyboard *keyboard = &root->keyboard; // TODO Review the handling of the various revents cases. if (revents & EPOLLERR) return -1; if (revents & EPOLLHUP) return -1; if (revents != EPOLLIN) die("Unexpected condition on keyboard."); struct input_event input_events[16]; int ret; do { ret = read(fd, input_events, sizeof(input_events)); } while (ret == -1 && errno == EINTR); if (ret == 0) die("Failed while reading keyboard."); if (ret % sizeof(struct input_event) != 0) die("Failed while reading keyboard."); int num_events = ret / sizeof(struct input_event); for (int i = 0; i < num_events; i++) { struct input_event *ev = &input_events[i]; if (ev->type != EV_KEY) continue; switch (ev->value) { case 0: on_key_release(source, ev->code); break; case 1: case 2: on_key_press(source, ev->code); break; } struct workspace *workspace = &root->workspaces[root->focus]; struct window *window = &workspace->windows[workspace->focus]; if (window->pid == -1) continue; if (ev->code == KEY_LEFTMETA || ev->code == KEY_RIGHTMETA) continue; if (fetch_bit(keyboard->key_bits, KEY_LEFTMETA)) continue; if (fetch_bit(keyboard->key_bits, KEY_RIGHTMETA)) continue; send_keyboard_event(window, ev); } return 0; } static int properties_changed_handler(sd_bus_message *m, void *data, sd_bus_error *ret_error) { struct root *root = data; root->is_active = !root->is_active; if (root->is_active) fprintf(stderr, "Session is active.\n"); else fprintf(stderr, "Session is not active.\n"); if (root->is_active) draw(root); return 0; } static int pause_device_handler(sd_bus_message *message, void *data, sd_bus_error *ret_error) { int r; uint32_t major, minor; char *reason; r = sd_bus_message_read(message, "uus", &major, &minor, &reason); if (r < 0) die("Failed to read message from DBus."); fprintf(stderr, "Device paused: %d %d %s.\n", (int)major, (int)minor, reason); return 0; } static int resume_device_handler(sd_bus_message *message, void *data, sd_bus_error *ret_error) { int r; struct root *root = data; struct keyboard *keyboard = &root->keyboard; uint32_t major, minor; int fd; r = sd_bus_message_read(message, "uuh", &major, &minor, &fd); if (r < 0) die("Failed to communicate with DBus."); fprintf(stderr, "Device resumed: %d %d.\n", (int)major, (int)minor); if (major == keyboard->major && minor == keyboard->minor) { fd = dup(fd); enable_cloexec(fd); r = keyboard_connect(keyboard, fd, keyboard->major, keyboard->minor); if (r) die("Failed to connect to the keyboard."); r = sd_event_add_io(root->event, NULL, fd, EPOLLIN, keyboard_event_handler, root); if (r < 0) die("Failed to add keyboard event handler."); } else if (major == 226 && minor == 0) { struct display *disp = &root->display; if (disp->state & DISPLAY_CONTROLLER_CHOSEN) { struct display_buffer *front_buffer = display_front_buffer(disp); r = drmModeSetCrtc(disp->fd, disp->crtc, front_buffer->fb, 0, 0, &disp->conn, 1, &disp->mode); if (r) die("Failed to reconnect to display."); draw(root); } } return 0; } /* * easy_sprintf is like sprintf but it allocates a buffer for you. */ static char * easy_sprintf(const char *template, ...) __attribute__((format(printf, 1, 2))); static char * easy_sprintf(const char *template, ...) { /* * We use vsnprintf twice: once to find out how much space is needed and * once to store the result. */ va_list args; int ret; char scratch[1]; va_start(args, template); ret = vsnprintf(scratch, 0, template, args); va_end(args); if (ret <= 0) goto fail; size_t size = ret + 1; char *s = xmalloc(size); va_start(args, template); ret = vsnprintf(s, size, template, args); va_end(args); if (ret + 1 != size) goto fail; return s; fail: die("Failed while formatting a string."); } int main(void) { int r; struct root root; sd_bus *bus = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message *message = NULL; sd_event *event = NULL; char *session_path = NULL; r = sd_bus_default_system(&bus); if (r < 0) die("Failed to connect to the system bus."); // Determine the logind object path for our current session. { char *session_short_name = NULL; r = sd_pid_get_session(getpid(), &session_short_name); if (r < 0) die("Failed to determine user session name."); session_path = easy_sprintf("/org/freedesktop/login1/session/%s", session_short_name); free(session_short_name); } // Take control of the session. r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path, "org.freedesktop.login1.Session", "TakeControl", &error, &message, "b", (int32_t)0); if (r < 0) die("Failed to take control of session."); sd_bus_message_unref(message); // Take control of the display. struct display *disp = &root.display; display_init(disp); uint32_t disp_width; uint32_t disp_height; { uint32_t major = 226; uint32_t minor = 0; int32_t paused; int fd; r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path, "org.freedesktop.login1.Session", "TakeDevice", &error, &message, "uu", major, minor); if (r < 0) die("Failed while communicating with logind."); r = sd_bus_message_read(message, "hb", &fd, &paused); if (r < 0) die("Failed while communicating with logind."); if (paused) die("Failed to take control of the display."); fd = dup(fd); sd_bus_message_unref(message); enable_cloexec(fd); disable_nonblock(fd); display_connect(disp, fd); struct display_buffer *back_buffer = display_back_buffer(disp); disp_width = back_buffer->width; disp_height = back_buffer->height; } // Take control of the keyboard. struct keyboard *keyboard = &root.keyboard; { int fd; uint32_t major; uint32_t minor; int32_t paused; choose_keyboard(&major, &minor); r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path, "org.freedesktop.login1.Session", "TakeDevice", &error, &message, "uu", major, minor); if (r < 0) die("Failed while communicating with logind."); r = sd_bus_message_read(message, "hb", &fd, &paused); if (r < 0) die("Failed while communicating with logind."); if (paused) die("Failed to take control of the keyboard."); fd = dup(fd); enable_cloexec(fd); sd_bus_message_unref(message); r = keyboard_connect(keyboard, fd, major, minor); if (r) die("Failed to connect to the keyboard."); } // Create the event loop object. r = sd_event_default(&event); if (r < 0) die("Failed to initialize the event loop."); // Initialize the root. // // TODO This whole initialization process needs attention. root_init(&root, bus, event, disp_width, disp_height); // Subscribe to DBus signals. { char *pattern = NULL; pattern = easy_sprintf("type='signal',path='%s'," "interface='org.freedesktop.login1.Session'," "member='PauseDevice'", session_path); r = sd_bus_add_match(bus, NULL, pattern, pause_device_handler, &root); if (r < 0) die("Failed to subscribe to PauseDevice signal."); free(pattern); pattern = easy_sprintf("type='signal',path='%s'," "interface='org.freedesktop.login1.Session'," "member='ResumeDevice'", session_path); r = sd_bus_add_match(bus, NULL, pattern, resume_device_handler, &root); if (r < 0) die("Failed to subscribe to ResumeDevice signal."); free(pattern); pattern = easy_sprintf("type='signal',path='%s'," "interface='org.freedesktop.DBus.Properties'," "member='PropertiesChanged'", session_path); r = sd_bus_add_match(bus, NULL, pattern, properties_changed_handler, &root); if (r < 0) die("Failed to subscribe to PropertiesChanged signal."); free(pattern); } // Add signals to the event loop. { sigset_t sigset; const char error_message[] = "Failed to initialize signal handling."; signal(SIGPIPE, SIG_IGN); r = sigemptyset(&sigset); if (r == -1) die(error_message); r = sigaddset(&sigset, SIGTERM); if (r == -1) die(error_message); r = sigaddset(&sigset, SIGINT); if (r == -1) die(error_message); r = sigaddset(&sigset, SIGCHLD); if (r == -1) die(error_message); r = sigprocmask(SIG_BLOCK, &sigset, NULL); if (r == -1) die(error_message); r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); if (r < 0) die(error_message); r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); if (r < 0) die(error_message); r = sd_event_add_signal(event, NULL, SIGCHLD, sigchld_handler, &root); if (r < 0) die(error_message); } // Add the display to the event loop. r = sd_event_add_io(event, NULL, disp->fd, EPOLLIN, display_event_handler, &root); if (r < 0) die("Failed to add display event handler."); // Add the keyboard to the event loop. r = sd_event_add_io(event, NULL, keyboard->fd, EPOLLIN, keyboard_event_handler, &root); if (r < 0) die("Failed to add keyboard event handler."); // Add the bus to the event loop. { int fd = sd_bus_get_fd(bus); if (fd < 0) die("Failed to initialize DBus monitoring."); r = sd_event_add_io(event, NULL, fd, EPOLLIN, bus_event_handler, &root); if (r < 0) die("Failed to initialize DBus monitoring."); } // Draw the first frame and enter the event loop. draw(&root); r = sd_event_loop(event); if (r < 0) die("Failed while processing events."); // Disconnect from display. display_disconnect(disp); // Release control of the session. r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path, "org.freedesktop.login1.Session", "ReleaseControl", &error, &message, ""); if (r < 0) die("Failed to release control of session."); sd_bus_message_unref(message); // Cleanup and exit. sd_event_unref(event); free(session_path); sd_bus_unref(bus); return 0; }