├── .gitignore ├── .gitmodules ├── README.md ├── client.c ├── client.h ├── emit.c ├── emit.h ├── event.c ├── event.h ├── geometry.c ├── geometry.h ├── globals.h ├── handle.c ├── handle.h ├── ind.c ├── ind.h ├── ipc.c ├── ipc.h ├── makefile ├── mod ├── makefile ├── xcb.c └── xcb.h ├── modules.c ├── modules.h ├── notes.txt ├── tasks.txt ├── tests ├── event_test.c ├── ipc_test.c └── test_all.c ├── wm.c ├── xcb_handlers.c └── xcb_handlers.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/dlist"] 2 | path = deps/dlist 3 | url = git://github.com/laserswald/dlist 4 | [submodule "deps/greatest"] 5 | path = deps/greatest 6 | url = git://github.com/silentbicycle/greatest 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nitro 2 | An experimental X window manager designed to be entirely modular 3 | 4 | ## Introduction 5 | Nitro is an attempt at making a plugin-based minimal window manager with some fresh ideas on how window managing should work. Some of the reasons for this include: 6 | * Compiled plugins for a WM haven't been done as far as I know 7 | * All the cool kids are making window managers these days 8 | * Issues with dealing with my current setup (looking at you fuzzy focus in wmutils) 9 | * A vehicle for my ideas about how we can improve the concepts behind window management 10 | 11 | Mostly, this WM solidifies some of the ideas I have been having with regards to [Backflip](http://github.com/laserswald/backflip). 12 | 13 | ## Interesting ideas 14 | 15 | - **Multiple window selection**: 16 | This, I think, is the main innovation of Nitro. The concept involves being able to select several windows and perform an operation on all of them at once. Operations on windows like CWM style grouping and workspaces boil down into a program selecting the windows (all the ones on a monitor, perhaps?) and then performing an operation on all of them. I've experimented with this with Backflip a bit, and the idea can be very powerful. 17 | 18 | - **Pseudo-windows**: 19 | Another interesting concept is the idea of a rectangle on the screen that can be used to specify where windows should go. Imagine drawing a window in Plan 9's Rio, but better. One use case is drawing a rectangle on the screen and making it the "master" part, a la Xmonad. The possibilities are endless. 20 | 21 | - **FIFO based IPC** 22 | - **Compiled plugins** 23 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "globals.h" 4 | #include "client.h" 5 | 6 | struct ni_client { 7 | xcb_window_t window; 8 | }; 9 | 10 | struct ni_client* ni_client_new(xcb_window_t window){ 11 | struct ni_client* ret = malloc(sizeof ret); 12 | ret->window = window; 13 | 14 | // Listen to events 15 | uint32_t values[2]; 16 | values[0] = XCB_EVENT_MASK_ENTER_WINDOW; 17 | values[1] = XCB_EVENT_MASK_FOCUS_CHANGE; 18 | xcb_change_window_attributes(conn, ret->window, XCB_CW_EVENT_MASK, values); 19 | 20 | return ret; 21 | } 22 | 23 | int ni_client_init(struct ni_client* this){ 24 | } 25 | 26 | void ni_client_free(struct ni_client* this){ 27 | free(this); 28 | } 29 | 30 | void ni_client_focus(struct ni_client* this){ 31 | xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, this->window, XCB_CURRENT_TIME); 32 | 33 | xcb_flush(conn); 34 | } 35 | 36 | void ni_client_raise(struct ni_client* this){ 37 | } 38 | 39 | void ni_client_lower(struct ni_client* this){ 40 | } 41 | 42 | void ni_client_close(struct ni_client* this){ 43 | xcb_kill_client(conn, this->window); 44 | } 45 | 46 | void ni_client_teleport(struct ni_client* this){ 47 | } 48 | 49 | void ni_client_resize(struct ni_client* this){ 50 | } 51 | 52 | void ni_client_border(struct ni_client* this){ 53 | } 54 | 55 | void ni_client_undo_resize(struct ni_client* this){ 56 | } 57 | 58 | // Comparison function for ni_clients. 59 | int ni_client_cmp(void *a, void *b){ 60 | ni_client_t *ac = a; 61 | ni_client_t *bc = b; 62 | // Yeah, yeah, "ordering" makes no sense. Whatever. 63 | return ac->window - bc->window; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_CLIENT 2 | 3 | #define NI_CLIENT 4 | 5 | #include 6 | 7 | #include "geometry.h" 8 | #include "dlist.h" 9 | 10 | // A client window. 11 | typedef struct ni_client ni_client_t; 12 | dlist_declare(ni_client_t*, ni_client); 13 | 14 | // Allocate a new client 15 | ni_client_t* ni_client_new(xcb_window_t window); 16 | 17 | // Initialize a stack allocated client 18 | int ni_client_init(ni_client_t*); 19 | 20 | // Free a heap allocated client 21 | void ni_client_free(ni_client_t*); 22 | 23 | int ni_client_cmp(void*, void*); 24 | 25 | // Place focus on this client 26 | void ni_client_focus(ni_client_t*); 27 | 28 | // Raise this client to front 29 | void ni_client_raise(ni_client_t*); 30 | 31 | // Lower this client to back 32 | void ni_client_lower(ni_client_t*); 33 | void ni_client_close(ni_client_t*); 34 | void ni_client_teleport(ni_client_t*); 35 | void ni_client_resize(ni_client_t*); 36 | void ni_client_border(ni_client_t*); 37 | void ni_client_undo_resize(ni_client_t*); 38 | 39 | #endif /* end of include guard: NI_CLIENT */ 40 | -------------------------------------------------------------------------------- /emit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ind.h" 5 | #include "globals.h" 6 | #include "emit.h" 7 | 8 | void* ni_dummy_get_event(ni_emitter *this); 9 | void ni_dummy_free(ni_emitter *this); 10 | 11 | ni_emitter* ni_emitter_new(){ 12 | ni_emitter* this = mallocz(sizeof(*this), 2); 13 | this->get_event = &ni_dummy_get_event; 14 | this->destroy = &ni_dummy_free; 15 | return this; 16 | } 17 | 18 | void ni_emitter_free(ni_emitter* this){ 19 | assert(this != NULL); 20 | this->destroy(this); 21 | this = NULL; 22 | } 23 | 24 | void* ni_emitter_get_event(ni_emitter* this){ 25 | return this->get_event(this); 26 | } 27 | 28 | // Basic emitter implementations 29 | 30 | void* ni_dummy_get_event(ni_emitter *this){ 31 | char* event= "exit"; 32 | return memdupz(event, strlen(event)); 33 | } 34 | 35 | void ni_dummy_free(ni_emitter *this){ 36 | return; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /emit.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_EMITTERS 2 | 3 | #define NI_EMITTERS 4 | 5 | #include 6 | 7 | #include "dlist.h" 8 | 9 | // Just... something 10 | typedef struct _ni_emitter { 11 | void* (*get_event)(struct _ni_emitter*); 12 | void (*destroy)(struct _ni_emitter*); 13 | } ni_emitter; 14 | 15 | dlist_declare(ni_emitter*, ni_emitter); 16 | 17 | ni_emitter* ni_emitter_new(); 18 | int ni_emitter_init(ni_emitter* this); 19 | void ni_emitter_free(ni_emitter* this); 20 | void* ni_emitter_get_event(ni_emitter* this); 21 | 22 | 23 | #endif /* end of include guard: NI_EMITTERS */ 24 | -------------------------------------------------------------------------------- /event.c: -------------------------------------------------------------------------------- 1 | 2 | #include "event.h" 3 | 4 | struct ni_event_arg* ni_event_arg_num(int number){ 5 | struct ni_event_arg* argument = mallocz(sizeof (*argument), 2); 6 | argument->type = NI_EVENT_ARG_NUMBER; 7 | argument->number = number; 8 | return argument; 9 | } 10 | 11 | struct ni_event_arg* ni_event_arg_str(char* string){ 12 | struct ni_event_arg* argument = mallocz(sizeof (*argument), 2); 13 | argument->type = NI_EVENT_ARG_STRING; 14 | argument->string = memdupz(string, strlen(string)); 15 | return argument; 16 | } 17 | 18 | void ni_event_arg_get_num(int* number, struct ni_event_arg* this){ 19 | assert(this != NULL); 20 | if (this->type != NI_EVENT_ARG_NUMBER){ 21 | fprintf(stderr, "nitro: event argument is not a number, but treated like one."); 22 | exit(1); 23 | } 24 | *number = this->number; 25 | } 26 | 27 | void ni_event_arg_get_str(char** string, struct ni_event_arg* this){ 28 | assert(this != NULL); 29 | if (this->type != NI_EVENT_ARG_STRING){ 30 | fprintf(stderr, "nitro: event argument is not a string, but treated like one."); 31 | exit(1); 32 | } 33 | *string = this->string; 34 | } 35 | 36 | // ni_event implementation 37 | struct ni_event *ni_event_new(const char* name, dlist(ni_event_arg)* arguments, void* data){ 38 | if (!name) 39 | return NULL; 40 | struct ni_event *this = mallocz(sizeof (*this), 2); 41 | this->event_name = memdupz(name, strlen(name)); 42 | this->arguments = arguments; 43 | this->extra = data; 44 | return this; 45 | } 46 | 47 | void ni_event_free(ni_event_t *event){ 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_EVENT_H 2 | #define NI_EVENT_H 3 | 4 | #include 5 | #include 6 | #include "client.h" 7 | #include "ind.h" 8 | 9 | /** 10 | * What kind of argument this is. 11 | */ 12 | enum ni_event_arg_type { 13 | NI_EVENT_ARG_STRING, 14 | NI_EVENT_ARG_NUMBER 15 | }; 16 | 17 | /** Variant type for an event argument. 18 | */ 19 | typedef struct ni_event_arg { 20 | enum ni_event_arg_type type; 21 | union { 22 | int number; 23 | char* string; 24 | }; 25 | } ni_event_arg_t; 26 | dlist_declare(ni_event_arg_t, ni_event_arg); 27 | 28 | // Make an event arg given a variable. 29 | #define ni_event_arg_new(T) \ 30 | _Generic((T), \ 31 | int: ni_event_arg_num, \ 32 | char*: ni_event_arg_str)(T) 33 | 34 | #define ni_event_arg_get(T, arg) \ 35 | _Generic((T), \ 36 | int*: ni_event_arg_get_num, \ 37 | char**: ni_event_arg_get_str \ 38 | )(T, arg) 39 | 40 | // You don't have to use these at all. Use the above for generic goodness. 41 | ni_event_arg_t* ni_event_arg_num(int number); 42 | ni_event_arg_t* ni_event_arg_str(char* string); 43 | void ni_event_arg_get_num(int* number, ni_event_arg_t* argument); 44 | void ni_event_arg_get_str(char** string, ni_event_arg_t* argument); 45 | 46 | typedef struct ni_event { 47 | char* event_name; 48 | dlist(ni_event_arg) *arguments; 49 | void *extra; 50 | } ni_event_t; 51 | dlist_declare(ni_event_t, ni_event); 52 | 53 | ni_event_t *ni_event_new(const char* name, dlist(ni_event_arg)* arguments, void* data); 54 | void ni_event_free(ni_event_t* event); 55 | ni_event_t *ni_event_from_xcb(xcb_generic_event_t *event); 56 | 57 | #endif /* end of include guard: NI_EVENT_H */ 58 | -------------------------------------------------------------------------------- /geometry.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "geometry.h" 4 | 5 | int ni_rect_init(ni_rect *r, int x, int y, unsigned int w, unsigned int h){ 6 | if (!r) r = malloc(sizeof(ni_rect)); 7 | r->x = x; 8 | r->y = y; 9 | r->w = w; 10 | r->h = h; 11 | } 12 | 13 | // returns true if A is within B 14 | bool rect_within(const ni_rect *a, const ni_rect *b){ 15 | if (a->x >= b->x && 16 | a->y >= b->y && 17 | a->x + a->w <= b->x + b->w && 18 | a->y + a->h <= b->y + b->h) 19 | return true; 20 | else return false; 21 | } 22 | 23 | // returns true if A and B touch 24 | bool rect_collide(const ni_rect *a, const ni_rect *b){ 25 | if (rect_within(a, b)) return true; 26 | if (a->x > b->x + b->w || 27 | a->y > b->y + b->h || 28 | b->x > a->x + a->w || 29 | b->y > a->y + a->h) return false; 30 | return true; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /geometry.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_GEOMETRY 2 | 3 | #define NI_GEOMETRY 4 | 5 | #include 6 | 7 | /* Rectangular geometry for manipulating windows. */ 8 | 9 | typedef struct ni_rect{ 10 | int x, y; 11 | unsigned int w, h; 12 | } ni_rect; 13 | 14 | /* Initialize (or allocate a new) rectangle. */ 15 | int ni_rect_init(ni_rect*, int, int, unsigned int, unsigned int); 16 | 17 | /* Return true if the second rectangle is contained entirely within the first. */ 18 | bool ni_rect_within(const ni_rect*, const ni_rect*); 19 | 20 | /* Return true when two rectangles collide (either touch or overlap) */ 21 | bool ni_rect_collide(const ni_rect*, const ni_rect*); 22 | 23 | /* Free a dynamically allocated rectangle. */ 24 | void ni_rect_free(ni_rect*); 25 | 26 | #endif /* end of include guard: NI_GEOMETRY */ 27 | -------------------------------------------------------------------------------- /globals.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_GLOBALS 2 | 3 | #define NI_GLOBALS 4 | 5 | #include "client.h" 6 | #include "emit.h" 7 | #include "handle.h" 8 | 9 | extern dlist(ni_client) *all_clients; 10 | extern dlist(ni_handler) *handlers; 11 | extern dlist(ni_emitter) *emitters; 12 | extern xcb_connection_t *conn; 13 | extern bool is_running; 14 | 15 | 16 | #endif /* end of include guard: NI_GLOBALS */ 17 | -------------------------------------------------------------------------------- /handle.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "handle.h" 6 | 7 | struct ni_handler* ni_handler_new(const char *name, ni_handler_func function){ 8 | struct ni_handler* h = malloc(sizeof(struct ni_handler)); 9 | h->event_name = strdup(name); 10 | h->handler_func = function; 11 | return h; 12 | } 13 | 14 | /* 15 | * Using the given handler list, call the correct handler. 16 | */ 17 | 18 | int ni_handler_handle(dlist(ni_handler) *list, ni_event_t *event, dlist(ni_client) *clients){ 19 | 20 | if (!list) return -1; 21 | if (!event) return -1; 22 | 23 | bool found_handler = false; 24 | 25 | dlist_foreach(ni_handler, list, cursor){ 26 | printf("Comparing '%s' with '%s' \n", cursor->data->event_name, event->event_name); 27 | if (strcmp(cursor->data->event_name, event->event_name) == 0) { 28 | cursor->data->handler_func(event, clients); 29 | found_handler = true; 30 | } 31 | } 32 | 33 | // cursor is either NULL or the handler we want now 34 | if (found_handler == false){ 35 | fprintf(stderr, "nitro - could not find handler %s. \n", event->event_name); 36 | return -1; 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /handle.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_HANDLER 2 | 3 | #define NI_HANDLER 4 | 5 | #include "client.h" 6 | #include "event.h" 7 | #include "dlist.h" 8 | 9 | /** Backflip event handler */ 10 | typedef int (*ni_handler_func)(ni_event_t* event, dlist(ni_client)*); 11 | 12 | typedef struct ni_handler { 13 | char *event_name; 14 | ni_handler_func handler_func; 15 | } ni_handler_t; 16 | 17 | dlist_declare(ni_handler_t*, ni_handler); 18 | 19 | struct ni_handler* ni_handler_new(const char *, ni_handler_func); 20 | 21 | int ni_handler_handle (dlist(ni_handler)*, ni_event_t*, dlist(ni_client)*); 22 | 23 | #endif /* end of include guard: NI_HANDLER */ 24 | -------------------------------------------------------------------------------- /ind.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h, documentation by lazr 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "ind.h" 20 | 21 | /// Exit with a status message. 22 | // 23 | // Prints out a string to stderr like fprintf(), but also appends ": " and 24 | // the string returned from perror. 25 | // 26 | // Does not return. 27 | void 28 | edie(char *fmt, ...) 29 | { 30 | va_list fmtargs; 31 | 32 | va_start(fmtargs, fmt); 33 | vfprintf(stderr, fmt, fmtargs); 34 | va_end(fmtargs); 35 | fprintf(stderr, ": "); 36 | 37 | perror(NULL); 38 | 39 | exit(1); 40 | } 41 | 42 | /* 43 | * Exit without a status message. 44 | * 45 | * Prints out a string to stdout, and exits with an error. 46 | */ 47 | void 48 | die(char *fmt, ...) 49 | { 50 | va_list fmtargs; 51 | 52 | va_start(fmtargs, fmt); 53 | vfprintf(stderr, fmt, fmtargs); 54 | va_end(fmtargs); 55 | 56 | exit(1); 57 | } 58 | 59 | 60 | /* You can use these constants if you want to be clearer. Up to you. */ 61 | const int ALLOCZ_CLEAR = 1; 62 | const int ALLOCZ_DIRTY = 0; 63 | 64 | /// Reallocate and zero memory. 65 | // 66 | // p: a pointer to memory to reallocate. Give NULL here to allocate new memory. 67 | // l: an integer to the size of the memory to allocate, in bytes. 68 | // z: a flag where, if truthy, the memory is set to all 0 bytes. 69 | // 70 | // returns the pointer to the memory in question, or NULL if there was an issue. 71 | // 72 | void * 73 | reallocz(void *p, const size_t l, const int z) 74 | { 75 | p = realloc(p, l); 76 | if(p == NULL) 77 | edie("realloc"); 78 | if(z) 79 | memset(p, 0, l); 80 | 81 | return p; 82 | } 83 | 84 | // Allocate and zero new memory. 85 | void * 86 | mallocz(const size_t l, int z) 87 | { 88 | return reallocz(NULL, l, z); 89 | } 90 | 91 | 92 | void * 93 | memdup(void *p, const size_t l) 94 | { 95 | char *ret; 96 | 97 | ret = reallocz(NULL, l, 2); // 2 looks like a Z and compares true. 98 | memmove(ret, p, l); 99 | 100 | return (void *)ret; 101 | } 102 | 103 | // Duplicate memory. 104 | // 105 | void * 106 | memdupz(const void *p, const size_t l) 107 | { 108 | char *ret; 109 | 110 | ret = reallocz(NULL, l+1, 2); 111 | memmove(ret, p, l); 112 | 113 | return (void *)ret; 114 | } 115 | 116 | // Create a duplicate of the given memory and concatenate another chunk of memory to it. 117 | void * 118 | memdupcat(void *p, const size_t lp, void *c, const size_t lc) 119 | { 120 | p = reallocz(p, lp+lc, 0); 121 | memset(&((char *)p)[lp], 0, lc); 122 | 123 | memmove(&((char *)p)[lp], c, lc); 124 | 125 | return p; 126 | } 127 | 128 | // Using a variable argument list, create a new allocated string in memory. 129 | char * 130 | vsmprintf(char *fmt, va_list fmtargs, const size_t size) 131 | { 132 | char *ret; 133 | 134 | ret = reallocz(NULL, (size+1), ALLOCZ_CLEAR); 135 | vsnprintf(ret, size+1, fmt, fmtargs); 136 | 137 | return ret; 138 | } 139 | 140 | // Create a new allocated string in memory. 141 | // 142 | // Don't forget to free()! 143 | char * 144 | smprintf(char *fmt, ...) 145 | { 146 | va_list fmtargs; 147 | char *ret; 148 | int len; 149 | 150 | va_start(fmtargs, fmt); 151 | len = vsnprintf(NULL, 0, fmt, fmtargs); 152 | va_end(fmtargs); 153 | 154 | va_start(fmtargs, fmt); 155 | ret = vsmprintf(fmt, fmtargs, len); 156 | va_end(fmtargs); 157 | 158 | return ret; 159 | } 160 | 161 | // Read in an entire file into a string 162 | char * 163 | readtoeoffd(int fd, int *len) 164 | { 165 | char *ret, buf[4096]; 166 | int olen, nlen, rl; 167 | 168 | for (nlen = 0, olen = 0, ret = NULL; 169 | (rl = read(fd, buf, sizeof(buf))); olen = nlen) { 170 | if (rl > 0) { 171 | nlen += rl; 172 | ret = reallocz(ret, nlen+1, 0); 173 | ret[nlen] = '\0'; 174 | 175 | memmove(&ret[olen], buf, rl); 176 | } else if (rl == 0) { 177 | break; 178 | } else { 179 | edie("readtoeoffd"); 180 | } 181 | } 182 | 183 | *len = nlen; 184 | return ret; 185 | } 186 | 187 | char * 188 | freadall(FILE* f, int *len) 189 | { 190 | *len = fseek(f, 0, SEEK_END); 191 | rewind(f); 192 | char *s = malloc(*len); 193 | fread(s, sizeof(char), *len, f); 194 | if (ferror(f)) { 195 | return NULL; 196 | } 197 | return s; 198 | } 199 | 200 | 201 | /// Get the next line from a string. 202 | // 203 | // Returns NULL if p is NULL. 204 | // When a line is found, p points to the rest of the string. 205 | // If the whole string is moved to s, p will be NULL. 206 | // 207 | // s : The line from the string. 208 | // size : Maximum size of the string to be extracted. 209 | // p : The buffer to extract the string from. 210 | // 211 | // Returns: the extracted string. 212 | char * 213 | sgets(char *s, const size_t size, char **p) 214 | { 215 | char *newline; 216 | int cur_len; 217 | 218 | if (*p == NULL) 219 | return NULL; 220 | 221 | newline = strchr(*p, '\n'); 222 | if (newline == NULL) { 223 | cur_len = strlen(*p); 224 | if (cur_len < 1) { 225 | *p = NULL; 226 | return NULL; 227 | } 228 | } else { 229 | cur_len = newline - *p; 230 | } 231 | 232 | if (cur_len >= size) 233 | cur_len = size - 1; 234 | memmove(s, *p, cur_len); 235 | s[cur_len] = '\0'; 236 | 237 | if (newline == NULL) { 238 | *p = NULL; 239 | } else { 240 | *p = newline+1; 241 | } 242 | 243 | return s; 244 | } 245 | 246 | int 247 | ssplit(const char *s, const char *delimiters, char **parts) { 248 | 249 | } 250 | -------------------------------------------------------------------------------- /ind.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h 4 | * some additions by lazr 5 | */ 6 | 7 | #ifndef __IND_H__ 8 | #define __IND_H__ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* A collection of useful utility functions that honestly should be in 15 | * the standard library anyways. 16 | */ 17 | 18 | // Error functions 19 | void die(char *fmt, ...); 20 | void edie(char *fmt, ...); 21 | 22 | // Memory functions 23 | void *reallocz(void *p, const size_t l, const int z); 24 | void *mallocz(const size_t l, int z); 25 | void *memdup(void *p, const size_t l); 26 | void *memdupz(const void *p, const size_t l); 27 | void *memdupcat(void *p, const size_t lp, void *c, const size_t lc); 28 | 29 | // String functions 30 | char *vsmprintf(char *fmt, va_list fmtargs, const size_t size); 31 | char *smprintf(char *fmt, ...); 32 | char *readtoeoffd(int fd, int *len); 33 | char *freadall(FILE *f, int *len); 34 | char *sgets(char *s, const size_t size, char **p); 35 | 36 | // Use this for getting a container type from a pointer to an element 37 | #define container_of(ptr, type, member) ({ \ 38 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 39 | (type *)( (char *)__mptr - offsetof(type,member) );}) 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /ipc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "deps/dlist/dlist.h" 7 | #include "emit.h" 8 | #include "event.h" 9 | #include "ind.h" 10 | #include "ipc.h" 11 | 12 | struct ni_ipc { 13 | ni_emitter emitter; 14 | int infd; 15 | int outfd; 16 | char *buffer; // Storage for later event processing 17 | }; 18 | 19 | struct ni_ipc* ni_ipc_new(const char* fin, const char* fout){ 20 | struct ni_ipc *this = mallocz(sizeof(*this), 2); 21 | this->emitter.get_event = &ni_ipc_get_event; 22 | this->emitter.destroy = &ni_ipc_free; 23 | // Make the file nodes 24 | mkfifo(fin, S_IRWXU | S_IRWXG); 25 | 26 | // Start listening 27 | this->infd = open(fin, O_RDONLY | O_NONBLOCK); 28 | 29 | return this; 30 | } 31 | 32 | void ni_ipc_free(struct ni_ipc *ipc){ 33 | if (ipc) { 34 | close(ipc->infd); 35 | } 36 | free(ipc); 37 | } 38 | 39 | /** 40 | * Convert a string to a ni_event. 41 | */ 42 | static 43 | ni_event_t *ni_ipc_strtoevent(char *string){ 44 | ni_event_t *retval = NULL; 45 | if (string) { 46 | char* evt_name = strsep(&string, "\t "); 47 | dlist(ni_event_arg) *args = NULL; 48 | while (string){ 49 | char *argstr = strsep(&string, "\t "); 50 | char *end; 51 | int possible_num = strtol(argstr, &end, 10); 52 | if (end == '\0') { 53 | dlist_push(ni_event_arg, args, *ni_event_arg_num(possible_num)); 54 | } 55 | } 56 | retval = ni_event_new(evt_name, args, NULL); 57 | } 58 | return retval; 59 | } 60 | 61 | char* ni_ipc_get_line(ni_emitter *emitter){ 62 | struct ni_ipc* ipc = (struct ni_ipc*) emitter; 63 | 64 | int amount = 0; 65 | if (!ipc->buffer) { 66 | ipc->buffer = readtoeoffd(ipc->infd, &amount); 67 | if (0 == amount) { 68 | return NULL; 69 | } 70 | } 71 | 72 | char *line = malloc(255); 73 | sgets(line, 255, &ipc->buffer); 74 | 75 | return line; 76 | } 77 | 78 | ni_event_t *ni_ipc_get_event(ni_emitter *emitter){ 79 | ni_event_t *retval = NULL; 80 | 81 | char *line = ni_ipc_get_line(emitter); 82 | if (line) { 83 | retval = ni_ipc_strtoevent(line); 84 | } 85 | 86 | return retval; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_IPC_H 2 | 3 | #define NI_IPC_H 4 | 5 | #include "emit.h" 6 | #include "event.h" 7 | 8 | typedef struct ni_ipc ni_ipc_t; 9 | 10 | ni_ipc_t *ni_ipc_new(const char* fin, const char* fout); 11 | ni_event_t *ni_ipc_get_event(ni_emitter *emitter); 12 | char *ni_ipc_get_line(ni_emitter *emitter); 13 | void ni_ipc_free(ni_ipc_t *ipc); 14 | 15 | #endif /* end of include guard: NI_IPC_H */ 16 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CFLAGS= -Wall -ldl -lxcb -g -I./deps/dlist -I./deps/greatest 2 | 3 | BINNAME=nitro 4 | 5 | TEST_OBJS=tests/event_test.o event.o tests/ipc_test.o ipc.o 6 | 7 | all: $(BINNAME) modules check 8 | 9 | modules: 10 | cd mod && $(MAKE) && cp *.so ../modbins 11 | 12 | clean: clean-modules 13 | rm *.o $(BINNAME) modbins/*.so 14 | 15 | clean-modules: 16 | cd mod && $(MAKE) clean 17 | 18 | $(BINNAME): wm.o client.o modules.o emit.o handle.o ind.o event.o xcb_handlers.o 19 | gcc $(CFLAGS) -o $@ $^ -Wl,--export-dynamic 20 | 21 | tests/test_all: tests/test_all.o $(TEST_OBJS) ind.o 22 | gcc $(CFLAGS) -o $@ $^ 23 | 24 | check: tests/test_all 25 | exec tests/test_all -v | awk -f deps/greatest/contrib/greenest 26 | 27 | *.o: makefile 28 | 29 | .PHONY: modules clean clean-modules check 30 | -------------------------------------------------------------------------------- /mod/makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS=-g -I../deps/dlist 3 | 4 | all: xcb.so 5 | 6 | %.so: %.c 7 | $(CC) $(CFLAGS) -fPIC -shared -o $@ $^ 8 | 9 | clean: 10 | rm *.so 11 | 12 | .PHONY: clean all 13 | -------------------------------------------------------------------------------- /mod/xcb.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../globals.h" 4 | #include "../client.h" 5 | #include "../event.h" 6 | #include "../handle.h" 7 | 8 | int dummy_handler(ni_event_t* event, dlist(ni_client) *clients){ 9 | puts("I'm a dummy handler."); 10 | } 11 | 12 | int ni_mod_load(){ 13 | ni_handler_t *dummy = ni_handler_new("dummy", &dummy_handler); 14 | dlist_push(ni_handler, handlers, dummy); 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /mod/xcb.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laserswald/nitro/b8b92b36fb69311fcee9de8262b4953d8edad932/mod/xcb.h -------------------------------------------------------------------------------- /modules.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ind.h" 9 | #include "modules.h" 10 | 11 | int _ni_load_module(const char *filename){ 12 | void *dlhandle; 13 | int (*mod_entry)(void); 14 | dlhandle = dlopen(filename, RTLD_LAZY | RTLD_GLOBAL); 15 | if (!dlhandle){ 16 | perror(dlerror()); 17 | } 18 | mod_entry = dlsym(dlhandle, "ni_mod_load"); 19 | if (!mod_entry) edie("nitro - could not load module '%s'", filename); 20 | return mod_entry(); 21 | } 22 | 23 | int ni_load_mods(const char *directory){ 24 | struct dirent *dirent; 25 | DIR *dir = opendir(directory); 26 | if (dir) { 27 | while ((dirent = readdir(dir))) { 28 | if (dirent->d_type == DT_REG){ 29 | char *fullpath = smprintf("%s/%s", directory, dirent->d_name); 30 | _ni_load_module(fullpath); 31 | free(fullpath); 32 | } 33 | } 34 | closedir(dir); 35 | } 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_MODULES_H 2 | 3 | #define NI_MODULES_H 4 | 5 | // Load modules 6 | int ni_load_mods(const char *directory); 7 | 8 | #endif /* end of include guard: */ 9 | 10 | 11 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Nitro notes 2 | 3 | Event handlers 4 | Types needed: 5 | List of clients 6 | XCB connection pointer 7 | XCB event type pointer 8 | void pointer 9 | 10 | Groups 11 | A list of contained clients 12 | ni_group_select - set the selection list to the current group 13 | 14 | 15 | Selection list 16 | 17 | Operations 18 | List of clients 19 | XCB connection 20 | 21 | Check emitters for events. 22 | If emitter has event, apply the event. 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tasks.txt: -------------------------------------------------------------------------------- 1 | Create the XCB event emitter 2 | x Create a simple emitter to test whole stack 3 | -------------------------------------------------------------------------------- /tests/event_test.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../event.h" 3 | 4 | SUITE(event_arg_suite); 5 | 6 | TEST event_arg_type(void){ 7 | ni_event_arg_t* tester = ni_event_arg_new(5); 8 | int test; 9 | ni_event_arg_get(&test, tester); 10 | ASSERT_EQ(5, test); 11 | 12 | tester = ni_event_arg_new("This is a string"); 13 | char* test_str; 14 | ni_event_arg_get(&test_str, tester); 15 | ASSERT_STR_EQ("This is a string", test_str); 16 | PASS(); 17 | } 18 | 19 | GREATEST_SUITE(event_arg_suite){ 20 | RUN_TEST(event_arg_type); 21 | } 22 | -------------------------------------------------------------------------------- /tests/ipc_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "greatest.h" 8 | #include "../ipc.h" 9 | 10 | SUITE(ipc_suite); 11 | 12 | ni_ipc_t *get_test_ipc(){ 13 | return ni_ipc_new("infifo", "outfifo"); 14 | } 15 | 16 | void send_msg(char* msg){ 17 | int fd = open("infifo", O_WRONLY | O_NONBLOCK); 18 | dprintf(fd, "%s\n", msg); 19 | close(fd); 20 | } 21 | 22 | // Can we create and delete an IPC? 23 | TEST ipc_create(void){ 24 | ni_ipc_t *ipc = get_test_ipc(); 25 | ASSERT(ipc != NULL); 26 | ni_ipc_free(ipc); 27 | PASS(); 28 | } 29 | 30 | TEST ipc_returns_null_with_no_msgs(void){ 31 | ni_ipc_t *ipc = get_test_ipc(); 32 | 33 | ni_event_t *evt = ni_ipc_get_event(ipc); 34 | ASSERT_FALSE(evt); 35 | 36 | ni_ipc_free(ipc); 37 | PASS(); 38 | } 39 | 40 | TEST ipc_returns_not_null_with_msg(void){ 41 | ni_ipc_t *ipc = get_test_ipc(); 42 | 43 | // Send an event. 44 | send_msg("This is a message!"); 45 | 46 | ni_event_t *evt = ni_ipc_get_event(ipc); 47 | ASSERT(evt); 48 | 49 | ni_ipc_free(ipc); 50 | PASS(); 51 | } 52 | 53 | TEST ipc_returns_line_given(void){ 54 | ni_ipc_t *ipc = get_test_ipc(); 55 | 56 | // Send an event. 57 | send_msg("This is a message!"); 58 | 59 | char* message = ni_ipc_get_line(ipc); 60 | ASSERT(strcmp("This is a message!", message) == 0); 61 | 62 | ni_ipc_free(ipc); 63 | PASS(); 64 | } 65 | 66 | TEST ipc_returns_correct_event(void) { 67 | ni_ipc_t *ipc = get_test_ipc(); 68 | 69 | // Send an event. 70 | send_msg("event_name arg1 2"); 71 | 72 | ni_event_t *evt = ni_ipc_get_event(ipc); 73 | ASSERT_STR_EQ("event_name", evt->event_name); 74 | 75 | ni_ipc_free(ipc); 76 | PASS(); 77 | } 78 | 79 | TEST ipc_returns_correct_event_args(void) { 80 | ni_ipc_t *ipc = get_test_ipc(); 81 | 82 | // Send an event. 83 | send_msg("event_name arg1 2"); 84 | 85 | ni_event_t *evt = ni_ipc_get_event(ipc); 86 | ASSERT_STR_EQ("event_name", evt->event_name); 87 | ASSERT_STR_EQ("event_name", evt->event_name); 88 | 89 | ni_ipc_free(ipc); 90 | PASS(); 91 | } 92 | 93 | GREATEST_SUITE(ipc_suite){ 94 | RUN_TEST(ipc_create); 95 | RUN_TEST(ipc_returns_null_with_no_msgs); 96 | RUN_TEST(ipc_returns_not_null_with_msg); 97 | RUN_TEST(ipc_returns_line_given); 98 | RUN_TEST(ipc_returns_correct_event); 99 | RUN_TEST(ipc_returns_correct_event_args); 100 | } 101 | -------------------------------------------------------------------------------- /tests/test_all.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | 3 | SUITE_EXTERN(event_arg_suite); 4 | SUITE_EXTERN(ipc_suite); 5 | 6 | GREATEST_MAIN_DEFS(); 7 | 8 | int main(int argc, char **argv) { 9 | GREATEST_MAIN_BEGIN(); 10 | RUN_SUITE(event_arg_suite); 11 | RUN_SUITE(ipc_suite); 12 | GREATEST_MAIN_END(); 13 | } 14 | -------------------------------------------------------------------------------- /wm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "dlist.h" 5 | #include "ind.h" 6 | #include "globals.h" 7 | #include "handle.h" 8 | #include "modules.h" 9 | #include "event.h" 10 | #include "ipc.h" 11 | 12 | // Initialize global variables. 13 | dlist(ni_handler) *handlers = NULL; 14 | dlist(ni_emitter) *emitters = NULL; 15 | dlist(ni_client) *all_clients = NULL; 16 | bool is_running = false; 17 | 18 | void initialize() { 19 | is_running = true; 20 | } 21 | 22 | void shutdown(){ 23 | ni_event_t* exit_event = ni_event_new("exit", NULL, NULL); 24 | ni_handler_handle(handlers, exit_event, all_clients); 25 | dlist_free(ni_handler, handlers); 26 | dlist_free(ni_emitter, emitters); 27 | } 28 | 29 | int handle_exit(ni_event_t* event, dlist(ni_client)* clients){ 30 | is_running = false; 31 | return 0; 32 | } 33 | 34 | void add_default_handlers(){ 35 | ni_handler_t* exit_handler = ni_handler_new("exit", &handle_exit); 36 | dlist_push(ni_handler, handlers, exit_handler); 37 | } 38 | 39 | void add_default_emitters(){ 40 | ni_emitter* ipc_emitter = (ni_emitter*) ni_ipc_new("/tmp/nitro-in", "/tmp/nitro-out"); 41 | dlist_push(ni_emitter, emitters, ipc_emitter); 42 | } 43 | 44 | void listen(){ 45 | while (is_running) { 46 | puts("running"); 47 | dlist_foreach(ni_emitter, emitters, current){ 48 | ni_event_t *event = ni_emitter_get_event(current->data); 49 | if (event != NULL) { 50 | printf("Got a message: %s\n", event->event_name); 51 | ni_handler_handle(handlers, event, all_clients); 52 | ni_event_free(event); 53 | } 54 | } 55 | } 56 | } 57 | 58 | void destroy_handlers() { 59 | dlist_foreach(ni_handler, handlers, current) { 60 | free(current->data); 61 | } 62 | } 63 | 64 | void destroy_emitters() { 65 | dlist_foreach(ni_emitter, emitters, current) { 66 | ni_emitter_free(current->data); 67 | } 68 | } 69 | 70 | 71 | int main(int argc, const char *argv[]) 72 | { 73 | initialize(); 74 | atexit(shutdown); 75 | ni_load_mods("./modbins"); 76 | 77 | // Load any built in listeners 78 | add_default_handlers(); 79 | add_default_emitters(); 80 | 81 | // Start any plugin startup functions. 82 | ni_event_t* start_event = ni_event_new("start", NULL, NULL); 83 | ni_handler_handle(handlers, start_event, NULL); 84 | 85 | // Start listening for events. 86 | listen(); 87 | 88 | puts("Exiting"); 89 | destroy_handlers(); 90 | destroy_emitters(); 91 | 92 | return 0; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /xcb_handlers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ind.h" 5 | #include "globals.h" 6 | #include "dlist.h" 7 | #include "xcb_handlers.h" 8 | 9 | int xcb_map_no_handler(ni_event_t* event, dlist(ni_client)* selected){ 10 | puts("test handler"); 11 | return 0; 12 | } 13 | 14 | int xcb_create_no_handler(ni_event_t* event, dlist(ni_client)* selected){ 15 | xcb_create_notify_event_t *evt; 16 | evt = event->extra; 17 | 18 | // Create a new client 19 | ni_client_t *cli = ni_client_new(evt->window); 20 | 21 | // Add it to the global list of windows 22 | dlist_push(ni_client, all_clients, cli); 23 | ni_client_focus(cli); 24 | exit(0); 25 | 26 | return 0; 27 | } 28 | 29 | int xcb_delete_no_handler(ni_event_t* event, dlist(ni_client)* selected){ 30 | xcb_destroy_notify_event_t *evt; 31 | evt = event->extra; 32 | 33 | dlist_foreach(ni_client, all_clients, c){ 34 | 35 | ni_client_t *current = c->data; 36 | if (ni_client_cmp(current, evt->window)) { 37 | 38 | ni_client_close(current); 39 | ni_client_free(current); 40 | } 41 | } 42 | return 0; 43 | } 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /xcb_handlers.h: -------------------------------------------------------------------------------- 1 | #ifndef NI_XCB_HANDLERS 2 | 3 | #define NI_XCB_HANDLERS 4 | 5 | #include "event.h" 6 | 7 | 8 | int xcb_create_no_handler(ni_event_t* event, dlist(ni_client)*); 9 | int xcb_delete_no_handler(ni_event_t* event, dlist(ni_client)*); 10 | int xcb_enter_no_handler(ni_event_t* event, dlist(ni_client)*); 11 | int xcb_map_no_handler(ni_event_t* event, dlist(ni_client)*); 12 | int xcb_circulate_rq_handler(ni_event_t* event, dlist(ni_client)*); 13 | int xcb_client_msg_handler(ni_event_t* event, dlist(ni_client)*); 14 | int xcb_config_no_handler(ni_event_t* event, dlist(ni_client)*); 15 | int xcb_config_rq_handler(ni_event_t* event, dlist(ni_client)*); 16 | int xcb_focus_out_handler(ni_event_t* event, dlist(ni_client)*); 17 | 18 | #endif /* end of include guard: NI_XCB_HANDLERS */ 19 | 20 | --------------------------------------------------------------------------------