kiwmi/lit/wmaffle.lit
2018-10-01 12:09:19 +02:00

351 lines
6.5 KiB
Text

@code_type c .c
@comment_type /* %s */
@title wmaffle
@s Introduction
This is the source code for wmaffle.
It parses it's arguments and prints either help / info text, or runs the window manager.
Here's an overview:
--- wmaffle.c
@{copyright notice}
@{c headers}
@{posix headers}
@{defines}
const char *argv0;
int g_about_to_quit = 0;
@{function definitions}
int
main(int argc, char *argv[])
{
argv0 = argv[0];
@{variables local to main}
@{parse arguments}
@{open ipc connection}
@{setup signal handlers}
@{execute config}
@{main loop}
@{cleanup}
}
---
Note that this is version NaV (not a version).
--- defines
#define VERSION_STRING "NaV"
---
@s Copyright
This project is licensed under the Mozilla Public License Version 2.0. Please read LICENSE for more details.
--- copyright notice
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
---
@s Helper function
`die` prints an error message and then exist unsuccessfully.
--- function definitions
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
---
Note that this requires the `stdarg.h` header.
--- c headers
#include <stdarg.h>
---
@s Parse arguments
There are only two arguments and two of them are rather straight forward.
I use getopt for this, since it's quite good and allows us to extract the config argument easily.
This requires `getopt.h`.
--- posix headers
#include <getopt.h>
#include <limits.h>
---
That one we save in a local variable in main.
--- variables local to main
char config_path[PATH_MAX];
int option;
---
--- parse arguments
config_path[0] = '\0'; // Used to check if config was already set
while ((option = getopt(argc, argv, "hvc:")) != -1) {
switch (option) {
case 'h':
printf("Usage: %s [-h|-v|-c <config_path>]\n", argv0);
exit(EXIT_SUCCESS);
break;
case 'v':
printf("v" VERSION_STRING "\n");
exit(EXIT_SUCCESS);
break;
case 'c':
strncpy(config_path, optarg, sizeof(config_path));
break;
}
}
if (!config_path[0]) {
@{get default config path}
}
---
We also need `string.h`, `stdio.h`, and `stdlib.h`.
--- c headers +=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
---
@s Config default path
I can clearly see how a user doesn't specify a config path.
This was a design decision to make, whether to load a default config. I decided to go with it.
It loads `"${XDG_CONFIG_HOME:-$HOME/.config}/wmaffle/wmafflerc"`.
---get default config path
const char *config_home = getenv("XDG_CONFIG_HOME");
if (config_home) {
snprintf(
config_path,
sizeof(config_path),
"%s/%s",
config_home,
"wmafflw/wmafflerc"
);
} else {
snprintf(
config_path,
sizeof(config_path),
"%s/%s",
getenv("HOME"),
".config/wmaffle/wmafflerc"
);
}
---
@s Open IPC connection
Pretty straight forward, we open a `AF_UNIX` socket and that's it.
We need both a sockaddr and a file descriptor.
--- variables local to main +=
struct sockaddr_un sock_addr;
int sock_fd;
const char *sock_path;
---
This requires the following headers.
--- posix headers +=
#include <sys/socket.h>
#include <sys/un.h>
---
Opening the server is pretty straight forward.
--- open ipc connection
memset(&sock_addr, 0, sizeof(sock_addr));
@{get socket path}
unlink(sock_addr.sun_path);
sock_addr.sun_family = AF_UNIX;
if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
die("%s: failed to create socket\n", argv0);
}
if (bind(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
die("%s: failed to bind socket\n", argv0);
}
if (listen(sock_fd, 1) < 0) {
die("%s: failed to listen to socket\n", argv0);
}
---
This requires this header file.
--- posix headers +=
#include <unistd.h>
---
@s Get socket path
We default to `/tmp/wmaffle.sock` if `WMAFFLE_SOCKET` isn't set.
--- get socket path
sock_path = getenv("WMAFFLE_SOCKET");
if (sock_path) {
strncpy(sock_addr.sun_path, sock_path, sizeof(sock_addr.sun_path));
} else {
strncpy(sock_addr.sun_path, "/tmp/wmaffle.sock", sizeof(sock_addr.sun_path));
}
---
@s Close socket
Important part of our tear down process: closing the IPC socket again. Let's do it.
--- cleanup
close(sock_fd);
---
@s Setup signal handlers
We don't want out window manager to just exit on us, so we need to setup signal handler.
Here is it:
--- function definitions +=
static void
sig_handler(int sig)
{
if (sig == SIGCHLD) {
signal(sig, sig_handler);
while (waitpid(-1, 0, WNOHANG) > 0) {
// EMPTY
}
} else if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
// TODO: Set about to quit to 1
}
}
---
Now we only need to register it.
--- setup signal handlers
signal(SIGINT, sig_handler);
signal(SIGHUP, sig_handler);
signal(SIGTERM, sig_handler);
signal(SIGCHLD, sig_handler);
signal(SIGPIPE, SIG_IGN);
---
Which of course requires this.
--- posix headers +=
#include <signal.h>
#include <sys/wait.h>
---
@s Execute config
We'll do this in a function, so we can execute later on, when the user requests it.
--- function definitions +=
static void
exec_config(const char *path)
{
switch (fork()) {
case -1:
fprintf(stderr, "%s: failed to execute config\n", argv0);
break;
case 0:
execl(path, path, NULL);
die("%s: failed to execute config\n", argv0);
break;
}
}
---
We need to invoke this once all the connections are open
--- execute config
exec_config(config_path);
---
@s Main loop
This is the heart of wmaffle.
A pretty basic loop: fetch event, decide if it's from X or the client, handle it.
`select` is used for the first two steps.
--- posix headers +=
#include <sys/select.h>
---
--- variables local to main +=
int max_fd;
fd_set file_descriptors;
---
--- main loop
max_fd = sock_fd + 1;
while (!g_about_to_quit) {
FD_ZERO(&file_descriptors);
FD_SET(sock_fd, &file_descriptors);
select(max_fd, &file_descriptors, NULL, NULL, NULL);
if (FD_ISSET(sock_fd, &file_descriptors)) {
@{handle client event}
}
}
---
@s Handle client event
Until there's an actual WM, I'm just going to read messages and then print them out, so we have some sort of feedback.
--- variables local to main +=
int client_fd;
---
--- handle client event
char msg[BUFSIZ];
int msg_len;
client_fd = accept(sock_fd, NULL, 0);
if (!(client_fd < 0) && (msg_len = read(client_fd, msg, sizeof(msg))) > 0) {
// Client sent something
msg[msg_len] = '\0';
printf("Client sent: %s\n", msg);
close(client_fd);
}
---