Transformed seed to a non-literate version
This commit is contained in:
parent
6c981120bf
commit
065eadc49d
4 changed files with 117 additions and 434 deletions
434
lit/kiwmi.lit
434
lit/kiwmi.lit
|
@ -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 <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}/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 <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/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 <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 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 <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';
|
|
||||||
// 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 <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);
|
|
||||||
}
|
|
||||||
---
|
|
18
src/common.c
Normal file
18
src/common.c
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
11
src/common.h
Normal file
11
src/common.h
Normal file
|
@ -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 */
|
88
src/seed/main.c
Normal file
88
src/seed/main.c
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue