diff --git a/lit/kiwmi.lit b/lit/kiwmi.lit deleted file mode 100644 index 39dd32b..0000000 --- a/lit/kiwmi.lit +++ /dev/null @@ -1,434 +0,0 @@ -@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'; - // TODO: Actually handle - 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); -} ---- diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..7adbec5 --- /dev/null +++ b/src/common.c @@ -0,0 +1,18 @@ +#include "common.h" + +#include +#include +#include + +const char *argv0; + +void +die(char *fmt, ...) +{ + fprintf(stderr, "%s: ", argv0); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..45a3e69 --- /dev/null +++ b/src/common.h @@ -0,0 +1,11 @@ +#ifndef UTILITY_H +#define UTILITY_H + +#define SOCK_ENV_VAR "KIWMI_SOCKET" +#define SOCK_DEF_PATH "/tmp/kiwmi.sock" + +void die(char *fmt, ...); + +extern const char *argv0; + +#endif /* UTILITY_H */ diff --git a/src/seed/main.c b/src/seed/main.c new file mode 100644 index 0000000..d8aedb1 --- /dev/null +++ b/src/seed/main.c @@ -0,0 +1,88 @@ +/* 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/. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "common.h" + +int +main(int argc, const char *argv[]) +{ + argv0 = argv[0]; + + if (argc < 2) { + die("not enough arguments\n"); + } + + int sock_fd; + + if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + die("failed to open socket\n"); + } + + struct sockaddr_un sock_addr; + sock_addr.sun_family = AF_UNIX; + + char *sock_path = getenv(SOCK_ENV_VAR); + + if (sock_path) { + strncpy(sock_addr.sun_path, sock_path, sizeof(sock_addr.sun_path)); + } else { + strncpy(sock_addr.sun_path, SOCK_DEF_PATH, sizeof(sock_addr.sun_path)); + } + + if (connect(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { + die("failed to connect to socket\n"); + } + + char msg[BUFSIZ]; + size_t msg_len = 0; + + for (int i = 1; i < argc; ++i) { + msg_len += (size_t)snprintf(msg + msg_len, sizeof(msg), "%s", argv[i]); + } + + msg_len -= 1; // remove trailing space + + if (send(sock_fd, msg, msg_len, 0) < 0) { + die("failed to send message\n"); + } + + struct pollfd fds[] = { + { sock_fd, POLLIN, 0 }, + { STDOUT_FILENO, POLLHUP, 0 }, + }; + + while (poll(fds, 2, -1) > 0) { + if (fds[1].revents & (POLLERR | POLLHUP)) { + break; + } + + if (fds[0].revents & POLLIN) { + if ((msg_len = recv(sock_fd, msg, sizeof(msg) - 1, 0)) > 0) { + msg[msg_len] = '\0'; + if (msg_len == 1) { + break; + } + + printf("%s", msg); + fflush(stdout); + } else { + break; + } + } + } + + close(sock_fd); +}