server/client: add support for sending SIGUSR to footclient

This patch adds the IPC infrastructure necessary to propagate
SIGUSR1/SIGUSR2 from a footclient process to the server process.

By targeting a particular footclient instance, only that particular
instance changes theme. This is different from when targeting the
server process, where all instances change theme.

Closes #2156
This commit is contained in:
Daniel Eklöf 2025-07-31 17:37:19 +02:00
parent 70d99a8051
commit b13a8f12d2
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
6 changed files with 151 additions and 12 deletions

View file

@ -74,6 +74,10 @@
* Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes * Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes
newly spawned client instances to use the selected theme, instead of newly spawned client instances to use the selected theme, instead of
the original one. the original one.
* SIGUSR1/SIGUSR2 can now be sent to `footclient` processes, to change
the theme of that particular instance ([#2156][2156]).
[2156]: https://codeberg.org/dnkl/foot/issues/2156
### Deprecated ### Deprecated

View file

@ -29,3 +29,17 @@ struct client_data {
} __attribute__((packed)); } __attribute__((packed));
_Static_assert(sizeof(struct client_data) == 10, "protocol struct size error"); _Static_assert(sizeof(struct client_data) == 10, "protocol struct size error");
enum client_ipc_code {
FOOT_IPC_SIGUSR,
};
struct client_ipc_hdr {
enum client_ipc_code ipc_code;
uint8_t size;
} __attribute__((packed));
struct client_ipc_sigusr {
int signo;
} __attribute__((packed));

View file

@ -33,13 +33,20 @@ struct string {
typedef tll(struct string) string_list_t; typedef tll(struct string) string_list_t;
static volatile sig_atomic_t aborted = 0; static volatile sig_atomic_t aborted = 0;
static volatile sig_atomic_t sigusr = 0;
static void static void
sig_handler(int signo) sigint_handler(int signo)
{ {
aborted = 1; aborted = 1;
} }
static void
sigusr_handler(int signo)
{
sigusr = signo;
}
static ssize_t static ssize_t
sendall(int sock, const void *_buf, size_t len) sendall(int sock, const void *_buf, size_t len)
{ {
@ -507,15 +514,63 @@ main(int argc, char *const *argv)
if (!send_string_list(fd, &envp)) if (!send_string_list(fd, &envp))
goto err; goto err;
struct sigaction sa = {.sa_handler = &sig_handler}; struct sigaction sa_int = {.sa_handler = &sigint_handler};
sigemptyset(&sa.sa_mask); struct sigaction sa_usr = {.sa_handler = &sigusr_handler};
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) { sigemptyset(&sa_int.sa_mask);
sigemptyset(&sa_usr.sa_mask);
if (sigaction(SIGINT, &sa_int, NULL) < 0 ||
sigaction(SIGTERM, &sa_int, NULL) < 0 ||
sigaction(SIGUSR1, &sa_usr, NULL) < 0 ||
sigaction(SIGUSR2, &sa_usr, NULL) < 0)
{
LOG_ERRNO("failed to register signal handlers"); LOG_ERRNO("failed to register signal handlers");
goto err; goto err;
} }
int exit_code; int exit_code;
ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0); ssize_t rcvd = -1;
while (true) {
rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
const int got_sigusr = sigusr;
sigusr = 0;
if (rcvd < 0 && errno == EINTR) {
if (aborted)
break;
else if (got_sigusr != 0) {
LOG_DBG("sending sigusr %d to server", got_sigusr);
struct {
struct client_ipc_hdr hdr;
struct client_ipc_sigusr sigusr;
} ipc = {
.hdr = {
.ipc_code = FOOT_IPC_SIGUSR,
.size = sizeof(struct client_ipc_sigusr),
},
.sigusr = {
.signo = got_sigusr,
},
};
ssize_t count = send(fd, &ipc, sizeof(ipc), 0);
if (count < 0) {
LOG_ERRNO("failed to send SIGUSR IPC to server");
goto err;
} else if ((size_t)count != sizeof(ipc)) {
LOG_ERR("failed to send SIGUSR IPC to server");
goto err;
}
}
continue;
}
break;
}
if (rcvd == -1 && errno == EINTR) if (rcvd == -1 && errno == EINTR)
xassert(aborted); xassert(aborted);

View file

@ -694,14 +694,14 @@ variables to unset may be defined in *foot.ini*(5).
The following signals have special meaning in foot: The following signals have special meaning in foot:
- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). - SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section).
- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section)- - SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section).
Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too, Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too,
in which case all client instances will switch theme. Furthermore, all in which case all client instances will switch theme. Furthermore, all
future client instances will also use the selected theme. future client instances will also use the selected theme.
Sending SIGUSR1/SIGUSR2 to a footclient instance is currently not You can also send SIGUSR1/SIGUSR2 to a footclient instance, see
supported. *footclient*(1) for details.
# BUGS # BUGS

View file

@ -189,6 +189,21 @@ variables may be defined in *foot.ini*(5).
In addition to the variables listed above, custom environment In addition to the variables listed above, custom environment
variables to unset may be defined in *foot.ini*(5). variables to unset may be defined in *foot.ini*(5).
# Signals
The following signals have special meaning in footclient:
- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section).
- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section).
When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is
changed in that instance only. This is different from when you send
SIGUSR1/SIGUSR2 to the server process, where all instances change the
theme.
Note: for obvious reasons, this is not supported when footclient is
started with *--no-wait*.
# SEE ALSO # SEE ALSO
*foot*(1) *foot*(1)

View file

@ -156,10 +156,61 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
xassert(events & EPOLLIN); xassert(events & EPOLLIN);
if (client->instance != NULL) { if (client->instance != NULL) {
uint8_t dummy[128]; struct client_ipc_hdr ipc_hdr;
ssize_t count = read(fd, dummy, sizeof(dummy)); ssize_t count = read(fd, &ipc_hdr, sizeof(ipc_hdr));
LOG_WARN("client unexpectedly sent %zd bytes", count);
return true; /* TODO: shutdown instead? */ if (count != sizeof(ipc_hdr)) {
LOG_WARN("client unexpectedly sent %zd bytes", count);
return true; /* TODO: shutdown instead? */
}
switch (ipc_hdr.ipc_code) {
case FOOT_IPC_SIGUSR: {
xassert(ipc_hdr.size == sizeof(struct client_ipc_sigusr));
struct client_ipc_sigusr sigusr;
count = read(fd, &sigusr, sizeof(sigusr));
if (count < 0) {
LOG_ERRNO("failed to read SIGUSR IPC data from client");
return true; /* TODO: shutdown instead? */
}
if ((size_t)count != sizeof(sigusr)) {
LOG_ERR("failed to read SIGUSR IPC data from client");
return true; /* TODO: shutdown instead? */
}
switch (sigusr.signo) {
case SIGUSR1:
term_theme_switch_to_1(client->instance->terminal);
break;
case SIGUSR2:
term_theme_switch_to_2(client->instance->terminal);
break;
default:
LOG_ERR(
"client sent bad SIGUSR number: %d "
"(expected SIGUSR1=%d or SIGUSR2=%d)",
sigusr.signo, SIGUSR1, SIGUSR2);
break;
}
return true;
}
default:
LOG_WARN(
"client sent unrecognized IPC (0x%04x), ignoring %hhu bytes",
ipc_hdr.ipc_code, ipc_hdr.size);
/* TODO: slightly broken, since not all data is guaranteed
to be readable yet */
uint8_t dummy[ipc_hdr.size];
read(fd, dummy, ipc_hdr.size);
return true;
}
} }
if (client->buffer.data == NULL) { if (client->buffer.data == NULL) {