kiwmi/lit/wmaffle.lit

433 lines
8.4 KiB
Text
Raw Normal View History

2018-09-27 16:43:17 +02:00
@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.
2018-09-27 16:43:17 +02:00
Here's an overview:
2018-09-27 16:43:17 +02:00
--- wmaffle.c
@{copyright notice}
@{c headers}
@{posix headers}
2018-10-02 19:17:35 +02:00
@{xcb headers}
@{defines}
2018-09-27 16:43:17 +02:00
2018-10-01 12:09:19 +02:00
const char *argv0;
2018-09-30 20:05:37 +02:00
2018-10-01 11:45:38 +02:00
int g_about_to_quit = 0;
2018-09-30 20:05:37 +02:00
@{function definitions}
2018-09-27 16:43:17 +02:00
int
main(int argc, char *argv[])
2018-09-27 16:43:17 +02:00
{
2018-09-30 20:05:37 +02:00
argv0 = argv[0];
@{variables local to main}
@{parse arguments}
2018-09-30 20:05:37 +02:00
@{open ipc connection}
2018-10-02 19:17:35 +02:00
@{open xcb connection}
2018-09-30 20:06:21 +02:00
@{setup signal handlers}
2018-09-30 20:07:00 +02:00
@{execute config}
2018-09-30 20:09:39 +02:00
2018-10-01 11:45:38 +02:00
@{main loop}
2018-09-30 20:09:39 +02:00
@{cleanup}
2018-09-27 16:43:17 +02:00
}
---
Note that this is version NaV (not a version).
--- defines
#define VERSION_STRING "NaV"
---
2018-09-27 16:43:17 +02:00
@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/.
*/
---
2018-09-30 20:05:37 +02:00
@s Helper function
`die` prints an error message and then exist unsuccessfully.
--- function definitions
static void
2018-10-01 12:09:19 +02:00
die(const char *fmt, ...)
2018-09-30 20:05:37 +02:00
{
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
2018-09-27 16:43:17 +02:00
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.
2018-09-27 16:43:17 +02:00
This requires `getopt.h`.
--- posix headers
#include <getopt.h>
#include <limits.h>
2018-09-27 16:43:17 +02:00
---
That one we save in a local variable in main.
--- variables local to main
char config_path[PATH_MAX];
int option;
---
2018-09-27 16:43:17 +02:00
--- 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':
2018-09-30 20:05:37 +02:00
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`.
2018-09-30 20:05:37 +02:00
--- c headers +=
2018-09-27 16:43:17 +02:00
#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
2018-10-01 12:09:19 +02:00
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"
);
}
2018-09-27 16:43:17 +02:00
---
2018-09-30 20:05:37 +02:00
@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;
2018-10-01 12:09:19 +02:00
const char *sock_path;
2018-09-30 20:05:37 +02:00
---
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));
}
---
2018-09-30 20:06:21 +02:00
2018-09-30 20:09:39 +02:00
@s Close socket
Important part of our tear down process: closing the IPC socket again. Let's do it.
--- cleanup
close(sock_fd);
---
2018-09-30 20:06:21 +02:00
@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) {
2018-10-02 17:33:03 +02:00
g_about_to_quit = 1;
2018-09-30 20:06:21 +02:00
}
}
---
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>
---
2018-09-30 20:07:00 +02:00
@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);
---
2018-10-01 11:45:38 +02:00
@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);
2018-10-01 12:55:43 +02:00
} else {
2018-10-02 19:17:35 +02:00
fprintf(stderr, "%s: unable to accept connection\n", argv0);
}
---
@s XCB connection
Not a big surprise, but to talk to X we need to connect to it. XCB is the library of my choice for this.
So we need this.
--- xcb headers
#include <xcb/xcb.h>
---
To inititalize the XCB connection, we open it, and then do some stuff like subscribing to the substructure events. If that fails, we also know that another WM is already running.
--- open xcb connection
@{open connection}
@{open default screen}
@{register wm}
dpy_fd = xcb_get_file_descriptor(dpy);
---
This requires us a few variables to store the stuff.
--- variables local to main +=
int dpy_fd;
int default_screen;
xcb_connection_t *dpy;
xcb_screen_t *screen;
xcb_window_t root_window;
xcb_generic_error_t *xcb_error;
uint32_t event_values[] = { ROOT_EVENT_MASK };
---
And we need to define what events we want to listen to.
--- defines +=
#define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY)
---
After we open the connection we check if it has an error.
--- open connection
dpy = xcb_connect(NULL, &default_screen);
if (xcb_connection_has_error(dpy)) {
die("%s: failed to open XCB connection\n", argv0);
}
---
Opening the default screen is a little more complicated, since XCB gives us a little more information at once.
--- open default screen
if (!(screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data)) {
die("%s: failed to open default screen\n", argv0);
}
root_window = screen->root;
---
Now to register the WM. A WM is the program listening to the substructure events.
If this errors, we know another WM is already running, in which case we just terminate.
--- register wm
xcb_error = xcb_request_check(
dpy,
xcb_change_window_attributes_checked(
dpy,
root_window,
XCB_CW_EVENT_MASK,
event_values
)
);
if (xcb_error) {
die("%s: another window manager is already running\n", argv0);
2018-10-01 11:45:38 +02:00
}
---