mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-06 04:06:06 -05:00
Merge branch 'multi-window'
This commit is contained in:
commit
25311fc4eb
19 changed files with 925 additions and 173 deletions
2
PKGBUILD
2
PKGBUILD
|
|
@ -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
153
client.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
completions/zsh/_footclient
Normal file
13
completions/zsh/_footclient
Normal 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
68
fdm.c
|
|
@ -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
1
fdm.h
|
|
@ -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
52
font.c
|
|
@ -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
63
main.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
16
render.c
16
render.c
|
|
@ -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
324
server.c
Normal 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
9
server.h
Normal 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
13
slave.c
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
slave.h
2
slave.h
|
|
@ -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);
|
||||
|
|
|
|||
256
terminal.c
256
terminal.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
90
wayland.c
90
wayland.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue