@code_type c .c @comment_type /* %s */ @title kiwmi @s Introduction This is the source code for kiwmi. It parses it's arguments and prints either help / info text, or runs the window manager. Here's an overview: --- kiwmi.c @{copyright notice} @{c headers} @{posix headers} @{xcb 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} @{open xcb 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 * * 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 --- @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 #include --- 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 ]\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 #include #include --- @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}/kiwmi/kiwmirc"`. ---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, "kiwmi/kiwmirc" ); } else { snprintf( config_path, sizeof(config_path), "%s/%s", getenv("HOME"), ".config/kiwmi/kiwmi" ); } --- @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 #include --- 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 --- @s Get socket path We default to `/tmp/kiwmi.sock` if `KIWMI_SOCKET` isn't set. --- get socket path sock_path = getenv("KIWMI_SOCKET"); if (sock_path) { strncpy(sock_addr.sun_path, sock_path, sizeof(sock_addr.sun_path)); } else { strncpy(sock_addr.sun_path, "/tmp/kiwmi.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) { g_about_to_quit = 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 #include --- @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 kiwmi. 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 --- --- 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); } else { 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 --- 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); } ---