Merge branch 'multi-window'

This commit is contained in:
Daniel Eklöf 2019-11-01 23:49:54 +01:00
commit 25311fc4eb
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
19 changed files with 925 additions and 173 deletions

View file

@ -22,6 +22,8 @@ build() {
./foot --term=xterm -- sh -c "../scripts/generate-alt-random-writes.py --scroll --scroll-region --colors-regular --colors-bright --colors-rgb ${tmp_file} && cat ${tmp_file}"
rm "${tmp_file}"
./footclient --version
meson configure -Db_pgo=use
ninja
}

153
client.c Normal file
View file

@ -0,0 +1,153 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/un.h>
#define LOG_MODULE "foot-client"
#include "log.h"
#include "version.h"
static volatile sig_atomic_t aborted = 0;
static void
sig_handler(int signo)
{
aborted = 1;
}
static void
print_usage(const char *prog_name)
{
printf("Usage: %s [OPTIONS]...\n", prog_name);
printf("\n");
printf("Options:\n");
printf(" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
" -v,--version show the version number and quit\n");
}
int
main(int argc, char *const *argv)
{
int ret = EXIT_FAILURE;
const char *const prog_name = argv[0];
static const struct option longopts[] = {
{"term", required_argument, 0, 't'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{NULL, no_argument, 0, 0},
};
const char *term = "";
while (true) {
int c = getopt_long(argc, argv, ":t:hv", longopts, NULL);
if (c == -1)
break;
switch (c) {
case 't':
term = optarg;
break;
case 'v':
printf("footclient version %s\n", FOOT_VERSION);
return EXIT_SUCCESS;
case 'h':
print_usage(prog_name);
return EXIT_SUCCESS;
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;
}
}
argc -= optind;
argv += optind;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
LOG_ERRNO("failed to create socket");
goto err;
}
bool connected = false;
struct sockaddr_un addr = {.sun_family = AF_UNIX};
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime != NULL) {
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/foot.sock", xdg_runtime);
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
connected = true;
}
if (!connected) {
strncpy(addr.sun_path, "/tmp/foot.sock", sizeof(addr.sun_path) - 1);
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOG_ERRNO("failed to connect (is 'foot --server' running?)");
goto err;
}
}
uint16_t term_len = strlen(term);
if (send(fd, &term_len, sizeof(term_len), 0) != sizeof(term_len) ||
send(fd, term, term_len, 0) != term_len)
{
LOG_ERRNO("failed to send TERM to server");
goto err;
}
if (send(fd, &argc, sizeof(argc), 0) != sizeof(argc)) {
LOG_ERRNO("failed to send argc/argv to server");
goto err;
}
for (int i = 0; i < argc; i++) {
uint16_t len = strlen(argv[i]);
if (send(fd, &len, sizeof(len), 0) != sizeof(len) ||
send(fd, argv[i], len, 0) != sizeof(len))
{
LOG_ERRNO("failed to send argc/argv to server");
goto err;
}
}
const struct sigaction sa = {.sa_handler = &sig_handler};
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
LOG_ERRNO("failed to register signal handlers");
goto err;
}
int exit_code;
ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
if (rcvd == -1 && errno == EINTR)
assert(aborted);
else if (rcvd != sizeof(exit_code))
LOG_ERRNO("failed to read server response");
else
ret = exit_code;
err:
if (fd != -1)
close(fd);
return ret;
}

View file

@ -1,2 +1,3 @@
zsh_install_dir = join_paths(get_option('datadir'), 'zsh/site-functions')
install_data('zsh/_foot', install_dir: zsh_install_dir)
install_data('zsh/_footclient', install_dir: zsh_install_dir)

View file

@ -2,13 +2,19 @@
_arguments \
-s \
'(-v --version)'{-v,--version}'[show the version number and quit]' \
'(-h --help)'{-h,--help}'[show help message and quit]' \
'(-f --font)'{-f,--font}'[font name and style in fontconfig format (monospace)]:font:->fonts' \
'(-g --geometry)'{-g,--geometry}'[window WIDTHxHEIGHT, in pixels (84x24 cells)]'
'(-t,--term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
'(-g --geometry)'{-g,--geometry}'[window WIDTHxHEIGHT, in pixels (84x24 cells)]' \
'(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]' \
'(-v --version)'{-v,--version}'[show the version number and quit]' \
'(-h --help)'{-h,--help}'[show help message and quit]'
case "${state}" in
case ${state} in
fonts)
_values 'font families' $(fc-list : family | tr -d ' ')
;;
terms)
_values 'terminal definitions' $(find /usr/share/terminfo -type f -printf "%f\n")
;;
esac

View file

@ -0,0 +1,13 @@
#compdef footclient
_arguments \
-s \
'(-t,--term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
'(-v --version)'{-v,--version}'[show the version number and quit]' \
'(-h --help)'{-h,--help}'[show help message and quit]'
case ${state} in
terms)
_values 'terminal definitions' $(find /usr/share/terminfo -type f -printf "%f\n")
;;
esac

68
fdm.c
View file

@ -1,6 +1,7 @@
#include "fdm.h"
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
@ -15,11 +16,14 @@ struct fd {
int fd;
void *data;
fdm_handler_t handler;
bool deleted;
};
struct fdm {
int epoll_fd;
tll(struct fd) fds;
bool is_polling;
tll(struct fd *) fds;
tll(struct fd *) deferred_delete;
};
struct fdm *
@ -34,7 +38,9 @@ fdm_init(void)
struct fdm *fdm = malloc(sizeof(*fdm));
*fdm = (struct fdm){
.epoll_fd = epoll_fd,
.is_polling = false,
.fds = tll_init(),
.deferred_delete = tll_init(),
};
return fdm;
}
@ -60,23 +66,31 @@ 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) {
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 fd *fd_data = malloc(sizeof(*fd_data));
*fd_data = (struct fd) {
.fd = fd,
.data = data,
.handler = handler,
.deleted = false,
};
tll_push_back(fdm->fds, fd_data);
struct epoll_event ev = {
.events = events,
.data = {.ptr = &tll_back(fdm->fds)},
.data = {.ptr = fd_data},
};
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
LOG_ERRNO("failed to register FD with epoll");
free(fd_data);
tll_pop_back(fdm->fds);
return false;
}
@ -84,14 +98,26 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
return true;
}
bool
fdm_del(struct fdm *fdm, int fd)
static bool
fdm_del_internal(struct fdm *fdm, int fd, bool close_fd)
{
if (fd == -1)
return true;
tll_foreach(fdm->fds, it) {
if (it->item.fd == fd) {
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);
if (close_fd)
close(it->item->fd);
it->item->deleted = true;
if (fdm->is_polling)
tll_push_back(fdm->deferred_delete, it->item);
else
free(it->item);
tll_remove(fdm->fds, it);
return true;
}
@ -101,6 +127,18 @@ fdm_del(struct fdm *fdm, int fd)
return false;
}
bool
fdm_del(struct fdm *fdm, int fd)
{
return fdm_del_internal(fdm, fd, true);
}
bool
fdm_del_no_close(struct fdm *fdm, int fd)
{
return fdm_del_internal(fdm, fd, false);
}
bool
fdm_poll(struct fdm *fdm)
{
@ -114,10 +152,22 @@ fdm_poll(struct fdm *fdm)
return false;
}
fdm->is_polling = true;
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))
if (fd->deleted)
continue;
if (!fd->handler(fdm, fd->fd, events[i].events, fd->data)) {
fdm->is_polling = false;
return false;
}
}
fdm->is_polling = false;
tll_foreach(fdm->deferred_delete, it) {
free(it->item);
tll_remove(fdm->deferred_delete, it);
}
return true;

1
fdm.h
View file

@ -11,5 +11,6 @@ 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_del_no_close(struct fdm *fdm, int fd);
bool fdm_poll(struct fdm *fdm);

52
font.c
View file

@ -22,6 +22,13 @@ static mtx_t ft_lock;
static const size_t cache_size = 512;
struct font_cache_entry {
uint64_t hash;
struct font *font;
};
static tll(struct font_cache_entry) font_cache = tll_init();
static void __attribute__((constructor))
init(void)
{
@ -33,6 +40,9 @@ init(void)
static void __attribute__((destructor))
fini(void)
{
while (tll_length(font_cache) > 0)
font_destroy(tll_front(font_cache).font);
mtx_destroy(&ft_lock);
FT_Done_FreeType(ft_lib);
FcFini();
@ -275,12 +285,46 @@ from_name(const char *name, bool is_fallback)
return font;
}
static uint64_t
sdbm_hash(const char *s)
{
uint64_t hash = 0;
for (; *s != '\0'; s++) {
int c = *s;
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
static uint64_t
font_hash(font_list_t names, const char *attributes)
{
uint64_t hash = 0;
tll_foreach(names, it)
hash ^= sdbm_hash(it->item);
if (attributes != NULL)
hash ^= sdbm_hash(attributes);
return hash;
}
struct font *
font_from_name(font_list_t names, const char *attributes)
{
if (tll_length(names) == 0)
return false;
uint64_t hash = font_hash(names, attributes);
tll_foreach(font_cache, it) {
if (it->item.hash == hash) {
it->item.font->ref_counter++;
return it->item.font;
}
}
struct font *font = NULL;
bool have_attrs = attributes != NULL && strlen(attributes) > 0;
@ -312,6 +356,7 @@ font_from_name(font_list_t names, const char *attributes)
font->fallbacks, ((struct font_fallback){.pattern = strdup(name)}));
}
tll_push_back(font_cache, ((struct font_cache_entry){.hash = hash, .font = font}));
return font;
}
@ -605,6 +650,13 @@ font_destroy(struct font *font)
if (--font->ref_counter > 0)
return;
tll_foreach(font_cache, it) {
if (it->item.font == font) {
tll_remove(font_cache, it);
break;
}
}
free(font->name);
tll_foreach(font->fallbacks, it) {

63
main.c
View file

@ -5,6 +5,7 @@
#include <stdbool.h>
#include <locale.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <sys/sysinfo.h>
@ -16,6 +17,7 @@
#include "config.h"
#include "fdm.h"
#include "font.h"
#include "server.h"
#include "shm.h"
#include "terminal.h"
#include "version.h"
@ -23,17 +25,38 @@
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
static volatile sig_atomic_t aborted = 0;
static void
sig_handler(int signo)
{
aborted = 1;
}
static void
print_usage(const char *prog_name)
{
printf("Usage: %s [OPTION]...\n", prog_name);
printf("Usage: %s [OPTIONS]...\n", prog_name);
printf("\n");
printf("Options:\n");
printf(" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
" -g,--geometry=WIDTHxHEIGHT set initial width and height\n"
" -s,--server run as a server\n"
" -v,--version show the version number and quit\n");
printf("\n");
}
struct shutdown_context {
struct terminal **term;
int exit_code;
};
static void
term_shutdown_cb(void *data, int exit_code)
{
struct shutdown_context *ctx = data;
*ctx->term = NULL;
ctx->exit_code = exit_code;
}
int
@ -55,11 +78,14 @@ main(int argc, char *const *argv)
{"term", required_argument, 0, 't'},
{"font", required_argument, 0, 'f'},
{"geometry", required_argument, 0, 'g'},
{"server", no_argument, 0, 's'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{NULL, no_argument, 0, 0},
};
bool as_server = false;
while (true) {
int c = getopt_long(argc, argv, ":t:f:g:vh", longopts, NULL);
if (c == -1)
@ -105,6 +131,10 @@ main(int argc, char *const *argv)
break;
}
case 's':
as_server = true;
break;
case 'v':
printf("foot version %s\n", FOOT_VERSION);
config_free(conf);
@ -131,11 +161,12 @@ main(int argc, char *const *argv)
argv += optind;
setlocale(LC_ALL, "");
setenv("TERM", conf.term, 1);
struct fdm *fdm = NULL;
struct wayland *wayl = NULL;
struct terminal *term = NULL;
struct server *server = NULL;
struct shutdown_context shutdown_ctx = {.term = &term, .exit_code = EXIT_FAILURE};
if ((fdm = fdm_init()) == NULL)
goto out;
@ -143,27 +174,37 @@ main(int argc, char *const *argv)
if ((wayl = wayl_init(fdm)) == NULL)
goto out;
if ((term = term_init(&conf, fdm, wayl, argc, argv)) == NULL)
if (!as_server && (term = term_init(&conf, fdm, wayl, conf.term, argc, argv,
&term_shutdown_cb, &shutdown_ctx)) == NULL)
goto out;
while (true) {
wl_display_flush(term->wl->display); /* TODO: figure out how to get rid of this */
if (as_server && (server = server_init(&conf, fdm, wayl)) == NULL)
goto out;
const struct sigaction sa = {.sa_handler = &sig_handler};
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
LOG_ERRNO("failed to register signal handlers");
goto out;
}
if (as_server)
LOG_INFO("running as server; launch terminals by running footclient");
while (!aborted && (as_server || tll_length(wayl->terms) > 0)) {
if (!fdm_poll(fdm))
break;
}
if (term->quit)
ret = EXIT_SUCCESS;
ret = aborted || tll_length(wayl->terms) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
out:
shm_fini();
int child_ret = term_destroy(term);
server_destroy(server);
term_destroy(term);
wayl_destroy(wayl);
fdm_destroy(fdm);
config_free(conf);
return ret == EXIT_SUCCESS ? child_ret : ret;
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
}

View file

@ -78,6 +78,7 @@ executable(
'render.c', 'render.h',
'search.c', 'search.h',
'selection.c', 'selection.h',
'server.c', 'server.h',
'shm.c', 'shm.h',
'slave.c', 'slave.h',
'terminal.c', 'terminal.h',
@ -89,6 +90,12 @@ executable(
dependencies: [threads, math, freetype, fontconfig, pixman, wayland_client, wayland_cursor, xkb],
install: true)
executable(
'footclient',
'client.c',
'log.c', 'log.h',
install: true)
custom_target(
'terminfo',
output: 'f',

View file

@ -693,6 +693,7 @@ grid_render(struct terminal *term)
if (all_clean) {
buf->busy = false;
wl_display_flush(term->wl->display);
return;
}
@ -727,6 +728,7 @@ grid_render(struct terminal *term)
LOG_INFO("frame rendered in %lds %ldus",
render_time.tv_sec, render_time.tv_usec);
#endif
wl_display_flush(term->wl->display);
}
static void
@ -948,9 +950,21 @@ render_resize(struct terminal *term, int width, int height)
}
void
render_set_title(struct terminal *term, const char *title)
render_set_title(struct terminal *term, const char *_title)
{
/* TODO: figure out what the limit actually is */
static const size_t max_len = 100;
const char *title = _title;
char *copy = NULL;
if (strlen(title) > max_len) {
copy = strndup(_title, max_len);
title = copy;
}
xdg_toplevel_set_title(term->window->xdg_toplevel, title);
free(copy);
}
void

324
server.c Normal file
View file

@ -0,0 +1,324 @@
#include "server.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <linux/un.h>
#define LOG_MODULE "server"
#define LOG_ENABLE_DBG 1
#include "log.h"
#include "terminal.h"
#include "tllist.h"
struct client;
struct server {
const struct config *conf;
struct fdm *fdm;
struct wayland *wayl;
int fd;
char *sock_path;
tll(struct client *) clients;
};
struct client {
struct server *server;
int fd;
struct terminal *term;
int argc;
char **argv;
};
static void
client_destroy(struct client *client)
{
if (client == NULL)
return;
if (client->term != NULL) {
LOG_WARN("client FD=%d: terminal still alive", client->fd);
term_destroy(client->term);
}
if (client->argv != NULL) {
for (int i = 0; i < client->argc; i++)
free(client->argv[i]);
free(client->argv);
}
if (client->fd != -1) {
LOG_DBG("client FD=%d: disconnected", client->fd);
fdm_del(client->server->fdm, client->fd);
}
tll_foreach(client->server->clients, it) {
if (it->item == client) {
tll_remove(client->server->clients, it);
break;
}
}
free(client);
}
static void
client_send_exit_code(struct client *client, int exit_code)
{
if (client->fd == -1)
return;
if (write(client->fd, &exit_code, sizeof(exit_code)) != sizeof(exit_code))
LOG_ERRNO("failed to write slave exit code to client");
}
static void
term_shutdown_handler(void *data, int exit_code)
{
struct client *client = data;
client_send_exit_code(client, exit_code);
client->term = NULL;
client_destroy(client);
}
static bool
fdm_client(struct fdm *fdm, int fd, int events, void *data)
{
struct client *client = data;
struct server *server = client->server;
char *term_env = NULL;
if (events & EPOLLHUP)
goto shutdown;
assert(events & EPOLLIN);
uint16_t term_env_len;
if (recv(fd, &term_env_len, sizeof(term_env_len), 0) != sizeof(term_env_len))
goto shutdown;
term_env = malloc(term_env_len + 1);
term_env[term_env_len] = '\0';
if (recv(fd, term_env, term_env_len, 0) != term_env_len)
goto shutdown;
if (recv(fd, &client->argc, sizeof(client->argc), 0) != sizeof(client->argc))
goto shutdown;
client->argv = calloc(client->argc + 1, sizeof(client->argv[0]));
for (int i = 0; i < client->argc; i++) {
uint16_t len;
if (recv(fd, &len, sizeof(len), 0) != sizeof(len))
goto shutdown;
client->argv[i] = malloc(len + 1);
client->argv[i][len] = '\0';
if (recv(fd, client->argv[i], len, 0) != len)
goto shutdown;
}
assert(client->term == NULL);
client->term = term_init(
server->conf, server->fdm, server->wayl,
term_env_len > 0 ? term_env : server->conf->term,
client->argc, client->argv, &term_shutdown_handler, client);
if (client->term == NULL) {
LOG_ERR("failed to instantiate new terminal");
goto shutdown;
}
free(term_env);
return true;
shutdown:
LOG_DBG("client FD=%d: disconnected", client->fd);
free(term_env);
fdm_del(fdm, fd);
client->fd = -1;
if (client->term != NULL && !client->term->is_shutting_down)
term_shutdown(client->term);
else
client_destroy(client);
return true;
}
static bool
fdm_server(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct server *server = data;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
int client_fd = accept4(
server->fd, (struct sockaddr *)&addr, &addr_size, SOCK_CLOEXEC);
if (client_fd == -1) {
LOG_ERRNO("failed to accept client connection");
return false;
}
struct client *client = malloc(sizeof(*client));
*client = (struct client) {
.server = server,
.fd = client_fd,
};
if (!fdm_add(server->fdm, client_fd, EPOLLIN, &fdm_client, client)) {
LOG_ERR("client FD=%d: failed to add client to FDM", client_fd);
close(client_fd);
free(client);
return false;
}
LOG_DBG("client FD=%d: connected", client_fd);
tll_push_back(server->clients, client);
return true;
}
static char *
get_socket_path(void)
{
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime == NULL)
return strdup("/tmp/foot.sock");
char *path = malloc(strlen(xdg_runtime) + 1 + strlen("foot.sock") + 1);
sprintf(path, "%s/foot.sock", xdg_runtime);
return path;
}
enum connect_status {CONNECT_ERR, CONNECT_FAIL, CONNECT_SUCCESS};
static enum connect_status
try_connect(const char *sock_path)
{
enum connect_status ret = CONNECT_ERR;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
LOG_ERRNO("failed to create UNIX socket");
goto err;
}
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
ret = connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0
? CONNECT_FAIL : CONNECT_SUCCESS;
err:
if (fd != -1)
close(fd);
return ret;
}
struct server *
server_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
LOG_ERRNO("failed to create UNIX socket");
return NULL;
}
struct server *server = NULL;
char *sock_path = NULL;
if ((sock_path = get_socket_path()) == NULL)
goto err;
switch (try_connect(sock_path)) {
case CONNECT_FAIL:
break;
case CONNECT_SUCCESS:
LOG_ERR("foot --server already running");
/* FALLTHROUGH */
case CONNECT_ERR:
goto err;
}
unlink(sock_path);
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FD_CLOEXEC) < 0) {
LOG_ERRNO("failed to set FD_CLOEXEC on socket");
goto err;
}
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOG_ERRNO("%s: failed to bind", addr.sun_path);
goto err;
}
if (listen(fd, 0) < 0) {
LOG_ERRNO("%s: failed to listen", addr.sun_path);
goto err;
}
server = malloc(sizeof(*server));
*server = (struct server) {
.conf = conf,
.fdm = fdm,
.wayl = wayl,
.fd = fd,
.sock_path = sock_path,
.clients = tll_init(),
};
if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) {
LOG_ERR("failed to add server FD to the FDM");
goto err;
}
return server;
err:
free(server);
free(sock_path);
if (fd != -1)
close(fd);
return NULL;
}
void
server_destroy(struct server *server)
{
if (server == NULL)
return;
LOG_DBG("server destroy, %zu clients still alive",
tll_length(server->clients));
tll_foreach(server->clients, it) {
client_send_exit_code(it->item, 1);
client_destroy(it->item);
}
tll_free(server->clients);
fdm_del(server->fdm, server->fd);
free(server->sock_path);
free(server);
}

9
server.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include "fdm.h"
#include "config.h"
#include "wayland.h"
struct server;
struct server *server_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl);
void server_destroy(struct server *server);

13
slave.c
View file

@ -1,5 +1,6 @@
#define _XOPEN_SOURCE 500
#include "slave.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@ -68,7 +69,7 @@ err:
pid_t
slave_spawn(int ptmx, int argc, char *const *argv,
const char *conf_shell)
const char *term_env, const char *conf_shell)
{
int fork_pipe[2];
if (pipe2(fork_pipe, O_CLOEXEC) < 0) {
@ -88,6 +89,8 @@ slave_spawn(int ptmx, int argc, char *const *argv,
/* Child */
close(fork_pipe[0]); /* Close read end */
setenv("TERM", term_env, 1);
char **_shell_argv = NULL;
char *const *shell_argv = argv;
@ -107,7 +110,7 @@ slave_spawn(int ptmx, int argc, char *const *argv,
default: {
close(fork_pipe[1]); /* Close write end */
LOG_DBG("slave has PID %d", term->slave);
LOG_DBG("slave has PID %d", pid);
int _errno;
static_assert(sizeof(errno) == sizeof(_errno), "errno size mismatch");
@ -119,11 +122,11 @@ slave_spawn(int ptmx, int argc, char *const *argv,
LOG_ERRNO("failed to read from pipe");
return -1;
} else if (ret == sizeof(_errno)) {
LOG_ERRNO(
"%s: failed to execute", argc == 0 ? conf_shell : argv[0]);
LOG_ERRNO_P(
"%s: failed to execute", _errno, argc == 0 ? conf_shell : argv[0]);
return -1;
} else
LOG_DBG("%s: successfully started", conf->shell);
LOG_DBG("%s: successfully started", conf_shell);
break;
}
}

View file

@ -4,4 +4,4 @@
#include <sys/types.h>
pid_t slave_spawn(
int ptmx, int argc, char *const *argv, const char *conf_shell);
int ptmx, int argc, char *const *argv, const char *term_env, const char *conf_shell);

View file

@ -8,6 +8,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <fcntl.h>
#include <linux/input-event-codes.h>
@ -30,12 +31,8 @@ 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;
}
if ((events & EPOLLHUP) && !(events & EPOLLIN))
return term_shutdown(term);
assert(events & EPOLLIN);
@ -43,9 +40,6 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
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;
}
@ -101,7 +95,10 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
}
}
return !(events & EPOLLHUP);
if (events & EPOLLHUP)
return term_shutdown(term);
return true;
}
static bool
@ -116,9 +113,6 @@ fdm_flash(struct fdm *fdm, int fd, int events, void *data)
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;
}
@ -144,9 +138,6 @@ fdm_blink(struct fdm *fdm, int fd, int events, void *data)
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;
}
@ -181,7 +172,8 @@ fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data)
return false;
struct terminal *term = data;
assert(term->delayed_render_timer.is_armed);
if (!term->delayed_render_timer.is_armed)
return true;
uint64_t unused;
ssize_t ret1 = 0;
@ -192,17 +184,18 @@ fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data)
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)
if ((ret1 < 0 || ret2 < 0)) {
LOG_ERRNO("failed to read timeout timer");
else if (ret1 > 0 || ret2 > 0) {
render_refresh(term);
return false;
}
/* 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);
render_refresh(term);
/* Reset timers */
struct itimerspec reset = {{0}};
timerfd_settime(term->delayed_render_timer.lower_fd, 0, &reset, NULL);
timerfd_settime(term->delayed_render_timer.upper_fd, 0, &reset, NULL);
term->delayed_render_timer.is_armed = false;
return true;
}
@ -296,7 +289,8 @@ initialize_fonts(struct terminal *term, const struct config *conf)
struct terminal *
term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
int argc, char *const *argv)
const char *term_env, int argc, char *const *argv,
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
{
int ptmx = -1;
int flash_fd = -1;
@ -304,29 +298,50 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
int delay_lower_fd = -1;
int delay_upper_fd = -1;
struct terminal *term = NULL;
struct terminal *term = malloc(sizeof(*term));
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) {
if ((flash_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC)) == -1) {
LOG_ERRNO("failed to create flash timer FD");
goto close_fds;
}
if ((blink_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) {
if ((blink_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC)) == -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)
if ((delay_lower_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC)) == -1 ||
(delay_upper_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC)) == -1)
{
LOG_ERRNO("failed to create delayed rendering timer FDs");
goto close_fds;
}
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;
}
/* Initialize configure-based terminal attributes */
term = malloc(sizeof(*term));
*term = (struct terminal) {
.fdm = fdm,
.quit = false,
@ -397,6 +412,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
.lower_fd = delay_lower_fd,
.upper_fd = delay_upper_fd,
},
.shutdown_cb = shutdown_cb,
.shutdown_data = shutdown_data,
};
initialize_color_cube(term);
@ -410,6 +427,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
term->cell_height = (int)ceil(term->fextents.height);
LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
/* Start the slave/client */
if ((term->slave = slave_spawn(term->ptmx, argc, argv, term_env, conf->shell)) == -1)
goto err;
/* Initiailze the Wayland window backend */
if ((term->window = wayl_win_init(wayl)) == NULL)
goto err;
@ -432,47 +453,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
height = max(height, term->cell_height);
render_resize(term, width, height);
/* Start the slave/client */
if (!slave_spawn(term->ptmx, argc, argv, conf->shell))
goto err;
/* 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;
tll_push_back(wayl->terms, term);
return term;
err:
@ -480,37 +461,109 @@ err:
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);
fdm_del(fdm, ptmx);
fdm_del(fdm, flash_fd);
fdm_del(fdm, blink_fd);
fdm_del(fdm, delay_lower_fd);
fdm_del(fdm, delay_upper_fd);
free(term);
return NULL;
}
static bool
fdm_shutdown(struct fdm *fdm, int fd, int events, void *data)
{
LOG_DBG("FDM shutdown");
struct terminal *term = data;
/* Kill the event FD */
fdm_del(term->fdm, fd);
wayl_win_destroy(term->window);
term->window = NULL;
struct wayland *wayl __attribute__((unused)) = term->wl;
assert(wayl->focused != term);
assert(wayl->moused != term);
void (*cb)(void *, int) = term->shutdown_cb;
void *cb_data = term->shutdown_data;
int exit_code = term_destroy(term);
if (cb != NULL)
cb(cb_data, exit_code);
return true;
}
bool
term_shutdown(struct terminal *term)
{
if (term->is_shutting_down)
return true;
term->is_shutting_down = true;
/*
* Close FDs then postpone self-destruction to the next poll
* iteration, by creating an event FD that we trigger immediately.
*/
fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
fdm_del(term->fdm, term->blink.fd);
fdm_del(term->fdm, term->flash.fd);
fdm_del(term->fdm, term->ptmx);
term->delayed_render_timer.lower_fd = -1;
term->delayed_render_timer.upper_fd = -1;
term->blink.fd = -1;
term->flash.fd = -1;
term->ptmx = -1;
int event_fd = eventfd(0, EFD_CLOEXEC);
if (event_fd == -1) {
LOG_ERRNO("failed to create terminal shutdown event FD");
return false;
}
if (!fdm_add(term->fdm, event_fd, EPOLLIN, &fdm_shutdown, term)) {
LOG_ERR("failed to add terminal shutdown event FD to the FDM");
close(event_fd);
return false;
}
if (write(event_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERRNO("failed to send terminal shutdown event");
fdm_del(term->fdm, event_fd);
return false;
}
return true;
}
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);
tll_foreach(term->wl->terms, it) {
if (it->item == term) {
tll_remove(term->wl->terms, it);
break;
}
}
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);
}
fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
fdm_del(term->fdm, term->blink.fd);
fdm_del(term->fdm, term->flash.fd);
fdm_del(term->fdm, term->ptmx);
if (term->window != NULL)
wayl_win_destroy(term->window);
mtx_lock(&term->render.workers.lock);
assert(tll_length(term->render.workers.queue) == 0);
@ -546,21 +599,6 @@ term_destroy(struct terminal *term)
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++) {
if (term->render.workers.threads[i] != 0)
thrd_join(term->render.workers.threads[i], NULL);

View file

@ -287,12 +287,19 @@ struct terminal {
int lower_fd;
int upper_fd;
} delayed_render_timer;
bool is_shutting_down;
void (*shutdown_cb)(void *data, int exit_code);
void *shutdown_data;
};
struct config;
struct terminal *term_init(
const struct config *conf, struct fdm *fdm, struct wayland *wayl,
int argc, char *const *argv);
const char *term_env, int argc, char *const *argv,
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data);
bool term_shutdown(struct terminal *term);
int term_destroy(struct terminal *term);
void term_reset(struct terminal *term, bool hard);

View file

@ -123,8 +123,8 @@ output_scale(void *data, struct wl_output *wl_output, int32_t factor)
mon->scale = factor;
struct terminal *term = mon->wayl->term;
if (term != NULL) {
tll_foreach(mon->wayl->terms, it) {
struct terminal *term = it->item;
render_resize(term, term->width / term->scale, term->height / term->scale);
wayl_reload_cursor_theme(mon->wayl, term);
}
@ -325,9 +325,7 @@ 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);
term_shutdown(term);
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
@ -394,7 +392,7 @@ fdm_wayl(struct fdm *fdm, int fd, int events, void *data)
return false;
}
return event_count != -1 && !wayl->term->quit;
return event_count != -1;
}
static bool
@ -409,9 +407,6 @@ fdm_repeat(struct fdm *fdm, int fd, int events, void *data)
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;
}
@ -515,23 +510,16 @@ wayl_init(struct fdm *fdm)
goto out;
}
wayl->kbd.repeat.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK);
/* All wayland initialization done - make it so */
wl_display_roundtrip(wayl->display);
wayl->kbd.repeat.fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
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;
@ -542,8 +530,6 @@ wayl_init(struct fdm *fdm)
goto out;
}
//wl_display_dispatch_pending(wayl->display);
//wl_display_flush(wayl->display);
return wayl;
out:
@ -558,11 +544,19 @@ 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->terms, it) {
static bool have_warned = false;
if (!have_warned) {
have_warned = true;
LOG_WARN("there are terminals still running");
term_destroy(it->item);
}
}
tll_free(wayl->terms);
fdm_del(wayl->fdm, wayl->kbd.repeat.fd);
tll_foreach(wayl->monitors, it) {
free(it->item.name);
if (it->item.xdg != NULL)
@ -630,7 +624,7 @@ wayl_destroy(struct wayland *wayl)
if (wayl->registry != NULL)
wl_registry_destroy(wayl->registry);
if (wayl->display != NULL) {
fdm_del(wayl->fdm, wl_display_get_fd(wayl->display));
fdm_del_no_close(wayl->fdm, wl_display_get_fd(wayl->display));
wl_display_disconnect(wayl->display);
}
@ -641,6 +635,7 @@ struct wl_window *
wayl_win_init(struct wayland *wayl)
{
struct wl_window *win = calloc(1, sizeof(*win));
win->wayl = wayl;
win->surface = wl_compositor_create_surface(wayl->compositor);
if (win->surface == NULL) {
@ -686,6 +681,28 @@ out:
void
wayl_win_destroy(struct wl_window *win)
{
if (win == NULL)
return;
/*
* First, unmap all surfaces to trigger things like
* keyboard_leave() and wl_pointer_leave().
*
* This ensures we remove all references to *this* window from the
* global wayland struct (since it no longer has neither keyboard
* nor mouse focus).
*/
/* Scrollback search */
wl_surface_attach(win->search_surface, NULL, 0, 0);
wl_surface_commit(win->search_surface);
wl_display_roundtrip(win->wayl->display);
/* Main window */
wl_surface_attach(win->surface, NULL, 0, 0);
wl_surface_commit(win->surface);
wl_display_roundtrip(win->wayl->display);
tll_free(win->on_outputs);
if (win->search_sub_surface != NULL)
wl_subsurface_destroy(win->search_sub_surface);
@ -701,6 +718,9 @@ wayl_win_destroy(struct wl_window *win)
xdg_surface_destroy(win->xdg_surface);
if (win->surface != NULL)
wl_surface_destroy(win->surface);
wl_display_roundtrip(win->wayl->display);
free(win);
}
@ -763,14 +783,24 @@ wayl_update_cursor_surface(struct wayland *wayl, struct terminal *term)
struct terminal *
wayl_terminal_from_surface(struct wayland *wayl, struct wl_surface *surface)
{
assert(surface == wayl->term->window->surface);
return wayl->term;
tll_foreach(wayl->terms, it) {
if (it->item->window->surface == surface)
return it->item;
}
assert(false);
return NULL;
}
struct terminal *
wayl_terminal_from_xdg_toplevel(struct wayland *wayl,
struct xdg_toplevel *toplevel)
{
assert(toplevel == wayl->term->window->xdg_toplevel);
return wayl->term;
tll_foreach(wayl->terms, it) {
if (it->item->window->xdg_toplevel == toplevel)
return it->item;
}
assert(false);
return NULL;
}

View file

@ -72,7 +72,9 @@ struct wl_primary {
uint32_t serial;
};
struct wayland;
struct wl_window {
struct wayland *wayl;
struct wl_surface *surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
@ -145,8 +147,7 @@ struct wayland {
bool have_argb8888;
tll(struct monitor) monitors; /* All available outputs */
/* TODO: turn into a list to support multiple windows */
struct terminal *term;
tll(struct terminal *) terms;
struct terminal *focused;
struct terminal *moused;
};