2019-06-12 20:08:54 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
2019-09-21 20:01:55 +02:00
|
|
|
#include <ctype.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
#include <stdbool.h>
|
2019-06-13 15:19:10 +02:00
|
|
|
#include <unistd.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
#include <assert.h>
|
2019-06-13 15:19:10 +02:00
|
|
|
#include <fcntl.h>
|
2019-06-13 21:23:52 +02:00
|
|
|
#include <locale.h>
|
2019-07-03 09:46:13 +02:00
|
|
|
#include <getopt.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
#include <poll.h>
|
2019-07-03 15:16:38 +02:00
|
|
|
#include <errno.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-21 19:14:19 +02:00
|
|
|
#include <sys/timerfd.h>
|
2019-07-29 20:13:26 +02:00
|
|
|
#include <sys/sysinfo.h>
|
2019-08-01 20:09:16 +02:00
|
|
|
#include <sys/prctl.h>
|
2019-08-23 17:23:47 +02:00
|
|
|
#include <sys/wait.h>
|
2019-08-30 17:57:46 +02:00
|
|
|
#include <sys/time.h>
|
2019-07-21 19:14:19 +02:00
|
|
|
|
2019-07-16 15:08:02 +02:00
|
|
|
#include <freetype/tttables.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
#include <wayland-client.h>
|
2019-07-05 10:44:09 +02:00
|
|
|
#include <wayland-cursor.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
#include <xdg-shell.h>
|
2019-07-09 10:00:54 +02:00
|
|
|
#include <xkbcommon/xkbcommon-compose.h>
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-08-12 21:22:38 +02:00
|
|
|
#include <xdg-output-unstable-v1.h>
|
2019-08-30 17:55:45 +02:00
|
|
|
#include <xdg-decoration-unstable-v1.h>
|
2019-08-12 21:22:38 +02:00
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
#define LOG_MODULE "main"
|
2019-07-03 20:21:03 +02:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-12 20:08:54 +02:00
|
|
|
#include "log.h"
|
2019-06-13 15:19:10 +02:00
|
|
|
|
2019-07-16 11:52:22 +02:00
|
|
|
#include "config.h"
|
2019-06-13 16:24:35 +02:00
|
|
|
#include "font.h"
|
2019-07-05 10:16:56 +02:00
|
|
|
#include "grid.h"
|
|
|
|
|
#include "input.h"
|
|
|
|
|
#include "render.h"
|
2019-07-16 11:52:22 +02:00
|
|
|
#include "selection.h"
|
2019-06-12 20:08:54 +02:00
|
|
|
#include "shm.h"
|
2019-06-13 15:19:10 +02:00
|
|
|
#include "slave.h"
|
2019-06-15 22:22:44 +02:00
|
|
|
#include "terminal.h"
|
2019-07-17 09:55:36 +02:00
|
|
|
#include "tokenize.h"
|
2019-06-15 22:22:44 +02:00
|
|
|
#include "vt.h"
|
2019-06-19 14:17:43 +02:00
|
|
|
|
|
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
2019-06-26 19:33:39 +02:00
|
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
2019-06-12 20:08:54 +02:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
|
|
|
|
|
{
|
2019-07-08 16:12:02 +02:00
|
|
|
struct terminal *term = data;
|
|
|
|
|
if (format == WL_SHM_FORMAT_ARGB8888)
|
|
|
|
|
term->wl.have_argb8888 = true;
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-19 10:04:47 +02:00
|
|
|
static void
|
|
|
|
|
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
|
|
|
|
|
enum wl_seat_capability caps)
|
|
|
|
|
{
|
2019-07-05 10:16:56 +02:00
|
|
|
struct terminal *term = data;
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2019-07-05 10:44:57 +02:00
|
|
|
if (term->wl.keyboard != NULL) {
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_keyboard_release(term->wl.keyboard);
|
2019-07-05 10:44:57 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-12 21:22:38 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2019-09-20 22:15:18 +02:00
|
|
|
if ((flags & WL_OUTPUT_MODE_CURRENT) == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
mon->refresh = (float)refresh / 1000;
|
2019-08-12 21:22:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2019-08-30 17:56:23 +02:00
|
|
|
|
|
|
|
|
int old_scale = mon->term->scale >= 1 ? mon->term->scale : 1;
|
2019-08-21 17:53:52 +02:00
|
|
|
render_reload_cursor_theme(mon->term);
|
2019-08-30 17:56:23 +02:00
|
|
|
render_resize(mon->term, mon->term->width / old_scale, mon->term->height / old_scale);
|
2019-08-12 21:22:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
static void
|
|
|
|
|
handle_global(void *data, struct wl_registry *registry,
|
|
|
|
|
uint32_t name, const char *interface, uint32_t version)
|
|
|
|
|
{
|
2019-09-24 19:43:43 +02:00
|
|
|
LOG_DBG("global: %s, version=%u", interface, version);
|
2019-07-05 10:16:56 +02:00
|
|
|
struct terminal *term = data;
|
2019-06-12 20:08:54 +02:00
|
|
|
|
|
|
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
2019-07-05 10:16:56 +02:00
|
|
|
term->wl.compositor = wl_registry_bind(
|
|
|
|
|
term->wl.registry, name, &wl_compositor_interface, 4);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-29 19:34:41 +02:00
|
|
|
else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
|
|
|
|
|
term->wl.sub_compositor = wl_registry_bind(
|
|
|
|
|
term->wl.registry, name, &wl_subcompositor_interface, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
2019-07-05 10:16:56 +02:00
|
|
|
term->wl.shm = wl_registry_bind(
|
|
|
|
|
term->wl.registry, name, &wl_shm_interface, 1);
|
2019-07-08 16:12:02 +02:00
|
|
|
wl_shm_add_listener(term->wl.shm, &shm_listener, term);
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_display_roundtrip(term->wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
2019-07-05 10:16:56 +02:00
|
|
|
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);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-30 17:55:45 +02:00
|
|
|
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);
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
2019-07-05 10:16:56 +02:00
|
|
|
term->wl.seat = wl_registry_bind(
|
2019-07-26 18:47:56 +02:00
|
|
|
term->wl.registry, name, &wl_seat_interface, 5);
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_seat_add_listener(term->wl.seat, &seat_listener, term);
|
|
|
|
|
wl_display_roundtrip(term->wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-08-12 21:22:38 +02:00
|
|
|
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
|
|
|
|
term->wl.xdg_output_manager = wl_registry_bind(
|
2019-09-24 19:44:14 +02:00
|
|
|
term->wl.registry, name, &zxdg_output_manager_v1_interface, min(version, 2));
|
2019-08-12 21:22:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(
|
2019-08-12 21:32:38 +02:00
|
|
|
term->wl.monitors, ((struct monitor){
|
|
|
|
|
.term = term, .output = output}));
|
2019-08-12 21:22:38 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2019-07-11 17:02:21 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-12 21:49:17 +02:00
|
|
|
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 */
|
2019-08-30 17:56:23 +02:00
|
|
|
int scale = term->scale >= 1 ? term->scale : 1;
|
2019-08-21 17:53:52 +02:00
|
|
|
render_reload_cursor_theme(term);
|
2019-08-30 17:56:23 +02:00
|
|
|
render_resize(term, term->width / scale, term->height / scale);
|
2019-08-12 21:49:17 +02:00
|
|
|
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 */
|
2019-08-30 17:56:23 +02:00
|
|
|
int scale = term->scale >= 1 ? term->scale : 1;
|
2019-08-21 17:53:52 +02:00
|
|
|
render_reload_cursor_theme(term);
|
2019-08-30 17:56:23 +02:00
|
|
|
render_resize(term, term->width / scale, term->height / scale);
|
2019-08-12 21:49:17 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("unmapped from unknown output");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
|
|
|
.enter = &surface_enter,
|
|
|
|
|
.leave = &surface_leave,
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
int32_t width, int32_t height, struct wl_array *states)
|
|
|
|
|
{
|
2019-08-12 21:49:17 +02:00
|
|
|
LOG_DBG("xdg-toplevel: configure: %dx%d", width, height);
|
|
|
|
|
|
2019-06-12 20:17:48 +02:00
|
|
|
if (width <= 0 || height <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-08-12 21:32:38 +02:00
|
|
|
struct terminal *term = data;
|
2019-08-12 21:49:17 +02:00
|
|
|
render_resize(term, width, height);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
|
|
|
{
|
2019-07-05 10:16:56 +02:00
|
|
|
struct terminal *term = data;
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_DBG("xdg-toplevel: close");
|
2019-07-05 10:16:56 +02:00
|
|
|
term->quit = true;
|
|
|
|
|
wl_display_roundtrip(term->wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2019-06-13 15:19:10 +02:00
|
|
|
//LOG_DBG("xdg-surface: configure");
|
2019-06-12 20:08:54 +02:00
|
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
|
|
|
|
.configure = &xdg_surface_configure,
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-30 17:55:45 +02:00
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-11 16:03:29 +02:00
|
|
|
static void
|
|
|
|
|
print_usage(const char *prog_name)
|
|
|
|
|
{
|
|
|
|
|
printf("Usage: %s [OPTION]...\n", prog_name);
|
|
|
|
|
printf("\n");
|
|
|
|
|
printf("Options:\n");
|
2019-09-21 20:01:55 +02:00
|
|
|
printf(" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
|
2019-08-23 17:26:41 +02:00
|
|
|
" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
|
|
|
|
|
" -g,--geometry=WIDTHxHEIGHT set initial width and height\n"
|
|
|
|
|
" -v,--version show the version number and quit\n");
|
2019-08-11 16:03:29 +02:00
|
|
|
printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
int
|
2019-07-03 09:46:13 +02:00
|
|
|
main(int argc, char *const *argv)
|
2019-06-12 20:08:54 +02:00
|
|
|
{
|
|
|
|
|
int ret = EXIT_FAILURE;
|
|
|
|
|
|
2019-07-17 10:12:14 +02:00
|
|
|
struct config conf = {NULL};
|
|
|
|
|
if (!config_load(&conf))
|
|
|
|
|
return ret;
|
2019-07-16 11:52:22 +02:00
|
|
|
|
2019-08-11 16:03:29 +02:00
|
|
|
const char *const prog_name = argv[0];
|
|
|
|
|
|
2019-07-03 09:46:13 +02:00
|
|
|
static const struct option longopts[] = {
|
2019-08-23 17:26:41 +02:00
|
|
|
{"term", required_argument, 0, 't'},
|
|
|
|
|
{"font", required_argument, 0, 'f'},
|
|
|
|
|
{"geometry", required_argument, 0, 'g'},
|
|
|
|
|
{"version", no_argument, 0, 'v'},
|
|
|
|
|
{"help", no_argument, 0, 'h'},
|
|
|
|
|
{NULL, no_argument, 0, 0},
|
2019-07-03 09:46:13 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
while (true) {
|
2019-08-23 17:26:41 +02:00
|
|
|
int c = getopt_long(argc, argv, ":t:f:g:vh", longopts, NULL);
|
2019-07-03 09:46:13 +02:00
|
|
|
if (c == -1)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
switch (c) {
|
2019-07-18 14:34:45 +02:00
|
|
|
case 't':
|
|
|
|
|
free(conf.term);
|
|
|
|
|
conf.term = strdup(optarg);
|
|
|
|
|
break;
|
|
|
|
|
|
2019-07-03 09:46:13 +02:00
|
|
|
case 'f':
|
2019-07-30 18:04:28 +02:00
|
|
|
tll_free_and_free(conf.fonts, free);
|
2019-09-21 20:01:55 +02:00
|
|
|
//tll_push_back(conf.fonts, strdup(optarg));
|
|
|
|
|
for (char *font = strtok(optarg, ","); font != NULL; font = strtok(NULL, ",")) {
|
|
|
|
|
|
|
|
|
|
/* Strip leading spaces */
|
|
|
|
|
while (*font != '\0' && isspace(*font))
|
|
|
|
|
font++;
|
|
|
|
|
|
|
|
|
|
/* Strip trailing spaces */
|
|
|
|
|
char *end = font + strlen(font);
|
|
|
|
|
assert(*end == '\0');
|
|
|
|
|
end--;
|
|
|
|
|
while (end > font && isspace(*end))
|
|
|
|
|
*(end--) = '\0';
|
|
|
|
|
|
|
|
|
|
if (strlen(font) == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
tll_push_back(conf.fonts, strdup(font));
|
|
|
|
|
}
|
2019-07-03 09:46:13 +02:00
|
|
|
break;
|
|
|
|
|
|
2019-08-23 17:26:41 +02:00
|
|
|
case 'g': {
|
|
|
|
|
unsigned width, height;
|
|
|
|
|
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
|
|
|
|
|
fprintf(stderr, "error: invalid geometry: %s\n", optarg);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conf.width = width;
|
|
|
|
|
conf.height = height;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-11 16:03:29 +02:00
|
|
|
case 'v':
|
|
|
|
|
printf("foot version %s\n", FOOT_VERSION);
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
|
2019-07-03 09:46:13 +02:00
|
|
|
case 'h':
|
2019-08-11 16:03:29 +02:00
|
|
|
print_usage(prog_name);
|
|
|
|
|
return EXIT_SUCCESS;
|
2019-07-03 09:46:13 +02:00
|
|
|
|
|
|
|
|
case ':':
|
|
|
|
|
fprintf(stderr, "error: -%c: missing required argument\n", optopt);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
case '?':
|
|
|
|
|
fprintf(stderr, "error: -%c: invalid option\n", optopt);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 09:55:36 +02:00
|
|
|
argc -= optind;
|
|
|
|
|
argv += optind;
|
|
|
|
|
|
2019-06-13 21:23:52 +02:00
|
|
|
setlocale(LC_ALL, "");
|
2019-07-18 14:29:40 +02:00
|
|
|
setenv("TERM", conf.term, 1);
|
2019-06-13 21:23:52 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
struct terminal term = {
|
2019-06-12 20:08:54 +02:00
|
|
|
.quit = false,
|
2019-07-05 10:16:56 +02:00
|
|
|
.ptmx = posix_openpt(O_RDWR | O_NOCTTY),
|
2019-07-09 11:07:06 +02:00
|
|
|
.cursor_keys_mode = CURSOR_KEYS_NORMAL,
|
|
|
|
|
.keypad_keys_mode = KEYPAD_NUMERICAL,
|
2019-07-05 10:16:56 +02:00
|
|
|
.auto_margin = true,
|
2019-07-21 17:48:06 +02:00
|
|
|
.window_title_stack = tll_init(),
|
2019-07-22 19:15:56 +02:00
|
|
|
.flash = {
|
2019-08-05 20:30:06 +02:00
|
|
|
.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK),
|
2019-07-22 19:15:56 +02:00
|
|
|
},
|
2019-07-22 19:17:57 +02:00
|
|
|
.blink = {
|
2019-08-05 20:30:06 +02:00
|
|
|
.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK),
|
2019-07-22 19:17:57 +02:00
|
|
|
},
|
2019-07-05 10:16:56 +02:00
|
|
|
.vt = {
|
|
|
|
|
.state = 1, /* STATE_GROUND */
|
|
|
|
|
.attrs = {
|
2019-07-21 11:06:28 +02:00
|
|
|
//.foreground = conf.colors.fg,
|
|
|
|
|
//.background = conf.colors.bg
|
2019-06-15 22:22:44 +02:00
|
|
|
},
|
2019-07-05 10:16:56 +02:00
|
|
|
},
|
|
|
|
|
.kbd = {
|
|
|
|
|
.repeat = {
|
2019-08-05 20:30:06 +02:00
|
|
|
.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK),
|
2019-06-19 10:04:47 +02:00
|
|
|
},
|
2019-06-15 22:22:44 +02:00
|
|
|
},
|
2019-07-21 11:06:28 +02:00
|
|
|
.colors = {
|
|
|
|
|
.default_fg = conf.colors.fg,
|
|
|
|
|
.default_bg = conf.colors.bg,
|
2019-08-21 18:50:24 +02:00
|
|
|
.default_table = {
|
2019-07-21 11:06:28 +02:00
|
|
|
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],
|
2019-08-21 18:47:48 +02:00
|
|
|
|
2019-07-21 11:06:28 +02:00
|
|
|
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],
|
|
|
|
|
},
|
2019-08-15 18:15:43 +02:00
|
|
|
.alpha = conf.colors.alpha,
|
2019-07-21 11:06:28 +02:00
|
|
|
},
|
2019-07-22 20:19:27 +02:00
|
|
|
.default_cursor_style = conf.cursor.style,
|
2019-07-22 20:15:14 +02:00
|
|
|
.cursor_style = conf.cursor.style,
|
2019-07-23 18:54:58 +02:00
|
|
|
.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,
|
|
|
|
|
},
|
2019-07-10 20:57:09 +02:00
|
|
|
.selection = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
2019-07-05 10:16:56 +02:00
|
|
|
.normal = {.damage = tll_init(), .scroll_damage = tll_init()},
|
|
|
|
|
.alt = {.damage = tll_init(), .scroll_damage = tll_init()},
|
|
|
|
|
.grid = &term.normal,
|
2019-07-29 20:13:26 +02:00
|
|
|
.render = {
|
2019-08-01 19:28:14 +02:00
|
|
|
.scrollback_lines = conf.scrollback_lines,
|
2019-07-29 20:13:26 +02:00
|
|
|
.workers = {
|
|
|
|
|
.count = conf.render_worker_count,
|
|
|
|
|
.queue = tll_init(),
|
|
|
|
|
},
|
|
|
|
|
},
|
2019-06-12 20:08:54 +02:00
|
|
|
};
|
|
|
|
|
|
2019-08-21 18:47:48 +02:00
|
|
|
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;
|
|
|
|
|
|
2019-08-21 17:56:21 +02:00
|
|
|
/* Initialize the 256 gray-scale color cube */
|
|
|
|
|
{
|
2019-08-21 18:50:24 +02:00
|
|
|
/* First 16 entries have already been initialized from conf */
|
2019-08-21 17:56:21 +02:00
|
|
|
for (size_t r = 0; r < 6; r++) {
|
|
|
|
|
for (size_t g = 0; g < 6; g++) {
|
|
|
|
|
for (size_t b = 0; b < 6; b++) {
|
2019-08-21 18:50:24 +02:00
|
|
|
term.colors.default_table[16 + r * 6 * 6 + g * 6 + b]
|
2019-08-21 17:56:21 +02:00
|
|
|
= r * 51 << 16 | g * 51 << 8 | b * 51;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 24; i++)
|
2019-08-21 18:50:24 +02:00
|
|
|
term.colors.default_table[232 + i] = i * 11 << 16 | i * 11 << 8 | i * 11;
|
2019-08-21 17:56:21 +02:00
|
|
|
|
2019-08-21 18:50:24 +02:00
|
|
|
memcpy(term.colors.table, term.colors.default_table, sizeof(term.colors.table));
|
2019-08-21 17:56:21 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-18 13:03:21 +02:00
|
|
|
if (term.ptmx == -1) {
|
2019-08-05 19:33:01 +02:00
|
|
|
LOG_ERR("failed to open pseudo terminal");
|
2019-07-18 13:03:21 +02:00
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 19:33:01 +02:00
|
|
|
if (term.flash.fd == -1 || term.blink.fd == -1 || term.kbd.repeat.fd == -1) {
|
|
|
|
|
LOG_ERR("failed to create timers");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
font_list_t font_names = tll_init();
|
|
|
|
|
tll_foreach(conf.fonts, it)
|
|
|
|
|
tll_push_back(font_names, it->item);
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
if (!font_from_name(font_names, "", &term.fonts[0])) {
|
|
|
|
|
tll_free(font_names);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-06-22 21:32:51 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
font_from_name(font_names, "style=bold", &term.fonts[1]);
|
|
|
|
|
font_from_name(font_names, "style=italic", &term.fonts[2]);
|
|
|
|
|
font_from_name(font_names, "style=bold italic", &term.fonts[3]);
|
2019-06-22 21:32:51 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
tll_free(font_names);
|
2019-06-22 21:32:51 +02:00
|
|
|
|
2019-07-16 14:20:39 +02:00
|
|
|
/* Underline position and size */
|
|
|
|
|
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) {
|
|
|
|
|
struct font *f = &term.fonts[i];
|
|
|
|
|
|
2019-07-28 12:39:56 +02:00
|
|
|
if (f->face == NULL)
|
2019-07-16 14:20:39 +02:00
|
|
|
continue;
|
|
|
|
|
|
2019-07-28 12:39:56 +02:00
|
|
|
FT_Face ft_face = f->face;
|
2019-07-16 14:20:39 +02:00
|
|
|
|
|
|
|
|
double x_scale = ft_face->size->metrics.x_scale / 65526.;
|
2019-07-16 15:08:02 +02:00
|
|
|
double height = ft_face->size->metrics.height / 64;
|
|
|
|
|
double descent = ft_face->size->metrics.descender / 64;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("ft: x-scale: %f, height: %f, descent: %f",
|
|
|
|
|
x_scale, height, descent);
|
|
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
f->underline.position = round(ft_face->underline_position * x_scale / 64.);
|
|
|
|
|
f->underline.thickness = ceil(ft_face->underline_thickness * x_scale / 64.);
|
2019-07-16 14:20:39 +02:00
|
|
|
|
|
|
|
|
if (f->underline.position == 0.) {
|
2019-09-21 00:47:17 +02:00
|
|
|
f->underline.position = round(descent / 2.);
|
2019-07-16 14:20:39 +02:00
|
|
|
f->underline.thickness = fabs(round(descent / 5.));
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 17:55:40 +02:00
|
|
|
LOG_DBG("underline: pos=%d, thick=%d",
|
2019-07-16 14:20:39 +02:00
|
|
|
f->underline.position, f->underline.thickness);
|
|
|
|
|
|
2019-07-16 15:08:02 +02:00
|
|
|
TT_OS2 *os2 = FT_Get_Sfnt_Table(ft_face, ft_sfnt_os2);
|
|
|
|
|
if (os2 != NULL) {
|
2019-09-21 00:46:15 +02:00
|
|
|
f->strikeout.position = round(os2->yStrikeoutPosition * x_scale / 64.);
|
|
|
|
|
f->strikeout.thickness = ceil(os2->yStrikeoutSize * x_scale / 64.);
|
2019-07-16 15:08:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (f->strikeout.position == 0.) {
|
2019-09-21 00:47:17 +02:00
|
|
|
f->strikeout.position = round(height / 2. + descent);
|
2019-07-16 15:08:02 +02:00
|
|
|
f->strikeout.thickness = f->underline.thickness;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 17:55:40 +02:00
|
|
|
LOG_DBG("strikeout: pos=%d, thick=%d",
|
2019-07-16 15:08:02 +02:00
|
|
|
f->strikeout.position, f->strikeout.thickness);
|
2019-07-28 12:39:56 +02:00
|
|
|
}
|
2019-07-16 15:08:02 +02:00
|
|
|
|
2019-07-28 12:39:56 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
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;
|
2019-07-28 12:39:56 +02:00
|
|
|
|
2019-07-30 21:47:59 +02:00
|
|
|
LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d",
|
2019-07-28 12:39:56 +02:00
|
|
|
height, descent, ascent, max_x_advance);
|
2019-07-16 14:20:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
term.cell_width = (int)ceil(term.fextents.max_x_advance);
|
|
|
|
|
term.cell_height = (int)ceil(term.fextents.height);
|
2019-08-28 21:01:36 +02:00
|
|
|
LOG_INFO("cell width=%d, height=%d", term.cell_width, term.cell_height);
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
term.wl.display = wl_display_connect(NULL);
|
|
|
|
|
if (term.wl.display == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("failed to connect to wayland; no compositor running?");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
term.wl.registry = wl_display_get_registry(term.wl.display);
|
|
|
|
|
if (term.wl.registry == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("failed to get wayland registry");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_registry_add_listener(term.wl.registry, ®istry_listener, &term);
|
|
|
|
|
wl_display_roundtrip(term.wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.wl.compositor == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("no compositor");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.wl.shm == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("no shared memory buffers interface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.wl.shell == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("no XDG shell interface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-07-08 16:12:02 +02:00
|
|
|
if (!term.wl.have_argb8888) {
|
|
|
|
|
LOG_ERR("compositor does not support ARGB surfaces");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-07-11 12:16:50 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2019-09-25 19:26:55 +02:00
|
|
|
if (term.wl.primary_selection_device_manager == NULL)
|
|
|
|
|
LOG_WARN("no primary selection available");
|
2019-07-11 12:16:50 +02:00
|
|
|
|
2019-08-12 21:22:38 +02:00
|
|
|
tll_foreach(term.wl.monitors, it) {
|
2019-09-21 20:09:06 +02:00
|
|
|
LOG_INFO("%s: %dx%d+%dx%d (scale=%d, refresh=%.2fHz)",
|
2019-08-12 21:22:38 +02:00
|
|
|
it->item.name, it->item.width_px, it->item.height_px,
|
2019-09-20 22:15:18 +02:00
|
|
|
it->item.x, it->item.y, it->item.scale, it->item.refresh);
|
2019-08-12 21:22:38 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 12:16:50 +02:00
|
|
|
/* 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);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-11 17:02:21 +02:00
|
|
|
/* Primary selection */
|
2019-09-25 19:26:55 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2019-07-11 17:02:21 +02:00
|
|
|
|
2019-07-05 10:44:09 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 17:53:52 +02:00
|
|
|
/* Note: theme is (re)loaded on scale and output changes */
|
2019-07-05 10:44:09 +02:00
|
|
|
LOG_INFO("cursor theme: %s, size: %u", cursor_theme, cursor_size);
|
2019-08-21 17:53:52 +02:00
|
|
|
term.wl.pointer.size = cursor_size;
|
|
|
|
|
term.wl.pointer.theme_name = cursor_theme != NULL ? strdup(cursor_theme) : NULL;
|
2019-07-05 10:44:09 +02:00
|
|
|
|
2019-08-21 17:53:52 +02:00
|
|
|
term.wl.pointer.surface = wl_compositor_create_surface(term.wl.compositor);
|
|
|
|
|
if (term.wl.pointer.surface == NULL) {
|
|
|
|
|
LOG_ERR("failed to create cursor surface");
|
2019-07-17 09:38:54 +02:00
|
|
|
goto out;
|
2019-07-05 10:44:09 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-29 20:22:07 +02:00
|
|
|
/* Main window */
|
2019-07-05 10:16:56 +02:00
|
|
|
term.wl.surface = wl_compositor_create_surface(term.wl.compositor);
|
|
|
|
|
if (term.wl.surface == NULL) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_ERR("failed to create wayland surface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-12 21:49:17 +02:00
|
|
|
wl_surface_add_listener(term.wl.surface, &surface_listener, &term);
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
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);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
term.wl.xdg_toplevel = xdg_surface_get_toplevel(term.wl.xdg_surface);
|
|
|
|
|
xdg_toplevel_add_listener(term.wl.xdg_toplevel, &xdg_toplevel_listener, &term);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-11 20:10:59 +02:00
|
|
|
xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot");
|
2019-07-21 17:35:53 +02:00
|
|
|
term_set_window_title(&term, "foot");
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-08-30 17:55:45 +02:00
|
|
|
/* 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);
|
|
|
|
|
|
2019-08-29 20:22:07 +02:00
|
|
|
/* 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);
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_surface_commit(term.wl.surface);
|
|
|
|
|
wl_display_roundtrip(term.wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-08-23 17:26:41 +02:00
|
|
|
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);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_display_dispatch_pending(term.wl.display);
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-07-17 09:39:12 +02:00
|
|
|
{
|
|
|
|
|
int fork_pipe[2];
|
|
|
|
|
if (pipe2(fork_pipe, O_CLOEXEC) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create pipe");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
|
2019-07-17 09:39:12 +02:00
|
|
|
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 */
|
2019-07-17 09:55:36 +02:00
|
|
|
|
|
|
|
|
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]);
|
2019-07-17 09:39:12 +02:00
|
|
|
assert(false);
|
|
|
|
|
break;
|
2019-06-13 15:19:10 +02:00
|
|
|
|
2019-07-17 09:39:12 +02:00
|
|
|
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)) {
|
2019-07-17 09:55:36 +02:00
|
|
|
LOG_ERRNO(
|
|
|
|
|
"%s: failed to execute", argc == 0 ? conf.shell : argv[0]);
|
2019-07-17 09:39:12 +02:00
|
|
|
goto out;
|
|
|
|
|
} else
|
|
|
|
|
LOG_DBG("%s: successfully started", conf.shell);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-03 15:16:38 +02:00
|
|
|
/* Read logic requires non-blocking mode */
|
|
|
|
|
{
|
2019-07-05 10:16:56 +02:00
|
|
|
int fd_flags = fcntl(term.ptmx, F_GETFL);
|
2019-07-03 15:16:38 +02:00
|
|
|
if (fd_flags == -1) {
|
|
|
|
|
LOG_ERRNO("failed to set non blocking mode on PTY master");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
if (fcntl(term.ptmx, F_SETFL, fd_flags | O_NONBLOCK) == -1) {
|
2019-07-03 15:16:38 +02:00
|
|
|
LOG_ERRNO("failed to set non blocking mode on PTY master");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 09:54:40 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-30 17:57:46 +02:00
|
|
|
bool timeout_is_armed = false;
|
2019-09-20 22:21:06 +02:00
|
|
|
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);
|
2019-08-05 20:30:06 +02:00
|
|
|
|
2019-06-13 21:55:32 +02:00
|
|
|
while (true) {
|
2019-06-12 20:08:54 +02:00
|
|
|
struct pollfd fds[] = {
|
2019-07-05 10:16:56 +02:00
|
|
|
{.fd = wl_display_get_fd(term.wl.display), .events = POLLIN},
|
|
|
|
|
{.fd = term.ptmx, .events = POLLIN},
|
2019-08-05 19:33:01 +02:00
|
|
|
{.fd = term.kbd.repeat.fd, .events = POLLIN},
|
2019-07-22 19:15:56 +02:00
|
|
|
{.fd = term.flash.fd, .events = POLLIN},
|
2019-07-22 19:17:57 +02:00
|
|
|
{.fd = term.blink.fd, .events = POLLIN},
|
2019-09-20 22:21:06 +02:00
|
|
|
{.fd = delay_render_timer_lower, .events = POLLIN},
|
|
|
|
|
{.fd = delay_render_timer_upper, .events = POLLIN},
|
2019-06-12 20:08:54 +02:00
|
|
|
};
|
|
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
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;
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_display_flush(term.wl.display);
|
2019-08-30 17:57:46 +02:00
|
|
|
int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
2019-07-04 09:54:40 +02:00
|
|
|
|
2019-07-30 22:01:16 +02:00
|
|
|
if (pret == -1) {
|
2019-07-04 19:16:32 +02:00
|
|
|
if (errno == EINTR)
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-07-04 09:54:40 +02:00
|
|
|
LOG_ERRNO("failed to poll file descriptors");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 18:08:15 +02:00
|
|
|
/* Delayed rendering timers (created when we receive input) */
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[DELAY_LOWER_FD].revents & POLLIN ||
|
|
|
|
|
fds[DELAY_UPPER_FD].revents & POLLIN)
|
|
|
|
|
{
|
2019-08-30 17:57:46 +02:00
|
|
|
assert(timeout_is_armed);
|
|
|
|
|
|
|
|
|
|
uint64_t unused;
|
2019-09-20 18:08:15 +02:00
|
|
|
ssize_t ret1 = 0;
|
|
|
|
|
ssize_t ret2 = 0;
|
2019-07-04 09:54:40 +02:00
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[DELAY_LOWER_FD].revents & POLLIN)
|
2019-09-20 22:21:06 +02:00
|
|
|
ret1 = read(delay_render_timer_lower, &unused, sizeof(unused));
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[DELAY_UPPER_FD].revents & POLLIN)
|
2019-09-20 22:21:06 +02:00
|
|
|
ret2 = read(delay_render_timer_upper, &unused, sizeof(unused));
|
2019-09-20 18:08:15 +02:00
|
|
|
|
|
|
|
|
if ((ret1 < 0 || ret2 < 0) && errno != EAGAIN)
|
2019-08-30 17:57:46 +02:00
|
|
|
LOG_ERRNO("failed to read timeout timer");
|
2019-09-20 18:08:15 +02:00
|
|
|
else if (ret1 > 0 || ret2 > 0) {
|
2019-08-30 17:57:46 +02:00
|
|
|
render_refresh(&term);
|
2019-09-20 18:08:15 +02:00
|
|
|
|
|
|
|
|
/* Reset timers */
|
|
|
|
|
timeout_is_armed = false;
|
2019-09-20 22:21:06 +02:00
|
|
|
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);
|
2019-09-20 18:08:15 +02:00
|
|
|
} else
|
|
|
|
|
assert(false);
|
2019-08-30 17:57:46 +02:00
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[WL_FD].revents & POLLIN) {
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_display_dispatch(term.wl.display);
|
|
|
|
|
if (term.quit) {
|
2019-06-13 21:55:32 +02:00
|
|
|
ret = EXIT_SUCCESS;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
}
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[WL_FD].revents & POLLHUP) {
|
2019-06-12 20:08:54 +02:00
|
|
|
LOG_WARN("disconnected from wayland");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[PTMX_FD].revents & POLLIN) {
|
2019-07-30 18:07:44 +02:00
|
|
|
uint8_t data[24 * 1024];
|
2019-07-05 10:16:56 +02:00
|
|
|
ssize_t count = read(term.ptmx, data, sizeof(data));
|
2019-08-05 20:30:06 +02:00
|
|
|
if (count < 0 && errno != EAGAIN) {
|
|
|
|
|
LOG_ERRNO("failed to read from pseudo terminal");
|
2019-07-04 09:54:40 +02:00
|
|
|
break;
|
2019-07-03 15:16:38 +02:00
|
|
|
}
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
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).
|
|
|
|
|
*
|
2019-09-20 18:08:15 +02:00
|
|
|
* 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.
|
|
|
|
|
*
|
2019-08-05 20:30:06 +02:00
|
|
|
* 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?
|
|
|
|
|
*/
|
2019-09-20 18:08:15 +02:00
|
|
|
if (term.render.frame_callback == NULL) {
|
|
|
|
|
/* First timeout - reset each time we receive input. */
|
2019-09-20 22:21:06 +02:00
|
|
|
timerfd_settime(
|
|
|
|
|
delay_render_timer_lower, 0,
|
|
|
|
|
&(struct itimerspec){.it_value = {.tv_nsec = 1000000}},
|
|
|
|
|
NULL);
|
2019-09-20 18:08:15 +02:00
|
|
|
|
2019-09-21 20:09:06 +02:00
|
|
|
/* Second timeout - only reset when we render. Set to one frame (assuming 60Hz) */
|
2019-09-20 18:08:15 +02:00
|
|
|
if (!timeout_is_armed) {
|
2019-09-20 22:21:06 +02:00
|
|
|
timerfd_settime(
|
|
|
|
|
delay_render_timer_upper, 0,
|
|
|
|
|
&(struct itimerspec){.it_value = {.tv_nsec = 16666666}},
|
|
|
|
|
NULL);
|
2019-09-20 18:08:15 +02:00
|
|
|
timeout_is_armed = true;
|
|
|
|
|
}
|
2019-08-30 17:57:46 +02:00
|
|
|
}
|
2019-08-05 20:30:06 +02:00
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[PTMX_FD].revents & POLLHUP) {
|
2019-06-13 15:19:10 +02:00
|
|
|
ret = EXIT_SUCCESS;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[KBD_REPEAT_FD].revents & POLLIN) {
|
2019-08-05 19:33:01 +02:00
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
term.kbd.repeat.fd, &expiration_count, sizeof(expiration_count));
|
2019-06-19 10:04:47 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
if (ret < 0 && errno != EAGAIN)
|
2019-08-05 19:33:01 +02:00
|
|
|
LOG_ERRNO("failed to read repeat key from repeat timer fd");
|
2019-08-05 20:30:06 +02:00
|
|
|
else if (ret > 0) {
|
2019-08-05 19:33:01 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2019-06-19 10:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[FLASH_FD].revents & POLLIN) {
|
2019-07-21 19:14:19 +02:00
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
2019-07-22 19:15:56 +02:00
|
|
|
term.flash.fd, &expiration_count, sizeof(expiration_count));
|
2019-07-21 19:14:19 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
if (ret < 0 && errno != EAGAIN)
|
2019-07-21 19:14:19 +02:00
|
|
|
LOG_ERRNO("failed to read flash timer");
|
2019-08-05 20:30:06 +02:00
|
|
|
else if (ret > 0) {
|
2019-07-21 19:14:19 +02:00
|
|
|
LOG_DBG("flash timer expired %llu times",
|
|
|
|
|
(unsigned long long)expiration_count);
|
|
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
term.flash.active = false;
|
|
|
|
|
term_damage_view(&term);
|
|
|
|
|
render_refresh(&term);
|
|
|
|
|
}
|
2019-07-21 19:14:19 +02:00
|
|
|
}
|
2019-07-21 20:11:20 +02:00
|
|
|
|
2019-09-20 22:27:27 +02:00
|
|
|
if (fds[BLINK_FD].revents & POLLIN) {
|
2019-07-21 20:11:20 +02:00
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
2019-07-22 19:17:57 +02:00
|
|
|
term.blink.fd, &expiration_count, sizeof(expiration_count));
|
2019-07-21 20:11:20 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
if (ret < 0 && errno != EAGAIN)
|
2019-07-21 20:11:20 +02:00
|
|
|
LOG_ERRNO("failed to read blink timer");
|
2019-08-05 20:30:06 +02:00
|
|
|
else if (ret > 0) {
|
2019-07-21 20:11:20 +02:00
|
|
|
LOG_DBG("blink timer expired %llu times",
|
|
|
|
|
(unsigned long long)expiration_count);
|
|
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
term.blink.state = term.blink.state == BLINK_ON
|
|
|
|
|
? BLINK_OFF : BLINK_ON;
|
2019-07-21 20:15:18 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
/* 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];
|
2019-07-30 18:03:03 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
if (cell->attrs.blink) {
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
}
|
2019-07-21 20:15:18 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-21 20:11:20 +02:00
|
|
|
|
2019-08-05 20:30:06 +02:00
|
|
|
render_refresh(&term);
|
|
|
|
|
}
|
2019-07-21 20:11:20 +02:00
|
|
|
}
|
2019-06-13 15:19:10 +02:00
|
|
|
}
|
2019-06-12 20:08:54 +02:00
|
|
|
|
2019-09-20 22:21:06 +02:00
|
|
|
close(delay_render_timer_lower);
|
|
|
|
|
close(delay_render_timer_upper);
|
2019-08-30 17:57:46 +02:00
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
out:
|
2019-07-29 20:13:26 +02:00
|
|
|
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);
|
|
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
shm_fini();
|
2019-08-12 21:22:38 +02:00
|
|
|
|
2019-08-12 21:49:17 +02:00
|
|
|
tll_free(term.wl.on_outputs);
|
2019-08-12 21:22:38 +02:00
|
|
|
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);
|
|
|
|
|
|
2019-08-21 17:53:52 +02:00
|
|
|
free(term.wl.pointer.theme_name);
|
2019-07-05 10:44:09 +02:00
|
|
|
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);
|
2019-07-08 13:57:31 +02:00
|
|
|
if (term.wl.keyboard != NULL)
|
|
|
|
|
wl_keyboard_destroy(term.wl.keyboard);
|
2019-07-11 12:16:50 +02:00
|
|
|
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);
|
2019-07-11 17:02:21 +02:00
|
|
|
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);
|
2019-07-08 13:57:31 +02:00
|
|
|
if (term.wl.seat != NULL)
|
|
|
|
|
wl_seat_destroy(term.wl.seat);
|
2019-08-29 20:15:33 +02:00
|
|
|
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);
|
2019-08-30 17:55:45 +02:00
|
|
|
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);
|
2019-08-29 20:15:33 +02:00
|
|
|
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);
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.wl.shell != NULL)
|
|
|
|
|
xdg_wm_base_destroy(term.wl.shell);
|
2019-08-29 20:15:33 +02:00
|
|
|
if (term.wl.surface != NULL)
|
|
|
|
|
wl_surface_destroy(term.wl.surface);
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.wl.shm != NULL)
|
|
|
|
|
wl_shm_destroy(term.wl.shm);
|
2019-08-29 20:17:53 +02:00
|
|
|
if (term.wl.sub_compositor != NULL)
|
|
|
|
|
wl_subcompositor_destroy(term.wl.sub_compositor);
|
2019-07-05 10:16:56 +02:00
|
|
|
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);
|
2019-07-09 10:00:54 +02:00
|
|
|
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);
|
2019-07-08 13:57:31 +02:00
|
|
|
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);
|
|
|
|
|
|
2019-07-19 08:59:35 +02:00
|
|
|
free(term.vt.osc.data);
|
2019-07-10 16:27:55 +02:00
|
|
|
for (int row = 0; row < term.normal.num_rows; row++)
|
|
|
|
|
grid_row_free(term.normal.rows[row]);
|
2019-07-08 13:57:31 +02:00
|
|
|
free(term.normal.rows);
|
2019-07-10 16:27:55 +02:00
|
|
|
for (int row = 0; row < term.alt.num_rows; row++)
|
|
|
|
|
grid_row_free(term.alt.rows[row]);
|
2019-07-08 13:57:31 +02:00
|
|
|
free(term.alt.rows);
|
2019-07-21 17:48:06 +02:00
|
|
|
|
2019-07-21 17:35:53 +02:00
|
|
|
free(term.window_title);
|
2019-07-21 17:48:06 +02:00
|
|
|
tll_free_and_free(term.window_title_stack, free);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-28 21:03:38 +02:00
|
|
|
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++)
|
|
|
|
|
font_destroy(&term.fonts[i]);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-08-27 19:43:50 +02:00
|
|
|
free(term.search.buf);
|
|
|
|
|
|
2019-07-22 19:15:56 +02:00
|
|
|
if (term.flash.fd != -1)
|
|
|
|
|
close(term.flash.fd);
|
2019-07-22 19:17:57 +02:00
|
|
|
if (term.blink.fd != -1)
|
|
|
|
|
close(term.blink.fd);
|
2019-08-05 19:33:01 +02:00
|
|
|
if (term.kbd.repeat.fd != -1)
|
|
|
|
|
close(term.kbd.repeat.fd);
|
2019-07-21 19:14:19 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
if (term.ptmx != -1)
|
|
|
|
|
close(term.ptmx);
|
2019-06-13 15:19:10 +02:00
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
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);
|
|
|
|
|
|
2019-08-23 17:23:47 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 11:52:22 +02:00
|
|
|
config_free(conf);
|
2019-06-12 20:08:54 +02:00
|
|
|
return ret;
|
2019-07-28 21:03:38 +02:00
|
|
|
|
2019-06-12 20:08:54 +02:00
|
|
|
}
|