diff --git a/fdm.c b/fdm.c new file mode 100644 index 00000000..9523ab5f --- /dev/null +++ b/fdm.c @@ -0,0 +1,124 @@ +#include "fdm.h" + +#include +#include +#include +#include +#include + +#define LOG_MODULE "fdm" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "tllist.h" + +struct fd { + int fd; + void *data; + fdm_handler_t handler; +}; + +struct fdm { + int epoll_fd; + tll(struct fd) fds; +}; + +struct fdm * +fdm_init(void) +{ + int epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (epoll_fd == -1) { + LOG_ERRNO("failed to create epoll FD"); + return NULL; + } + + struct fdm *fdm = malloc(sizeof(*fdm)); + *fdm = (struct fdm){ + .epoll_fd = epoll_fd, + .fds = tll_init(), + }; + return fdm; +} + +void +fdm_destroy(struct fdm *fdm) +{ + if (fdm == NULL) + return; + + if (tll_length(fdm->fds) > 0) + LOG_WARN("FD list not empty"); + + assert(tll_length(fdm->fds) == 0); + + tll_free(fdm->fds); + close(fdm->epoll_fd); + free(fdm); +} + +bool +fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) +{ +#if defined(_DEBUG) + tll_foreach(fdm->fds, it) { + if (it->item.fd == fd) { + LOG_ERR("FD=%d already registered", fd); + return false; + } + } +#endif + + tll_push_back( + fdm->fds, ((struct fd){.fd = fd, .data = data, .handler = handler})); + + struct epoll_event ev = { + .events = events, + .data = {.ptr = &tll_back(fdm->fds)}, + }; + + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + LOG_ERRNO("failed to register FD with epoll"); + tll_pop_back(fdm->fds); + return false; + } + + return true; +} + +bool +fdm_del(struct fdm *fdm, int fd) +{ + tll_foreach(fdm->fds, it) { + if (it->item.fd == fd) { + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) + LOG_ERRNO("failed to unregister FD=%d from epoll", fd); + + tll_remove(fdm->fds, it); + return true; + } + } + + LOG_ERR("no such FD: %d", fd); + return false; +} + +bool +fdm_poll(struct fdm *fdm) +{ + struct epoll_event events[tll_length(fdm->fds)]; + int ret = epoll_wait(fdm->epoll_fd, events, tll_length(fdm->fds), -1); + if (ret == -1) { + if (errno == EINTR) + return true; + + LOG_ERRNO("failed to epoll"); + return false; + } + + for (int i = 0; i < ret; i++) { + struct fd *fd = events[i].data.ptr; + if (!fd->handler(fdm, fd->fd, events[i].events, fd->data)) + return false; + } + + return true; +} diff --git a/fdm.h b/fdm.h new file mode 100644 index 00000000..cd0fee2a --- /dev/null +++ b/fdm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct fdm; + +typedef bool (*fdm_handler_t)(struct fdm *fdm, int fd, int events, void *data); + +struct fdm *fdm_init(void); +void fdm_destroy(struct fdm *fdm); + +bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data); +bool fdm_del(struct fdm *fdm, int fd); + +bool fdm_poll(struct fdm *fdm); diff --git a/input.c b/input.c index 00397974..bee51244 100644 --- a/input.c +++ b/input.c @@ -30,50 +30,50 @@ static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - struct terminal *term = data; + struct wayland *wayl = data; char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); /* TODO: free old context + keymap */ - if (term->kbd.xkb_compose_state != NULL) { - xkb_compose_state_unref(term->kbd.xkb_compose_state); - term->kbd.xkb_compose_state = NULL; + if (wayl->kbd.xkb_compose_state != NULL) { + xkb_compose_state_unref(wayl->kbd.xkb_compose_state); + wayl->kbd.xkb_compose_state = NULL; } - if (term->kbd.xkb_compose_table != NULL) { - xkb_compose_table_unref(term->kbd.xkb_compose_table); - term->kbd.xkb_compose_table = NULL; + if (wayl->kbd.xkb_compose_table != NULL) { + xkb_compose_table_unref(wayl->kbd.xkb_compose_table); + wayl->kbd.xkb_compose_table = NULL; } - if (term->kbd.xkb_keymap != NULL) { - xkb_keymap_unref(term->kbd.xkb_keymap); - term->kbd.xkb_keymap = NULL; + if (wayl->kbd.xkb_keymap != NULL) { + xkb_keymap_unref(wayl->kbd.xkb_keymap); + wayl->kbd.xkb_keymap = NULL; } - if (term->kbd.xkb_state != NULL) { - xkb_state_unref(term->kbd.xkb_state); - term->kbd.xkb_state = NULL; + if (wayl->kbd.xkb_state != NULL) { + xkb_state_unref(wayl->kbd.xkb_state); + wayl->kbd.xkb_state = NULL; } - if (term->kbd.xkb != NULL) { - xkb_context_unref(term->kbd.xkb); - term->kbd.xkb = NULL; + if (wayl->kbd.xkb != NULL) { + xkb_context_unref(wayl->kbd.xkb); + wayl->kbd.xkb = NULL; } - term->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - term->kbd.xkb_keymap = xkb_keymap_new_from_string( - term->kbd.xkb, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, + wayl->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + wayl->kbd.xkb_keymap = xkb_keymap_new_from_string( + wayl->kbd.xkb, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); /* TODO: initialize in enter? */ - term->kbd.xkb_state = xkb_state_new(term->kbd.xkb_keymap); + wayl->kbd.xkb_state = xkb_state_new(wayl->kbd.xkb_keymap); - term->kbd.mod_shift = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Shift"); - term->kbd.mod_alt = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Mod1") ; - term->kbd.mod_ctrl = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Control"); - term->kbd.mod_meta = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Mod4"); + wayl->kbd.mod_shift = xkb_keymap_mod_get_index(wayl->kbd.xkb_keymap, "Shift"); + wayl->kbd.mod_alt = xkb_keymap_mod_get_index(wayl->kbd.xkb_keymap, "Mod1") ; + wayl->kbd.mod_ctrl = xkb_keymap_mod_get_index(wayl->kbd.xkb_keymap, "Control"); + wayl->kbd.mod_meta = xkb_keymap_mod_get_index(wayl->kbd.xkb_keymap, "Mod4"); /* Compose (dead keys) */ - term->kbd.xkb_compose_table = xkb_compose_table_new_from_locale( - term->kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); - term->kbd.xkb_compose_state = xkb_compose_state_new( - term->kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + wayl->kbd.xkb_compose_table = xkb_compose_table_new_from_locale( + wayl->kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); + wayl->kbd.xkb_compose_state = xkb_compose_state_new( + wayl->kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); munmap(map_str, size); close(fd); @@ -83,20 +83,22 @@ static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - struct terminal *term = data; - term->input_serial = serial; - term_focus_in(term); + struct wayland *wayl = data; + wayl->input_serial = serial; + wayl->focused = wayl_terminal_from_surface(wayl, surface); + assert(wayl->focused != NULL); + term_focus_in(wayl->focused); } static bool -start_repeater(struct terminal *term, uint32_t key) +start_repeater(struct wayland *wayl, uint32_t key) { - if (term->kbd.repeat.dont_re_repeat) + if (wayl->kbd.repeat.dont_re_repeat) return true; struct itimerspec t = { - .it_value = {.tv_sec = 0, .tv_nsec = term->kbd.repeat.delay * 1000000}, - .it_interval = {.tv_sec = 0, .tv_nsec = 1000000000 / term->kbd.repeat.rate}, + .it_value = {.tv_sec = 0, .tv_nsec = wayl->kbd.repeat.delay * 1000000}, + .it_interval = {.tv_sec = 0, .tv_nsec = 1000000000 / wayl->kbd.repeat.rate}, }; if (t.it_value.tv_nsec >= 1000000000) { @@ -107,22 +109,22 @@ start_repeater(struct terminal *term, uint32_t key) t.it_interval.tv_sec += t.it_interval.tv_nsec / 1000000000; t.it_interval.tv_nsec %= 1000000000; } - if (timerfd_settime(term->kbd.repeat.fd, 0, &t, NULL) < 0) { + if (timerfd_settime(wayl->kbd.repeat.fd, 0, &t, NULL) < 0) { LOG_ERRNO("failed to arm keyboard repeat timer"); return false; } - term->kbd.repeat.key = key; + wayl->kbd.repeat.key = key; return true; } static bool -stop_repeater(struct terminal *term, uint32_t key) +stop_repeater(struct wayland *wayl, uint32_t key) { - if (key != -1 && key != term->kbd.repeat.key) + if (key != -1 && key != wayl->kbd.repeat.key) return true; - if (timerfd_settime(term->kbd.repeat.fd, 0, &(struct itimerspec){{0}}, NULL) < 0) { + if (timerfd_settime(wayl->kbd.repeat.fd, 0, &(struct itimerspec){{0}}, NULL) < 0) { LOG_ERRNO("failed to disarm keyboard repeat timer"); return false; } @@ -134,30 +136,33 @@ static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { - struct terminal *term = data; + struct wayland *wayl = data; - stop_repeater(term, -1); - term_focus_out(term); + stop_repeater(wayl, -1); + term_focus_out(wayl->focused); + wayl->focused = NULL; } static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - struct terminal *term = data; + struct wayland *wayl = data; + struct terminal *term = wayl->focused; + assert(term != NULL); - const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl; - const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt; - const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift; - const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta; + const xkb_mod_mask_t ctrl = 1 << wayl->kbd.mod_ctrl; + const xkb_mod_mask_t alt = 1 << wayl->kbd.mod_alt; + const xkb_mod_mask_t shift = 1 << wayl->kbd.mod_shift; + const xkb_mod_mask_t meta = 1 << wayl->kbd.mod_meta; if (state == XKB_KEY_UP) { - stop_repeater(term, key); + stop_repeater(wayl, key); return; } key += 8; - xkb_keysym_t sym = xkb_state_key_get_one_sym(term->kbd.xkb_state, key); + xkb_keysym_t sym = xkb_state_key_get_one_sym(wayl->kbd.xkb_state, key); #if 0 char foo[100]; @@ -165,22 +170,22 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, LOG_INFO("%s", foo); #endif - xkb_compose_state_feed(term->kbd.xkb_compose_state, sym); + xkb_compose_state_feed(wayl->kbd.xkb_compose_state, sym); enum xkb_compose_status compose_status = xkb_compose_state_get_status( - term->kbd.xkb_compose_state); + wayl->kbd.xkb_compose_state); if (compose_status == XKB_COMPOSE_COMPOSING) return; xkb_mod_mask_t mods = xkb_state_serialize_mods( - term->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED); - //xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(term->kbd.xkb_state, key); + wayl->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED); + //xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(wayl->kbd.xkb_state, key); xkb_mod_mask_t consumed = 0x0; xkb_mod_mask_t significant = ctrl | alt | shift | meta; xkb_mod_mask_t effective_mods = mods & ~consumed & significant; if (term->is_searching) { - start_repeater(term, key - 8); + start_repeater(wayl, key - 8); search_input(term, key, sym, effective_mods); return; } @@ -188,7 +193,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, #if 0 for (size_t i = 0; i < 32; i++) { if (mods & (1 << i)) { - LOG_INFO("%s", xkb_keymap_mod_get_name(term->kbd.xkb_keymap, i)); + LOG_INFO("%s", xkb_keymap_mod_get_name(wayl->kbd.xkb_keymap, i)); } } #endif @@ -200,10 +205,10 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, bool found_map = false; enum modifier keymap_mods = MOD_NONE; - keymap_mods |= term->kbd.shift ? MOD_SHIFT : MOD_NONE; - keymap_mods |= term->kbd.alt ? MOD_ALT : MOD_NONE; - keymap_mods |= term->kbd.ctrl ? MOD_CTRL : MOD_NONE; - keymap_mods |= term->kbd.meta ? MOD_META : MOD_NONE; + keymap_mods |= wayl->kbd.shift ? MOD_SHIFT : MOD_NONE; + keymap_mods |= wayl->kbd.alt ? MOD_ALT : MOD_NONE; + keymap_mods |= wayl->kbd.ctrl ? MOD_CTRL : MOD_NONE; + keymap_mods |= wayl->kbd.meta ? MOD_META : MOD_NONE; if (effective_mods == shift) { if (sym == XKB_KEY_Page_Up) { @@ -268,11 +273,11 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, if (compose_status == XKB_COMPOSE_COMPOSED) { count = xkb_compose_state_get_utf8( - term->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); - xkb_compose_state_reset(term->kbd.xkb_compose_state); + wayl->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); + xkb_compose_state_reset(wayl->kbd.xkb_compose_state); } else { count = xkb_state_key_get_utf8( - term->kbd.xkb_state, key, (char *)buf, sizeof(buf)); + wayl->kbd.xkb_state, key, (char *)buf, sizeof(buf)); } if (count > 0) { @@ -324,7 +329,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, } } - start_repeater(term, key - 8); + start_repeater(wayl, key - 8); } static void @@ -332,33 +337,33 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct terminal *term = data; + struct wayland *wayl = data; LOG_DBG("modifiers: depressed=0x%x, latched=0x%x, locked=0x%x, group=%u", mods_depressed, mods_latched, mods_locked, group); xkb_state_update_mask( - term->kbd.xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); + wayl->kbd.xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); /* Update state of modifiers we're interrested in for e.g mouse events */ - term->kbd.shift = xkb_state_mod_index_is_active( - term->kbd.xkb_state, term->kbd.mod_shift, XKB_STATE_MODS_DEPRESSED); - term->kbd.alt = xkb_state_mod_index_is_active( - term->kbd.xkb_state, term->kbd.mod_alt, XKB_STATE_MODS_DEPRESSED); - term->kbd.ctrl = xkb_state_mod_index_is_active( - term->kbd.xkb_state, term->kbd.mod_ctrl, XKB_STATE_MODS_DEPRESSED); - term->kbd.meta = xkb_state_mod_index_is_active( - term->kbd.xkb_state, term->kbd.mod_meta, XKB_STATE_MODS_DEPRESSED); + wayl->kbd.shift = xkb_state_mod_index_is_active( + wayl->kbd.xkb_state, wayl->kbd.mod_shift, XKB_STATE_MODS_DEPRESSED); + wayl->kbd.alt = xkb_state_mod_index_is_active( + wayl->kbd.xkb_state, wayl->kbd.mod_alt, XKB_STATE_MODS_DEPRESSED); + wayl->kbd.ctrl = xkb_state_mod_index_is_active( + wayl->kbd.xkb_state, wayl->kbd.mod_ctrl, XKB_STATE_MODS_DEPRESSED); + wayl->kbd.meta = xkb_state_mod_index_is_active( + wayl->kbd.xkb_state, wayl->kbd.mod_meta, XKB_STATE_MODS_DEPRESSED); } static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { - struct terminal *term = data; + struct wayland *wayl = data; LOG_DBG("keyboard repeat: rate=%d, delay=%d", rate, delay); - term->kbd.repeat.rate = rate; - term->kbd.repeat.delay = delay; + wayl->kbd.repeat.rate = rate; + wayl->kbd.repeat.delay = delay; } const struct wl_keyboard_listener keyboard_listener = { @@ -371,9 +376,9 @@ const struct wl_keyboard_listener keyboard_listener = { }; void -input_repeat(struct terminal *term, uint32_t key) +input_repeat(struct wayland *wayl, uint32_t key) { - keyboard_key(term, NULL, 0, 0, key, XKB_KEY_DOWN); + keyboard_key(wayl, NULL, 0, 0, key, XKB_KEY_DOWN); } static void @@ -381,28 +386,35 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - struct terminal *term = data; + struct wayland *wayl = data; + struct terminal *term = wayl_terminal_from_surface(wayl, surface); + + wayl->moused = term; int x = wl_fixed_to_int(surface_x) * term->scale; int y = wl_fixed_to_int(surface_y) * term->scale; - term->mouse.col = x / term->cell_width; - term->mouse.row = y / term->cell_height; + wayl->mouse.col = x / term->cell_width; + wayl->mouse.row = y / term->cell_height; - render_update_cursor_surface(term); + wayl_update_cursor_surface(wayl, term); } static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { + struct wayland *wayl = data; + wayl->moused = NULL; } static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { - struct terminal *term = data; + struct wayland *wayl = data; + struct terminal *term = wayl->moused; + assert(term != NULL); int x = wl_fixed_to_int(surface_x) * term->scale; int y = wl_fixed_to_int(surface_y) * term->scale; @@ -413,24 +425,24 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, if (col < 0 || row < 0 || col >= term->cols || row >= term->rows) return; - bool update_selection = term->mouse.button == BTN_LEFT; + bool update_selection = wayl->mouse.button == BTN_LEFT; bool update_selection_early = term->selection.end.row == -1; if (update_selection && update_selection_early) selection_update(term, col, row); - if (col == term->mouse.col && row == term->mouse.row) + if (col == wayl->mouse.col && row == wayl->mouse.row) return; - term->mouse.col = col; - term->mouse.row = row; + wayl->mouse.col = col; + wayl->mouse.row = row; if (update_selection && !update_selection_early) selection_update(term, col, row); term_mouse_motion( - term, term->mouse.button, term->mouse.row, term->mouse.col, - term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + term, wayl->mouse.button, wayl->mouse.row, wayl->mouse.col, + wayl->kbd.shift, wayl->kbd.alt, wayl->kbd.ctrl); } static void @@ -439,7 +451,9 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, { LOG_DBG("BUTTON: button=%x, state=%u", button, state); - struct terminal *term = data; + struct wayland *wayl = data; + struct terminal *term = wayl->moused; + assert(term != NULL); search_cancel(term); @@ -448,43 +462,43 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, /* Time since last click */ struct timeval now, since_last; gettimeofday(&now, NULL); - timersub(&now, &term->mouse.last_time, &since_last); + timersub(&now, &wayl->mouse.last_time, &since_last); /* Double- or triple click? */ - if (button == term->mouse.last_button && + if (button == wayl->mouse.last_button && since_last.tv_sec == 0 && since_last.tv_usec <= 300 * 1000) { - term->mouse.count++; + wayl->mouse.count++; } else - term->mouse.count = 1; + wayl->mouse.count = 1; if (button == BTN_LEFT) { - switch (term->mouse.count) { + switch (wayl->mouse.count) { case 1: - selection_start(term, term->mouse.col, term->mouse.row); + selection_start(term, wayl->mouse.col, wayl->mouse.row); break; case 2: - selection_mark_word(term, term->mouse.col, term->mouse.row, - term->kbd.ctrl, serial); + selection_mark_word(term, wayl->mouse.col, wayl->mouse.row, + wayl->kbd.ctrl, serial); break; case 3: - selection_mark_row(term, term->mouse.row, serial); + selection_mark_row(term, wayl->mouse.row, serial); break; } } else { - if (term->mouse.count == 1 && button == BTN_MIDDLE && selection_enabled(term)) + if (wayl->mouse.count == 1 && button == BTN_MIDDLE && selection_enabled(term)) selection_from_primary(term); selection_cancel(term); } - term->mouse.button = button; /* For motion events */ - term->mouse.last_button = button; - term->mouse.last_time = now; - term_mouse_down(term, button, term->mouse.row, term->mouse.col, - term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + wayl->mouse.button = button; /* For motion events */ + wayl->mouse.last_button = button; + wayl->mouse.last_time = now; + term_mouse_down(term, button, wayl->mouse.row, wayl->mouse.col, + wayl->kbd.shift, wayl->kbd.alt, wayl->kbd.ctrl); break; } @@ -494,16 +508,19 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, else selection_finalize(term, serial); - term->mouse.button = 0; /* For motion events */ - term_mouse_up(term, button, term->mouse.row, term->mouse.col, - term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + wayl->mouse.button = 0; /* For motion events */ + term_mouse_up(term, button, wayl->mouse.row, wayl->mouse.col, + wayl->kbd.shift, wayl->kbd.alt, wayl->kbd.ctrl); break; } } static void -mouse_scroll(struct terminal *term, int amount) +mouse_scroll(struct wayland *wayl, int amount) { + struct terminal *term = wayl->moused; + assert(term != NULL); + int button = amount < 0 ? BTN_BACK : BTN_FORWARD; void (*scrollback)(struct terminal *term, int rows) @@ -520,15 +537,15 @@ mouse_scroll(struct terminal *term, int amount) */ xkb_keycode_t key = xkb_keymap_key_by_name( - term->kbd.xkb_keymap, button == BTN_BACK ? "UP" : "DOWN"); + wayl->kbd.xkb_keymap, button == BTN_BACK ? "UP" : "DOWN"); for (int i = 0; i < amount; i++) - keyboard_key(term, NULL, term->input_serial, 0, key - 8, XKB_KEY_DOWN); - keyboard_key(term, NULL, term->input_serial, 0, key - 8, XKB_KEY_UP); + keyboard_key(term, NULL, wayl->input_serial, 0, key - 8, XKB_KEY_DOWN); + keyboard_key(term, NULL, wayl->input_serial, 0, key - 8, XKB_KEY_UP); } else { for (int i = 0; i < amount; i++) - term_mouse_down(term, button, term->mouse.row, term->mouse.col, - term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + term_mouse_down(term, button, wayl->mouse.row, wayl->mouse.col, + wayl->kbd.shift, wayl->kbd.alt, wayl->kbd.ctrl); scrollback(term, amount); } @@ -541,11 +558,12 @@ wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; - struct terminal *term = data; - if (term->mouse.have_discrete) + struct wayland *wayl = data; + + if (wayl->mouse.have_discrete) return; - mouse_scroll(term, wl_fixed_to_int(value)); + mouse_scroll(wayl, wl_fixed_to_int(value)); } static void @@ -555,16 +573,16 @@ wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; - struct terminal *term = data; - term->mouse.have_discrete = true; - mouse_scroll(term, discrete); + struct wayland *wayl = data; + wayl->mouse.have_discrete = true; + mouse_scroll(wayl, discrete); } static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { - struct terminal *term = data; - term->mouse.have_discrete = false; + struct wayland *wayl = data; + wayl->mouse.have_discrete = false; } static void diff --git a/input.h b/input.h index 8ffa1c15..2483389a 100644 --- a/input.h +++ b/input.h @@ -2,9 +2,9 @@ #include -#include "terminal.h" +#include "wayland.h" extern const struct wl_keyboard_listener keyboard_listener; extern const struct wl_pointer_listener pointer_listener; -void input_repeat(struct terminal *term, uint32_t key); +void input_repeat(struct wayland *wayl, uint32_t key); diff --git a/main.c b/main.c index dfb1982d..64dea222 100644 --- a/main.c +++ b/main.c @@ -3,392 +3,26 @@ #include #include #include -#include -#include -#include #include #include -#include #include -#include #include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include #define LOG_MODULE "main" #define LOG_ENABLE_DBG 0 #include "log.h" #include "config.h" +#include "fdm.h" #include "font.h" -#include "grid.h" -#include "input.h" -#include "render.h" -#include "selection.h" #include "shm.h" -#include "slave.h" #include "terminal.h" -#include "tokenize.h" #include "version.h" -#include "vt.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) -static void -shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) -{ - struct terminal *term = data; - if (format == WL_SHM_FORMAT_ARGB8888) - term->wl.have_argb8888 = true; -} - -static const struct wl_shm_listener shm_listener = { - .format = &shm_format, -}; - -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) -{ - LOG_DBG("wm base ping"); - xdg_wm_base_pong(shell, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = &xdg_wm_base_ping, -}; - -static void -seat_handle_capabilities(void *data, struct wl_seat *wl_seat, - enum wl_seat_capability caps) -{ - struct terminal *term = data; - - if (term->wl.keyboard != NULL) { - wl_keyboard_release(term->wl.keyboard); - term->wl.keyboard = NULL; - } - - if (term->wl.pointer.pointer != NULL) { - wl_pointer_release(term->wl.pointer.pointer); - term->wl.pointer.pointer = NULL; - } - - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - term->wl.keyboard = wl_seat_get_keyboard(wl_seat); - wl_keyboard_add_listener(term->wl.keyboard, &keyboard_listener, term); - } - - if (caps & WL_SEAT_CAPABILITY_POINTER) { - term->wl.pointer.pointer = wl_seat_get_pointer(wl_seat); - wl_pointer_add_listener(term->wl.pointer.pointer, &pointer_listener, term); - } -} - -static void -seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) -{ -} - -static const struct wl_seat_listener seat_listener = { - .capabilities = seat_handle_capabilities, - .name = seat_handle_name, -}; - -static void -output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, - int32_t physical_width, int32_t physical_height, - int32_t subpixel, const char *make, const char *model, - int32_t transform) -{ - struct monitor *mon = data; - mon->width_mm = physical_width; - mon->height_mm = physical_height; -} - -static void -output_mode(void *data, struct wl_output *wl_output, uint32_t flags, - int32_t width, int32_t height, int32_t refresh) -{ - if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) - return; - - struct monitor *mon = data; - mon->refresh = (float)refresh / 1000; -} - -static void -output_done(void *data, struct wl_output *wl_output) -{ -} - -static void -output_scale(void *data, struct wl_output *wl_output, int32_t factor) -{ - struct monitor *mon = data; - mon->scale = factor; - - render_resize(mon->term, mon->term->width / mon->term->scale, mon->term->height / mon->term->scale); - render_reload_cursor_theme(mon->term); -} - -static const struct wl_output_listener output_listener = { - .geometry = &output_geometry, - .mode = &output_mode, - .done = &output_done, - .scale = &output_scale, -}; - -static void -xdg_output_handle_logical_position( - void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) -{ - struct monitor *mon = data; - mon->x = x; - mon->y = y; -} - -static void -xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, - int32_t width, int32_t height) -{ - struct monitor *mon = data; - mon->width_px = width; - mon->height_px = height; -} - -static void -xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) -{ -} - -static void -xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, - const char *name) -{ - struct monitor *mon = data; - mon->name = strdup(name); -} - -static void -xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, - const char *description) -{ -} - -static struct zxdg_output_v1_listener xdg_output_listener = { - .logical_position = xdg_output_handle_logical_position, - .logical_size = xdg_output_handle_logical_size, - .done = xdg_output_handle_done, - .name = xdg_output_handle_name, - .description = xdg_output_handle_description, -}; - -static void -handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) -{ - LOG_DBG("global: %s, version=%u", interface, version); - struct terminal *term = data; - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - term->wl.compositor = wl_registry_bind( - term->wl.registry, name, &wl_compositor_interface, 4); - } - - else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { - term->wl.sub_compositor = wl_registry_bind( - term->wl.registry, name, &wl_subcompositor_interface, 1); - } - - else if (strcmp(interface, wl_shm_interface.name) == 0) { - term->wl.shm = wl_registry_bind( - term->wl.registry, name, &wl_shm_interface, 1); - wl_shm_add_listener(term->wl.shm, &shm_listener, term); - wl_display_roundtrip(term->wl.display); - } - - else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - term->wl.shell = wl_registry_bind( - term->wl.registry, name, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(term->wl.shell, &xdg_wm_base_listener, term); - } - - else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) - term->wl.xdg_decoration_manager = wl_registry_bind( - term->wl.registry, name, &zxdg_decoration_manager_v1_interface, 1); - - else if (strcmp(interface, wl_seat_interface.name) == 0) { - term->wl.seat = wl_registry_bind( - term->wl.registry, name, &wl_seat_interface, 5); - wl_seat_add_listener(term->wl.seat, &seat_listener, term); - wl_display_roundtrip(term->wl.display); - } - - else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - term->wl.xdg_output_manager = wl_registry_bind( - term->wl.registry, name, &zxdg_output_manager_v1_interface, min(version, 2)); - } - - else if (strcmp(interface, wl_output_interface.name) == 0) { - struct wl_output *output = wl_registry_bind( - term->wl.registry, name, &wl_output_interface, 3); - - tll_push_back( - term->wl.monitors, ((struct monitor){ - .term = term, .output = output})); - - struct monitor *mon = &tll_back(term->wl.monitors); - wl_output_add_listener(output, &output_listener, mon); - - mon->xdg = zxdg_output_manager_v1_get_xdg_output( - term->wl.xdg_output_manager, mon->output); - zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon); - wl_display_roundtrip(term->wl.display); - } - - else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { - term->wl.data_device_manager = wl_registry_bind( - term->wl.registry, name, &wl_data_device_manager_interface, 1); - } - - else if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { - term->wl.primary_selection_device_manager = wl_registry_bind( - term->wl.registry, name, &zwp_primary_selection_device_manager_v1_interface, 1); - } -} - -static void -surface_enter(void *data, struct wl_surface *wl_surface, - struct wl_output *wl_output) -{ - struct terminal *term = data; - tll_foreach(term->wl.monitors, it) { - if (it->item.output == wl_output) { - LOG_DBG("mapped on %s", it->item.name); - tll_push_back(term->wl.on_outputs, &it->item); - - /* Resize, since scale-to-use may have changed */ - render_resize(term, term->width / term->scale, term->height / term->scale); - render_reload_cursor_theme(term); - return; - } - } - - LOG_ERR("mapped on unknown output"); -} - -static void -surface_leave(void *data, struct wl_surface *wl_surface, - struct wl_output *wl_output) -{ - struct terminal *term = data; - tll_foreach(term->wl.on_outputs, it) { - if (it->item->output != wl_output) - continue; - - LOG_DBG("unmapped from %s", it->item->name); - tll_remove(term->wl.on_outputs, it); - - /* Resize, since scale-to-use may have changed */ - render_resize(term, term->width / term->scale, term->height / term->scale); - render_reload_cursor_theme(term); - return; - } - - LOG_ERR("unmapped from unknown output"); -} - -static const struct wl_surface_listener surface_listener = { - .enter = &surface_enter, - .leave = &surface_leave, -}; - -static void -xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height, struct wl_array *states) -{ - LOG_DBG("xdg-toplevel: configure: %dx%d", width, height); - - if (width <= 0 || height <= 0) - return; - - struct terminal *term = data; - render_resize(term, width, height); -} - -static void -xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) -{ - struct terminal *term = data; - LOG_DBG("xdg-toplevel: close"); - term->quit = true; - wl_display_roundtrip(term->wl.display); -} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = &xdg_toplevel_configure, - .close = &xdg_toplevel_close, -}; - -static void -xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, - uint32_t serial) -{ - //LOG_DBG("xdg-surface: configure"); - xdg_surface_ack_configure(xdg_surface, serial); -} - -static const struct xdg_surface_listener xdg_surface_listener = { - .configure = &xdg_surface_configure, -}; - -static void -xdg_toplevel_decoration_configure(void *data, - struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - uint32_t mode) -{ - switch (mode) { - case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: - LOG_ERR("unimplemented: client-side decorations"); - break; - - case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: - LOG_DBG("using server-side decorations"); - break; - - default: - LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode); - break; - } -} - -static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { - .configure = &xdg_toplevel_decoration_configure, -}; - -static void -handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) -{ - LOG_WARN("global removed: %u", name); - assert(false); -} - -static const struct wl_registry_listener registry_listener = { - .global = &handle_global, - .global_remove = &handle_global_remove, -}; - static void print_usage(const char *prog_name) { @@ -439,7 +73,6 @@ main(int argc, char *const *argv) case 'f': tll_free_and_free(conf.fonts, free); - //tll_push_back(conf.fonts, strdup(optarg)); for (char *font = strtok(optarg, ","); font != NULL; font = strtok(NULL, ",")) { /* Strip leading spaces */ @@ -496,741 +129,37 @@ main(int argc, char *const *argv) setlocale(LC_ALL, ""); setenv("TERM", conf.term, 1); - struct terminal term = { - .quit = false, - .ptmx = posix_openpt(O_RDWR | O_NOCTTY), - .cursor_keys_mode = CURSOR_KEYS_NORMAL, - .keypad_keys_mode = KEYPAD_NUMERICAL, - .auto_margin = true, - .window_title_stack = tll_init(), - .scale = 1, - .flash = { - .fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK), - }, - .blink = { - .fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK), - }, - .vt = { - .state = 1, /* STATE_GROUND */ - .attrs = { - //.foreground = conf.colors.fg, - //.background = conf.colors.bg - }, - }, - .kbd = { - .repeat = { - .fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK), - }, - }, - .colors = { - .default_fg = conf.colors.fg, - .default_bg = conf.colors.bg, - .default_table = { - conf.colors.regular[0], - conf.colors.regular[1], - conf.colors.regular[2], - conf.colors.regular[3], - conf.colors.regular[4], - conf.colors.regular[5], - conf.colors.regular[6], - conf.colors.regular[7], + struct fdm *fdm = NULL; + struct wayland *wayl = NULL; + struct terminal *term = NULL; - conf.colors.bright[0], - conf.colors.bright[1], - conf.colors.bright[2], - conf.colors.bright[3], - conf.colors.bright[4], - conf.colors.bright[5], - conf.colors.bright[6], - conf.colors.bright[7], - }, - .alpha = conf.colors.alpha, - }, - .default_cursor_style = conf.cursor.style, - .cursor_style = conf.cursor.style, - .default_cursor_color = { - .text = conf.cursor.color.text, - .cursor = conf.cursor.color.cursor, - }, - .cursor_color = { - .text = conf.cursor.color.text, - .cursor = conf.cursor.color.cursor, - }, - .selection = { - .start = {-1, -1}, - .end = {-1, -1}, - }, - .normal = {.damage = tll_init(), .scroll_damage = tll_init()}, - .alt = {.damage = tll_init(), .scroll_damage = tll_init()}, - .grid = &term.normal, - .render = { - .scrollback_lines = conf.scrollback_lines, - .workers = { - .count = conf.render_worker_count, - .queue = tll_init(), - }, - }, - }; - - LOG_INFO("using %zu rendering threads", term.render.workers.count); - - struct render_worker_context worker_context[term.render.workers.count]; - - /* Initialize 'current' colors from the default colors */ - term.colors.fg = term.colors.default_fg; - term.colors.bg = term.colors.default_bg; - - /* Initialize the 256 gray-scale color cube */ - { - /* First 16 entries have already been initialized from conf */ - for (size_t r = 0; r < 6; r++) { - for (size_t g = 0; g < 6; g++) { - for (size_t b = 0; b < 6; b++) { - term.colors.default_table[16 + r * 6 * 6 + g * 6 + b] - = r * 51 << 16 | g * 51 << 8 | b * 51; - } - } - } - - for (size_t i = 0; i < 24; i++) - term.colors.default_table[232 + i] = i * 11 << 16 | i * 11 << 8 | i * 11; - - memcpy(term.colors.table, term.colors.default_table, sizeof(term.colors.table)); - } - - if (term.ptmx == -1) { - LOG_ERR("failed to open pseudo terminal"); + if ((fdm = fdm_init()) == NULL) goto out; - } - if (term.flash.fd == -1 || term.blink.fd == -1 || term.kbd.repeat.fd == -1) { - LOG_ERR("failed to create timers"); + if ((wayl = wayl_init(fdm)) == NULL) goto out; - } - sem_init(&term.render.workers.start, 0, 0); - sem_init(&term.render.workers.done, 0, 0); - mtx_init(&term.render.workers.lock, mtx_plain); - cnd_init(&term.render.workers.cond); - - term.render.workers.threads = calloc(term.render.workers.count, sizeof(term.render.workers.threads[0])); - for (size_t i = 0; i < term.render.workers.count; i++) { - worker_context[i].term = &term; - worker_context[i].my_id = 1 + i; - thrd_create(&term.render.workers.threads[i], &render_worker_thread, &worker_context[i]); - } - - font_list_t font_names = tll_init(); - tll_foreach(conf.fonts, it) - tll_push_back(font_names, it->item); - - if ((term.fonts[0] = font_from_name(font_names, "")) == NULL) { - tll_free(font_names); + if ((term = term_init(&conf, fdm, wayl, argc, argv)) == NULL) goto out; - } - - term.fonts[1] = font_from_name(font_names, "style=bold"); - term.fonts[2] = font_from_name(font_names, "style=italic"); - term.fonts[3] = font_from_name(font_names, "style=bold italic"); - - tll_free(font_names); - - { - FT_Face ft_face = term.fonts[0]->face; - int max_x_advance = ft_face->size->metrics.max_advance / 64; - int height = ft_face->size->metrics.height / 64; - int descent = ft_face->size->metrics.descender / 64; - int ascent = ft_face->size->metrics.ascender / 64; - - term.fextents.height = height * term.fonts[0]->pixel_size_fixup; - term.fextents.descent = -descent * term.fonts[0]->pixel_size_fixup; - term.fextents.ascent = ascent * term.fonts[0]->pixel_size_fixup; - term.fextents.max_x_advance = max_x_advance * term.fonts[0]->pixel_size_fixup; - - LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d", - height, descent, ascent, max_x_advance); - } - - term.cell_width = (int)ceil(term.fextents.max_x_advance); - term.cell_height = (int)ceil(term.fextents.height); - LOG_INFO("cell width=%d, height=%d", term.cell_width, term.cell_height); - - term.wl.display = wl_display_connect(NULL); - if (term.wl.display == NULL) { - LOG_ERR("failed to connect to wayland; no compositor running?"); - goto out; - } - - term.wl.registry = wl_display_get_registry(term.wl.display); - if (term.wl.registry == NULL) { - LOG_ERR("failed to get wayland registry"); - goto out; - } - - wl_registry_add_listener(term.wl.registry, ®istry_listener, &term); - wl_display_roundtrip(term.wl.display); - - if (term.wl.compositor == NULL) { - LOG_ERR("no compositor"); - goto out; - } - if (term.wl.shm == NULL) { - LOG_ERR("no shared memory buffers interface"); - goto out; - } - if (term.wl.shell == NULL) { - LOG_ERR("no XDG shell interface"); - goto out; - } - if (!term.wl.have_argb8888) { - LOG_ERR("compositor does not support ARGB surfaces"); - goto out; - } - if (term.wl.seat == NULL) { - LOG_ERR("no seat available"); - goto out; - } - if (term.wl.data_device_manager == NULL) { - LOG_ERR("no clipboard available " - "(wl_data_device_manager not implemented by server)"); - goto out; - } - if (term.wl.primary_selection_device_manager == NULL) - LOG_WARN("no primary selection available"); - - tll_foreach(term.wl.monitors, it) { - LOG_INFO("%s: %dx%d+%dx%d (scale=%d, refresh=%.2fHz)", - it->item.name, it->item.width_px, it->item.height_px, - it->item.x, it->item.y, it->item.scale, it->item.refresh); - } - - /* Clipboard */ - term.wl.data_device = wl_data_device_manager_get_data_device( - term.wl.data_device_manager, term.wl.seat); - wl_data_device_add_listener(term.wl.data_device, &data_device_listener, &term); - - /* Primary selection */ - if (term.wl.primary_selection_device_manager != NULL) { - term.wl.primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( - term.wl.primary_selection_device_manager, term.wl.seat); - zwp_primary_selection_device_v1_add_listener( - term.wl.primary_selection_device, &primary_selection_device_listener, &term); - } - - /* Cursor */ - unsigned cursor_size = 24; - const char *cursor_theme = getenv("XCURSOR_THEME"); - - { - const char *env_cursor_size = getenv("XCURSOR_SIZE"); - if (env_cursor_size != NULL) { - unsigned size; - if (sscanf(env_cursor_size, "%u", &size) == 1) - cursor_size = size; - } - } - - /* Note: theme is (re)loaded on scale and output changes */ - LOG_INFO("cursor theme: %s, size: %u", cursor_theme, cursor_size); - term.wl.pointer.size = cursor_size; - term.wl.pointer.theme_name = cursor_theme != NULL ? strdup(cursor_theme) : NULL; - - term.wl.pointer.surface = wl_compositor_create_surface(term.wl.compositor); - if (term.wl.pointer.surface == NULL) { - LOG_ERR("failed to create cursor surface"); - goto out; - } - - /* Main window */ - term.wl.surface = wl_compositor_create_surface(term.wl.compositor); - if (term.wl.surface == NULL) { - LOG_ERR("failed to create wayland surface"); - goto out; - } - - wl_surface_add_listener(term.wl.surface, &surface_listener, &term); - - term.wl.xdg_surface = xdg_wm_base_get_xdg_surface(term.wl.shell, term.wl.surface); - xdg_surface_add_listener(term.wl.xdg_surface, &xdg_surface_listener, &term); - - term.wl.xdg_toplevel = xdg_surface_get_toplevel(term.wl.xdg_surface); - xdg_toplevel_add_listener(term.wl.xdg_toplevel, &xdg_toplevel_listener, &term); - - xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot"); - term_set_window_title(&term, "foot"); - - /* Request server-side decorations */ - term.wl.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - term.wl.xdg_decoration_manager, term.wl.xdg_toplevel); - zxdg_toplevel_decoration_v1_set_mode( - term.wl.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); - zxdg_toplevel_decoration_v1_add_listener( - term.wl.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &term); - - /* Scrollback search box */ - term.wl.search_surface = wl_compositor_create_surface(term.wl.compositor); - term.wl.search_sub_surface = wl_subcompositor_get_subsurface( - term.wl.sub_compositor, term.wl.search_surface, term.wl.surface); - wl_subsurface_set_desync(term.wl.search_sub_surface); - - wl_surface_commit(term.wl.surface); - wl_display_roundtrip(term.wl.display); - - if (conf.width == -1) { - assert(conf.height == -1); - conf.width = 80 * term.cell_width; - conf.height = 24 * term.cell_height; - } - conf.width = max(conf.width, term.cell_width); - conf.height = max(conf.height, term.cell_height); - render_resize(&term, conf.width, conf.height); - - wl_display_dispatch_pending(term.wl.display); - - { - int fork_pipe[2]; - if (pipe2(fork_pipe, O_CLOEXEC) < 0) { - LOG_ERRNO("failed to create pipe"); - goto out; - } - - term.slave = fork(); - switch (term.slave) { - case -1: - LOG_ERRNO("failed to fork"); - close(fork_pipe[0]); - close(fork_pipe[1]); - goto out; - - case 0: - /* Child */ - close(fork_pipe[0]); /* Close read end */ - - char **_shell_argv = NULL; - char *const *shell_argv = argv; - - if (argc == 0) { - if (!tokenize_cmdline(conf.shell, &_shell_argv)) { - (void)!write(fork_pipe[1], &errno, sizeof(errno)); - _exit(0); - } - shell_argv = _shell_argv; - } - - slave_spawn(term.ptmx, shell_argv, fork_pipe[1]); - assert(false); - break; - - default: { - close(fork_pipe[1]); /* Close write end */ - LOG_DBG("slave has PID %d", term.slave); - - int _errno; - static_assert(sizeof(errno) == sizeof(_errno), "errno size mismatch"); - - ssize_t ret = read(fork_pipe[0], &_errno, sizeof(_errno)); - close(fork_pipe[0]); - - if (ret < 0) { - LOG_ERRNO("failed to read from pipe"); - goto out; - } else if (ret == sizeof(_errno)) { - LOG_ERRNO( - "%s: failed to execute", argc == 0 ? conf.shell : argv[0]); - goto out; - } else - LOG_DBG("%s: successfully started", conf.shell); - break; - } - } - } - - /* Read logic requires non-blocking mode */ - { - int fd_flags = fcntl(term.ptmx, F_GETFL); - if (fd_flags == -1) { - LOG_ERRNO("failed to set non blocking mode on PTY master"); - goto out; - } - - if (fcntl(term.ptmx, F_SETFL, fd_flags | O_NONBLOCK) == -1) { - LOG_ERRNO("failed to set non blocking mode on PTY master"); - goto out; - } - } - - - { - int fd = wl_display_get_fd(term.wl.display); - int fd_flags = fcntl(fd, F_GETFL); - if (fd_flags == -1) { - LOG_ERRNO("failed to set non blocking mode on Wayland display connection"); - goto out; - } - if (fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK) == -1) { - LOG_ERRNO("failed to set non blocking mode on Wayland display connection"); - goto out; - } - } - - bool timeout_is_armed = false; - int delay_render_timer_lower = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); - int delay_render_timer_upper = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); while (true) { - struct pollfd fds[] = { - {.fd = wl_display_get_fd(term.wl.display), .events = POLLIN}, - {.fd = term.ptmx, .events = POLLIN}, - {.fd = term.kbd.repeat.fd, .events = POLLIN}, - {.fd = term.flash.fd, .events = POLLIN}, - {.fd = term.blink.fd, .events = POLLIN}, - {.fd = delay_render_timer_lower, .events = POLLIN}, - {.fd = delay_render_timer_upper, .events = POLLIN}, - }; + wl_display_flush(term->wl->display); /* TODO: figure out how to get rid of this */ - const size_t WL_FD = 0; - const size_t PTMX_FD = 1; - const size_t KBD_REPEAT_FD = 2; - const size_t FLASH_FD = 3; - const size_t BLINK_FD = 4; - const size_t DELAY_LOWER_FD = 5; - const size_t DELAY_UPPER_FD = 6; - - wl_display_flush(term.wl.display); - int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); - - if (pret == -1) { - if (errno == EINTR) - continue; - - LOG_ERRNO("failed to poll file descriptors"); + if (!fdm_poll(fdm)) break; - } - - /* Delayed rendering timers (created when we receive input) */ - if (fds[DELAY_LOWER_FD].revents & POLLIN || - fds[DELAY_UPPER_FD].revents & POLLIN) - { - assert(timeout_is_armed); - - uint64_t unused; - ssize_t ret1 = 0; - ssize_t ret2 = 0; - - if (fds[DELAY_LOWER_FD].revents & POLLIN) - ret1 = read(delay_render_timer_lower, &unused, sizeof(unused)); - if (fds[DELAY_UPPER_FD].revents & POLLIN) - ret2 = read(delay_render_timer_upper, &unused, sizeof(unused)); - - if ((ret1 < 0 || ret2 < 0) && errno != EAGAIN) - LOG_ERRNO("failed to read timeout timer"); - else if (ret1 > 0 || ret2 > 0) { - render_refresh(&term); - - /* Reset timers */ - timeout_is_armed = false; - timerfd_settime(delay_render_timer_lower, 0, &(struct itimerspec){.it_value = {0}}, NULL); - timerfd_settime(delay_render_timer_upper, 0, &(struct itimerspec){.it_value = {0}}, NULL); - } else - assert(false); - } - - if (fds[WL_FD].revents & POLLIN) { - wl_display_dispatch(term.wl.display); - if (term.quit) { - ret = EXIT_SUCCESS; - break; - } - } - - if (fds[WL_FD].revents & POLLHUP) { - LOG_WARN("disconnected from wayland"); - break; - } - - if (fds[PTMX_FD].revents & POLLIN) { - uint8_t data[24 * 1024]; - ssize_t count = read(term.ptmx, data, sizeof(data)); - if (count < 0 && errno != EAGAIN) { - LOG_ERRNO("failed to read from pseudo terminal"); - break; - } - - if (count > 0) { - vt_from_slave(&term, data, count); - - /* - * We likely need to re-render. But, we don't want to - * do it immediately. Often, a single client operation - * is done through multiple writes. Many times, we're - * so fast that we render mid-operation frames. - * - * For example, we might end up rendering a frame - * where the client just erased a line, while in the - * next frame, the client wrote to the same line. This - * causes screen "flashes". - * - * Mitigate by always incuring a small delay before - * rendering the next frame. This gives the client - * some time to finish the operation (and thus gives - * us time to receive the last writes before doing any - * actual rendering). - * - * We incur this delay *every* time we receive - * input. To ensure we don't delay rendering - * indefinitely, we start a second timer that is only - * reset when we render. - * - * Note that when the client is producing data at a - * very high pace, we're rate limited by the wayland - * compositor anyway. The delay we introduce here only - * has any effect when the renderer is idle. - * - * TODO: this adds input latency. Can we somehow hint - * ourselves we just received keyboard input, and in - * this case *not* delay rendering? - */ - if (term.render.frame_callback == NULL) { - /* First timeout - reset each time we receive input. */ - timerfd_settime( - delay_render_timer_lower, 0, - &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, - NULL); - - /* Second timeout - only reset when we render. Set to one frame (assuming 60Hz) */ - if (!timeout_is_armed) { - timerfd_settime( - delay_render_timer_upper, 0, - &(struct itimerspec){.it_value = {.tv_nsec = 16666666}}, - NULL); - timeout_is_armed = true; - } - } - } - } - - if (fds[PTMX_FD].revents & POLLHUP) { - ret = EXIT_SUCCESS; - break; - } - - if (fds[KBD_REPEAT_FD].revents & POLLIN) { - uint64_t expiration_count; - ssize_t ret = read( - term.kbd.repeat.fd, &expiration_count, sizeof(expiration_count)); - - if (ret < 0 && errno != EAGAIN) - LOG_ERRNO("failed to read repeat key from repeat timer fd"); - else if (ret > 0) { - term.kbd.repeat.dont_re_repeat = true; - for (size_t i = 0; i < expiration_count; i++) - input_repeat(&term, term.kbd.repeat.key); - term.kbd.repeat.dont_re_repeat = false; - } - } - - if (fds[FLASH_FD].revents & POLLIN) { - uint64_t expiration_count; - ssize_t ret = read( - term.flash.fd, &expiration_count, sizeof(expiration_count)); - - if (ret < 0 && errno != EAGAIN) - LOG_ERRNO("failed to read flash timer"); - else if (ret > 0) { - LOG_DBG("flash timer expired %llu times", - (unsigned long long)expiration_count); - - term.flash.active = false; - term_damage_view(&term); - render_refresh(&term); - } - } - - if (fds[BLINK_FD].revents & POLLIN) { - uint64_t expiration_count; - ssize_t ret = read( - term.blink.fd, &expiration_count, sizeof(expiration_count)); - - if (ret < 0 && errno != EAGAIN) - LOG_ERRNO("failed to read blink timer"); - else if (ret > 0) { - LOG_DBG("blink timer expired %llu times", - (unsigned long long)expiration_count); - - term.blink.state = term.blink.state == BLINK_ON - ? BLINK_OFF : BLINK_ON; - - /* Scan all visible cells and mark rows with blinking cells dirty */ - for (int r = 0; r < term.rows; r++) { - struct row *row = grid_row_in_view(term.grid, r); - for (int col = 0; col < term.cols; col++) { - struct cell *cell = &row->cells[col]; - - if (cell->attrs.blink) { - cell->attrs.clean = 0; - row->dirty = true; - } - } - } - - render_refresh(&term); - } - } } - close(delay_render_timer_lower); - close(delay_render_timer_upper); + if (term->quit) + ret = EXIT_SUCCESS; out: - mtx_lock(&term.render.workers.lock); - assert(tll_length(term.render.workers.queue) == 0); - for (size_t i = 0; i < term.render.workers.count; i++) { - sem_post(&term.render.workers.start); - tll_push_back(term.render.workers.queue, -2); - } - cnd_broadcast(&term.render.workers.cond); - mtx_unlock(&term.render.workers.lock); - shm_fini(); - tll_free(term.wl.on_outputs); - tll_foreach(term.wl.monitors, it) { - free(it->item.name); - if (it->item.xdg != NULL) - zxdg_output_v1_destroy(it->item.xdg); - if (it->item.output != NULL) - wl_output_destroy(it->item.output); - tll_remove(term.wl.monitors, it); - } - - if (term.wl.xdg_output_manager != NULL) - zxdg_output_manager_v1_destroy(term.wl.xdg_output_manager); - - free(term.wl.pointer.theme_name); - if (term.wl.pointer.theme != NULL) - wl_cursor_theme_destroy(term.wl.pointer.theme); - if (term.wl.pointer.pointer != NULL) - wl_pointer_destroy(term.wl.pointer.pointer); - if (term.wl.pointer.surface != NULL) - wl_surface_destroy(term.wl.pointer.surface); - if (term.wl.keyboard != NULL) - wl_keyboard_destroy(term.wl.keyboard); - if (term.selection.clipboard.data_source != NULL) - wl_data_source_destroy(term.selection.clipboard.data_source); - if (term.selection.clipboard.data_offer != NULL) - wl_data_offer_destroy(term.selection.clipboard.data_offer); - free(term.selection.clipboard.text); - if (term.wl.data_device != NULL) - wl_data_device_destroy(term.wl.data_device); - if (term.wl.data_device_manager != NULL) - wl_data_device_manager_destroy(term.wl.data_device_manager); - if (term.selection.primary.data_source != NULL) - zwp_primary_selection_source_v1_destroy(term.selection.primary.data_source); - if (term.selection.primary.data_offer != NULL) - zwp_primary_selection_offer_v1_destroy(term.selection.primary.data_offer); - free(term.selection.primary.text); - if (term.wl.primary_selection_device != NULL) - zwp_primary_selection_device_v1_destroy(term.wl.primary_selection_device); - if (term.wl.primary_selection_device_manager != NULL) - zwp_primary_selection_device_manager_v1_destroy(term.wl.primary_selection_device_manager); - if (term.wl.seat != NULL) - wl_seat_destroy(term.wl.seat); - if (term.wl.search_sub_surface != NULL) - wl_subsurface_destroy(term.wl.search_sub_surface); - if (term.wl.search_surface != NULL) - wl_surface_destroy(term.wl.search_surface); - if (term.render.frame_callback != NULL) - wl_callback_destroy(term.render.frame_callback); - if (term.wl.xdg_toplevel_decoration != NULL) - zxdg_toplevel_decoration_v1_destroy(term.wl.xdg_toplevel_decoration); - if (term.wl.xdg_decoration_manager != NULL) - zxdg_decoration_manager_v1_destroy(term.wl.xdg_decoration_manager); - if (term.wl.xdg_toplevel != NULL) - xdg_toplevel_destroy(term.wl.xdg_toplevel); - if (term.wl.xdg_surface != NULL) - xdg_surface_destroy(term.wl.xdg_surface); - if (term.wl.shell != NULL) - xdg_wm_base_destroy(term.wl.shell); - if (term.wl.surface != NULL) - wl_surface_destroy(term.wl.surface); - if (term.wl.shm != NULL) - wl_shm_destroy(term.wl.shm); - if (term.wl.sub_compositor != NULL) - wl_subcompositor_destroy(term.wl.sub_compositor); - if (term.wl.compositor != NULL) - wl_compositor_destroy(term.wl.compositor); - if (term.wl.registry != NULL) - wl_registry_destroy(term.wl.registry); - if (term.wl.display != NULL) - wl_display_disconnect(term.wl.display); - if (term.kbd.xkb_compose_state != NULL) - xkb_compose_state_unref(term.kbd.xkb_compose_state); - if (term.kbd.xkb_compose_table != NULL) - xkb_compose_table_unref(term.kbd.xkb_compose_table); - if (term.kbd.xkb_keymap != NULL) - xkb_keymap_unref(term.kbd.xkb_keymap); - if (term.kbd.xkb_state != NULL) - xkb_state_unref(term.kbd.xkb_state); - if (term.kbd.xkb != NULL) - xkb_context_unref(term.kbd.xkb); - - free(term.vt.osc.data); - for (int row = 0; row < term.normal.num_rows; row++) - grid_row_free(term.normal.rows[row]); - free(term.normal.rows); - for (int row = 0; row < term.alt.num_rows; row++) - grid_row_free(term.alt.rows[row]); - free(term.alt.rows); - - free(term.window_title); - tll_free_and_free(term.window_title_stack, free); - - for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) - font_destroy(term.fonts[i]); - - free(term.search.buf); - - if (term.flash.fd != -1) - close(term.flash.fd); - if (term.blink.fd != -1) - close(term.blink.fd); - if (term.kbd.repeat.fd != -1) - close(term.kbd.repeat.fd); - - if (term.ptmx != -1) - close(term.ptmx); - - for (size_t i = 0; i < term.render.workers.count; i++) - thrd_join(term.render.workers.threads[i], NULL); - free(term.render.workers.threads); - cnd_destroy(&term.render.workers.cond); - mtx_destroy(&term.render.workers.lock); - sem_destroy(&term.render.workers.start); - sem_destroy(&term.render.workers.done); - assert(tll_length(term.render.workers.queue) == 0); - tll_free(term.render.workers.queue); - - if (term.slave > 0) { - /* Note: we've closed ptmx, so the slave *should* exit... */ - int status; - waitpid(term.slave, &status, 0); - - int child_ret = EXIT_FAILURE; - if (WIFEXITED(status)) { - child_ret = WEXITSTATUS(status); - LOG_DBG("slave exited with code %d", child_ret); - } else if (WIFSIGNALED(status)) { - child_ret = WTERMSIG(status); - LOG_WARN("slave exited with signal %d", child_ret); - } else { - LOG_WARN("slave exited for unknown reason (status = 0x%08x)", status); - } - - if (ret == EXIT_SUCCESS) - ret = child_ret; - } - + int child_ret = term_destroy(term); + wayl_destroy(wayl); + fdm_destroy(fdm); config_free(conf); - return ret; + + return ret == EXIT_SUCCESS ? child_ret : ret; } diff --git a/meson.build b/meson.build index 22987aa4..da91ff91 100644 --- a/meson.build +++ b/meson.build @@ -68,6 +68,7 @@ executable( 'config.c', 'config.h', 'commands.c', 'commands.h', 'csi.c', 'csi.h', + 'fdm.c', 'fdm.h', 'font.c', 'font.h', 'grid.c', 'grid.h', 'input.c', 'input.h', @@ -83,6 +84,7 @@ executable( 'tokenize.c', 'tokenize.h', 'tllist.h', 'vt.c', 'vt.h', + 'wayland.c', 'wayland.h', wl_proto_src + wl_proto_headers, version, dependencies: [threads, math, freetype, fontconfig, pixman, wayland_client, wayland_cursor, xkb], install: true) diff --git a/osc.c b/osc.c index 694547c8..e860edcd 100644 --- a/osc.c +++ b/osc.c @@ -30,14 +30,14 @@ osc_to_clipboard(struct terminal *term, const char *target, switch (*t) { case 'c': { char *copy = strdup(decoded); - if (!text_to_clipboard(term, copy, term->input_serial)) + if (!text_to_clipboard(term, copy, term->wl->input_serial)) free(copy); break; } case 'p': { char *copy = strdup(decoded); - if (!text_to_primary(term, copy, term->input_serial)) + if (!text_to_primary(term, copy, term->wl->input_serial)) free(copy); break; } @@ -130,7 +130,7 @@ osc_from_clipboard(struct terminal *term, const char *source) switch (src) { case 'c': - text_from_clipboard(term, term->input_serial, &from_clipboard_cb, &ctx); + text_from_clipboard(term, term->wl->input_serial, &from_clipboard_cb, &ctx); break; case 'p': diff --git a/render.c b/render.c index ca127389..29f2ebf0 100644 --- a/render.c +++ b/render.c @@ -330,7 +330,8 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, raw + src_y * buf->stride, height * buf->stride); - wl_surface_damage_buffer(term->wl.surface, term->x_margin, dst_y, term->width - term->x_margin, height); + wl_surface_damage_buffer( + term->window->surface, term->x_margin, dst_y, term->width - term->x_margin, height); } } @@ -355,7 +356,8 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, raw + src_y * buf->stride, height * buf->stride); - wl_surface_damage_buffer(term->wl.surface, term->x_margin, dst_y, term->width - term->x_margin, height); + wl_surface_damage_buffer( + term->window->surface, term->x_margin, dst_y, term->width - term->x_margin, height); } } @@ -372,6 +374,7 @@ render_worker_thread(void *_ctx) struct render_worker_context *ctx = _ctx; struct terminal *term = ctx->term; const int my_id = ctx->my_id; + free(ctx); char proc_title[16]; snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id); @@ -438,8 +441,8 @@ grid_render(struct terminal *term) assert(term->width > 0); assert(term->height > 0); - struct buffer *buf = shm_get_buffer(term->wl.shm, term->width, term->height, 1 + term->render.workers.count); - wl_surface_attach(term->wl.surface, buf->wl_buf, 0, 0); + struct buffer *buf = shm_get_buffer(term->wl->shm, term->width, term->height, 1 + term->render.workers.count); + wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); pixman_image_t *pix = buf->pix; bool all_clean = tll_length(term->grid->scroll_damage) == 0; @@ -487,13 +490,13 @@ grid_render(struct terminal *term) {0, bmargin, term->width, bmargin_height}}); /* Bottom */ wl_surface_damage_buffer( - term->wl.surface, 0, 0, term->width, term->y_margin); + term->window->surface, 0, 0, term->width, term->y_margin); wl_surface_damage_buffer( - term->wl.surface, 0, 0, term->x_margin, term->height); + term->window->surface, 0, 0, term->x_margin, term->height); wl_surface_damage_buffer( - term->wl.surface, rmargin, 0, rmargin_width, term->height); + term->window->surface, rmargin, 0, rmargin_width, term->height); wl_surface_damage_buffer( - term->wl.surface, 0, bmargin, term->width, bmargin_height); + term->window->surface, 0, bmargin, term->width, bmargin_height); /* Force a full grid refresh */ term_damage_view(term); @@ -514,7 +517,7 @@ grid_render(struct terminal *term) render_cell(term, pix, cell, at.col, at.row, false); wl_surface_damage_buffer( - term->wl.surface, + term->window->surface, term->x_margin + at.col * term->cell_width, term->y_margin + at.row * term->cell_height, term->cell_width, term->cell_height); @@ -577,7 +580,7 @@ grid_render(struct terminal *term) all_clean = false; wl_surface_damage_buffer( - term->wl.surface, + term->window->surface, term->x_margin, term->y_margin + r * term->cell_height, term->width - term->x_margin, term->cell_height); } @@ -600,7 +603,7 @@ grid_render(struct terminal *term) all_clean = false; wl_surface_damage_buffer( - term->wl.surface, + term->window->surface, term->x_margin, term->y_margin + r * term->cell_height, term->width - term->x_margin, term->cell_height); } @@ -682,7 +685,7 @@ grid_render(struct terminal *term) term, pix, cell, term->cursor.col, view_aligned_row, true); wl_surface_damage_buffer( - term->wl.surface, + term->window->surface, term->x_margin + term->cursor.col * term->cell_width, term->y_margin + view_aligned_row * term->cell_height, cols_updated * term->cell_width, term->cell_height); @@ -702,18 +705,18 @@ grid_render(struct terminal *term) 1, &(pixman_rectangle16_t){0, 0, term->width, term->height}); wl_surface_damage_buffer( - term->wl.surface, 0, 0, term->width, term->height); + term->window->surface, 0, 0, term->width, term->height); } assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows); assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows); - assert(term->render.frame_callback == NULL); - term->render.frame_callback = wl_surface_frame(term->wl.surface); - wl_callback_add_listener(term->render.frame_callback, &frame_listener, term); + assert(term->window->frame_callback == NULL); + term->window->frame_callback = wl_surface_frame(term->window->surface); + wl_callback_add_listener(term->window->frame_callback, &frame_listener, term); - wl_surface_set_buffer_scale(term->wl.surface, term->scale); - wl_surface_commit(term->wl.surface); + wl_surface_set_buffer_scale(term->window->surface, term->scale); + wl_surface_commit(term->window->surface); #if TIME_FRAME_RENDERING struct timeval end_time; @@ -731,16 +734,16 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da { struct terminal *term = data; - assert(term->render.frame_callback == wl_callback); + assert(term->window->frame_callback == wl_callback); wl_callback_destroy(wl_callback); - term->render.frame_callback = NULL; + term->window->frame_callback = NULL; grid_render(term); } void render_search_box(struct terminal *term) { - assert(term->wl.search_sub_surface != NULL); + assert(term->window->search_sub_surface != NULL); /* TODO: at least sway allows the subsurface to extend outside the * main window. Do we want that? */ @@ -749,7 +752,7 @@ render_search_box(struct terminal *term) const int width = 2 * margin + max(20, term->search.len) * term->cell_width; const int height = 2 * margin + 1 * term->cell_height; - struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1); + struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, 1); /* Background - yellow on empty/match, red on mismatch */ pixman_color_t color = color_hex_to_pixman( @@ -788,13 +791,13 @@ render_search_box(struct terminal *term) draw_bar(term, buf->pix, font, &fg, x, y); wl_subsurface_set_position( - term->wl.search_sub_surface, + term->window->search_sub_surface, term->width - width - margin, term->height - height - margin); - wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height); - wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0); - wl_surface_set_buffer_scale(term->wl.search_surface, scale); - wl_surface_commit(term->wl.search_surface); + wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height); + wl_surface_attach(term->window->search_surface, buf->wl_buf, 0, 0); + wl_surface_set_buffer_scale(term->window->search_surface, scale); + wl_surface_commit(term->window->search_surface); } static void @@ -826,7 +829,7 @@ void render_resize(struct terminal *term, int width, int height) { int scale = -1; - tll_foreach(term->wl.on_outputs, it) { + tll_foreach(term->window->on_outputs, it) { if (it->item->scale > scale) scale = it->item->scale; } @@ -947,67 +950,12 @@ render_resize(struct terminal *term, int width, int height) void render_set_title(struct terminal *term, const char *title) { - xdg_toplevel_set_title(term->wl.xdg_toplevel, title); -} - -bool -render_reload_cursor_theme(struct terminal *term) -{ - if (term->wl.pointer.size == 0) - return true; - - if (term->wl.pointer.theme != NULL) { - wl_cursor_theme_destroy(term->wl.pointer.theme); - term->wl.pointer.theme = NULL; - term->wl.pointer.cursor = NULL; - } - - LOG_DBG("reloading cursor theme: %s@%d", - term->wl.pointer.theme_name, term->wl.pointer.size); - - term->wl.pointer.theme = wl_cursor_theme_load( - term->wl.pointer.theme_name, term->wl.pointer.size * term->scale, term->wl.shm); - if (term->wl.pointer.theme == NULL) { - LOG_ERR("failed to load cursor theme"); - return false; - } - - term->wl.pointer.cursor = wl_cursor_theme_get_cursor( - term->wl.pointer.theme, "left_ptr"); - assert(term->wl.pointer.cursor != NULL); - render_update_cursor_surface(term); - - return true; -} - -void -render_update_cursor_surface(struct terminal *term) -{ - if (term->wl.pointer.cursor == NULL) - return; - - const int scale = term->scale; - wl_surface_set_buffer_scale(term->wl.pointer.surface, scale); - - struct wl_cursor_image *image = term->wl.pointer.cursor->images[0]; - - wl_surface_attach( - term->wl.pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); - - wl_pointer_set_cursor( - term->wl.pointer.pointer, term->wl.pointer.serial, - term->wl.pointer.surface, - image->hotspot_x / scale, image->hotspot_y / scale); - - wl_surface_damage_buffer( - term->wl.pointer.surface, 0, 0, INT32_MAX, INT32_MAX); - - wl_surface_commit(term->wl.pointer.surface); + xdg_toplevel_set_title(term->window->xdg_toplevel, title); } void render_refresh(struct terminal *term) { - if (term->render.frame_callback == NULL) + if (term->window->frame_callback == NULL) grid_render(term); } diff --git a/render.h b/render.h index c2fa489e..1c6bbd7f 100644 --- a/render.h +++ b/render.h @@ -11,8 +11,6 @@ void render_set_title(struct terminal *term, const char *title); void render_refresh(struct terminal *term); void render_search_box(struct terminal *term); -bool render_reload_cursor_theme(struct terminal *term); -void render_update_cursor_surface(struct terminal *term); struct render_worker_context { int my_id; diff --git a/search.c b/search.c index 45ca675e..134c3527 100644 --- a/search.c +++ b/search.c @@ -19,8 +19,8 @@ static void search_cancel_keep_selection(struct terminal *term) { - wl_surface_attach(term->wl.search_surface, NULL, 0, 0); - wl_surface_commit(term->wl.search_surface); + wl_surface_attach(term->window->search_surface, NULL, 0, 0); + wl_surface_commit(term->window->search_surface); free(term->search.buf); term->search.buf = NULL; @@ -294,13 +294,13 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask { LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods); - const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl; - const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt; - //const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift; - //const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta; + const xkb_mod_mask_t ctrl = 1 << term->wl->kbd.mod_ctrl; + const xkb_mod_mask_t alt = 1 << term->wl->kbd.mod_alt; + //const xkb_mod_mask_t shift = 1 << term->wl->kbd.mod_shift; + //const xkb_mod_mask_t meta = 1 << term->wl->kbd.mod_meta; enum xkb_compose_status compose_status = xkb_compose_state_get_status( - term->kbd.xkb_compose_state); + term->wl->kbd.xkb_compose_state); /* Cancel search */ if ((mods == 0 && sym == XKB_KEY_Escape) || @@ -317,7 +317,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask /* "Commit" search - copy selection to primary and cancel search */ else if (mods == 0 && sym == XKB_KEY_Return) { - selection_finalize(term, term->input_serial); + selection_finalize(term, term->wl->input_serial); search_cancel_keep_selection(term); return; } @@ -449,11 +449,11 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask if (compose_status == XKB_COMPOSE_COMPOSED) { count = xkb_compose_state_get_utf8( - term->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); - xkb_compose_state_reset(term->kbd.xkb_compose_state); + term->wl->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); + xkb_compose_state_reset(term->wl->kbd.xkb_compose_state); } else { count = xkb_state_key_get_utf8( - term->kbd.xkb_state, key, (char *)buf, sizeof(buf)); + term->wl->kbd.xkb_state, key, (char *)buf, sizeof(buf)); } const char *src = (const char *)buf; diff --git a/selection.c b/selection.c index d5b89dba..d2ed7354 100644 --- a/selection.c +++ b/selection.c @@ -22,7 +22,7 @@ selection_enabled(const struct terminal *term) { return term->mouse_tracking == MOUSE_NONE || - term->kbd.shift || + term->wl->kbd.shift || term->is_searching; } @@ -319,7 +319,7 @@ static void send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { - const struct clipboard *clipboard + const struct wl_clipboard *clipboard = wl_data_source_get_user_data(wl_data_source); assert(clipboard != NULL); @@ -346,7 +346,7 @@ send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, static void cancelled(void *data, struct wl_data_source *wl_data_source) { - struct clipboard *clipboard = wl_data_source_get_user_data(wl_data_source); + struct wl_clipboard *clipboard = wl_data_source_get_user_data(wl_data_source); assert(clipboard->data_source == wl_data_source); wl_data_source_destroy(clipboard->data_source); @@ -386,7 +386,7 @@ primary_send(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, const char *mime_type, int32_t fd) { - const struct primary *primary + const struct wl_primary *primary = zwp_primary_selection_source_v1_get_user_data(zwp_primary_selection_source_v1); assert(primary != NULL); @@ -414,7 +414,7 @@ static void primary_cancelled(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1) { - struct primary *primary = zwp_primary_selection_source_v1_get_user_data( + struct wl_primary *primary = zwp_primary_selection_source_v1_get_user_data( zwp_primary_selection_source_v1); //assert(primary->data_source == zwp_primary_selection_source_v1); @@ -434,12 +434,12 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s bool text_to_clipboard(struct terminal *term, char *text, uint32_t serial) { - if (term->selection.clipboard.data_source != NULL) { - /* Kill previous data source */ - struct clipboard *clipboard = &term->selection.clipboard; + struct wl_clipboard *clipboard = &term->wl->clipboard; + if (term->wl->clipboard.data_source != NULL) { + /* Kill previous data source */ assert(clipboard->serial != 0); - wl_data_device_set_selection(term->wl.data_device, NULL, clipboard->serial); + wl_data_device_set_selection(term->wl->data_device, NULL, clipboard->serial); wl_data_source_destroy(clipboard->data_source); free(clipboard->text); @@ -447,10 +447,8 @@ text_to_clipboard(struct terminal *term, char *text, uint32_t serial) clipboard->serial = 0; } - struct clipboard *clipboard = &term->selection.clipboard; - clipboard->data_source - = wl_data_device_manager_create_data_source(term->wl.data_device_manager); + = wl_data_device_manager_create_data_source(term->wl->data_device_manager); if (clipboard->data_source == NULL) { LOG_ERR("failed to create clipboard data source"); @@ -462,7 +460,7 @@ text_to_clipboard(struct terminal *term, char *text, uint32_t serial) /* Configure source */ wl_data_source_offer(clipboard->data_source, "text/plain;charset=utf-8"); wl_data_source_add_listener(clipboard->data_source, &data_source_listener, term); - wl_data_device_set_selection(term->wl.data_device, clipboard->data_source, serial); + wl_data_device_set_selection(term->wl->data_device, clipboard->data_source, serial); wl_data_source_set_user_data(clipboard->data_source, clipboard); /* Needed when sending the selection to other client */ @@ -484,7 +482,7 @@ text_from_clipboard(struct terminal *term, uint32_t serial, void (*cb)(const char *data, size_t size, void *user), void *user) { - struct clipboard *clipboard = &term->selection.clipboard; + struct wl_clipboard *clipboard = &term->wl->clipboard; if (clipboard->data_offer == NULL) return; @@ -501,7 +499,7 @@ text_from_clipboard(struct terminal *term, uint32_t serial, /* Give write-end of pipe to other client */ wl_data_offer_receive( clipboard->data_offer, "text/plain;charset=utf-8", write_fd); - wl_display_roundtrip(term->wl.display); + wl_display_roundtrip(term->wl->display); /* Don't keep our copy of the write-end open (or we'll never get EOF) */ close(write_fd); @@ -541,7 +539,7 @@ from_clipboard_cb(const char *data, size_t size, void *user) void selection_from_clipboard(struct terminal *term, uint32_t serial) { - struct clipboard *clipboard = &term->selection.clipboard; + struct wl_clipboard *clipboard = &term->wl->clipboard; if (clipboard->data_offer == NULL) return; @@ -557,17 +555,18 @@ selection_from_clipboard(struct terminal *term, uint32_t serial) bool text_to_primary(struct terminal *term, char *text, uint32_t serial) { - if (term->wl.primary_selection_device_manager == NULL) + if (term->wl->primary_selection_device_manager == NULL) return false; + struct wl_primary *primary = &term->wl->primary; + /* TODO: somehow share code with the clipboard equivalent */ - if (term->selection.primary.data_source != NULL) { + if (term->wl->primary.data_source != NULL) { /* Kill previous data source */ - struct primary *primary = &term->selection.primary; assert(primary->serial != 0); zwp_primary_selection_device_v1_set_selection( - term->wl.primary_selection_device, NULL, primary->serial); + term->wl->primary_selection_device, NULL, primary->serial); zwp_primary_selection_source_v1_destroy(primary->data_source); free(primary->text); @@ -575,11 +574,9 @@ text_to_primary(struct terminal *term, char *text, uint32_t serial) primary->serial = 0; } - struct primary *primary = &term->selection.primary; - primary->data_source = zwp_primary_selection_device_manager_v1_create_source( - term->wl.primary_selection_device_manager); + term->wl->primary_selection_device_manager); if (primary->data_source == NULL) { LOG_ERR("failed to create clipboard data source"); @@ -592,7 +589,7 @@ text_to_primary(struct terminal *term, char *text, uint32_t serial) /* Configure source */ zwp_primary_selection_source_v1_offer(primary->data_source, "text/plain;charset=utf-8"); zwp_primary_selection_source_v1_add_listener(primary->data_source, &primary_selection_source_listener, term); - zwp_primary_selection_device_v1_set_selection(term->wl.primary_selection_device, primary->data_source, serial); + zwp_primary_selection_device_v1_set_selection(term->wl->primary_selection_device, primary->data_source, serial); zwp_primary_selection_source_v1_set_user_data(primary->data_source, primary); /* Needed when sending the selection to other client */ @@ -603,7 +600,7 @@ text_to_primary(struct terminal *term, char *text, uint32_t serial) void selection_to_primary(struct terminal *term, uint32_t serial) { - if (term->wl.primary_selection_device_manager == NULL) + if (term->wl->primary_selection_device_manager == NULL) return; /* Get selection as a string */ @@ -617,10 +614,10 @@ text_from_primary( struct terminal *term, void (*cb)(const char *data, size_t size, void *user), void *user) { - if (term->wl.primary_selection_device_manager == NULL) + if (term->wl->primary_selection_device_manager == NULL) return; - struct primary *primary = &term->selection.primary; + struct wl_primary *primary = &term->wl->primary; if (primary->data_offer == NULL) return; @@ -637,7 +634,7 @@ text_from_primary( /* Give write-end of pipe to other client */ zwp_primary_selection_offer_v1_receive( primary->data_offer, "text/plain;charset=utf-8", write_fd); - wl_display_roundtrip(term->wl.display); + wl_display_roundtrip(term->wl->display); /* Don't keep our copy of the write-end open (or we'll never get EOF) */ close(write_fd); @@ -670,10 +667,10 @@ text_from_primary( void selection_from_primary(struct terminal *term) { - if (term->wl.primary_selection_device_manager == NULL) + if (term->wl->primary_selection_device_manager == NULL) return; - struct clipboard *clipboard = &term->selection.clipboard; + struct wl_clipboard *clipboard = &term->wl->clipboard; if (clipboard->data_offer == NULL) return; @@ -745,8 +742,8 @@ selection(void *data, struct wl_data_device *wl_data_device, { /* Selection offer from other client */ - struct terminal *term = data; - struct clipboard *clipboard = &term->selection.clipboard; + struct wayland *wayl = data; + struct wl_clipboard *clipboard = &wayl->clipboard; if (clipboard->data_offer != NULL) wl_data_offer_destroy(clipboard->data_offer); @@ -794,8 +791,8 @@ primary_selection(void *data, { /* Selection offer from other client, for primary */ - struct terminal *term = data; - struct primary *primary = &term->selection.primary; + struct wayland *wayl = data; + struct wl_primary *primary = &wayl->primary; if (primary->data_offer != NULL) zwp_primary_selection_offer_v1_destroy(primary->data_offer); diff --git a/slave.c b/slave.c index 18fcf3ad..92571fde 100644 --- a/slave.c +++ b/slave.c @@ -13,7 +13,7 @@ #include "log.h" void -slave_spawn(int ptmx, char *const argv[], int err_fd) +slave_exec(int ptmx, char *const argv[], int err_fd) { int pts = -1; const char *pts_name = ptsname(ptmx); diff --git a/slave.h b/slave.h index 4161dbda..5ec59149 100644 --- a/slave.h +++ b/slave.h @@ -1,4 +1,4 @@ #pragma once #include -void slave_spawn(int ptmx, char *const argv[], int err_fd); +void slave_exec(int ptmx, char *const argv[], int err_fd); diff --git a/terminal.c b/terminal.c index 95a60682..9259cfd5 100644 --- a/terminal.c +++ b/terminal.c @@ -3,8 +3,13 @@ #include #include #include +#include +#include +#include +#include #include +#include #include #define LOG_MODULE "terminal" @@ -14,10 +19,601 @@ #include "render.h" #include "vt.h" #include "selection.h" +#include "config.h" +#include "tokenize.h" +#include "slave.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +static bool +fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + + if (events & EPOLLHUP) { + term->quit = true; + + if (!(events & EPOLLIN)) + return false; + } + + assert(events & EPOLLIN); + + uint8_t buf[24 * 1024]; + ssize_t count = read(term->ptmx, buf, sizeof(buf)); + + if (count < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read from pseudo terminal"); + return false; + } + + vt_from_slave(term, buf, count); + + /* + * We likely need to re-render. But, we don't want to + * do it immediately. Often, a single client operation + * is done through multiple writes. Many times, we're + * so fast that we render mid-operation frames. + * + * For example, we might end up rendering a frame + * where the client just erased a line, while in the + * next frame, the client wrote to the same line. This + * causes screen "flashes". + * + * Mitigate by always incuring a small delay before + * rendering the next frame. This gives the client + * some time to finish the operation (and thus gives + * us time to receive the last writes before doing any + * actual rendering). + * + * We incur this delay *every* time we receive + * input. To ensure we don't delay rendering + * indefinitely, we start a second timer that is only + * reset when we render. + * + * Note that when the client is producing data at a + * very high pace, we're rate limited by the wayland + * compositor anyway. The delay we introduce here only + * has any effect when the renderer is idle. + * + * TODO: this adds input latency. Can we somehow hint + * ourselves we just received keyboard input, and in + * this case *not* delay rendering? + */ + if (term->window->frame_callback == NULL) { + /* First timeout - reset each time we receive input. */ + timerfd_settime( + term->delayed_render_timer.lower_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, + NULL); + + /* Second timeout - only reset when we render. Set to one + * frame (assuming 60Hz) */ + if (!term->delayed_render_timer.is_armed) { + timerfd_settime( + term->delayed_render_timer.upper_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = 16666666}}, + NULL); + term->delayed_render_timer.is_armed = true; + } + } + + return !(events & EPOLLHUP); +} + +static bool +fdm_flash(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + uint64_t expiration_count; + ssize_t ret = read( + term->flash.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read flash timer"); + return false; + } + + LOG_DBG("flash timer expired %llu times", + (unsigned long long)expiration_count); + + term->flash.active = false; + term_damage_view(term); + render_refresh(term); + return true; +} + +static bool +fdm_blink(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + uint64_t expiration_count; + ssize_t ret = read( + term->blink.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read blink timer"); + return false; + } + + LOG_DBG("blink timer expired %llu times", + (unsigned long long)expiration_count); + + term->blink.state = term->blink.state == BLINK_ON + ? BLINK_OFF : BLINK_ON; + + /* Scan all visible cells and mark rows with blinking cells dirty */ + for (int r = 0; r < term->rows; r++) { + struct row *row = grid_row_in_view(term->grid, r); + for (int col = 0; col < term->cols; col++) { + struct cell *cell = &row->cells[col]; + + if (cell->attrs.blink) { + cell->attrs.clean = 0; + row->dirty = true; + } + } + } + + render_refresh(term); + return true; +} + +static bool +fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + assert(term->delayed_render_timer.is_armed); + + uint64_t unused; + ssize_t ret1 = 0; + ssize_t ret2 = 0; + + if (fd == term->delayed_render_timer.lower_fd) + ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused)); + if (fd == term->delayed_render_timer.upper_fd) + ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused)); + + if ((ret1 < 0 || ret2 < 0) && errno != EAGAIN) + LOG_ERRNO("failed to read timeout timer"); + else if (ret1 > 0 || ret2 > 0) { + render_refresh(term); + + /* Reset timers */ + term->delayed_render_timer.is_armed = false; + timerfd_settime(term->delayed_render_timer.lower_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); + timerfd_settime(term->delayed_render_timer.upper_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); + } else + assert(false); + + return true; +} + +struct terminal * +term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, + int argc, char *const *argv) +{ + int ptmx = -1; + int flash_fd = -1; + int blink_fd = -1; + int delay_lower_fd = -1; + int delay_upper_fd = -1; + + struct terminal *term = NULL; + + if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) == -1) { + LOG_ERRNO("failed to open PTY"); + goto close_fds; + } + if ((flash_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) { + LOG_ERRNO("failed to create flash timer FD"); + goto close_fds; + } + if ((blink_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) { + LOG_ERRNO("failed to create blink timer FD"); + goto close_fds; + } + if ((delay_lower_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1 || + (delay_upper_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) + { + LOG_ERRNO("failed to create delayed rendering timer FDs"); + goto close_fds; + } + + term = malloc(sizeof(*term)); + *term = (struct terminal) { + .fdm = fdm, + .quit = false, + .ptmx = ptmx, + .cursor_keys_mode = CURSOR_KEYS_NORMAL, + .keypad_keys_mode = KEYPAD_NUMERICAL, + .auto_margin = true, + .window_title_stack = tll_init(), + .scale = 1, + .flash = {.fd = flash_fd}, + .blink = {.fd = blink_fd}, + .vt = { + .state = 1, /* STATE_GROUND */ + }, + .colors = { + .default_fg = conf->colors.fg, + .default_bg = conf->colors.bg, + .default_table = { + conf->colors.regular[0], + conf->colors.regular[1], + conf->colors.regular[2], + conf->colors.regular[3], + conf->colors.regular[4], + conf->colors.regular[5], + conf->colors.regular[6], + conf->colors.regular[7], + + conf->colors.bright[0], + conf->colors.bright[1], + conf->colors.bright[2], + conf->colors.bright[3], + conf->colors.bright[4], + conf->colors.bright[5], + conf->colors.bright[6], + conf->colors.bright[7], + }, + .alpha = conf->colors.alpha, + }, + .default_cursor_style = conf->cursor.style, + .cursor_style = conf->cursor.style, + .default_cursor_color = { + .text = conf->cursor.color.text, + .cursor = conf->cursor.color.cursor, + }, + .cursor_color = { + .text = conf->cursor.color.text, + .cursor = conf->cursor.color.cursor, + }, + .selection = { + .start = {-1, -1}, + .end = {-1, -1}, + }, + .normal = {.damage = tll_init(), .scroll_damage = tll_init()}, + .alt = {.damage = tll_init(), .scroll_damage = tll_init()}, + .grid = &term->normal, + .wl = wayl, + .render = { + .scrollback_lines = conf->scrollback_lines, + .workers = { + .count = conf->render_worker_count, + .queue = tll_init(), + }, + }, + .delayed_render_timer = { + .is_armed = false, + .lower_fd = delay_lower_fd, + .upper_fd = delay_upper_fd, + }, + }; + + LOG_INFO("using %zu rendering threads", term->render.workers.count); + + + /* Initialize 'current' colors from the default colors */ + term->colors.fg = term->colors.default_fg; + term->colors.bg = term->colors.default_bg; + + /* Initialize the 256 gray-scale color cube */ + { + /* First 16 entries have already been initialized from conf */ + for (size_t r = 0; r < 6; r++) { + for (size_t g = 0; g < 6; g++) { + for (size_t b = 0; b < 6; b++) { + term->colors.default_table[16 + r * 6 * 6 + g * 6 + b] + = r * 51 << 16 | g * 51 << 8 | b * 51; + } + } + } + + for (size_t i = 0; i < 24; i++) + term->colors.default_table[232 + i] = i * 11 << 16 | i * 11 << 8 | i * 11; + + memcpy(term->colors.table, term->colors.default_table, sizeof(term->colors.table)); + } + + sem_init(&term->render.workers.start, 0, 0); + sem_init(&term->render.workers.done, 0, 0); + mtx_init(&term->render.workers.lock, mtx_plain); + cnd_init(&term->render.workers.cond); + + term->render.workers.threads = calloc( + term->render.workers.count, sizeof(term->render.workers.threads[0])); + + for (size_t i = 0; i < term->render.workers.count; i++) { + struct render_worker_context *ctx = malloc(sizeof(*ctx)); + *ctx = (struct render_worker_context) { + .term = term, + .my_id = 1 + i, + }; + thrd_create(&term->render.workers.threads[i], &render_worker_thread, ctx); + } + + font_list_t font_names = tll_init(); + tll_foreach(conf->fonts, it) + tll_push_back(font_names, it->item); + + if ((term->fonts[0] = font_from_name(font_names, "")) == NULL) { + tll_free(font_names); + goto err; + } + + term->fonts[1] = font_from_name(font_names, "style=bold"); + term->fonts[2] = font_from_name(font_names, "style=italic"); + term->fonts[3] = font_from_name(font_names, "style=bold italic"); + + tll_free(font_names); + + { + FT_Face ft_face = term->fonts[0]->face; + int max_x_advance = ft_face->size->metrics.max_advance / 64; + int height = ft_face->size->metrics.height / 64; + int descent = ft_face->size->metrics.descender / 64; + int ascent = ft_face->size->metrics.ascender / 64; + + term->fextents.height = height * term->fonts[0]->pixel_size_fixup; + term->fextents.descent = -descent * term->fonts[0]->pixel_size_fixup; + term->fextents.ascent = ascent * term->fonts[0]->pixel_size_fixup; + term->fextents.max_x_advance = max_x_advance * term->fonts[0]->pixel_size_fixup; + + LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d", + height, descent, ascent, max_x_advance); + } + + term->cell_width = (int)ceil(term->fextents.max_x_advance); + term->cell_height = (int)ceil(term->fextents.height); + LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height); + + term->window = wayl_win_init(wayl); + if (term->window == NULL) + goto err; + + term_set_window_title(term, "foot"); + + unsigned width = conf->width; + unsigned height = conf->height; + + if (width == -1) { + assert(height == -1); + width = 80 * term->cell_width; + height = 24 * term->cell_height; + } + + width = max(width, term->cell_width); + height = max(height, term->cell_height); + render_resize(term, width, height); + + { + int fork_pipe[2]; + if (pipe2(fork_pipe, O_CLOEXEC) < 0) { + LOG_ERRNO("failed to create pipe"); + goto err; + } + + term->slave = fork(); + switch (term->slave) { + case -1: + LOG_ERRNO("failed to fork"); + close(fork_pipe[0]); + close(fork_pipe[1]); + goto err; + + case 0: + /* Child */ + close(fork_pipe[0]); /* Close read end */ + + char **_shell_argv = NULL; + char *const *shell_argv = argv; + + if (argc == 0) { + if (!tokenize_cmdline(conf->shell, &_shell_argv)) { + (void)!write(fork_pipe[1], &errno, sizeof(errno)); + _exit(0); + } + shell_argv = _shell_argv; + } + + slave_exec(ptmx, shell_argv, fork_pipe[1]); + assert(false); + break; + + default: { + close(fork_pipe[1]); /* Close write end */ + LOG_DBG("slave has PID %d", term->slave); + + int _errno; + static_assert(sizeof(errno) == sizeof(_errno), "errno size mismatch"); + + ssize_t ret = read(fork_pipe[0], &_errno, sizeof(_errno)); + close(fork_pipe[0]); + + if (ret < 0) { + LOG_ERRNO("failed to read from pipe"); + goto err; + } else if (ret == sizeof(_errno)) { + LOG_ERRNO( + "%s: failed to execute", argc == 0 ? conf->shell : argv[0]); + goto err; + } else + LOG_DBG("%s: successfully started", conf->shell); + break; + } + } + } + + /* Read logic requires non-blocking mode */ + { + int fd_flags = fcntl(ptmx, F_GETFL); + if (fd_flags == -1) { + LOG_ERRNO("failed to set non blocking mode on PTY master"); + goto err; + } + + if (fcntl(ptmx, F_SETFL, fd_flags | O_NONBLOCK) == -1) { + LOG_ERRNO("failed to set non blocking mode on PTY master"); + goto err; + } + } + + if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term)) { + LOG_ERR("failed to add ptmx to FDM"); + goto err; + } + + if (!fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term)) { + LOG_ERR("failed to add flash timer FD to FDM"); + goto err; + } + + if (!fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term)) { + LOG_ERR("failed to add blink tiemr FD to FDM"); + goto err; + } + + if (!fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || + !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term)) + { + LOG_ERR("failed to add delayed rendering timer FDs to FDM"); + goto err; + } + + wayl->term = term; + return term; + +err: + term_destroy(term); + return NULL; + +close_fds: + if (ptmx != -1) + close(ptmx); + if (flash_fd != -1) + close(flash_fd); + if (blink_fd != -1) + close(blink_fd); + if (delay_lower_fd != -1) + close(delay_lower_fd); + if (delay_upper_fd != -1) + close(delay_upper_fd); + assert(term == NULL); + return NULL; +} + +int +term_destroy(struct terminal *term) +{ + if (term == NULL) + return 0; + + wayl_win_destroy(term->window); + + if (term->delayed_render_timer.lower_fd != -1) { + fdm_del(term->fdm, term->delayed_render_timer.lower_fd); + close(term->delayed_render_timer.lower_fd); + } + + if (term->delayed_render_timer.upper_fd != -1) { + fdm_del(term->fdm, term->delayed_render_timer.upper_fd); + close(term->delayed_render_timer.upper_fd); + } + + mtx_lock(&term->render.workers.lock); + assert(tll_length(term->render.workers.queue) == 0); + for (size_t i = 0; i < term->render.workers.count; i++) { + sem_post(&term->render.workers.start); + tll_push_back(term->render.workers.queue, -2); + } + cnd_broadcast(&term->render.workers.cond); + mtx_unlock(&term->render.workers.lock); + free(term->vt.osc.data); + for (int row = 0; row < term->normal.num_rows; row++) + grid_row_free(term->normal.rows[row]); + free(term->normal.rows); + for (int row = 0; row < term->alt.num_rows; row++) + grid_row_free(term->alt.rows[row]); + free(term->alt.rows); + + free(term->window_title); + tll_free_and_free(term->window_title_stack, free); + + for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++) + font_destroy(term->fonts[i]); + + free(term->search.buf); + + if (term->flash.fd != -1) { + fdm_del(term->fdm, term->flash.fd); + close(term->flash.fd); + } + + if (term->blink.fd != -1) { + fdm_del(term->fdm, term->blink.fd); + close(term->blink.fd); + } + + if (term->ptmx != -1) { + fdm_del(term->fdm, term->ptmx); + close(term->ptmx); + } + + for (size_t i = 0; i < term->render.workers.count; i++) + thrd_join(term->render.workers.threads[i], NULL); + free(term->render.workers.threads); + cnd_destroy(&term->render.workers.cond); + mtx_destroy(&term->render.workers.lock); + sem_destroy(&term->render.workers.start); + sem_destroy(&term->render.workers.done); + assert(tll_length(term->render.workers.queue) == 0); + tll_free(term->render.workers.queue); + + int ret = EXIT_SUCCESS; + + if (term->slave > 0) { + /* Note: we've closed ptmx, so the slave *should* exit... */ + int status; + waitpid(term->slave, &status, 0); + + int child_ret = EXIT_FAILURE; + if (WIFEXITED(status)) { + child_ret = WEXITSTATUS(status); + LOG_DBG("slave exited with code %d", child_ret); + } else if (WIFSIGNALED(status)) { + child_ret = WTERMSIG(status); + LOG_WARN("slave exited with signal %d", child_ret); + } else { + LOG_WARN("slave exited for unknown reason (status = 0x%08x)", status); + } + + ret = child_ret; + } + + free(term); + return ret; +} + void term_reset(struct terminal *term, bool hard) { @@ -480,7 +1076,7 @@ void term_mouse_down(struct terminal *term, int button, int row, int col, bool shift, bool alt, bool ctrl) { - if (term->kbd.shift) { + if (term->wl->kbd.shift) { /* "raw" mouse mode */ return; } @@ -513,7 +1109,7 @@ void term_mouse_up(struct terminal *term, int button, int row, int col, bool shift, bool alt, bool ctrl) { - if (term->kbd.shift) { + if (term->wl->kbd.shift) { /* "raw" mouse mode */ return; } @@ -551,7 +1147,7 @@ void term_mouse_motion(struct terminal *term, int button, int row, int col, bool shift, bool alt, bool ctrl) { - if (term->kbd.shift) { + if (term->wl->kbd.shift) { /* "raw" mouse mode */ return; } diff --git a/terminal.h b/terminal.h index 2a014d90..b16b9b51 100644 --- a/terminal.h +++ b/terminal.h @@ -8,83 +8,15 @@ #include #include -#include -#include -#include -#include - +//#include "config.h" +#include "fdm.h" #include "font.h" #include "tllist.h" +#include "wayland.h" #define likely(c) __builtin_expect(!!(c), 1) #define unlikely(c) __builtin_expect(!!(c), 0) -struct monitor { - struct terminal *term; - struct wl_output *output; - struct zxdg_output_v1 *xdg; - char *name; - - int x; - int y; - - int width_mm; - int height_mm; - - int width_px; - int height_px; - - int scale; - float refresh; -}; - -struct wayland { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_subcompositor *sub_compositor; - struct wl_shm *shm; - - struct wl_seat *seat; - struct wl_keyboard *keyboard; - struct zxdg_output_manager_v1 *xdg_output_manager; - - /* Clipboard */ - struct wl_data_device_manager *data_device_manager; - struct wl_data_device *data_device; - struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; - struct zwp_primary_selection_device_v1 *primary_selection_device; - - /* Cursor */ - struct { - struct wl_pointer *pointer; - uint32_t serial; - - struct wl_surface *surface; - struct wl_cursor_theme *theme; - struct wl_cursor *cursor; - int size; - char *theme_name; - } pointer; - - /* Main window */ - struct wl_surface *surface; - struct xdg_wm_base *shell; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; - - struct zxdg_decoration_manager_v1 *xdg_decoration_manager; - struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; - - /* Scrollback search */ - struct wl_surface *search_surface; - struct wl_subsurface *search_sub_surface; - - bool have_argb8888; - tll(struct monitor) monitors; /* All available outputs */ - tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */ -}; - struct rgb { float r, g, b; }; /* @@ -190,33 +122,6 @@ struct vt { struct attributes saved_attrs; }; -struct kbd { - struct xkb_context *xkb; - struct xkb_keymap *xkb_keymap; - struct xkb_state *xkb_state; - struct xkb_compose_table *xkb_compose_table; - struct xkb_compose_state *xkb_compose_state; - struct { - int fd; - - bool dont_re_repeat; - int32_t delay; - int32_t rate; - uint32_t key; - } repeat; - - xkb_mod_index_t mod_shift; - xkb_mod_index_t mod_alt; - xkb_mod_index_t mod_ctrl; - xkb_mod_index_t mod_meta; - - /* Enabled modifiers */ - bool shift; - bool alt; - bool ctrl; - bool meta; -}; - enum cursor_keys { CURSOR_KEYS_DONTCARE, CURSOR_KEYS_NORMAL, CURSOR_KEYS_APPLICATION}; enum keypad_keys { KEYPAD_DONTCARE, KEYPAD_NUMERICAL, KEYPAD_APPLICATION }; enum charset { CHARSET_ASCII, CHARSET_GRAPHIC }; @@ -238,23 +143,11 @@ enum mouse_reporting { MOUSE_URXVT, /* ?1015h */ }; -struct clipboard { - struct wl_data_source *data_source; - struct wl_data_offer *data_offer; - char *text; - uint32_t serial; -}; - -struct primary { - struct zwp_primary_selection_source_v1 *data_source; - struct zwp_primary_selection_offer_v1 *data_offer; - char *text; - uint32_t serial; -}; - enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; struct terminal { + struct fdm *fdm; + pid_t slave; int ptmx; bool quit; @@ -288,7 +181,6 @@ struct terminal { } blink; struct vt vt; - struct kbd kbd; int scale; int width; /* pixels */ @@ -314,19 +206,6 @@ struct terminal { uint32_t default_table[256]; } colors; - struct { - int col; - int row; - int button; - - int count; - int last_button; - struct timeval last_time; - - /* We used a discrete axis event in the current pointer frame */ - bool have_discrete; - } mouse; - struct coord cursor; struct coord saved_cursor; struct coord alt_saved_cursor; @@ -342,12 +221,9 @@ struct terminal { uint32_t cursor; } cursor_color; - uint32_t input_serial; struct { struct coord start; struct coord end; - struct clipboard clipboard; - struct primary primary; } selection; bool is_searching; @@ -376,10 +252,11 @@ struct terminal { int max_x_advance; } fextents; - struct wayland wl; + struct wayland *wl; + struct wl_window *window; + struct { int scrollback_lines; - struct wl_callback *frame_callback; struct { size_t count; @@ -403,8 +280,21 @@ struct terminal { bool was_flashing; /* Flash was active last time we rendered */ bool was_searching; } render; + + /* Temporary: for FDM */ + struct { + bool is_armed; + int lower_fd; + int upper_fd; + } delayed_render_timer; }; +struct config; +struct terminal *term_init( + const struct config *conf, struct fdm *fdm, struct wayland *wayl, + int argc, char *const *argv); +int term_destroy(struct terminal *term); + void term_reset(struct terminal *term, bool hard); void term_damage_rows(struct terminal *term, int start, int end); diff --git a/wayland.c b/wayland.c new file mode 100644 index 00000000..2fcf2340 --- /dev/null +++ b/wayland.c @@ -0,0 +1,776 @@ +#include "wayland.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define LOG_MODULE "wayland" +#define LOG_ENABLE_DBG 0 +#include "log.h" + +#include "tllist.h" +#include "terminal.h" +#include "input.h" +#include "render.h" +#include "selection.h" + +#define min(x, y) ((x) < (y) ? (x) : (y)) +#define max(x, y) ((x) > (y) ? (x) : (y)) + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct wayland *wayl = data; + if (format == WL_SHM_FORMAT_ARGB8888) + wayl->have_argb8888 = true; +} + +static const struct wl_shm_listener shm_listener = { + .format = &shm_format, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + LOG_DBG("wm base ping"); + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = &xdg_wm_base_ping, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + struct wayland *wayl = data; + + if (wayl->keyboard != NULL) { + wl_keyboard_release(wayl->keyboard); + wayl->keyboard = NULL; + } + + if (wayl->pointer.pointer != NULL) { + wl_pointer_release(wayl->pointer.pointer); + wayl->pointer.pointer = NULL; + } + + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + wayl->keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(wayl->keyboard, &keyboard_listener, wayl); + } + + if (caps & WL_SEAT_CAPABILITY_POINTER) { + wayl->pointer.pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(wayl->pointer.pointer, &pointer_listener, wayl); + } +} + +static void +seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + +static void +output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, + int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) +{ + struct monitor *mon = data; + mon->width_mm = physical_width; + mon->height_mm = physical_height; +} + +static void +output_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) +{ + if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) + return; + + struct monitor *mon = data; + mon->refresh = (float)refresh / 1000; +} + +static void +output_done(void *data, struct wl_output *wl_output) +{ +} + +static void +output_scale(void *data, struct wl_output *wl_output, int32_t factor) +{ + struct monitor *mon = data; + + mon->scale = factor; + + struct terminal *term = mon->wayl->term; + if (term != NULL) { + render_resize(term, term->width / term->scale, term->height / term->scale); + wayl_reload_cursor_theme(mon->wayl, term); + } +} + +static const struct wl_output_listener output_listener = { + .geometry = &output_geometry, + .mode = &output_mode, + .done = &output_done, + .scale = &output_scale, +}; + +static void +xdg_output_handle_logical_position( + void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) +{ + struct monitor *mon = data; + mon->x = x; + mon->y = y; +} + +static void +xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, + int32_t width, int32_t height) +{ + struct monitor *mon = data; + mon->width_px = width; + mon->height_px = height; +} + +static void +xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) +{ +} + +static void +xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, + const char *name) +{ + struct monitor *mon = data; + mon->name = strdup(name); +} + +static void +xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, + const char *description) +{ +} + +static struct zxdg_output_v1_listener xdg_output_listener = { + .logical_position = xdg_output_handle_logical_position, + .logical_size = xdg_output_handle_logical_size, + .done = xdg_output_handle_done, + .name = xdg_output_handle_name, + .description = xdg_output_handle_description, +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + LOG_DBG("global: %s, version=%u", interface, version); + struct wayland *wayl = data; + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + wayl->compositor = wl_registry_bind( + wayl->registry, name, &wl_compositor_interface, 4); + } + + else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + wayl->sub_compositor = wl_registry_bind( + wayl->registry, name, &wl_subcompositor_interface, 1); + } + + else if (strcmp(interface, wl_shm_interface.name) == 0) { + wayl->shm = wl_registry_bind( + wayl->registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(wayl->shm, &shm_listener, wayl); + wl_display_roundtrip(wayl->display); + } + + else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wayl->shell = wl_registry_bind( + wayl->registry, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl); + } + + else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) + wayl->xdg_decoration_manager = wl_registry_bind( + wayl->registry, name, &zxdg_decoration_manager_v1_interface, 1); + + else if (strcmp(interface, wl_seat_interface.name) == 0) { + wayl->seat = wl_registry_bind( + wayl->registry, name, &wl_seat_interface, 5); + wl_seat_add_listener(wayl->seat, &seat_listener, wayl); + wl_display_roundtrip(wayl->display); + } + + else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + wayl->xdg_output_manager = wl_registry_bind( + wayl->registry, name, &zxdg_output_manager_v1_interface, min(version, 2)); + } + + else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind( + wayl->registry, name, &wl_output_interface, 3); + + tll_push_back( + wayl->monitors, ((struct monitor){.wayl = wayl, .output = output})); + + struct monitor *mon = &tll_back(wayl->monitors); + wl_output_add_listener(output, &output_listener, mon); + + mon->xdg = zxdg_output_manager_v1_get_xdg_output( + wayl->xdg_output_manager, mon->output); + zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon); + wl_display_roundtrip(wayl->display); + } + + else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + wayl->data_device_manager = wl_registry_bind( + wayl->registry, name, &wl_data_device_manager_interface, 1); + } + + else if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { + wayl->primary_selection_device_manager = wl_registry_bind( + wayl->registry, name, &zwp_primary_selection_device_manager_v1_interface, 1); + } +} + +static void +surface_enter(void *data, struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct wayland *wayl = data; + struct terminal *term = wayl_terminal_from_surface(wayl, wl_surface); + + tll_foreach(wayl->monitors, it) { + if (it->item.output == wl_output) { + LOG_DBG("mapped on %s", it->item.name); + tll_push_back(term->window->on_outputs, &it->item); + + /* Resize, since scale-to-use may have changed */ + render_resize(term, term->width / term->scale, term->height / term->scale); + wayl_reload_cursor_theme(wayl, term); + return; + } + } + + LOG_ERR("mapped on unknown output"); +} + +static void +surface_leave(void *data, struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct wayland *wayl = data; + struct terminal *term = wayl_terminal_from_surface(wayl, wl_surface); + + tll_foreach(term->window->on_outputs, it) { + if (it->item->output != wl_output) + continue; + + LOG_DBG("unmapped from %s", it->item->name); + tll_remove(term->window->on_outputs, it); + + /* Resize, since scale-to-use may have changed */ + render_resize(term, term->width / term->scale, term->height / term->scale); + wayl_reload_cursor_theme(wayl, term); + return; + } + + LOG_ERR("unmapped from unknown output"); +} + +static const struct wl_surface_listener surface_listener = { + .enter = &surface_enter, + .leave = &surface_leave, +}; + +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + LOG_DBG("xdg-toplevel: configure: %dx%d", width, height); + + if (width <= 0 || height <= 0) + return; + + struct wayland *wayl = data; + struct terminal *term = wayl_terminal_from_xdg_toplevel(wayl, xdg_toplevel); + render_resize(term, width, height); +} + +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + struct wayland *wayl = data; + struct terminal *term = wayl_terminal_from_xdg_toplevel(wayl, xdg_toplevel); + LOG_DBG("xdg-toplevel: close"); + + term->quit = true; + wl_display_roundtrip(wayl->display); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = &xdg_toplevel_configure, + .close = &xdg_toplevel_close, +}; + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, + uint32_t serial) +{ + //LOG_DBG("xdg-surface: configure"); + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = &xdg_surface_configure, +}; + +static void +xdg_toplevel_decoration_configure(void *data, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + switch (mode) { + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: + LOG_ERR("unimplemented: client-side decorations"); + break; + + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: + LOG_DBG("using server-side decorations"); + break; + + default: + LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode); + break; + } +} + +static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { + .configure = &xdg_toplevel_decoration_configure, +}; + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + LOG_WARN("global removed: %u", name); + assert(false); +} + +static const struct wl_registry_listener registry_listener = { + .global = &handle_global, + .global_remove = &handle_global_remove, +}; + +static bool +fdm_wayl(struct fdm *fdm, int fd, int events, void *data) +{ + struct wayland *wayl = data; + int event_count = wl_display_dispatch(wayl->display); + + if (events & EPOLLHUP) { + LOG_ERR("disconnected from Wayland"); + return false; + } + + return event_count != -1 && !wayl->term->quit; +} + +static bool +fdm_repeat(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct wayland *wayl = data; + uint64_t expiration_count; + ssize_t ret = read( + wayl->kbd.repeat.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read repeat key from repeat timer fd"); + return false; + } + + wayl->kbd.repeat.dont_re_repeat = true; + for (size_t i = 0; i < expiration_count; i++) + input_repeat(wayl, wayl->kbd.repeat.key); + wayl->kbd.repeat.dont_re_repeat = false; + return true; +} + +struct wayland * +wayl_init(struct fdm *fdm) +{ + struct wayland *wayl = calloc(1, sizeof(*wayl)); + wayl->fdm = fdm; + + wayl->display = wl_display_connect(NULL); + if (wayl->display == NULL) { + LOG_ERR("failed to connect to wayland; no compositor running?"); + goto out; + } + + wayl->registry = wl_display_get_registry(wayl->display); + if (wayl->registry == NULL) { + LOG_ERR("failed to get wayland registry"); + goto out; + } + + wl_registry_add_listener(wayl->registry, ®istry_listener, wayl); + wl_display_roundtrip(wayl->display); + + if (wayl->compositor == NULL) { + LOG_ERR("no compositor"); + goto out; + } + if (wayl->shm == NULL) { + LOG_ERR("no shared memory buffers interface"); + goto out; + } + if (wayl->shell == NULL) { + LOG_ERR("no XDG shell interface"); + goto out; + } + if (!wayl->have_argb8888) { + LOG_ERR("compositor does not support ARGB surfaces"); + goto out; + } + if (wayl->seat == NULL) { + LOG_ERR("no seat available"); + goto out; + } + if (wayl->data_device_manager == NULL) { + LOG_ERR("no clipboard available " + "(wl_data_device_manager not implemented by server)"); + goto out; + } + if (wayl->primary_selection_device_manager == NULL) + LOG_WARN("no primary selection available"); + + tll_foreach(wayl->monitors, it) { + LOG_INFO("%s: %dx%d+%dx%d (scale=%d, refresh=%.2fHz)", + it->item.name, it->item.width_px, it->item.height_px, + it->item.x, it->item.y, it->item.scale, it->item.refresh); + } + + /* Clipboard */ + wayl->data_device = wl_data_device_manager_get_data_device( + wayl->data_device_manager, wayl->seat); + wl_data_device_add_listener(wayl->data_device, &data_device_listener, wayl); + + /* Primary selection */ + if (wayl->primary_selection_device_manager != NULL) { + wayl->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( + wayl->primary_selection_device_manager, wayl->seat); + zwp_primary_selection_device_v1_add_listener( + wayl->primary_selection_device, &primary_selection_device_listener, wayl); + } + + /* Cursor */ + unsigned cursor_size = 24; + const char *cursor_theme = getenv("XCURSOR_THEME"); + + { + const char *env_cursor_size = getenv("XCURSOR_SIZE"); + if (env_cursor_size != NULL) { + unsigned size; + if (sscanf(env_cursor_size, "%u", &size) == 1) + cursor_size = size; + } + } + + /* Note: theme is (re)loaded on scale and output changes */ + LOG_INFO("cursor theme: %s, size: %u", cursor_theme, cursor_size); + wayl->pointer.size = cursor_size; + wayl->pointer.theme_name = cursor_theme != NULL ? strdup(cursor_theme) : NULL; + + wayl->pointer.surface = wl_compositor_create_surface(wayl->compositor); + if (wayl->pointer.surface == NULL) { + LOG_ERR("failed to create cursor surface"); + goto out; + } + + wayl->kbd.repeat.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK); + if (wayl->kbd.repeat.fd == -1) { + LOG_ERRNO("failed to create keyboard repeat timer FD"); + goto out; + } + + int wl_fd = wl_display_get_fd(wayl->display); + int fd_flags = fcntl(wl_fd, F_GETFL); + if (fd_flags == -1) { + LOG_ERRNO("failed to set non blocking mode on Wayland display connection"); + goto out; + } + if (fcntl(wl_fd, F_SETFL, fd_flags | O_NONBLOCK) == -1) { + LOG_ERRNO("failed to set non blocking mode on Wayland display connection"); + goto out; + } + + if (!fdm_add(fdm, wl_fd, EPOLLIN, &fdm_wayl, wayl)) { + LOG_ERR("failed to register Wayland connection with the FDM"); + goto out; + } + + if (!fdm_add(fdm, wayl->kbd.repeat.fd, EPOLLIN, &fdm_repeat, wayl)) { + LOG_ERR("failed to register keyboard repeat timer with the FDM"); + goto out; + } + + //wl_display_dispatch_pending(wayl->display); + //wl_display_flush(wayl->display); + return wayl; + +out: + if (wayl != NULL) + wayl_destroy(wayl); + return NULL; +} + +void +wayl_destroy(struct wayland *wayl) +{ + if (wayl == NULL) + return; + + if (wayl->kbd.repeat.fd != 0) { + fdm_del(wayl->fdm, wayl->kbd.repeat.fd); + close(wayl->kbd.repeat.fd); + } + + tll_foreach(wayl->monitors, it) { + free(it->item.name); + if (it->item.xdg != NULL) + zxdg_output_v1_destroy(it->item.xdg); + if (it->item.output != NULL) + wl_output_destroy(it->item.output); + tll_remove(wayl->monitors, it); + } + + if (wayl->xdg_output_manager != NULL) + zxdg_output_manager_v1_destroy(wayl->xdg_output_manager); + if (wayl->shell != NULL) + xdg_wm_base_destroy(wayl->shell); + + if (wayl->xdg_decoration_manager != NULL) + zxdg_decoration_manager_v1_destroy(wayl->xdg_decoration_manager); + + if (wayl->kbd.xkb_compose_state != NULL) + xkb_compose_state_unref(wayl->kbd.xkb_compose_state); + if (wayl->kbd.xkb_compose_table != NULL) + xkb_compose_table_unref(wayl->kbd.xkb_compose_table); + if (wayl->kbd.xkb_keymap != NULL) + xkb_keymap_unref(wayl->kbd.xkb_keymap); + if (wayl->kbd.xkb_state != NULL) + xkb_state_unref(wayl->kbd.xkb_state); + if (wayl->kbd.xkb != NULL) + xkb_context_unref(wayl->kbd.xkb); + + if (wayl->clipboard.data_source != NULL) + wl_data_source_destroy(wayl->clipboard.data_source); + if (wayl->clipboard.data_offer != NULL) + wl_data_offer_destroy(wayl->clipboard.data_offer); + free(wayl->clipboard.text); + if (wayl->primary.data_source != NULL) + zwp_primary_selection_source_v1_destroy(wayl->primary.data_source); + if (wayl->primary.data_offer != NULL) + zwp_primary_selection_offer_v1_destroy(wayl->primary.data_offer); + free(wayl->primary.text); + + free(wayl->pointer.theme_name); + if (wayl->pointer.theme != NULL) + wl_cursor_theme_destroy(wayl->pointer.theme); + if (wayl->pointer.pointer != NULL) + wl_pointer_destroy(wayl->pointer.pointer); + if (wayl->pointer.surface != NULL) + wl_surface_destroy(wayl->pointer.surface); + if (wayl->keyboard != NULL) + wl_keyboard_destroy(wayl->keyboard); + if (wayl->data_device != NULL) + wl_data_device_destroy(wayl->data_device); + if (wayl->data_device_manager != NULL) + wl_data_device_manager_destroy(wayl->data_device_manager); + if (wayl->primary_selection_device != NULL) + zwp_primary_selection_device_v1_destroy(wayl->primary_selection_device); + if (wayl->primary_selection_device_manager != NULL) + zwp_primary_selection_device_manager_v1_destroy(wayl->primary_selection_device_manager); + if (wayl->seat != NULL) + wl_seat_destroy(wayl->seat); + if (wayl->shm != NULL) + wl_shm_destroy(wayl->shm); + if (wayl->sub_compositor != NULL) + wl_subcompositor_destroy(wayl->sub_compositor); + if (wayl->compositor != NULL) + wl_compositor_destroy(wayl->compositor); + if (wayl->registry != NULL) + wl_registry_destroy(wayl->registry); + if (wayl->display != NULL) { + fdm_del(wayl->fdm, wl_display_get_fd(wayl->display)); + wl_display_disconnect(wayl->display); + } + + free(wayl); +} + +struct wl_window * +wayl_win_init(struct wayland *wayl) +{ + struct wl_window *win = calloc(1, sizeof(*win)); + + win->surface = wl_compositor_create_surface(wayl->compositor); + if (win->surface == NULL) { + LOG_ERR("failed to create wayland surface"); + goto out; + } + + wl_surface_add_listener(win->surface, &surface_listener, wayl); + + win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface); + xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, wayl); + + win->xdg_toplevel = xdg_surface_get_toplevel(win->xdg_surface); + xdg_toplevel_add_listener(win->xdg_toplevel, &xdg_toplevel_listener, wayl); + + xdg_toplevel_set_app_id(win->xdg_toplevel, "foot"); + + /* Request server-side decorations */ + win->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + wayl->xdg_decoration_manager, win->xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode( + win->xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + zxdg_toplevel_decoration_v1_add_listener( + win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, wayl); + + /* Scrollback search box */ + win->search_surface = wl_compositor_create_surface(wayl->compositor); + win->search_sub_surface = wl_subcompositor_get_subsurface( + wayl->sub_compositor, win->search_surface, win->surface); + wl_subsurface_set_desync(win->search_sub_surface); + + wl_surface_commit(win->surface); + wl_display_roundtrip(wayl->display); + + return win; + +out: + if (win != NULL) + wayl_win_destroy(win); + return NULL; +} + +void +wayl_win_destroy(struct wl_window *win) +{ + tll_free(win->on_outputs); + if (win->search_sub_surface != NULL) + wl_subsurface_destroy(win->search_sub_surface); + if (win->search_surface != NULL) + wl_surface_destroy(win->search_surface); + if (win->frame_callback != NULL) + wl_callback_destroy(win->frame_callback); + if (win->xdg_toplevel_decoration != NULL) + zxdg_toplevel_decoration_v1_destroy(win->xdg_toplevel_decoration); + if (win->xdg_toplevel != NULL) + xdg_toplevel_destroy(win->xdg_toplevel); + if (win->xdg_surface != NULL) + xdg_surface_destroy(win->xdg_surface); + if (win->surface != NULL) + wl_surface_destroy(win->surface); + free(win); +} + +bool +wayl_reload_cursor_theme(struct wayland *wayl, struct terminal *term) +{ + if (wayl->pointer.size == 0) + return true; + + if (wayl->pointer.theme != NULL) { + wl_cursor_theme_destroy(wayl->pointer.theme); + wayl->pointer.theme = NULL; + wayl->pointer.cursor = NULL; + } + + LOG_DBG("reloading cursor theme: %s@%d", + wayl->pointer.theme_name, wayl->pointer.size); + + wayl->pointer.theme = wl_cursor_theme_load( + wayl->pointer.theme_name, wayl->pointer.size * term->scale, wayl->shm); + if (wayl->pointer.theme == NULL) { + LOG_ERR("failed to load cursor theme"); + return false; + } + + wayl->pointer.cursor = wl_cursor_theme_get_cursor( + wayl->pointer.theme, "left_ptr"); + assert(wayl->pointer.cursor != NULL); + wayl_update_cursor_surface(wayl, term); + + return true; +} + +void +wayl_update_cursor_surface(struct wayland *wayl, struct terminal *term) +{ + if (wayl->pointer.cursor == NULL) + return; + + const int scale = term->scale; + wl_surface_set_buffer_scale(wayl->pointer.surface, scale); + + struct wl_cursor_image *image = wayl->pointer.cursor->images[0]; + + wl_surface_attach( + wayl->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); + + wl_pointer_set_cursor( + wayl->pointer.pointer, wayl->pointer.serial, + wayl->pointer.surface, + image->hotspot_x / scale, image->hotspot_y / scale); + + wl_surface_damage_buffer( + wayl->pointer.surface, 0, 0, INT32_MAX, INT32_MAX); + + wl_surface_commit(wayl->pointer.surface); +} + + +struct terminal * +wayl_terminal_from_surface(struct wayland *wayl, struct wl_surface *surface) +{ + assert(surface == wayl->term->window->surface); + return wayl->term; +} + +struct terminal * +wayl_terminal_from_xdg_toplevel(struct wayland *wayl, + struct xdg_toplevel *toplevel) +{ + assert(toplevel == wayl->term->window->xdg_toplevel); + return wayl->term; +} diff --git a/wayland.h b/wayland.h new file mode 100644 index 00000000..a8aba13c --- /dev/null +++ b/wayland.h @@ -0,0 +1,169 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +#include "fdm.h" +#include "tllist.h" + +struct monitor { + struct wayland *wayl; + struct wl_output *output; + struct zxdg_output_v1 *xdg; + char *name; + + int x; + int y; + + int width_mm; + int height_mm; + + int width_px; + int height_px; + + int scale; + float refresh; +}; + +struct kbd { + struct xkb_context *xkb; + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + struct xkb_compose_table *xkb_compose_table; + struct xkb_compose_state *xkb_compose_state; + struct { + int fd; + + bool dont_re_repeat; + int32_t delay; + int32_t rate; + uint32_t key; + } repeat; + + xkb_mod_index_t mod_shift; + xkb_mod_index_t mod_alt; + xkb_mod_index_t mod_ctrl; + xkb_mod_index_t mod_meta; + + /* Enabled modifiers */ + bool shift; + bool alt; + bool ctrl; + bool meta; +}; + +struct wl_clipboard { + struct wl_data_source *data_source; + struct wl_data_offer *data_offer; + char *text; + uint32_t serial; +}; + +struct wl_primary { + struct zwp_primary_selection_source_v1 *data_source; + struct zwp_primary_selection_offer_v1 *data_offer; + char *text; + uint32_t serial; +}; + +struct wl_window { + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + + struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; + + /* Scrollback search */ + struct wl_surface *search_surface; + struct wl_subsurface *search_sub_surface; + + struct wl_callback *frame_callback; + + tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */ +}; + +struct terminal; +struct wayland { + struct fdm *fdm; + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_subcompositor *sub_compositor; + struct wl_shm *shm; + + struct wl_seat *seat; + struct wl_keyboard *keyboard; + struct zxdg_output_manager_v1 *xdg_output_manager; + + struct xdg_wm_base *shell; + struct zxdg_decoration_manager_v1 *xdg_decoration_manager; + + /* Keyboard */ + struct kbd kbd; + + /* Clipboard */ + uint32_t input_serial; + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; + struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; + struct zwp_primary_selection_device_v1 *primary_selection_device; + + struct wl_clipboard clipboard; + struct wl_primary primary; + + /* Cursor */ + struct { + struct wl_pointer *pointer; + uint32_t serial; + + struct wl_surface *surface; + struct wl_cursor_theme *theme; + struct wl_cursor *cursor; + int size; + char *theme_name; + } pointer; + + struct { + int col; + int row; + int button; + + int count; + int last_button; + struct timeval last_time; + + /* We used a discrete axis event in the current pointer frame */ + bool have_discrete; + } mouse; + + bool have_argb8888; + tll(struct monitor) monitors; /* All available outputs */ + + /* TODO: turn into a list to support multiple windows */ + struct terminal *term; + struct terminal *focused; + struct terminal *moused; +}; + +struct wayland *wayl_init(struct fdm *fdm); +void wayl_destroy(struct wayland *wayl); + +struct terminal *wayl_terminal_from_surface( + struct wayland *wayl, struct wl_surface *surface); +struct terminal *wayl_terminal_from_xdg_surface( + struct wayland *wayl, struct xdg_surface *surface); +struct terminal *wayl_terminal_from_xdg_toplevel( + struct wayland *wayl, struct xdg_toplevel *toplevel); + +/* TODO: pass something other than 'term'? Need scale... */ +bool wayl_reload_cursor_theme(struct wayland *wayl, struct terminal *term); +void wayl_update_cursor_surface(struct wayland *wayl, struct terminal *term); + +struct wl_window *wayl_win_init(struct wayland *wayl); +void wayl_win_destroy(struct wl_window *win);