gm

(orphaned) group manager using libcxb
git clone git://z3bra.org/gm
Log | Files | Refs | README | LICENSE

commit 85b3a79a2ddf360bc042035eb751d553de29cf95
Author: z3bra <willy@mailoo.org>
Date:   Sun Nov 16 23:54:57 2014

First commit: kinda works

Diffstat:
 LICENSE  |  14 +-
 Makefile |  38 ++++-
 README   |  27 +++-
 config.h |  20 ++-
 gm.c     | 616 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 715 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Makefile b/Makefile @@ -0,0 +1,38 @@ +# paths +PREFIX:=/usr +MANPREFIX:=${PREFIX}/share/man + +CC = gcc +LD = ${CC} +RM = rm +GZ = gzip +CFLAGS += -std=c99 -pedantic -Wall -g -D_XOPEN_SOURCE -Os +LDFLAGS += -lxcb -lxcb-keysyms + +.SUFFIXES: .c .o .1 .1.gz +.PHONY : all clean install uninstall + +.c.o: + @echo -e "CC $<" + @${CC} -c ${CFLAGS} $< -o $@ + +.1.1.gz: + @echo "GZ $<" + @${GZ} -c $< > $@ + +gm: gm.o + @echo -e "LD gm" + @${LD} $^ -o $@ ${LDFLAGS} + +all : gm gm.1.gz + +clean : + ${RM} -f gm *.o *.gz *~ + +install: gm gm.1.gz + install -D -m 0755 gm ${DESTDIR}${PREFIX}/bin/gm + #install -D -m 0644 gm.1.gz ${DESTDIR}${MANPREFIX}/man1/gm.1.gz + +uninstall: + ${RM} ${DESTDIR}${PREFIX}/bin/gm + #${RM} ${DESTDIR}${MANPREFIX}/man1/gm.1.gz diff --git a/README b/README @@ -0,0 +1,27 @@ + +┏━╸┏┳┓ +┃╺┓┃┃┃ +┗━┛╹ ╹ + -- by z3bra +=============== + +gm stands for 'G'roup 'M'anager. it's a tiny daemon that helps you to manage +your X windows in groups. You can then add windows to groups, and show/hide +groups on your desktop with a hotkey. + +default keybinds +---------------- + + SUPER + F[1-5] - toggle visibility of group [1-5] + SUPER + SHIFT + F[1-5] - add focused window to group [1-5] + +You can change the number of groups or the default keybinds by editing the +config.h + +compiling +--------- + +You will need the `xcb-util` and `xcb-util-keysym` to compile it. + + $ make + # make install diff --git a/config.h b/config.h @@ -0,0 +1,20 @@ +#define GROUPNUM 5 + +#define MOD XCB_MOD_MASK_4 +#define ALT XCB_MOD_MASK_1 +#define CTRL XCB_MOD_MASK_CONTROL +#define SHIFT XCB_MOD_MASK_SHIFT + +static struct hotkey keys[] = { + { MOD, XK_F1, togglegroup, 1}, + { MOD, XK_F2, togglegroup, 2}, + { MOD, XK_F3, togglegroup, 3}, + { MOD, XK_F4, togglegroup, 4}, + { MOD, XK_F5, togglegroup, 5}, + + { MOD|SHIFT, XK_F1, changegroup, 1}, + { MOD|SHIFT, XK_F2, changegroup, 2}, + { MOD|SHIFT, XK_F3, changegroup, 3}, + { MOD|SHIFT, XK_F4, changegroup, 4}, + { MOD|SHIFT, XK_F5, changegroup, 5}, +}; diff --git a/gm.c b/gm.c @@ -0,0 +1,616 @@ +/* + * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + * Version 2, December 2004 + * + * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + * + * Everyone is permitted to copy and distribute verbatim or modified + * copies of this license document, and changing it is allowed as long + * as the name is changed. + * + * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + * + * 0. You just DO WHAT THE FUCK YOU WANT TO. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <err.h> +#include <getopt.h> +#include <sys/shm.h> +#include <xcb/xcb.h> +#include <xcb/xcb_keysyms.h> +#include <X11/keysym.h> + +/* macro used to report stuff to the user in verbose mode */ +#define logx(...) if(verbose){printf(__VA_ARGS__);} + +#define LEN(x) (sizeof(x)/sizeof(*x)) +#define CLEAN(x) (x & ~0x80) + +struct window { + xcb_window_t id; + struct window *next; +}; + +struct hotkey { + unsigned int mod; + xcb_keysym_t sym; + void (*func)(struct window *, uint8_t); + uint8_t group; +}; + +void usage(char *); + +/* procedural functions */ +void cleanup (void); +void init (void); +void run (void); +void regevnt (uint32_t *); +void grabkeys (void); +xcb_keysym_t getkeysym (xcb_keycode_t); +xcb_keycode_t * getkeycode (xcb_keysym_t); +/* handle events */ +void evnt_create (xcb_generic_event_t *); +void evnt_enter (xcb_generic_event_t *); +void evnt_keypress (xcb_generic_event_t *); +void evnt_destroy (xcb_generic_event_t *); +/* manage nodes/windows */ +struct window * newnod (xcb_window_t); +struct window * getnod (xcb_window_t); +void delnod (struct window *); +uint8_t ismapped (xcb_window_t); +void discover (void); +/* deal with windows in groups */ +uint8_t getgrp (struct window *); +void mapgrp (uint8_t); +void hidgrp (uint8_t); +void delgroup (struct window *); +void addgroup (struct window *, uint8_t); +void changegroup (struct window *, uint8_t); +void togglegroup (struct window *, uint8_t); + +#include "config.h" + +static struct window *groups[GROUPNUM + 1]; + +static xcb_connection_t *con; /* connection to the X server */ +static xcb_screen_t *scn; /* default screen */ +static const uint32_t winev = XCB_EVENT_MASK_ENTER_WINDOW; +static xcb_window_t focus; + +static uint8_t verbose = 0; /* verbose mode ? */ + + +/* quick usage info */ +void usage(char *name) +{ + printf("usage: %s [-hv]\n", name); + exit(0); +} + +struct window *newnod(xcb_window_t w) +{ + struct window *np; + static const uint32_t mask = XCB_CW_EVENT_MASK; + static uint32_t val[2]; + + if (w == scn->root) { + return NULL; + } + + val[0] = winev; + xcb_change_window_attributes(con, w, mask, val); + xcb_flush(con); + + np = malloc(sizeof(struct window)); + + if (np) { + np->id = w; + np->next = NULL; + } else { + np = NULL; + warnx("WARN: can't create 0x%08x\n", w); + } + + return np; +} + +void delnod(struct window *np) +{ + uint8_t g; + + if (!np) { + return; + } + + /* find to which group the window is attached */ + g = getgrp(np); + + /* can't find window in group, so just free it */ + if (g >= GROUPNUM) { + free(np); + return; + } + + delgroup(np); + + free(np); + + return; /* void */ +} + +struct window *getnod(xcb_window_t w) +{ + uint8_t g; + struct window *np; + + for (g=0; g<GROUPNUM; g++) { + for (np = groups[g]; np; np = np->next) { + if (np->id == w) { + return np; + } + } + } + + warnx("getnod: 0x%08x not found\n", w); + + return NULL; +} + +uint8_t getgrp(struct window *wp) +{ + uint8_t g; + struct window *np; + + for (g = 0; g < GROUPNUM; g++) { + for (np = groups[g]; np; np = np->next) { + if (np == wp) { + return g; + } + } + } + + warnx("getgrp: 0x%08x is not in any group\n", wp->id); + + return 0; +} + +uint8_t ismapped(xcb_window_t w) +{ + uint8_t ms; + xcb_get_window_attributes_cookie_t c; + xcb_get_window_attributes_reply_t *r; + + c = xcb_get_window_attributes(con, w); + r = xcb_get_window_attributes_reply(con, c, NULL); + + if (r == NULL) { + return -1; + } + + ms = r->map_state; + + free(r); + + return ms; +} + +#ifdef DEBUG +void lsgrp() +{ + uint8_t g; + struct window *np; + + logx("\ngroup summary:" + "\n--------------\n"); + for (g = 0; g < GROUPNUM; g++) { + for (np = groups[g]; np; np = np->next) { + logx("\t%d:0x%08x\n", g, np->id); + } + } + return; /* void */ +} +#endif + +void changegroup(struct window *np, uint8_t g) +{ + if (!np || np->id == scn->root) { + return; + } + + delgroup(np); + addgroup(np, g); + + mapgrp(g); +} + +/* add a window to a group */ +void addgroup(struct window *np, uint8_t g) +{ + if (!np) { + return; + } + + if (g >= GROUPNUM) { + warnx("%d: no such group\n", g); + return; + } + + /* add in head, we don't mind the order here */ + groups[g] = groups[g] + ? np->next = groups[g], np + : np; + + logx("add: 0x%08x -> %d\n", np->id, g); + + return; /* void */ +} + +/* remove a window from a group */ +void delgroup(struct window *np) +{ + struct window *gp; + uint8_t g; + + /* we don't remove nothing or the root window */ + if (!np || np->id == scn->root) { + return; + } + + g = getgrp(np); + + if (g >= GROUPNUM) { + logx("del: 0x%08x not in any group\n", np->id); + return; + } + + if (groups[g] == np) { + groups[g] = np->next; + } else { + for (gp = groups[g]; gp && gp->next != np; gp = gp->next); + if (gp) { + gp->next = np->next; + } + } + + np->next = NULL; + + logx("del: 0x%08x -> %d\n", np->id, g); + + return; /* void */ +} + +/* either call 'map' or 'unmap' depending on group's state */ +void togglegroup(struct window *np, uint8_t g) +{ + if (g > 0 && groups[g]) { + switch (ismapped(groups[g]->id)) { + case XCB_MAP_STATE_UNMAPPED : mapgrp(g); break; + case XCB_MAP_STATE_VIEWABLE : hidgrp(g); break; + default : warnx("cannot retrieve map state"); + } + } + + xcb_flush(con); + + return; /* void */ +} + +/* map a group on the screen */ +void mapgrp(uint8_t g) +{ + struct window *np; + + for (np = groups[g]; np; np = np->next) { + xcb_map_window(con, np->id); + logx("map: %d - %08x\n", g, np->id); + } + + +#ifdef DEBUG + lsgrp(); +#endif + + + return; +} + +/* hide/unmap a group */ +void hidgrp(uint8_t g) +{ + struct window *np; + + /* don't unmap the "black hole" group */ + if (g == 0) { + return; + } + + for (np = groups[g]; np; np = np->next) { + if (np->id != scn->root) { + xcb_unmap_window(con, np->id); + logx("hid: %d - %08x\n", g, np->id); + } + } + + + return; +} + +void evnt_create (xcb_generic_event_t *e) +{ + struct window *np; + xcb_create_notify_event_t *ev = (xcb_create_notify_event_t *)e; + + np = newnod(ev->window); + + if (np) { + addgroup(np, 0); + } + + return; +} + +void evnt_enter (xcb_generic_event_t *e) +{ + struct window *np; + xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *)e; + + if (ev->event == scn->root) { + return; + } + + np = getnod(ev->event); + + if (!np) { + return; + } + + focus = np->id; + + /* logx("cur: 0x%08x\n", focus); */ + + return; /* void */ +} + +void evnt_keypress (xcb_generic_event_t *e) +{ + int i; + xcb_keysym_t ks; + xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; + struct window *np; + + /* don't add the root window to a group */ + if (focus == scn->root) { + return; + } + + ks = getkeysym(ev->detail); + np = getnod(focus); + + if (!np) { + warnx("evnt_keypress: cannot get focused window\n"); + return; + } + + for (i=0; i<LEN(keys); i++) { + if (keys[i].sym == ks + && CLEAN(keys[i].mod) == CLEAN(ev->state) + && keys[i].func ) { + keys[i].func(np, keys[i].group); + } + } + + return; /* void */ +} + +void evnt_destroy (xcb_generic_event_t *e) +{ + struct window *np; + xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *)e; + + np = getnod(ev->window); + delnod(np); + + focus = scn->root; + + return; /* void */ +} + +/* various operations requiered to get the program up and running */ +void init() +{ + uint8_t g; + + /* connect and retrieve infos about the X server */ + con = xcb_connect(NULL, NULL); + + if (xcb_connection_has_error(con)) { + errx(1, "xcb_connect"); + } + + scn = xcb_setup_roots_iterator(xcb_get_setup(con)).data; + + if (scn == NULL) { + errx(1, "xcb_setup_roots_iterator"); + } + + focus = scn->root; + + for (g=0; g<GROUPNUM; g++) { + groups[g] = NULL; + } + + return; /* void */ +} + +/* cleanup function. called upon exit() calls */ +void cleanup() +{ + if (con != NULL) { + xcb_disconnect(con); + } + return; /* void */ +} + +/* change the events registration attribute of all the childs of parent */ +void regevnt(uint32_t *val) +{ + int i; + xcb_query_tree_reply_t *rep; + xcb_window_t *win; + static const uint32_t mask = XCB_CW_EVENT_MASK; + + /* we fetch the win tree of the window */ + rep = xcb_query_tree_reply(con, xcb_query_tree(con, scn->root), NULL); + + if (rep == NULL) { + warnx("xcb_query_tree_reply"); + } + + win = xcb_query_tree_children(rep); + + for (i = 0; i < rep->children_len; i++) { + xcb_change_window_attributes(con, win[i], mask, val); + } + xcb_flush(con); + + free(rep); + + return; /* void */ +} + +xcb_keysym_t getkeysym (xcb_keycode_t keycode) +{ + xcb_key_symbols_t *keysyms; + xcb_keysym_t keysym; + + if (!(keysyms = xcb_key_symbols_alloc(con))) + return 0; + + keysym = xcb_key_symbols_get_keysym(keysyms, (unsigned char)keycode, 0); + xcb_key_symbols_free(keysyms); + + return keysym; +} + +xcb_keycode_t *getkeycode(xcb_keysym_t keysym) +{ + xcb_key_symbols_t *keysyms; + xcb_keycode_t *keycode; + + if (!(keysyms = xcb_key_symbols_alloc(con))) { + return 0; + } + + keycode = xcb_key_symbols_get_keycode(keysyms, keysym); + xcb_key_symbols_free(keysyms); + + return keycode; +} + +/* register key events */ +void grabkeys() +{ + int i, j; + xcb_keycode_t *kc; + + for (i=0; i<LEN(keys); i++) { + kc = getkeycode(keys[i].sym); + + for (j=0; kc[j] != XCB_NO_SYMBOL; j++) { + xcb_grab_key(con, 1, scn->root, keys[i].mod , kc[j], + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + } + + } + xcb_flush(con); + + free(kc); + return; /* void */ +} + +/* discover already existing windows and add them to the list */ +void discover() +{ + int i; + xcb_query_tree_reply_t *r; + xcb_window_t *w; + struct window *np; + + /* we fetch the w tree of the window */ + r = xcb_query_tree_reply(con, xcb_query_tree(con, scn->root), NULL); + + if (r == NULL) { + warnx("xcb_query_tree_reply"); + } + + w = xcb_query_tree_children(r); + + for (i = 0; i < r->children_len; i++) { + if (w[i] != scn->root) { + np = newnod(w[i]); + addgroup(np, 0); + } + } + xcb_flush(con); + + free(r); + + return; /* void */ +} + +void run() +{ + xcb_generic_event_t *e; + static uint32_t mask = XCB_CW_EVENT_MASK; + static uint32_t val[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY + }; + + xcb_change_window_attributes(con, scn->root, mask, val); + xcb_flush(con); + + val[0] = winev; + regevnt(val); + + for (;;) { + e = xcb_wait_for_event(con); + + switch (e->response_type & ~0x80) { + case XCB_CREATE_NOTIFY : evnt_create(e); break; + case XCB_DESTROY_NOTIFY : evnt_destroy(e); break; + case XCB_ENTER_NOTIFY : evnt_enter(e); break; + case XCB_KEY_PRESS : evnt_keypress(e); break; + } + } + + return; /* void */ +} + +int main (int c, char **v) +{ + char ch; + + while ((ch = getopt(c, v, "chsv")) > 0) { + switch (ch) { + case 'h': usage(v[0]); break; + case 'v': verbose = 1; break; + /* define the action to perform depending on the flag given */ + } + } + + /* kindly terminate the program. please. */ + atexit(cleanup); + + /* setup the X connection */ + init(); + discover(); + grabkeys(); + + /* ready... GO! */ + run(); + + return 0; +}