diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7280bef --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Makefile +wmfs diff --git a/src/barwin.c b/src/barwin.c new file mode 100644 index 0000000..9c473c1 --- /dev/null +++ b/src/barwin.c @@ -0,0 +1,124 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include "wmfs.h" +#include "barwin.h" +#include "util.h" + +/** Create a barwin + * \param parent Parent window of the BarWindow + * \param x X position + * \param y Y position + * \param w barwin Width + * \param h barwin Height + * \param color barwin color + * \param entermask bool for know if the EnterMask mask is needed + * \return The BarWindow pointer +*/ +struct barwin* +barwin_new(Window parent, int x, int y, int w, int h, Color fg, Color bg, bool entermask) +{ + struct barwin *b = (struct barwin*)xcalloc(1, sizeof(struct barwin)); + XSetWindowAttributes at = + { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = BARWIN_MASK + }; + + if(entermask) + at.event_mask |= BARWIN_ENTERMASK; + + /* Create window */ + b->win = XCreateWindow(W->dpy, parent, + x, y, w, h, + 0, W->xdepth, + CopyFromParent, + DefaultVisual(W->dpy, W->xscreen), + BARWIN_WINCW, + &at); + + b->dr = XCreatePixmap(W->dpy, parent, w, h, W->xdepth); + + /* Property */ + b->geo.x = x; + b->geo.y = y; + b->geo.w = w; + b->geo.h = h; + b->bg = bg; + b->fg = fg; + + SLIST_INIT(&b->mousebinds); + + /* Attach */ + SLIST_INSERT_HEAD(&W->h.barwin, b, next); + + return b; +} + +/** Delete a barwin + * \param bw barwin pointer +*/ +void +barwin_remove(struct barwin *b) +{ + SLIST_REMOVE(&W->h.barwin, b, barwin, next); + + XSelectInput(W->dpy, b->win, NoEventMask); + XDestroyWindow(W->dpy, b->win); + XFreePixmap(W->dpy, b->dr); + + /* Free mousebinds */ + FREE_LIST(mousebind, b->mousebinds); + + free(b); +} + +/** Resize a barwin + * \param bw barwin pointer + * \param w Width + * \param h Height +*/ +void +barwin_resize(struct barwin *b, int w, int h) +{ + /* Frame */ + XFreePixmap(W->dpy, b->dr); + + b->dr = XCreatePixmap(W->dpy, W->root, w, h, W->xdepth); + + b->geo.w = w; + b->geo.h = h; + + XResizeWindow(W->dpy, b->win, w, h); +} + +void +barwin_mousebind_new(struct barwin *b, unsigned int button, bool u, struct geo a, void (*func)(Uicb), Uicb cmd) +{ + struct mousebind *m = (struct mousebind*)xcalloc(1, sizeof(struct mousebind)); + + m->button = button; + m->use_area = u; + m->area = a; + m->func = func; + + m->cmd = (cmd ? xstrdup(cmd) : NULL); + + SLIST_INSERT_HEAD(&b->mousebinds, m, next); +} + +/** Refresh the barwin Color + * \param bw barwin pointer +*/ +void +barwin_refresh_color(struct barwin *b) +{ + XSetForeground(W->dpy, W->gc, b->bg); + XFillRectangle(W->dpy, b->dr, W->gc, 0, 0, b->geo.w, b->geo.h); +} + + + diff --git a/src/barwin.h b/src/barwin.h new file mode 100644 index 0000000..cc9b693 --- /dev/null +++ b/src/barwin.h @@ -0,0 +1,32 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef BARWIN_H +#define BARWIN_H + +#include "wmfs.h" + +#define BARWIN_MASK \ + (SubstructureRedirectMask | SubstructureNotifyMask \ + | ButtonMask | MouseMask | ExposureMask | VisibilityChangeMask \ + | StructureNotifyMask | SubstructureRedirectMask) + +#define BARWIN_ENTERMASK (EnterWindowMask | LeaveWindowMask | FocusChangeMask) +#define BARWIN_WINCW (CWOverrideRedirect | CWBackPixmap | CWEventMask) + +#define barwin_delete_subwin(b) XDestroySubwindows(W->dpy, b->win) +#define barwin_map_subwin(b) XMapSubwindows(W->dpy, b->win) +#define barwin_unmap_subwin(b) XUnmapSubwindows(W->dpy, b->win) +#define barwin_refresh(b) XCopyArea(W->dpy, b->dr, b->win, W->gc, 0, 0, b->geo.w, b->geo.h, 0, 0) +#define barwin_map(b) XMapWindow(W->dpy, b->win); +#define barwin_unmap(b) XUnmapWindow(W->dpy, b->win); + +struct barwin* barwin_new(Window parent, int x, int y, int w, int h, Color fg, Color bg, bool entermask); +void barwin_remove(struct barwin *b); +void barwin_resize(struct barwin *b, int w, int h); +void barwin_mousebind_new(struct barwin *b, unsigned int button, bool u, struct geo a, void (*func)(Uicb), Uicb cmd); +void barwin_refresh_color(struct barwin *b); + +#endif /* BARWIN_H */ diff --git a/src/barwin.o b/src/barwin.o new file mode 100644 index 0000000..27776af Binary files /dev/null and b/src/barwin.o differ diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..88edca9 --- /dev/null +++ b/src/client.c @@ -0,0 +1,432 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include + +#include "client.h" +#include "config.h" +#include "util.h" +#include "barwin.h" +#include "ewmh.h" +#include "layout.h" +#include "draw.h" + +#define CLIENT_MOUSE_MOD Mod1Mask + +#define CLIENT_RESIZE_DIR(D) \ +void uicb_client_resize_##D(Uicb cmd) \ +{ \ + if(W->client) \ + client_fac_resize(W->client, D, ATOI(cmd)); \ +} + +#define CLIENT_ACTION_DIR(A, D) \ +void uicb_client_##A##_##D(Uicb cmd) \ +{ \ + (void)cmd; \ + struct client *c; \ + if(W->client && (c = client_next_with_pos(W->client, D))) \ + client_##A(c); \ +} + +#define CLIENT_ACTION_LIST(A, L) \ +void uicb_client_##A##_##L(Uicb cmd) \ +{ \ + (void)cmd; \ + struct client *c; \ + if(W->client && (c = client_##L(W->client))) \ + client_##A(c); \ +} + +/* uicb_client_resize_dir() */ +CLIENT_RESIZE_DIR(Right) +CLIENT_RESIZE_DIR(Left) +CLIENT_RESIZE_DIR(Top) +CLIENT_RESIZE_DIR(Bottom) + +/* uicb_client_focus_dir() */ +CLIENT_ACTION_DIR(focus, Right) +CLIENT_ACTION_DIR(focus, Left) +CLIENT_ACTION_DIR(focus, Top) +CLIENT_ACTION_DIR(focus, Bottom) + +/* uicb_client_swapsel_dir() */ +#define client_swapsel(c) client_swap(W->client, c) +CLIENT_ACTION_DIR(swapsel, Right) +CLIENT_ACTION_DIR(swapsel, Left) +CLIENT_ACTION_DIR(swapsel, Top) +CLIENT_ACTION_DIR(swapsel, Bottom) + +/* uicb_client_focus_next/prev() */ +CLIENT_ACTION_LIST(focus, next) +CLIENT_ACTION_LIST(focus, prev) + +/* uicb_client_swapsel_next/prev() */ +CLIENT_ACTION_LIST(swapsel, next) +CLIENT_ACTION_LIST(swapsel, prev) + +/** Send a ConfigureRequest event to the struct client + * \param c struct client pointer +*/ +void +client_configure(struct client *c) +{ + XConfigureEvent ev = + { + .type = ConfigureNotify, + .event = c->win, + .window = c->win, + .x = c->geo.x, + .y = c->geo.y, + .width = c->geo.w, + .height = c->geo.h, + .above = None, + .border_width = 0, + .override_redirect = 0 + }; + + XSendEvent(W->dpy, c->win, False, StructureNotifyMask, (XEvent *)&ev); + XSync(W->dpy, False); +} + +struct client* +client_gb_win(Window w) +{ + struct client *c = SLIST_FIRST(&W->h.client); + + while(c && c->win != w) + c = SLIST_NEXT(c, next); + + return c; +} + +struct client* +client_gb_pos(struct tag *t, int x, int y) +{ + struct client *c = SLIST_FIRST(&t->clients); + + while(c) + { + if(INAREA(x, y, c->geo)) + return c; + + c = SLIST_NEXT(c, tnext); + } + + return NULL; +} + +/** Get client left/right/top/bottom of selected client + *\param bc Base client + *\param pos Position + *\return Client found or NULL +*/ +struct client* +client_next_with_pos(struct client *bc, Position p) +{ + struct client *c; + int x, y; + static const char scanfac[PositionLast] = { +10, -10, 0, 0 }; + Position ip = Bottom - p; + + /* + * Set start place of pointer (edge with position + * of base client) for faster scanning. + */ + x = bc->geo.x + ((p == Right) ? bc->geo.w : 0); + y = bc->geo.y + ((p == Bottom) ? bc->geo.h : 0); + y += ((LDIR(p)) ? (bc->geo.h >> 1) : 0); + x += ((p > Left) ? (bc->geo.w >> 1) : 0); + + /* Scan in right direction to next(p) physical client */ + while((c = client_gb_pos(bc->tag, x, y)) == bc) + { + x += scanfac[p]; + y += scanfac[ip]; + } + + return c; +} + +void +client_swap(struct client *c1, struct client *c2) +{ + struct tag *t; + struct geo g; + + if(c1 == c2 || !c1 || !c2) + return; + + t = c1->tag; + g = c1->geo; + + swap_ptr((void**)&c1->screen, (void**)&c2->screen); + + tag_client(c2->tag, c1); + tag_client(t, c2); + + client_moveresize(c1, c2->geo); + client_moveresize(c2, g); +} + +static void +client_grabbuttons(struct client *c, bool focused) +{ + XUngrabButton(W->dpy, AnyButton, AnyModifier, c->win); + + if(focused) + { + int i = 0; + + while(i++ != Button5) + { + XGrabButton(W->dpy, i, CLIENT_MOUSE_MOD, c->win, False, + ButtonMask, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(W->dpy, i, CLIENT_MOUSE_MOD | LockMask, c->win, False, + ButtonMask, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(W->dpy, i, CLIENT_MOUSE_MOD | W->numlockmask, c->win, False, + ButtonMask, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(W->dpy, i, CLIENT_MOUSE_MOD | LockMask | W->numlockmask, c->win, False, + ButtonMask, GrabModeAsync, GrabModeSync, None, None); + } + + return; + } + + XGrabButton(W->dpy, AnyButton, AnyModifier, c->win, False, + ButtonMask, GrabModeAsync, GrabModeSync, None, None); + +} + +static inline void +client_draw_bord(struct client *c) +{ + struct geo g = { 0, 0, c->screen->ugeo.w, c->screen->ugeo.h }; + + draw_rect(c->tag->frame, g, THEME_DEFAULT->client_n.bg); + + /* Selected client's border */ + if(W->client) + draw_rect(W->client->tag->frame, W->client->tag->sel->geo, THEME_DEFAULT->client_s.bg); +} + + +void +client_focus(struct client *c) +{ + /* Unfocus selected */ + if(W->client && W->client != c) + client_grabbuttons(W->client, false); + + /* Focus c */ + if((W->client = c)) + { + c->tag->sel = c; + + client_draw_bord(c); + client_grabbuttons(c, true); + + XSetInputFocus(W->dpy, c->win, RevertToPointerRoot, CurrentTime); + } + else + { + W->client = W->screen->seltag->sel = NULL; + XSetInputFocus(W->dpy, W->root, RevertToPointerRoot, CurrentTime); + } +} + +/** Get a client name + * \param c struct client pointer +*/ +void +client_get_name(struct client *c) +{ + Atom rt; + int rf; + unsigned long ir, il; + + /* This one instead XFetchName for utf8 name support */ + if(XGetWindowProperty(W->dpy, c->win, ATOM("_NET_WM_NAME"), 0, 4096, + False, ATOM("UTF8_STRING"), &rt, &rf, &ir, &il, (unsigned char**)&c->title) != Success) + XGetWindowProperty(W->dpy, c->win, ATOM("WM_NAME"), 0, 4096, + False, ATOM("UTF8_STRING"), &rt, &rf, &ir, &il, (unsigned char**)&c->title); + + /* Still no title... */ + if(!c->title) + XFetchName(W->dpy, c->win, &(c->title)); +} + +/** Close a client + * \param c struct client pointer +*/ +void +client_close(struct client *c) +{ + int proto; + XEvent ev; + Atom *atom = NULL; + + /* Event will call client_remove */ + if(XGetWMProtocols(W->dpy, c->win, &atom, &proto) && atom) + { + while(proto--) + if(atom[proto] == ATOM("WM_DELETE_WINDOW")) + { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = ATOM("WM_PROTOCOLS"); + ev.xclient.format = 32; + ev.xclient.data.l[0] = ATOM("WM_DELETE_WINDOW"); + ev.xclient.data.l[1] = CurrentTime; + + XSendEvent(W->dpy, c->win, False, NoEventMask, &ev); + XFree(atom); + + return; + } + } + + XKillClient(W->dpy, c->win); +} + +void +uicb_client_close(Uicb cmd) +{ + (void)cmd; + + if(W->client) + client_close(W->client); +} + +struct client* +client_new(Window w, XWindowAttributes *wa) +{ + struct client *c = xcalloc(1, sizeof(struct client)); + + /* C attributes */ + c->win = w; + c->screen = W->screen; + c->flags = 0; + c->tag = NULL; + + /* struct geometry */ + c->geo.x = wa->x; + c->geo.y = wa->y; + c->geo.w = wa->width; + c->geo.h = wa->height; + c->tgeo = c->wgeo = c->geo; + + /* Set tag */ + tag_client(W->screen->seltag, c); + + /* X window attributes */ + XSelectInput(W->dpy, w, EnterWindowMask | LeaveWindowMask | StructureNotifyMask | PropertyChangeMask); + XSetWindowBorderWidth(W->dpy, w, 0); + client_grabbuttons(c, false); + + /* Attach */ + SLIST_INSERT_HEAD(&W->h.client, c, next); + + /* Map */ + WIN_STATE(w, Map); + ewmh_set_wm_state(w, NormalState); + + client_get_name(c); + client_focus(c); + client_configure(c); + + return c; +} + +void +client_moveresize(struct client *c, struct geo g) +{ + int bord = THEME_DEFAULT->client_border_width; + + c->geo = g; + + /* Window geo */ + c->wgeo.x = g.x + bord; + c->wgeo.y = g.y + bord ; + c->wgeo.w = g.w - (bord << 1); + c->wgeo.h = g.h - (bord << 1); + + XMoveResizeWindow(W->dpy, c->win, + c->wgeo.x, c->wgeo.y, + c->wgeo.w, c->wgeo.h); + + client_draw_bord(c); + client_configure(c); +} + +void +client_maximize(struct client *c) +{ + c->geo = c->tag->screen->ugeo; + + c->geo.x = c->geo.y = 0; /* Frame x/y, not screen geo */ + c->geo.w = c->tag->screen->ugeo.w; + c->geo.h = c->tag->screen->ugeo.h; + + client_moveresize(c, c->geo); +} + +void +client_fac_resize(struct client *c, Position p, int fac) +{ + struct client *gc = client_next_with_pos(c, p); + Position rp = RPOS(p); + + if(!gc || gc->screen != c->screen) + return; + + /* Check futur size/pos */ + if(!client_fac_geo(c, p, fac) + || !client_fac_geo(gc, rp, -fac) + || !client_fac_check_row(c, p, fac) + || !client_fac_check_row(gc, rp, -fac)) + return; + + + /* Simple resize with only c & gc */ + if(GEO_CHECK2(c->geo, gc->geo, p)) + { + client_moveresize(c, c->tgeo); + client_moveresize(gc, gc->tgeo); + } + /* Resize with row parents */ + else + { + client_fac_arrange_row(c, p, fac); + client_fac_arrange_row(gc, rp, -fac); + } +} + +void +client_remove(struct client *c) +{ + XGrabServer(W->dpy); + XSetErrorHandler(wmfs_error_handler_dummy); + XReparentWindow(W->dpy, c->win, W->root, c->geo.x, c->geo.y); + + /* Remove from global client list */ + SLIST_REMOVE(&W->h.client, c, client, next); + + tag_client(NULL, c); + + ewmh_set_wm_state(c->win, WithdrawnState); + + XUngrabServer(W->dpy); + XSync(W->dpy, False); + XSetErrorHandler(wmfs_error_handler); + + free(c); +} + +void +client_free(void) +{ + FREE_LIST(client, W->h.client); +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..fce8ed2 --- /dev/null +++ b/src/client.h @@ -0,0 +1,130 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "wmfs.h" +#include "layout.h" + +void client_configure(struct client *c); +struct client *client_gb_win(Window w); +struct client *client_gb_pos(struct tag *t, int x, int y); +struct client *client_next_with_pos(struct client *bc, Position p); +void client_swap(struct client *c1, struct client *c2); +void client_focus(struct client *c); +void client_get_name(struct client *c); +void client_close(struct client *c); +void uicb_client_close(Uicb cmd); +struct client *client_new(Window w, XWindowAttributes *wa); +void client_moveresize(struct client *c, struct geo g); +void client_maximize(struct client *c); +void client_fac_resize(struct client *c, Position p, int fac); +void client_remove(struct client *c); +void client_free(void); + +/* Generated */ +void uicb_client_resize_Right(Uicb); +void uicb_client_resize_Left(Uicb); +void uicb_client_resize_Top(Uicb); +void uicb_client_resize_Bottom(Uicb); +void uicb_client_focus_Right(Uicb); +void uicb_client_focus_Left(Uicb); +void uicb_client_focus_Top(Uicb); +void uicb_client_focus_Bottom(Uicb); +void uicb_client_swapsel_Right(Uicb); +void uicb_client_swapsel_Left(Uicb); +void uicb_client_swapsel_Top(Uicb); +void uicb_client_swapsel_Bottom(Uicb); +void uicb_client_focus_next(Uicb); +void uicb_client_focus_prev(Uicb); +void uicb_client_swapsel_next(Uicb); +void uicb_client_swapsel_prev(Uicb); + +static inline struct client* +client_next(struct client *c) +{ + return (SLIST_NEXT(c, tnext) + ? SLIST_NEXT(c, tnext) + : SLIST_FIRST(&c->tag->clients)); +} + +static inline struct client* +client_prev(struct client *c) +{ + struct client *cc; + + for(cc = SLIST_FIRST(&c->tag->clients); + SLIST_NEXT(cc, tnext) && SLIST_NEXT(cc, tnext) != c; + cc = SLIST_NEXT(cc, tnext)); + + return cc; +} + +static inline bool +client_fac_geo(struct client *c, Position p, int fac) +{ + struct geo cg = c->geo; + + switch(p) + { + default: + case Right: + cg.w += fac; + break; + case Left: + cg.x -= fac; + cg.w += fac; + break; + case Top: + cg.y -= fac; + cg.h += fac; + break; + case Bottom: + cg.h += fac; + break; + } + + /* Check for incompatible geo */ + if(cg.w > c->screen->ugeo.w || cg.h > c->screen->ugeo.h + || cg.w < 5 || cg.h < 5) + return false; + + /* Set transformed geo in tmp geo */ + c->tgeo = cg; + + return true; +} + +static inline bool +client_fac_check_row(struct client *c, Position p, int fac) +{ + struct geo g = c->geo; + struct client *cc; + + /* Travel clients to search parents of row and check geos */ + SLIST_FOREACH(cc, &c->tag->clients, tnext) + if(GEO_PARENTROW(g, cc->geo, p) && !client_fac_geo(cc, p, fac)) + return false; + + return true; +} + +static inline void +client_fac_arrange_row(struct client *c, Position p, int fac) +{ + struct geo g = c->geo; + struct client *cc; + + /* Travel clients to search row parents and apply fac */ + SLIST_FOREACH(cc, &c->tag->clients, tnext) + if(GEO_PARENTROW(g, cc->geo, p)) + { + client_fac_geo(cc, p, fac); + client_moveresize(cc, cc->tgeo); + } +} + +#endif /* CLIENT_H */ diff --git a/src/client.o b/src/client.o new file mode 100644 index 0000000..80c4a45 Binary files /dev/null and b/src/client.o differ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..d62f016 --- /dev/null +++ b/src/config.c @@ -0,0 +1,216 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include "config.h" +#include "wmfs.h" +#include "parse.h" +#include "tag.h" +#include "screen.h" +#include "infobar.h" +#include "util.h" + +#define CONFIG_DEFAULT_PATH ".config/wmfs/wmfsrc2" /* tmp */ + +static void +config_theme(void) +{ + struct theme *t, *p = NULL; + size_t i, n; + struct conf_sec *sec, **ks; + + /* [themes] */ + sec = fetch_section_first(NULL, "themes"); + ks = fetch_section(sec, "theme"); + + /* No theme section? Make one with default value anyway. */ + if(!(n = fetch_section_count(ks))) + ++n; + + SLIST_INIT(&W->h.theme); + + /* [theme]*/ + for(i = 0; i < n; ++i) + { + t = (struct theme*)xcalloc(1, sizeof(struct theme)); + + t->name = fetch_opt_first(ks[i], "default", "name").str; + + wmfs_init_font(fetch_opt_first(ks[i], "fixed", "font").str, t); + + /* bars */ + t->bars.fg = color_atoh(fetch_opt_first(ks[i], "#CCCCCC", "bars_fg").str); + t->bars.bg = color_atoh(fetch_opt_first(ks[i], "#222222", "bars_bg").str); + t->bars_width = fetch_opt_first(ks[i], "12", "bars_width").num; + + /* + * Elements + */ + t->tags_n.fg = color_atoh(fetch_opt_first(ks[i], "#CCCCCC", "tags_normal_fg").str); + t->tags_n.bg = color_atoh(fetch_opt_first(ks[i], "#222222", "tags_normal_bg").str); + t->tags_s.fg = color_atoh(fetch_opt_first(ks[i], "#222222", "tags_sel_fg").str); + t->tags_s.bg = color_atoh(fetch_opt_first(ks[i], "#CCCCCC", "tags_sel_bg").str); + t->tags_border_col = color_atoh(fetch_opt_first(ks[i], "#888888", "tags_border_color").str); + t->tags_border_width = fetch_opt_first(ks[i], "0", "tags_border_width").num; + + /* Client / frame */ + t->client_n.fg = color_atoh(fetch_opt_first(ks[i], "#CCCCCC", "client_normal_fg").str); + t->client_n.bg = color_atoh(fetch_opt_first(ks[i], "#222222", "client_normal_bg").str); + t->client_s.fg = color_atoh(fetch_opt_first(ks[i], "#222222", "client_sel_fg").str); + t->client_s.bg = color_atoh(fetch_opt_first(ks[i], "#CCCCCC", "client_sel_bg").str); + t->frame_bg = color_atoh(fetch_opt_first(ks[i], "#555555", "frame_bg").str); + t->client_titlebar_width = fetch_opt_first(ks[i], "12", "client_titlebar_width").num; + t->client_border_width = fetch_opt_first(ks[i], "1", "client_border_width").num; + + /* insert_tail with SLIST */ + if(SLIST_EMPTY(&W->h.theme)) + SLIST_INSERT_HEAD(&W->h.theme, t, next); + else + SLIST_INSERT_AFTER(p, t, next); + + p = t; + } + + free(ks); +} + +static void +config_bars(void) +{ + struct screen *s; + struct theme *t; + size_t i, n; + struct conf_sec *sec, **ks; + int screenid; + char *elem; + Barpos pos = BarTop; + + /* [bars] */ + sec = fetch_section_first(NULL, "bars"); + ks = fetch_section(sec, "bar"); + n = fetch_section_count(ks); + + /* [bar] */ + for(i = 0; i < n; ++i) + { + elem = fetch_opt_first(ks[i], "", "elements").str; + screenid = fetch_opt_first(ks[i], "-1", "screen").num; + t = name_to_theme(fetch_opt_first(ks[i], "default", "theme").str); + pos = fetch_opt_first(ks[i], "0", "position").num; + + SLIST_FOREACH(s, &W->h.screen, next) + if(screenid == s->id || screenid == -1) + infobar_new(s, t, pos, elem); + } + + free(ks); +} + + +static void +config_tag(void) +{ + struct screen *s; + struct tag *t; + size_t i, n; + struct conf_sec *sec, **ks; + char *name; + int screenid; + + /* [tags] */ + sec = fetch_section_first(NULL, "tags"); + ks = fetch_section(sec, "tag"); + n = fetch_section_count(ks); + + /* [tag] */ + for(i = 0; i < n; ++i) + { + name = fetch_opt_first(ks[i], "tag", "name").str; + screenid = fetch_opt_first(ks[i], "-1", "screen").num; + + SLIST_FOREACH(s, &W->h.screen, next) + if(screenid == s->id || screenid == -1) + { + t = tag_new(s, name); + + /* Set first tag as seltag */ + if(t == TAILQ_FIRST(&s->tags)) + s->seltag = t; + } + } + + free(ks); +} + +static void +config_keybind(void) +{ + int i, n; + size_t j; + struct conf_sec *sec, **ks; + struct opt_type *opt; + char *cmd; + struct keybind *k; + + /* [keys] */ + sec = fetch_section_first(NULL, "keys"); + ks = fetch_section(sec, "key"); + n = fetch_section_count(ks); + + SLIST_INIT(&W->h.keybind); + + /* [key] */ + for(i = 0; i < n; ++i) + { + k = (struct keybind*)xcalloc(1, sizeof(struct keybind)); + + /* mod = {} */ + opt = fetch_opt(ks[i], "", "mod"); + + for(j = k->mod = 0; j < fetch_opt_count(opt); ++j) + k->mod |= modkey_keysym(opt[j].str); + + free(opt); + + /* key = */ + k->keysym = XStringToKeysym(fetch_opt_first(ks[i], "None", "key").str); + + /* func = */ + if(!(k->func = uicb_name_func(fetch_opt_first(ks[i], "", "func").str))) + { + warnx("configuration: Unknown Function \"%s\".", + fetch_opt_first(ks[i], "", "func").str); + k->func = uicb_spawn; + } + + /* cmd = */ + if((cmd = fetch_opt_first(ks[i], "", "cmd").str)) + k->cmd = xstrdup(cmd); + + SLIST_INSERT_HEAD(&W->h.keybind, k, next); + } + + wmfs_grab_keys(); + + free(ks); +} + +void +config_init(void) +{ + char *path; + + xasprintf(&path, "%s/"CONFIG_DEFAULT_PATH, getenv("HOME")); + + if(get_conf(path) == -1) + errx(1, "parsing configuration file (%s) failed.", path); + + config_theme(); + config_keybind(); + config_tag(); + config_bars(); + + free(path); + free_conf(); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..998bd2a --- /dev/null +++ b/src/config.h @@ -0,0 +1,115 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + + +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +#include "wmfs.h" +#include "util.h" +#include "tag.h" +#include "client.h" + +#define THEME_DEFAULT (SLIST_FIRST(&W->h.theme)) + +static const struct { char *name; void (*func)(Uicb cmd); } uicb_list[] = +{ + /* Sys */ + { "spawn", uicb_spawn }, + { "quit", uicb_quit }, + { "reload", uicb_reload }, + + /* Tag */ + { "tag_set", uicb_tag_set }, + { "tag", uicb_tag_set_with_name }, + { "tag_next", uicb_tag_next }, + { "tag_prev", uicb_tag_prev }, + + /* Layout */ + { "layout_vmirror", uicb_layout_vmirror }, + { "layout_hmirror", uicb_layout_hmirror }, + { "layout_rotate_left", uicb_layout_rotate_left }, + { "layout_rotate_right", uicb_layout_rotate_right }, + + /* Client */ + { "client_close", uicb_client_close }, + { "client_resize_right", uicb_client_resize_Right }, + { "client_resize_left", uicb_client_resize_Left }, + { "client_resize_top", uicb_client_resize_Top }, + { "client_resize_bottom", uicb_client_resize_Bottom }, + { "client_focus_right", uicb_client_focus_Right }, + { "client_focus_left", uicb_client_focus_Left }, + { "client_focus_top", uicb_client_focus_Top }, + { "client_focus_bottom", uicb_client_focus_Bottom }, + { "client_swap_right", uicb_client_swapsel_Right }, + { "client_swap_left", uicb_client_swapsel_Left }, + { "client_swap_top", uicb_client_swapsel_Top }, + { "client_swap_bottom", uicb_client_swapsel_Bottom }, + { "client_focus_next", uicb_client_focus_next }, + { "client_focus_prev", uicb_client_focus_prev }, + { "client_swap_next", uicb_client_swapsel_next }, + { "client_swap_prev", uicb_client_swapsel_prev }, + { NULL, NULL } +}; + +static inline void* +uicb_name_func(Uicb name) +{ + int i = 0; + + for(; uicb_list[i].func; ++i) + if(!strcmp(name, uicb_list[i].name)) + return uicb_list[i].func; + + return NULL; +} + +static const struct { const char *name; KeySym keysym; } key_list[] = +{ + {"Control", ControlMask }, + {"Shift", ShiftMask }, + {"Lock", LockMask }, + {"Alt", Mod1Mask }, + {"Mod1", Mod1Mask }, + {"Mod2", Mod2Mask }, + {"Mod3", Mod3Mask }, + {"Mod4", Mod4Mask }, + {"Super", Mod4Mask }, + {"Home", Mod4Mask }, + {"Mod5", Mod5Mask }, + {NULL, NoSymbol } +}; + +static inline KeySym +modkey_keysym(const char *name) +{ + int i = 0; + + for(; key_list[i].name; ++i) + if(!strcmp(name, key_list[i].name)) + return key_list[i].keysym; + + return NoSymbol; +} + +static inline struct theme* +name_to_theme(const char *name) +{ + struct theme *t; + + SLIST_FOREACH(t, &W->h.theme, next) + if(!strcmp(t->name, name)) + return t; + + return THEME_DEFAULT; +} + + +void config_init(void); + +#endif /* CONFIG_H */ diff --git a/src/config.o b/src/config.o new file mode 100644 index 0000000..8ff4fde Binary files /dev/null and b/src/config.o differ diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..f4cb968 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,42 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + + +#ifndef DRAW_H +#define DRAW_H + +#include +#include + +#include "wmfs.h" + +#define TEXTY(t, w) ((t->font.height - t->font.de) + ((w - t->font.height) >> 1)) +#define PAD (8) + +static inline void +draw_text(Drawable d, struct theme *t, int x, int y, Color fg, const char *str) +{ + XSetForeground(W->dpy, W->gc, fg); + XmbDrawString(W->dpy, d, t->font.fontset, W->gc, x, y, str, strlen(str)); +} + +static inline void +draw_rect(Drawable d, struct geo g, Color bg) +{ + XSetForeground(W->dpy, W->gc, bg); + XFillRectangle(W->dpy, d, W->gc, g.x, g.y, g.w, g.h); +} + +static inline unsigned short +draw_textw(struct theme *t, const char *str) +{ + XRectangle r; + + XmbTextExtents(t->font.fontset, str, strlen(str), NULL, &r); + + return r.width; +} + +#endif /* DRAW_H */ diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..e76ccde --- /dev/null +++ b/src/event.c @@ -0,0 +1,260 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include "event.h" +#include "ewmh.h" +#include "util.h" +#include "wmfs.h" +#include "client.h" +#include "barwin.h" +#include "screen.h" + +#define EVDPY(e) (e)->xany.display + +static void +event_buttonpress(XEvent *e) +{ + XButtonEvent *ev = &e->xbutton; + struct mousebind *m; + struct barwin *b; + + screen_update_sel(); + + SLIST_FOREACH(b, &W->h.barwin, next) + if(b->win == ev->window) + { + SLIST_FOREACH(m, &b->mousebinds, next) + if(m->button == ev->button) + if(!m->use_area || (m->use_area && INAREA(ev->x, ev->y, m->area))) + if(m->func) + m->func(m->cmd); + + break; + } +} + +static void +event_enternotify(XEvent *e) +{ + XCrossingEvent *ev = &e->xcrossing; + struct client *c; + + if((ev->mode != NotifyNormal + || ev->detail == NotifyInferior + || ev->detail == NotifyAncestor) + && ev->window != W->root) + return; + + if((c = client_gb_win(ev->window))) + client_focus(c); +} + +static void +event_clientmessageevent(XEvent *e) +{ + (void)e; + /* XClientMessageEvent *ev = &e->xclient; + client *c;*/ +} + +static void +event_configureevent(XEvent *e) +{ + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + struct client *c; + + if((c = client_gb_win(ev->window))) + { + if(ev->value_mask & CWX) + c->geo.x = ev->x; + if(ev->value_mask & CWY) + c->geo.y = ev->y; + if(ev->value_mask & CWWidth) + c->geo.w = ev->width; + if(ev->value_mask & CWHeight) + c->geo.h = ev->height; + + client_configure(c); + + XMoveResizeWindow(EVDPY(e), c->win, c->geo.x, c->geo.y, c->geo.w, c->geo.h); + } + else + { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + + XConfigureWindow(EVDPY(e), ev->window, ev->value_mask, &wc); + } +} + +static void +event_destroynotify(XEvent *e) +{ + XDestroyWindowEvent *ev = &e->xdestroywindow; + struct client *c; + + if((c = client_gb_win(ev->window))) + client_remove(c); +} + +static void +event_focusin(XEvent *e) +{ + if(W->client && e->xfocus.window != W->client->win) + client_focus(W->client); +} + +static void +event_maprequest(XEvent *e) +{ + XMapRequestEvent *ev = &e->xmaprequest; + XWindowAttributes at; + + /* Which windows to manage */ + if(!XGetWindowAttributes(EVDPY(e), ev->window, &at) + || at.override_redirect) + return; + + if(!client_gb_win(ev->window)) + client_new(ev->window, &at); +} + +static void +event_mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + XRefreshKeyboardMapping(ev); + + if(ev->request == MappingKeyboard) + wmfs_grab_keys(); +} + +static void +event_propertynotify(XEvent *e) +{ + XPropertyEvent *ev = &e->xproperty; + struct client *c; + + if(ev->state == PropertyDelete) + return; + + if((c = client_gb_win(ev->window))) + { + switch(ev->atom) + { + case XA_WM_TRANSIENT_FOR: + break; + case XA_WM_NORMAL_HINTS: + /* client_get_size_hints(c); */ + break; + case XA_WM_HINTS: + /* + XWMHints *h; + + if((h = XGetWMHints(EVDPY, c->win)) && (h->flags & XUrgencyHint) && c != sel) + { + client_urgent(c, True); + XFree(h); + } + */ + break; + default: + if(ev->atom == XA_WM_NAME || ev->atom == W->net_atom[net_wm_name]) + client_get_name(c); + break; + } + } +} + +static void +event_unmapnotify(XEvent *e) +{ + XUnmapEvent *ev = &e->xunmap; + struct client *c; + + if((c = client_gb_win(ev->window)) && ev->send_event) + client_remove(c); +} + +/* +static void +event_motionnotify(XEvent *e) +{ + XMotionEvent *ev = &e->xmotion; + struct client *c; + + if((c = client_gb_win(ev->subwindow)) && c != c->tag->sel) + client_focus(c); +} +*/ + +static void +event_keypress(XEvent *e) +{ + XKeyPressedEvent *ev = &e->xkey; + KeySym keysym = XKeycodeToKeysym(EVDPY(e), (KeyCode)ev->keycode, 0); + struct keybind *k; + + screen_update_sel(); + + SLIST_FOREACH(k, &W->h.keybind, next) + if(k->keysym == keysym && KEYPRESS_MASK(k->mod) == KEYPRESS_MASK(ev->state)) + if(k->func) + k->func(k->cmd); +} + +static void +event_expose(XEvent *e) +{ + XExposeEvent *ev = &e->xexpose; + struct barwin *b; + + SLIST_FOREACH(b, &W->h.barwin, next) + if(b->win == ev->window) + { + barwin_refresh(b); + return; + } +} + +static void +event_dummy(XEvent *e) +{ + /* printf("%d\n", e->type);*/ + (void)e; +} + +void +event_init(void) +{ + int i = MAX_EV; + + while(i--) + event_handle[i] = event_dummy; + + event_handle[ButtonPress] = event_buttonpress; + event_handle[ClientMessage] = event_clientmessageevent; + event_handle[ConfigureRequest] = event_configureevent; + event_handle[DestroyNotify] = event_destroynotify; + event_handle[EnterNotify] = event_enternotify; + event_handle[Expose] = event_expose; + event_handle[FocusIn] = event_focusin; + event_handle[KeyPress] = event_keypress; + /*event_handle[MapNotify] = event_mapnotify;*/ + event_handle[MapRequest] = event_maprequest; + event_handle[MappingNotify] = event_mappingnotify; + /*event_handle[MotionNotify] = event_motionnotify;*/ + event_handle[PropertyNotify] = event_propertynotify; + /*event_handle[ReparentNotify] = event_reparentnotify;*/ + /*event_handle[SelectionClear] = event_selectionclearevent;*/ + event_handle[UnmapNotify] = event_unmapnotify; +} + diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..b43e2e8 --- /dev/null +++ b/src/event.h @@ -0,0 +1,20 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef EVENT_H +#define EVENT_H + +#include "wmfs.h" + +#define MAX_EV 256 + +#define KEYPRESS_MASK(m) (m & ~(W->numlockmask | LockMask)) +#define EVENT_HANDLE(e) event_handle[(e)->type](e); + +void event_init(void); + +void (*event_handle[MAX_EV])(XEvent*); + +#endif /* EVENT_H */ diff --git a/src/event.o b/src/event.o new file mode 100644 index 0000000..76b12c4 Binary files /dev/null and b/src/event.o differ diff --git a/src/ewmh.c b/src/ewmh.c new file mode 100644 index 0000000..00c379c --- /dev/null +++ b/src/ewmh.c @@ -0,0 +1,106 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include +#include + +#include "ewmh.h" +#include "util.h" + +void +ewmh_init(void) +{ + int b = 1; + + W->net_atom = xcalloc(net_last, sizeof(Atom)); + + /* EWMH hints */ + W->net_atom[wm_state] = ATOM("WM_STATE"); + W->net_atom[net_supported] = ATOM("_NET_SUPPORTED"); + W->net_atom[net_client_list] = ATOM("_NET_CLIENT_LIST"); + W->net_atom[net_frame_extents] = ATOM("_NET_FRAME_EXTENTS"); + W->net_atom[net_number_of_desktops] = ATOM("_NET_NUMBER_OF_DESKTOPS"); + W->net_atom[net_current_desktop] = ATOM("_NET_CURRENT_DESKTOP"); + W->net_atom[net_desktop_names] = ATOM("_NET_DESKTOP_NAMES"); + W->net_atom[net_desktop_geometry] = ATOM("_NET_DESKTOP_GEOMETRY"); + W->net_atom[net_active_window] = ATOM("_NET_ACTIVE_WINDOW"); + W->net_atom[net_close_window] = ATOM("_NET_CLOSE_WINDOW"); + W->net_atom[net_wm_name] = ATOM("_NET_WM_NAME"); + W->net_atom[net_wm_pid] = ATOM("_NET_WM_PID"); + W->net_atom[net_wm_desktop] = ATOM("_NET_WM_DESKTOP"); + W->net_atom[net_showing_desktop] = ATOM("_NET_SHOWING_DESKTOP"); + W->net_atom[net_wm_icon_name] = ATOM("_NET_WM_ICON_NAME"); + W->net_atom[net_wm_window_type] = ATOM("_NET_WM_WINDOW_TYPE"); + W->net_atom[net_supporting_wm_check] = ATOM("_NET_SUPPORTING_WM_CHECK"); + W->net_atom[net_wm_window_opacity] = ATOM("_NET_WM_WINDOW_OPACITY"); + W->net_atom[net_wm_window_type_normal] = ATOM("_NET_WM_WINDOW_TYPE_NORMAL"); + W->net_atom[net_wm_window_type_dock] = ATOM("_NET_WM_WINDOW_TYPE_DOCK"); + W->net_atom[net_wm_window_type_splash] = ATOM("_NET_WM_WINDOW_TYPE_SPLASH"); + W->net_atom[net_wm_window_type_dialog] = ATOM("_NET_WM_WINDOW_TYPE_DIALOG"); + W->net_atom[net_wm_icon] = ATOM("_NET_WM_ICON"); + W->net_atom[net_wm_state] = ATOM("_NET_WM_STATE"); + W->net_atom[net_wm_state_fullscreen] = ATOM("_NET_WM_STATE_FULLSCREEN"); + W->net_atom[net_wm_state_sticky] = ATOM("_NET_WM_STATE_STICKY"); + W->net_atom[net_wm_state_demands_attention] = ATOM("_NET_WM_STATE_DEMANDS_ATTENTION"); + W->net_atom[net_wm_system_tray_opcode] = ATOM("_NET_SYSTEM_TRAY_OPCODE"); + W->net_atom[net_system_tray_message_data] = ATOM("_NET_SYSTEM_TRAY_MESSAGE_DATA"); + W->net_atom[net_system_tray_visual] = ATOM("_NET_SYSTEM_TRAY_VISUAL"); + W->net_atom[net_system_tray_orientation] = ATOM("_NET_SYSTEM_TRAY_ORIENTATION"); + W->net_atom[xembed] = ATOM("_XEMBED"); + W->net_atom[xembedinfo] = ATOM("_XEMBED_INFO"); + W->net_atom[manager] = ATOM("MANAGER"); + W->net_atom[utf8_string] = ATOM("UTF8_STRING"); + + /* WMFS hints */ + W->net_atom[wmfs_running] = ATOM("_WMFS_RUNNING"); + W->net_atom[wmfs_update_hints] = ATOM("_WMFS_UPDATE_HINTS"); + W->net_atom[wmfs_set_screen] = ATOM("_WMFS_SET_SCREEN"); + W->net_atom[wmfs_screen_count] = ATOM("_WMFS_SCREEN_COUNT"); + W->net_atom[wmfs_current_tag] = ATOM("_WMFS_CURRENT_TAG"); + W->net_atom[wmfs_tag_list] = ATOM("_WMFS_TAG_LIST"); + W->net_atom[wmfs_current_screen] = ATOM("_WMFS_CURRENT_SCREEN"); + W->net_atom[wmfs_current_layout] = ATOM("_WMFS_CURRENT_LAYOUT"); + W->net_atom[wmfs_mwfact] = ATOM("_WMFS_MWFACT"); + W->net_atom[wmfs_nmaster] = ATOM("_WMFS_NMASTER"); + W->net_atom[wmfs_function] = ATOM("_WMFS_FUNCTION"); + W->net_atom[wmfs_cmd] = ATOM("_WMFS_CMD"); + W->net_atom[wmfs_font] = ATOM("_WMFS_FONT"); + + XChangeProperty(W->dpy, W->root, W->net_atom[net_supported], XA_ATOM, 32, + PropModeReplace, (unsigned char*)W->net_atom, net_last); + + XChangeProperty(W->dpy, W->root, W->net_atom[wmfs_running], XA_CARDINAL, 32, + PropModeReplace, (unsigned char*)&b, 1); + + /* Set _NET_SUPPORTING_WM_CHECK */ + XChangeProperty(W->dpy, W->root, W->net_atom[net_supporting_wm_check], XA_WINDOW, 32, + PropModeReplace, (unsigned char*)&W->root, 1); + /* + XChangeProperty(W->dpy, W->root, W->net_atom[net_wm_name], W->net_atom[utf8_string], 8, + PropModeReplace, (unsigned char*)&rootn, strlen(rootn)); + + XChangeProperty(W->dpy, W->root, ATOM("WM_CLASS"), XA_STRING, 8, + PropModeReplace, (unsigned char*)&class, strlen(class)); + + * Set _NET_WM_PID + XChangeProperty(W->dpy, W->root, W->net_atom[net_wm_pid], XA_CARDINAL, 32, + PropModeReplace, (unsigned char*)&pid, 1); + + * Set _NET_SHOWING_DESKTOP + XChangeProperty(W->dpy, W->root, W->net_atom[net_showing_desktop], XA_CARDINAL, 32, + PropModeReplace, (unsigned char*)&showing_desk, 1); + */ + +} + +void +ewmh_set_wm_state(Window w, int state) +{ + unsigned char d[] = { state, None }; + + XChangeProperty(W->dpy, w, W->net_atom[wm_state], + W->net_atom[wm_state], 32, PropModeReplace, d, 2); +} + diff --git a/src/ewmh.h b/src/ewmh.h new file mode 100644 index 0000000..cfadd11 --- /dev/null +++ b/src/ewmh.h @@ -0,0 +1,74 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + + +#ifndef EWMH_H +#define EWMH_H + +#include "wmfs.h" + +/* Ewmh hints list */ +enum +{ + /* ICCCM */ + wm_state, + /* EWMH */ + net_supported, + net_wm_name, + net_client_list, + net_frame_extents, + net_number_of_desktops, + net_current_desktop, + net_desktop_names, + net_desktop_geometry, + net_active_window, + net_close_window, + net_wm_icon_name, + net_wm_window_type, + net_wm_pid, + net_showing_desktop, + net_supporting_wm_check, + net_wm_window_opacity, + net_wm_window_type_normal, + net_wm_window_type_dock, + net_wm_window_type_splash, + net_wm_window_type_dialog, + net_wm_desktop, + net_wm_icon, + net_wm_state, + net_wm_state_fullscreen, + net_wm_state_sticky, + net_wm_state_demands_attention, + net_wm_system_tray_opcode, + net_system_tray_message_data, + net_system_tray_s, + net_system_tray_visual, + net_system_tray_orientation, + xembed, + xembedinfo, + manager, + utf8_string, + /* WMFS HINTS */ + wmfs_running, + wmfs_update_hints, + wmfs_current_tag, + wmfs_current_screen, + wmfs_current_layout, + wmfs_tag_list, + wmfs_mwfact, + wmfs_nmaster, + wmfs_set_screen, + wmfs_screen_count, + wmfs_function, + wmfs_cmd, + wmfs_font, + wmfs_statustext, + net_last +}; + +void ewmh_init(void); +void ewmh_set_wm_state(Window w, int state); + +#endif /* EWMH_H */ diff --git a/src/ewmh.o b/src/ewmh.o new file mode 100644 index 0000000..14ece66 Binary files /dev/null and b/src/ewmh.o differ diff --git a/src/infobar.c b/src/infobar.c new file mode 100644 index 0000000..dd871c8 --- /dev/null +++ b/src/infobar.c @@ -0,0 +1,246 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include "wmfs.h" +#include "draw.h" +#include "infobar.h" +#include "barwin.h" +#include "util.h" +#include "tag.h" + +static void infobar_elem_tag_init(struct element *e); +static void infobar_elem_tag_update(struct element *e); + +const struct elem_funcs +{ + char c; + void (*func_init)(struct element *e); + void (*func_update)(struct element *e); +} elem_funcs[] = +{ + { 't', infobar_elem_tag_init, infobar_elem_tag_update }, + + /* { 'l', infobar_elem_layout_init, infobar_elem_layout_update }, + { 's', infobar_elem_selbar_init, infobar_elem_selbar_update }, + { 'S', infobar_elem_status_init, infobar_elem_status_update },*/ + + { '\0', NULL, NULL } +}; + +static void +infobar_elem_tag_init(struct element *e) +{ + struct tag *t; + struct barwin *b, *prev = NULL; + struct geo g = { 0, 0, 0, 0 }; + int s, j; + + infobar_elem_placement(e); + + j = e->geo.x; + e->geo.h -= (e->infobar->theme->tags_border_width << 1); + + TAILQ_FOREACH(t, &e->infobar->screen->tags, next) + { + s = draw_textw(e->infobar->theme, t->name) + PAD; + + /* Init barwin */ + b = barwin_new(e->infobar->bar->win, j, 0, s, e->geo.h, 0, 0, false); + + /* Set border */ + if(e->infobar->theme->tags_border_width) + { + XSetWindowBorder(W->dpy, b->win, e->infobar->theme->tags_border_col); + XSetWindowBorderWidth(W->dpy, b->win, e->infobar->theme->tags_border_width); + } + + b->ptr = (void*)t; + barwin_map(b); + + /* TODO: refer to tag element configuration */ + barwin_mousebind_new(b, Button1, false, g, uicb_tag_set_with_name, (Uicb)t->name); + barwin_mousebind_new(b, Button4, false, g, uicb_tag_next, NULL); + barwin_mousebind_new(b, Button5, false, g, uicb_tag_prev, NULL); + + /* insert_tail with SLIST */ + if(SLIST_EMPTY(&e->bars)) + SLIST_INSERT_HEAD(&e->bars, b, enext); + else + SLIST_INSERT_AFTER(prev, b, enext); + + prev = b; + j += s; + } + + e->infobar->screen->elemupdate |= FLAGINT(ElemTag); + + e->geo.w = j; +} + +static void +infobar_elem_tag_update(struct element *e) +{ + struct tag *t, *sel = e->infobar->screen->seltag; + struct barwin *b; + + SLIST_FOREACH(b, &e->bars, enext) + { + t = (struct tag*)b->ptr; + + /* Selected */ + /* TODO: color from conf */ + if(t == sel) + { + b->fg = e->infobar->theme->tags_s.fg; + b->bg = e->infobar->theme->tags_s.bg; + } + else + { + b->fg = e->infobar->theme->tags_n.fg; + b->bg = e->infobar->theme->tags_n.bg; + } + + barwin_refresh_color(b); + + draw_text(b->dr, e->infobar->theme, (PAD >> 1), + TEXTY(e->infobar->theme, e->geo.h), b->fg, t->name); + + barwin_refresh(b); + } +} + +static void +infobar_elem_init(struct infobar *i) +{ + struct element *e; + int n, j; + + TAILQ_INIT(&i->elements); + + for(n = 0; n < (int)strlen(i->elemorder); ++n) + { + for(j = 0; j < (int)LEN(elem_funcs); ++j) + if(elem_funcs[j].c == i->elemorder[n]) + { + e = xcalloc(1, sizeof(struct element)); + + SLIST_INIT(&e->bars); + + e->infobar = i; + e->type = j; + e->func_init = elem_funcs[j].func_init; + e->func_update = elem_funcs[j].func_update; + + TAILQ_INSERT_TAIL(&i->elements, e, next); + + e->func_init(e); + + break; + } + } +} + +void +infobar_elem_update(struct infobar *i) +{ + struct element *e; + + TAILQ_FOREACH(e, &i->elements, next) + if(i->screen->elemupdate & FLAGINT(e->type)) + e->func_update(e); +} + +void +infobar_elem_remove(struct element *e) +{ + struct barwin *b; + + TAILQ_REMOVE(&e->infobar->elements, e, next); + + while(!SLIST_EMPTY(&e->bars)) + { + b = SLIST_FIRST(&e->bars); + SLIST_REMOVE_HEAD(&e->bars, enext); + barwin_remove(b); + } +} + +struct infobar* +infobar_new(struct screen *s, struct theme *theme, Barpos pos, const char *elem) +{ + bool map; + struct infobar *i = (struct infobar*)xcalloc(1, sizeof(struct infobar)); + + i->screen = s; + i->theme = theme; + i->elemorder = xstrdup(elem); + + map = infobar_placement(i, pos); + + /* struct barwin create */ + i->bar = barwin_new(W->root, i->geo.x, i->geo.y, i->geo.w, i->geo.h, + theme->bars.fg, theme->bars.bg, false); + + SLIST_INSERT_HEAD(&s->infobars, i, next); + + /* struct elements */ + infobar_elem_init(i); + + /* Render, only if pos is Top or Bottom */ + if(!map) + return i; + + barwin_map(i->bar); + barwin_map_subwin(i->bar); + barwin_refresh_color(i->bar); + infobar_refresh(i); + + return i; +} + +void +infobar_refresh(struct infobar *i) +{ + infobar_elem_update(i); + + barwin_refresh(i->bar); +} + +void +infobar_remove(struct infobar *i) +{ + struct element *e; + + free(i->elemorder); + + TAILQ_FOREACH(e, &i->elements, next) + infobar_elem_remove(e); + + barwin_remove(i->bar); + + SLIST_REMOVE(&i->screen->infobars, i, infobar, next); + + free(i); +} + +void +infobar_free(struct screen *s) +{ + struct infobar *i; + + while(!SLIST_EMPTY(&s->infobars)) + { + i = SLIST_FIRST(&s->infobars); + + /* SLIST_REMOVE is done by infobar_remove */ + infobar_remove(i); + } +} + + + + + + diff --git a/src/infobar.h b/src/infobar.h new file mode 100644 index 0000000..ea759c4 --- /dev/null +++ b/src/infobar.h @@ -0,0 +1,74 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef INFOBAR_H +#define INFOBAR_H + +#include "wmfs.h" +#include "util.h" +#include "draw.h" +#include "tag.h" + +enum { ElemTag = 0, ElemLayout, ElemSelbar, ElemStatus, ElemCustom, ElemLast }; + +struct infobar *infobar_new(struct screen *s, struct theme *theme, Barpos pos, const char *elem); +void infobar_elem_update(struct infobar *i); +void infobar_refresh(struct infobar *i); +void infobar_remove(struct infobar *i); +void infobar_free(struct screen *s); + +/* Basic placement of elements */ +static inline void +infobar_elem_placement(struct element *e) +{ + struct element *p = TAILQ_PREV(e, esub, next); + + e->geo.y = e->geo.w = 0; + e->geo.h = e->infobar->geo.h; + e->geo.x = (p ? p->geo.x + p->geo.w + PAD : 0); +} + +/* Bars placement management and usable space management */ +static inline bool +infobar_placement(struct infobar *i, Barpos p) +{ + i->pos = p; + i->geo = i->screen->ugeo; + i->geo.h = i->theme->bars_width; + + switch(p) + { + case BarTop: + i->screen->ugeo.y += i->geo.h; + i->screen->ugeo.h -= i->geo.h; + break; + case BarBottom: + i->geo.y = (i->screen->ugeo.y + i->screen->ugeo.h) - i->geo.h; + i->screen->ugeo.h -= i->geo.h; + break; + default: + case BarHide: + return false; + } + + tag_update_frame_geo(i->screen); + + return true; +} + +static inline void +infobar_elem_screen_update(struct screen *s, int addf) +{ + struct infobar *i; + + s->elemupdate |= FLAGINT(addf); + + SLIST_FOREACH(i, &s->infobars, next) + infobar_elem_update(i); + + s->elemupdate &= ~FLAGINT(ElemTag); +} + +#endif /* INFOBAR_H */ diff --git a/src/infobar.o b/src/infobar.o new file mode 100644 index 0000000..3aa5a17 Binary files /dev/null and b/src/infobar.o differ diff --git a/src/layout.c b/src/layout.c new file mode 100644 index 0000000..7fba18e --- /dev/null +++ b/src/layout.c @@ -0,0 +1,316 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include "layout.h" +#include "config.h" +#include "client.h" +#include "util.h" + +static struct geo +layout_split(struct client *c, bool vertical) +{ + struct geo og, geo; + + geo = og = c->geo; + + if(vertical) + { + c->geo.w >>= 1; + geo.x = c->geo.x + c->geo.w; + geo.w >>= 1; + + /* Remainder */ + geo.w += (og.x + og.w) - (geo.x + geo.w); + } + else + { + c->geo.h >>= 1; + geo.y = c->geo.y + c->geo.h; + geo.h >>= 1; + + /* Remainder */ + geo.h += (og.y + og.h) - (geo.y + geo.h); + } + + client_moveresize(c, c->geo); + + return geo; +} + +static inline void +layout_split_arrange_size(struct geo g, struct client *c, Position p) +{ + if(LDIR(p)) + { + c->geo.w += g.w; + + if(p == Right) + c->geo.x = g.x; + } + else + { + c->geo.h += g.h; + + if(p == Bottom) + c->geo.y = g.y; + } + + client_moveresize(c, c->geo); +} + +static inline bool +layout_split_check_row_dir(struct client *c, struct client *g, Position p) +{ + struct geo cgeo = c->geo; + struct client *cc; + int s = 0, cs = (LDIR(p) ? g->geo.h : g->geo.w); + + SLIST_FOREACH(cc, &c->tag->clients, tnext) + if(GEO_PARENTROW(cgeo, cc->geo, RPOS(p)) + && GEO_CHECK_ROW(cc->geo, g->geo, p)) + { + s += (LDIR(p) ? cc->geo.h : cc->geo.w); + + if(s == cs) + return true; + if(s > cs) + return false; + } + + return false; +} + +/* Use ghost client properties to fix holes in tile + * + * ~ .--. ~ ~ + *_____ ~ /xx \ ~ ~ + * |>>| ~\O _ (____ ~ + * | |__.| .--'-==~ ~ + * |>>'---\ '. ~ , ~ + *__|__| '. '-.___.-'/ ~ + * '-.__ _.' ~ + * ````` ~ + */ +void +layout_split_arrange_closed(struct client *ghost) +{ + struct client *c, *cc; + struct geo g; + bool b = false; + Position p; + + + /* Search for single parent for easy resize + * Example case: + * ___________ ___________ + * | | B | -> -> | | | + * | A |_____| -> Close -> | A | B | + * | | C | -> C -> | |v v v| + * |_____|_____| -> -> |_____|_____| + */ + for(p = Right; p < Center; ++p) /* Check every direction */ + { + if((c = client_next_with_pos(ghost, p))) + if(GEO_CHECK2(ghost->geo, c->geo, p)) + { + layout_split_arrange_size(ghost->geo, c, p); + return; + } + } + + /* Check row parents for full resize + * Example case: + * ___________ ___________ + * | | B | -> -> | << B | + * | A |_____| -> Close -> |___________| + * | | C | -> A -> | << C | + * |_____|_____| -> -> |___________| + */ + for(p = Right; p < Center && !b; ++p) + { + if((c = client_next_with_pos(ghost, p)) + && layout_split_check_row_dir(c, ghost, p)) + { + g = c->geo; + SLIST_FOREACH(cc, &c->tag->clients, tnext) + if(GEO_PARENTROW(g, cc->geo, RPOS(p)) + && GEO_CHECK_ROW(cc->geo, ghost->geo, p)) + { + layout_split_arrange_size(ghost->geo, cc, p); + b = true; + } + } + } +} + +/* Integrate a client in split layout: split sc and fill c in new geo */ +void +layout_split_integrate(struct client *c, struct client *sc) +{ + struct geo g; + + /* No sc */ + if(!sc || sc == c || sc->tag != c->tag) + { + /* + * Not even a first client in list, then + * maximize the lonely client + */ + if(!(sc = SLIST_FIRST(&c->tag->clients))) + { + client_maximize(c); + return; + } + } + + g = layout_split(sc, (sc->geo.h < sc->geo.w)); + client_moveresize(c, g); +} + +/* Arrange inter-clients holes: + * ___________ ___________ + * | || | -> | | | + * | A || B | -> | A >| B | + * | || | -> | >| | + * |_____||____| -> |______|____| + * ^ void + * + * and client-screen edge holes + * ___________ ___________ + * | | || -> | | | + * | A | B || -> | A | B >| + * | | || -> | | >| + * |_____|----'| -> |_____|__v__| + * ^^^ void + */ +static inline void +layout_fix_hole(struct client *c) +{ + struct client *cr = client_next_with_pos(c, Right); + struct client *cb = client_next_with_pos(c, Bottom); + + c->geo.w += (cr ? cr->geo.x : c->screen->ugeo.w) - (c->geo.x + c->geo.w); + c->geo.h += (cb ? cb->geo.y : c->screen->ugeo.h) - (c->geo.y + c->geo.h); + + client_moveresize(c, c->geo); +} + +/* Layout rotation: Rotate 90° all client to right or left. + * Avoid if(left) condition in layout_rotate loop; use func ptr + * + * Left rotation + * ____________ ____________ + * | | B | -> | | A | + * | A |_______| -> |__|_________| + * |____| C | D | -> |_____| B | + * |____|___|___| -> |_____|______| + * + * Right rotation + * ____________ ____________ + * | | B | -> | B |_____| + * | A |_______| -> |______|_____| + * |____| C | D | -> | A | | + * |____|___|___| -> |_________|__| + * + */ + +static inline void +_pos_rotate_left(struct geo *g, struct geo ug, struct geo og) +{ + g->x = (ug.h - (og.y + og.h)); + g->y = og.x; +} + +static inline void +_pos_rotate_right(struct geo *g, struct geo ug, struct geo og) +{ + g->x = og.y; + g->y = (ug.w - (og.x + og.w)); +} + +static void +layout_rotate(struct tag *t, bool left) +{ + struct client *c; + struct geo g; + float f1 = (float)t->screen->ugeo.w / (float)t->screen->ugeo.h; + float f2 = 1 / f1; + void (*pos)(struct geo*, struct geo, struct geo) = + (left ? _pos_rotate_left : _pos_rotate_right); + + SLIST_FOREACH(c, &t->clients, tnext) + { + pos(&g, t->screen->ugeo, c->geo); + + g.x *= f1; + g.y *= f2; + g.w = c->geo.h * f1; + g.h = c->geo.w * f2; + + client_moveresize(c, g); + } + + /* Rotate sometimes do not set back perfect size.. */ + SLIST_FOREACH(c, &t->clients, tnext) + layout_fix_hole(c); +} + +void +uicb_layout_rotate_left(Uicb cmd) +{ + (void)cmd; + layout_rotate(W->screen->seltag, true); +} + +void +uicb_layout_rotate_right(Uicb cmd) +{ + (void)cmd; + layout_rotate(W->screen->seltag, false); +} + +/* + * Really simple functions, don't need static no-uicb backend + * so we avoid the use of if(vertical) .. else + * + * Vertical mirror + * ____________ ____________ + * | | B | -> | B | | + * | A |_______| -> |_______| A | + * | | C | D | -> | D | C | | + * |____|___|___| -> |___|___|____| + * + * Horizontal mirror + * ____________ ____________ + * | | B | -> | | C | D | + * | A |_______| -> | A |___|___| + * | | C | D | -> | | B | + * |____|___|___| -> |____|_______| + */ +void +uicb_layout_vmirror(Uicb cmd) +{ + (void)cmd; + struct client *c; + + SLIST_FOREACH(c, &W->screen->seltag->clients, tnext) + { + c->geo.x = W->screen->ugeo.w - (c->geo.x + c->geo.w); + client_moveresize(c, c->geo); + } +} + +void +uicb_layout_hmirror(Uicb cmd) +{ + (void)cmd; + struct client *c; + + SLIST_FOREACH(c, &W->screen->seltag->clients, tnext) + { + c->geo.y = W->screen->ugeo.h - (c->geo.y + c->geo.h); + client_moveresize(c, c->geo); + } +} diff --git a/src/layout.h b/src/layout.h new file mode 100644 index 0000000..0cdc7a7 --- /dev/null +++ b/src/layout.h @@ -0,0 +1,40 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef LAYOUT_H +#define LAYOUT_H + +#include "wmfs.h" + +/* Check lateral direction (if p is Right or Left) */ +#define LDIR(P) (P < Top) + +/* Reverse position */ +#define RPOS(P) (P & 1 ? P - 1 : P + 1) + +/* geo comparaison */ +#define GEO_CHECK2(g1, g2, p) (LDIR(p) ? ((g1).h == (g2).h) : ((g1).w == (g2).w)) +#define GEO_CHECK_ROW(g1, g2, p) \ + (LDIR(p) \ + ? ((g1).y >= (g2).y && ((g1).y + (g1).h) <= ((g2).y + (g2).h)) \ + : ((g1).x >= (g2).x && ((g1).x + (g1).w) <= ((g2).x + (g2).w))) +#define GEO_PARENTROW(g1, g2, p) \ + (LDIR(p) \ + ? (p == Left ? ((g1).x == (g2).x) : ((g1).x + (g1).w == (g2).x + (g2).w)) \ + : (p == Top ? ((g1).y == (g2).y) : ((g1).y + (g1).h == (g2).y + (g2).h))) + + +/* Debug */ +#define DGEO(G) printf(": %d %d %d %d\n", G.x, G.y, G.w, G.h) + +void layout_split_integrate(struct client *c, struct client *sc); +void layout_split_arrange_closed(struct client *ghost); +void uicb_layout_vmirror(Uicb cmd); +void uicb_layout_hmirror(Uicb cmd); +void uicb_layout_rotate_left(Uicb cmd); +void uicb_layout_rotate_right(Uicb cmd); + +#endif /* LAYOUT_H */ + diff --git a/src/layout.o b/src/layout.o new file mode 100644 index 0000000..a8b683c Binary files /dev/null and b/src/layout.o differ diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..8247441 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,693 @@ +/* + * Copyright (c) 2010 Philippe Pepiot + * Copyright (c) 2011 Martin Duquesnoy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "parse.h" +#include "util.h" + +extern char *__progname; + +enum keyword_t { SEC_START, SEC_END, INCLUDE, WORD, EQUAL, LIST_START, LIST_END, NONE }; + +#ifdef DEBUG +static const struct +{ + const char *name; + enum keyword_t type; +} kw_t_name[] = +{ + {"SEC_START", SEC_START}, + {"SEC_END", SEC_END}, + {"INCLUDE", INCLUDE}, + {"WORD", WORD}, + {"EQUAL", EQUAL}, + {"LIST_START", LIST_START}, + {"LIST_END", LIST_END}, + {"NONE", NONE}, +}; +#endif + +struct files +{ + char *name; + struct files *parent; +}; + +struct keyword +{ + enum keyword_t type; + /* if WORD */ + int line; + struct files *file; + char *name; + struct keyword *next; +}; + +struct state +{ + bool quote; + bool comment; + char quote_char; +}; + +/* TO REMOVE (use a identifier for config and fallback XDG in api functions) */ +TAILQ_HEAD(, conf_sec) config; +static struct keyword *keywords = NULL; + +static struct keyword * +push_keyword(struct keyword *tail, enum keyword_t type, char *buf, size_t *offset, struct files *file, int line) +{ + struct keyword *kw; +#ifdef DEBUG + int i = 0; +#endif + + if(type == WORD && *offset == 0) + return tail; + + kw = xcalloc(1, sizeof(*kw)); + kw->type = type; + kw->line = line; + kw->file = file; + kw->next = NULL; + + if(*offset) + { + buf[*offset] = '\0'; + + if(!strcmp(buf, INCLUDE_CMD)) + kw->type = INCLUDE; + else + kw->name = strdup(buf); + + *offset = 0; + } + else + kw->name = NULL; + + if(tail) + tail->next = kw; + +#ifdef DEBUG + for(i = 0; kw_t_name[i].type != NONE; ++i) + if(kw_t_name[i].type == kw->type) + warnx("%s %s %s:%d\n", kw_t_name[i].name, + (kw->name) ? kw->name : "", + kw->file->name, kw->line); +#endif + + return kw; +} + +static void +syntax(struct keyword *kw, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "%s:", __progname); + + if(kw && kw->file && kw->file->name) + fprintf(stderr, "%s:%d", kw->file->name, kw->line); + + if(kw && kw->name) + fprintf(stderr, ", near '%s'", kw->name); + fprintf(stderr, ": "); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); +} + + +#define PUSH_KEYWORD(type) tail = push_keyword(tail, type, bufname, &j, file, line) +static struct keyword * +parse_keywords(const char *filename) +{ + int fd; + struct stat st; + char *buf; + struct keyword *head = NULL; + struct keyword *tail = NULL; + struct files *file; + enum keyword_t type; /* keyword type to push */ + struct state s = { false, false, '\0'}; + char *bufname; + char path[PATH_MAX]; + size_t i, j; + int line; + bool error = false; + + + if((fd = open(filename, O_RDONLY)) == -1 || stat(filename, &st) == -1) + { + warn("%s", filename); + return NULL; + } + + if(!st.st_size) + { + warnx("%s: empty file", filename); + close(fd); + return NULL; + } + + if(!realpath(filename, path)) + { + warn("%s", filename); + close(fd); + return NULL; + } + + buf = xmalloc(1, st.st_size + 1); + + if(read(fd, buf, st.st_size) == -1) + { + warn("%s", filename); + free(buf); + close(fd); + return NULL; + } + + buf[st.st_size] = '\0'; + + file = xcalloc(1, sizeof(*file)); + bufname = xcalloc(BUFSIZ, sizeof(*bufname)); + file->name = strdup(path); + file->parent = NULL; + + for(i = j = 0, line = 1; i < (size_t)st.st_size; ++i) + { + if(!head && tail) + head = tail; + + if(buf[i] == '\n' && s.comment) + { + line++; + s.comment = false; + continue; + } + + if(buf[i] == '#' && !s.quote) + { + s.comment = true; + continue; + } + + if(s.comment) + continue; + + /* end of quotted string */ + if(s.quote && buf[i] == s.quote_char) + { + PUSH_KEYWORD(WORD); + s.quote = false; + continue; + } + + if(!s.quote) + { + if((buf[i] == '"' || buf[i] == '\'')) + { + PUSH_KEYWORD(WORD); + /* begin quotted string */ + s.quote_char = buf[i]; + s.quote = true; + continue; + } + + if(buf[i] == '[') + { + PUSH_KEYWORD(WORD); + + if(buf[i + 1] == '/') + { + i += 2; + type = SEC_END; + } + else + { + ++i; + type = SEC_START; + } + + /* get section name */ + while(buf[i] != ']') + { + if(i >= ((size_t)st.st_size-1) || j >= (BUFSIZ-1)) + { + bufname[j] = '\0'; + syntax(NULL, "word too long in %s:%d near '%s'", + file->name, line, bufname); + error = true; + break; + } + bufname[j++] = buf[i++]; + } + PUSH_KEYWORD(type); + continue; + } + + if(buf[i] == '{') + { + PUSH_KEYWORD(WORD); + PUSH_KEYWORD(LIST_START); + continue; + } + + if(buf[i] == '}') + { + PUSH_KEYWORD(WORD); + PUSH_KEYWORD(LIST_END); + continue; + } + + if(buf[i] == ',') + { + PUSH_KEYWORD(WORD); + continue; + } + + if(buf[i] == '=') + { + PUSH_KEYWORD(WORD); + PUSH_KEYWORD(EQUAL); + continue; + } + + if(strchr("\t\n ", buf[i])) + { + PUSH_KEYWORD(WORD); + + if(buf[i] == '\n') + ++line; + + continue; + } + } + + if(j >= (BUFSIZ - 1)) + { + bufname[j] = '\0'; + syntax(NULL, "word too long in %s:%d near '%s'", + file->name, line, bufname); + error = true; + break; + } + + bufname[j++] = buf[i]; + } + + free(buf); + free(bufname); + close(fd); + warnx("%s read", file->name); + + return (error ? NULL: head); +} + +/* + * return NULL on failure and head->next if + * no config found (of file doesn't exist) + * NOTE to devs: head->name is the file to include + */ +static struct keyword * +include(struct keyword *head) +{ + struct keyword *kw; + struct keyword *tail; + struct files *file; + struct passwd *user; + char *filename = NULL; + char *base = NULL; + + head = head->next; + + if(!head || head->type != WORD) + { + syntax(head, "missing filename to include"); + return NULL; + } + + /* replace ~ by user directory */ + if(head->name && head->name[0] == '~') + { + if((user = getpwuid(getuid())) && user->pw_dir) + xasprintf(&filename, "%s%s", user->pw_dir, head->name + 1); + else if(getenv("HOME")) + xasprintf(&filename, "%s%s", getenv("HOME"), head->name + 1); + else /* to warning ? */ + filename = head->name; + } + + /* relative path from parent file */ + else if(head->name && head->name[0] != '/') + { + base = strdup(head->file->name); + xasprintf(&filename, "%s/%s", dirname(base), head->name); + free(base); + } + else + filename = head->name; + + if(!(kw = parse_keywords(filename))) + { + warnx("no config found in include file %s", head->name); + + if(filename != head->name) + free(filename); + + return NULL; + } + + kw->file->parent = head->file; + + /* detect circular include */ + for(file = kw->file->parent; file != NULL; file = file->parent) + if(!strcmp(file->name, kw->file->name)) + { + syntax(kw, "circular include of %s", kw->file->name); + + if(filename != head->name) + free(filename); + + return NULL; + } + + if(filename != head->name) + free(filename); + + head = head->next; + + if(kw) + { + for(tail = kw; tail->next; tail = tail->next); + tail->next = head; + } + + return kw; +} + +static void * +free_opt(struct conf_opt *o) +{ + free(o); + return NULL; +} + +static struct conf_opt * +get_option(struct keyword **head) +{ + struct conf_opt *o; + size_t j = 0; + struct keyword *kw = *head; + + o = xcalloc(1, sizeof(*o)); + o->name = kw->name; + o->used = false; + o->line = kw->line; + o->filename = kw->file->name; + + kw = kw->next; + + if(kw->type != EQUAL) + { + syntax(kw, "missing '=' here"); + return free_opt(o); + } + + if(!(kw = kw->next)) + { + syntax(kw, "missing value"); + return free_opt(o); + } + + switch(kw->type) + { + case INCLUDE: + if(!(kw = include(kw))) + return free_opt(o); + break; + + case WORD: + o->val[0] = kw->name; + o->val[1] = NULL; + kw = kw->next; + break; + + case LIST_START: + kw = kw->next; + + while(kw && kw->type != LIST_END) + { + switch(kw->type) + { + case WORD: + if(j >= (PARSE_MAX_LIST - 1)) + { + syntax(kw, "too much values in list"); + return free_opt(o); + } + + o->val[j++] = kw->name; + kw = kw->next; + break; + + case INCLUDE: + if(!(kw = include(kw))) + return free_opt(o); + break; + + default: + syntax(kw, "declaration into a list"); + return free_opt(o); + break; + } + } + + if(!kw) + { + syntax(kw, "list unclosed"); + return free_opt(o); + } + + kw = kw->next; + break; + + default: + syntax(kw, "missing value"); + return free_opt(o); + break; + } + + *head = kw; + return o; +} + +static void * +free_sec(struct conf_sec *sec) +{ + struct conf_opt *o; + struct conf_sec *s; + + if(sec) + { + while(!SLIST_EMPTY(&sec->optlist)) + { + o = SLIST_FIRST(&sec->optlist); + SLIST_REMOVE_HEAD(&sec->optlist, entry); + free_opt(o); + } + + while(!TAILQ_EMPTY(&sec->sub)) + { + s = TAILQ_FIRST(&sec->sub); + TAILQ_REMOVE(&sec->sub, s, entry); + free_sec(s); + } + + free(sec); + } + + return NULL; +} + +static struct conf_sec * +get_section(struct keyword **head) +{ + struct conf_sec *s; + struct conf_opt *o; + struct conf_sec *sub; + struct keyword *kw = *head; + + s = xcalloc(1, sizeof(*s)); + s->name = kw->name; + + TAILQ_INIT(&s->sub); + SLIST_INIT(&s->optlist); + + kw = kw->next; + + while(kw && kw->type != SEC_END) + { + switch(kw->type) + { + case INCLUDE: + if(!(kw = include(kw))) + return free_sec(s); + break; + + case SEC_START: + if(!(sub = get_section(&kw))) + return free_sec(s); + TAILQ_INSERT_TAIL(&s->sub, sub, entry); + ++s->nsub; + break; + + case WORD: + if(!(o = get_option(&kw))) + return free_sec(s); + SLIST_INSERT_HEAD(&s->optlist, o, entry); + ++s->nopt; + break; + + default: + syntax(kw, "syntax error"); + return free_sec(s); + break; + } + } + + if(!kw || strcmp(kw->name, s->name)) + { + syntax(kw, "missing end section %s", s->name); + return free_sec(s); + } + + kw = kw->next; + *head = kw; + + return s; +} + +int +free_conf(void) +{ + struct conf_sec *s; + struct keyword *kw, *nkw; + struct files **f = NULL; + int i, nf = 0; + + while(!TAILQ_EMPTY(&config)) + { + s = TAILQ_FIRST(&config); + TAILQ_REMOVE(&config, s, entry); + free_sec(s); + } + + kw = keywords; + + while(kw) + { + nkw = kw->next; + + free(kw->name); + + for(i = 0; i < nf; ++i) + if(f[i] == kw->file) + { + if(!(f = realloc(f, sizeof(*f) * (++i)))) + err(EXIT_FAILURE, "realloc"); + + f[i - 1] = kw->file; + } + + kw = nkw; + } + + if(nf > 0) + { + for(i = 0; i < nf; ++i) + { + free(f[i]->name); + free(f[i]); + } + free(f); + } + + return -1; +} + +int +get_conf(const char *filename) +{ + struct conf_sec *s; + struct keyword *head, *kw; + + head = kw = parse_keywords(filename); + + if(!head) + return -1; /* TODO ERREUR */ + + keywords = head; + + TAILQ_INIT(&config); + + while(kw) + { + switch(kw->type) + { + case INCLUDE: + if(!(kw = include(kw))) + return free_conf(); + break; + + case SEC_START: + if(!(s = get_section(&kw))) + return free_conf(); + TAILQ_INSERT_TAIL(&config, s, entry); + break; + + default: + syntax(kw, "out of any section"); + return free_conf(); + break; + } + } + + return 0; +} + diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 0000000..868f5b1 --- /dev/null +++ b/src/parse.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010 Philippe Pepiot + * Copyright (c) 2011 Martin Duquesnoy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef PARSE_H +#define PARSE_H + +#include "wmfs.h" + +#define INCLUDE_CMD "@include" +#define PARSE_MAX_LIST 32 + +struct conf_opt +{ + char *name; + char *val[PARSE_MAX_LIST]; + size_t nval; + bool used; + int line; + char *filename; + SLIST_ENTRY(conf_opt) entry; +}; + +struct conf_sec +{ + char *name; + SLIST_HEAD(, conf_opt) optlist; + TAILQ_HEAD(cshead, conf_sec) sub; + size_t nopt; + size_t nsub; + TAILQ_ENTRY(conf_sec) entry; +}; + +struct opt_type +{ + long int num; + float fnum; + bool boolean; + char *str; +}; + +/* + * Create config from file + * return -1 on failure + */ +int get_conf(const char *); + +/* + * Print unused option name from section s (and subsections). + * If s == NULL print unused option name for all config struct. + */ +void print_unused(struct conf_sec *s); + +/* + * Free the config struct. + * WARNING: This make all string + * returned by fetch_(opt|section)(_first) unusable. + */ +int free_conf(void); + +/* + * Get all subsection matching the given name on the given + * section. + * If section == NULL, return subsections from root section. + * Return a NULL terminated array. + * Subsections are returned in order as they are in config file + * WARNING : This MUST be free() after use. + */ +struct conf_sec **fetch_section(struct conf_sec *, char *); + +/* + * Get first subsection matching the given name + * on the given section. (first found on the file) + */ +struct conf_sec *fetch_section_first(struct conf_sec *, char *); + +/* + * Count member of a conf_sec ** + */ +size_t fetch_section_count(struct conf_sec **); + +/* + * Return all options matching the given name on the given subsection. + * If none match or section == NULL return opt_type build with the + * given default param. + * WARNING: This MUST be free() after use. + * WARNING: The string member is directly taken from the config struct. + * WARNING: Returned in reverse order as they are in config file. + * (I think the last option MUST overwrite all others) + */ +struct opt_type fetch_opt_first(struct conf_sec *, char *, char *); + +/* + * Get first (last in config file) option matching the given name + * on the given section. + * WARNING: The string member is directly taken from the config struct. + */ +struct opt_type *fetch_opt(struct conf_sec *, char *, char *); + +/* + * Count member of a opt_type * + */ +size_t fetch_opt_count(struct opt_type *); + +#endif /* PARSE_H */ diff --git a/src/parse.o b/src/parse.o new file mode 100644 index 0000000..4bc143a Binary files /dev/null and b/src/parse.o differ diff --git a/src/parse_api.c b/src/parse_api.c new file mode 100644 index 0000000..9e1b663 --- /dev/null +++ b/src/parse_api.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2010 Philippe Pepiot + * Copyright (c) 2011 Martin Duquesnoy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#include +#include +#include + +#include "wmfs.h" +#include "parse.h" +#include "util.h" + +extern TAILQ_HEAD(, conf_sec) config; + +static const struct opt_type opt_type_null = { 0, 0, false, NULL }; + +static struct opt_type +string_to_opt(char *s) +{ + struct opt_type ret = opt_type_null; + + if(!s || !strlen(s)) + return ret; + + ret.num = strtol(s, (char**)NULL, 10); + ret.fnum = strtod(s, NULL); + + ret.boolean = (!strcmp(s, "true") + || !strcmp(s, "true") + || !strcmp(s, "TRUE") + || !strcmp(s, "1")); + + ret.str = s; + + return ret; +} + + +void +print_unused(struct conf_sec *sec) +{ + struct conf_sec *s; + struct conf_opt *o; + + if(!sec) + { + TAILQ_FOREACH(s, &config, entry) + print_unused(s); + return; + } + + SLIST_FOREACH(o, &sec->optlist, entry) + if(!o->used) + warnx("%s:%d, unused param %s", o->filename, o->line, o->name); + + TAILQ_FOREACH(s, &sec->sub, entry) + if(!TAILQ_EMPTY(&s->sub)) + print_unused(s); +} + +struct conf_sec ** +fetch_section(struct conf_sec *s, char *name) +{ + struct conf_sec **ret; + struct conf_sec *sec; + size_t i = 0; + + if(!name) + return NULL; + + if(!s) + { + ret = xcalloc(2, sizeof(struct conf_sec *)); + + TAILQ_FOREACH(sec, &config, entry) + if(!strcmp(sec->name, name)) + { + ret[0] = sec; + ret[1] = NULL; + break; + } + } + else + { + ret = xcalloc(s->nsub + 1, sizeof(struct conf_sec *)); + + TAILQ_FOREACH(sec, &s->sub, entry) + if(!strcmp(sec->name, name) && i < s->nsub) + ret[i++] = sec; + + ret[i] = NULL; + } + + return ret; +} + +struct conf_sec * +fetch_section_first(struct conf_sec *s, char *name) +{ + struct conf_sec *sec, *ret = NULL; + TAILQ_HEAD(cshead, conf_sec) *head = + (s + ? (struct cshead*)&s->sub + : (struct cshead*)&config); + + if(!name) + return NULL; + + TAILQ_FOREACH(sec, head, entry) + if(sec->name && !strcmp(sec->name, name)) + { + ret = sec; + break; + } + + return ret; +} + +size_t +fetch_section_count(struct conf_sec **s) +{ + size_t ret = 0; + + while(s[ret]) + ++ret; + + return ret; +} + +struct opt_type * +fetch_opt(struct conf_sec *s, char *dfl, char *name) +{ + struct conf_opt *o; + struct opt_type *ret; + size_t i = 0; + + if(!name) + return NULL; + + ret = xcalloc(10, sizeof(struct opt_type)); + + if(s) + { + SLIST_FOREACH(o, &s->optlist, entry) + if(!strcmp(o->name, name)) + { + while(o->val[i]) + { + o->used = true; + ret[i] = string_to_opt(o->val[i]); + ++i; + } + + ret[i] = opt_type_null; + + return ret; + } + } + + ret[0] = string_to_opt(dfl); + ret[1] = opt_type_null; + + return ret; +} + +struct opt_type +fetch_opt_first(struct conf_sec *s, char *dfl, char *name) +{ + struct conf_opt *o; + + if(!name) + return opt_type_null; + else if(s) + { + SLIST_FOREACH(o, &s->optlist, entry) + if(!strcmp(o->name, name)) + { + o->used = true; + + return string_to_opt(o->val[0]); + } + } + + return string_to_opt(dfl); +} + +size_t +fetch_opt_count(struct opt_type *o) +{ + size_t ret = 0; + + while(o[ret].str) + ++ret; + + return ret; +} + + diff --git a/src/parse_api.o b/src/parse_api.o new file mode 100644 index 0000000..15af36f Binary files /dev/null and b/src/parse_api.o differ diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 0000000..8724dcd --- /dev/null +++ b/src/screen.c @@ -0,0 +1,114 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifdef HAVE_XINERAMA +#include +#endif /* HAVE_XINERAMA */ + +#include "screen.h" +#include "util.h" +#include "tag.h" +#include "infobar.h" +#include "client.h" + +static struct screen* +screen_new(struct geo *g, int id) +{ + struct screen *s = (struct screen*)xcalloc(1, sizeof(struct screen)); + + s->geo = s->ugeo = *g; + s->seltag = NULL; + s->id = id; + + TAILQ_INIT(&s->tags); + SLIST_INIT(&s->infobars); + + SLIST_INSERT_HEAD(&W->h.screen, s, next); + + /* Set as selected screen */ + W->screen = s; + + return s; +} + +void +screen_init(void) +{ + struct geo g; + + SLIST_INIT(&W->h.screen); + +#ifdef HAVE_XINERAMA + XineramaScreenInfo *xsi; + int i, n = 0; + + if(XineramaIsActive(W->dpy)) + { + xsi = XineramaQueryScreens(W->dpy, &n); + + for(i = 0; i < n; ++i) + { + g.x = xsi[i].x_org; + g.y = xsi[i].y_org; + g.w = xsi[i].width; + g.h = xsi[i].height; + + screen_new(&g, i); + } + + XFree(xsi); + } + else +#endif /* HAVE_XINERAMA */ + { + g.x = g.y = 0; + g.w = DisplayWidth(W->dpy, W->xscreen); + g.h = DisplayHeight(W->dpy, W->xscreen); + + screen_new(&g, 0); + } +} + +/* + * Update selected screen with mouse location + */ +struct screen* +screen_update_sel(void) +{ +#ifdef HAVE_XINERAMA + if(XineramaIsActive(W->dpy)) + { + struct screen *s; + Window w; + int d, x, y; + + XQueryPointer(W->dpy, W->root, &w, &w, &x, &y, &d, &d, (unsigned int *)&d); + + SLIST_FOREACH(s, &W->h.screen, next) + if(INAREA(x, y, s->geo)) + break; + + return (W->screen = s); + } +#endif /* HAVE_XINERAMA */ + + return W->screen; +} + +void +screen_free(void) +{ + struct screen *s; + + while(!SLIST_EMPTY(&W->h.screen)) + { + s = SLIST_FIRST(&W->h.screen); + SLIST_REMOVE_HEAD(&W->h.screen, next); + + infobar_free(s); + tag_free(s); + free(s); + } +} diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..7d86f02 --- /dev/null +++ b/src/screen.h @@ -0,0 +1,27 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef SCREEN_H +#define SCREEN_H + +#include "wmfs.h" + +static inline struct screen* +screen_gb_id(int id) +{ + struct screen *s; + + SLIST_FOREACH(s, &W->h.screen, next) + if(s->id == id) + return s; + + return SLIST_FIRST(&W->h.screen); +} + +void screen_init(void); +struct screen* screen_update_sel(void); +void screen_free(void); + +#endif /* SCREEN_H */ diff --git a/src/screen.o b/src/screen.o new file mode 100644 index 0000000..ce75167 Binary files /dev/null and b/src/screen.o differ diff --git a/src/tag.c b/src/tag.c new file mode 100644 index 0000000..842e83f --- /dev/null +++ b/src/tag.c @@ -0,0 +1,198 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include /* IconicState / NormalState */ + +#include "tag.h" +#include "util.h" +#include "infobar.h" +#include "client.h" +#include "config.h" +#include "barwin.h" +#include "ewmh.h" +#include "layout.h" + +struct tag* +tag_new(struct screen *s, char *name) +{ + struct tag *t; + XSetWindowAttributes at = + { + .background_pixel = THEME_DEFAULT->frame_bg, + .override_redirect = true, + .background_pixmap = ParentRelative, + .event_mask = BARWIN_MASK + }; + + t = xcalloc(1, sizeof(struct tag)); + + t->screen = s; + t->name = xstrdup(name); + t->flags = 0; + t->sel = NULL; + + /* Frame window */ + t->frame = XCreateWindow(W->dpy, W->root, + s->ugeo.x, s->ugeo.y, + s->ugeo.w, s->ugeo.h, + 0, CopyFromParent, + InputOutput, + CopyFromParent, + (CWOverrideRedirect | CWBackPixmap + | CWBackPixel | CWEventMask), + &at); + + SLIST_INIT(&t->clients); + + TAILQ_INSERT_TAIL(&s->tags, t, next); + + return t; +} + +void +tag_screen(struct screen *s, struct tag *t) +{ + struct client *c; + + /* Unmap previous tag's frame */ + WIN_STATE(s->seltag->frame, Unmap); + SLIST_FOREACH(c, &s->seltag->clients, tnext) + ewmh_set_wm_state(c->win, IconicState); + + /* + * Map selected tag's frame, only if there is + * clients in t + */ + if(!SLIST_EMPTY(&t->clients)) + { + WIN_STATE(t->frame, Map); + SLIST_FOREACH(c, &t->clients, tnext) + ewmh_set_wm_state(c->win, NormalState); + + client_focus(t->sel); + } + + s->seltag = t; + + infobar_elem_screen_update(s, ElemTag); +} + +/* Set t to NULL to untag c from c->tag */ +void +tag_client(struct tag *t, struct client *c) +{ + /* Remove client from its previous tag */ + if(c->tag) + { + if(c->tag == t) + return; + + layout_split_arrange_closed(c); + + SLIST_REMOVE(&c->tag->clients, c, client, tnext); + + if(c->tag->sel == c || W->client == c) + client_focus(client_next(c)); + } + + /* + * Case of client removing: umap frame if empty + */ + if(!t) + { + /* Unmap frame if tag is now empty */ + if(SLIST_EMPTY(&c->tag->clients)) + WIN_STATE(c->tag->frame, Unmap); + + return; + } + + /* Reparent client win in frame win */ + XReparentWindow(W->dpy, c->win, t->frame, 0, 0); + + /* Map frame if tag was empty */ + if(SLIST_EMPTY(&t->clients)) + WIN_STATE(t->frame, Map); + + c->tag = t; + + layout_split_integrate(c, t->sel); + + /* Insert in new tag list */ + SLIST_INSERT_HEAD(&t->clients, c, tnext); +} + +void +uicb_tag_set(Uicb cmd) +{ + int i = 0, n = ATOI(cmd); + struct tag *t; + + TAILQ_FOREACH(t, &W->screen->tags, next) + if(++i == n) + { + tag_screen(W->screen, t); + return; + } +} + +void +uicb_tag_set_with_name(Uicb cmd) +{ + struct tag *t; + + TAILQ_FOREACH(t, &W->screen->tags, next) + if(!strcmp(cmd, t->name)) + { + tag_screen(W->screen, t); + return; + } +} + +void +uicb_tag_next(Uicb cmd) +{ + (void)cmd; + struct tag *t; + + if((t = TAILQ_NEXT(W->screen->seltag, next))) + tag_screen(W->screen, t); + else if( /* CIRCULAR OPTION */ 1) + tag_screen(W->screen, TAILQ_FIRST(&W->screen->tags)); +} + +void +uicb_tag_prev(Uicb cmd) +{ + (void)cmd; + struct tag *t; + + if((t = TAILQ_PREV(W->screen->seltag, tsub, next))) + tag_screen(W->screen, t); + else if( /* CIRCULAR OPTION */ 1) + tag_screen(W->screen, TAILQ_LAST(&W->screen->tags, tsub)); +} + +static void +tag_remove(struct tag *t) +{ + free(t->name); + + XDestroyWindow(W->dpy, t->frame); + + free(t); +} + +void +tag_free(struct screen *s) +{ + struct tag *t; + + TAILQ_FOREACH(t, &s->tags, next) + { + TAILQ_REMOVE(&s->tags, t, next); + tag_remove(t); + } +} diff --git a/src/tag.h b/src/tag.h new file mode 100644 index 0000000..39c9fe5 --- /dev/null +++ b/src/tag.h @@ -0,0 +1,38 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef TAG_H +#define TAG_H + +#include "wmfs.h" + +struct tag *tag_new(struct screen *s, char *name); +void tag_screen(struct screen *s, struct tag *t); +void tag_client(struct tag *t, struct client *c); +void tag_free(struct screen *s); +void uicb_tag_set(Uicb cmd); +void uicb_tag_set_with_name(Uicb cmd); +void uicb_tag_next(Uicb cmd); +void uicb_tag_prev(Uicb cmd); + +/* + * Update frames size with screen usable geo + */ +static inline void +tag_update_frame_geo(struct screen *s) +{ + struct tag *t; + + TAILQ_FOREACH(t, &s->tags, next) + XMoveResizeWindow(W->dpy, + t->frame, + s->ugeo.x, + s->ugeo.y, + s->ugeo.w, + s->ugeo.h); +} + + +#endif /* TAG_H */ diff --git a/src/tag.o b/src/tag.o new file mode 100644 index 0000000..c1fffc8 Binary files /dev/null and b/src/tag.o differ diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..f66b71a --- /dev/null +++ b/src/util.c @@ -0,0 +1,128 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#define _GNU_SOURCE /* vasprintf() */ + +#include +#include + +#include "util.h" + +/** malloc with error support and size_t overflow protection + * \param nmemb number of objects + * \param size size of single object + * \return non null void pointer + */ +void* +xmalloc(size_t nmemb, size_t size) +{ + void *ret; + + if(SIZE_MAX / nmemb < size) + err(EXIT_FAILURE, "xmalloc(%zu, %zu), " + "size_t overflow detected", nmemb, size); + + if((ret = malloc(nmemb * size)) == NULL) + err(EXIT_FAILURE, "malloc(%zu)", nmemb * size); + + return ret; +} + +/** calloc with error support + * \param nmemb Number of objects + * \param size size of single object + * \return non null void pointer +*/ +void* +xcalloc(size_t nmemb, size_t size) +{ + void *ret; + + if((ret = calloc(nmemb, size)) == NULL) + err(EXIT_FAILURE, "calloc(%zu * %zu)", nmemb, size); + + return ret; +} + +/** asprintf wrapper + * \param strp target string + * \param fmt format + * \return non zero integer + */ +int +xasprintf(char **strp, const char *fmt, ...) +{ + int ret; + va_list args; + + va_start(args, fmt); + ret = vasprintf(strp, fmt, args); + va_end(args); + + if (ret == -1) + err(EXIT_FAILURE, "asprintf(%s)", fmt); + + return ret; +} + +/** strdup with error support + * \param str char pointer + * \retun non null void pointer + */ +char * +xstrdup(const char *str) +{ + char *ret; + + if (str == NULL || (ret = strdup(str)) == NULL) + err(EXIT_FAILURE, "strdup(%s)", str); + + return ret; +} + +/** Execute a system command + * \param cmd Command + * \return child pid +*/ +pid_t +spawn(const char *format, ...) +{ + char *sh = NULL; + char cmd[512]; + va_list ap; + pid_t pid; + size_t len; + + va_start(ap, format); + len = vsnprintf(cmd, sizeof(cmd), format, ap); + va_end(ap); + + if (len >= sizeof(cmd)) + { + warnx("command too long (> 512 bytes)"); + return -1; + } + + if(!(sh = getenv("SHELL"))) + sh = "/bin/sh"; + + if((pid = fork()) == 0) + { + setsid(); + if (execl(sh, sh, "-c", cmd, (char*)NULL) == -1) + warn("execl(sh -c %s)", cmd); + exit(EXIT_FAILURE); + } + else if (pid == -1) + warn("fork"); + + return pid; +} + +void +uicb_spawn(Uicb cmd) +{ + spawn("%s", cmd); +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..9359bd7 --- /dev/null +++ b/src/util.h @@ -0,0 +1,63 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef UTIL_H +#define UTIL_H + +#include "wmfs.h" + +/* Todo FREE_LIST(type, head, function_remove) */ +#define FREE_LIST(type, head) \ + do { \ + struct type *Z; \ + while(!SLIST_EMPTY(&head)) { \ + Z = SLIST_FIRST(&head); \ + SLIST_REMOVE_HEAD(&head, next); \ + free(Z); /* function_remove(t)*/ \ + } \ + } while(/* CONSTCOND */ 0); + +/* t is Map or Unmap */ +#define WIN_STATE(w, t) do { \ + X##t##Subwindows(W->dpy, w); \ + X##t##Window(W->dpy, w); \ +} while( /* CONSTCOND */ 0); + + +#define ATOM(a) XInternAtom(W->dpy, (a), False) +#define LEN(x) (sizeof(x) / sizeof(*x)) +#define FLAGINT(i) (1 << i) +#define ATOI(s) strtol(s, NULL, 10) +#define ABS(j) (j < 0 ? -j : j) +#define INAREA(i, j, a) ((i) >= (a).x && (i) <= (a).x + (a).w && (j) >= (a).y && (j) <= (a).y + (a).h) + +/* + * "#RRGGBB" -> 0xRRGGBB + */ +static inline Color +color_atoh(const char *col) +{ + int shift = (col[0] == '#'); + + return (Color)strtol(col + shift, NULL, 16); +} + +static inline void +swap_ptr(void **x, void **y) +{ + void *t = *x; + + *x = *y; + *y = t; +} + +void *xmalloc(size_t nmemb, size_t size); +void *xcalloc(size_t nmemb, size_t size); +int xasprintf(char **strp, const char *fmt, ...); +char *xstrdup(const char *str); +pid_t spawn(const char *format, ...); +void uicb_spawn(Uicb cmd); + +#endif /* UTIL_H */ diff --git a/src/util.o b/src/util.o new file mode 100644 index 0000000..377fbc6 Binary files /dev/null and b/src/util.o differ diff --git a/src/wmfs.c b/src/wmfs.c new file mode 100644 index 0000000..bba470e --- /dev/null +++ b/src/wmfs.c @@ -0,0 +1,344 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#include +#include +#include + +#include "wmfs.h" +#include "event.h" +#include "ewmh.h" +#include "screen.h" +#include "infobar.h" +#include "util.h" +#include "config.h" +#include "client.h" + +int +wmfs_error_handler(Display *d, XErrorEvent *event) +{ + char mess[256]; + + /* Check if there is another WM running */ + if(event->error_code == BadAccess + && W->root == event->resourceid) + errx(EXIT_FAILURE, "Another Window Manager is already running."); + + /* Ignore focus change error for unmapped client + * 42 = X_SetInputFocus + * 28 = X_GrabButton + */ + if(client_gb_win(event->resourceid)) + if(event->error_code == BadWindow + || event->request_code == 42 + || event->request_code == 28) + return 0; + + + if(XGetErrorText(d, event->error_code, mess, 128)) + warnx("%s(%d) opcodes %d/%d\n resource #%lx\n", + mess, + event->error_code, + event->request_code, + event->minor_code, + event->resourceid); + + return 1; +} + +int +wmfs_error_handler_dummy(Display *d, XErrorEvent *event) +{ + (void)d; + (void)event; + + return 0; +} + +void +wmfs_numlockmask(void) +{ + int i, j; + XModifierKeymap *mm = XGetModifierMapping(W->dpy); + + for(i = 0; i < 8; i++) + for(j = 0; j < mm->max_keypermod; ++j) + if(mm->modifiermap[i * mm->max_keypermod + j] + == XKeysymToKeycode(W->dpy, XK_Num_Lock)) + W->numlockmask = (1 << i); + + XFreeModifiermap(mm); +} + +void +wmfs_init_font(char *font, struct theme *t) +{ + XFontStruct **xfs = NULL; + char **misschar, **names, *defstring; + int d; + + if(!(t->font.fontset = XCreateFontSet(W->dpy, font, &misschar, &d, &defstring))) + { + warnx("Can't load font '%s'", font); + t->font.fontset = XCreateFontSet(W->dpy, "fixed", &misschar, &d, &defstring); + } + + XExtentsOfFontSet(t->font.fontset); + XFontsOfFontSet(t->font.fontset, &xfs, &names); + + t->font.as = xfs[0]->max_bounds.ascent; + t->font.de = xfs[0]->max_bounds.descent; + t->font.width = xfs[0]->max_bounds.width; + + t->font.height = t->font.as + t->font.de; + + if(misschar) + XFreeStringList(misschar); +} + +static void +wmfs_xinit(void) +{ + XSetWindowAttributes at = + { + .event_mask = (KeyMask | ButtonMask | MouseMask + | PropertyChangeMask | SubstructureRedirectMask + | SubstructureNotifyMask | StructureNotifyMask), + .cursor = XCreateFontCursor(W->dpy, XC_left_ptr) + }; + + /* + * X Error handler + */ + XSetErrorHandler(wmfs_error_handler); + + /* + * X var + */ + W->xscreen = DefaultScreen(W->dpy); + W->xdepth = DefaultDepth(W->dpy, W->xscreen); + W->gc = DefaultGC(W->dpy, W->xscreen); + + /* + * Keys + */ + wmfs_numlockmask(); + + /* + * Root window/cursor + */ + W->root = RootWindow(W->dpy, W->xscreen); + XChangeWindowAttributes(W->dpy, W->root, CWEventMask | CWCursor, &at); + + /* + * Locale (font encode) + */ + setlocale(LC_CTYPE, ""); + + /* + * Barwin linked list + */ + SLIST_INIT(&W->h.barwin); + + W->running = true; +} + +void +wmfs_grab_keys(void) +{ + KeyCode c; + struct keybind *k; + + wmfs_numlockmask(); + + XUngrabKey(W->dpy, AnyKey, AnyModifier, W->root); + + SLIST_FOREACH(k, &W->h.keybind, next) + if((c = XKeysymToKeycode(W->dpy, k->keysym))) + { + XGrabKey(W->dpy, c, k->mod, W->root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(W->dpy, c, k->mod | LockMask, W->root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(W->dpy, c, k->mod | W->numlockmask, W->root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(W->dpy, c, k->mod | LockMask | W->numlockmask, W->root, True, GrabModeAsync, GrabModeAsync); + } +} + +/** Scan if there are windows on X + * for manage it +*/ +static void +wmfs_scan(void) +{ + int i, n; + XWindowAttributes wa; + Window usl, usl2, *w = NULL; + + SLIST_INIT(&W->h.client); + + /* + Atom rt; + int s, rf, tag = -1, screen = -1, flags = -1, i; + ulong ir, il; + uchar *ret; + */ + + if(XQueryTree(W->dpy, W->root, &usl, &usl2, &w, (unsigned int*)&n)) + for(i = n - 1; i != -1; --i) + { + XGetWindowAttributes(W->dpy, w[i], &wa); + + if(!wa.override_redirect && wa.map_state == IsViewable) + {/* + if(XGetWindowProperty(dpy, w[i], ATOM("_WMFS_TAG"), 0, 32, + False, XA_CARDINAL, &rt, &rf, &ir, &il, &ret) == Success && ret) + { + tag = *ret; + XFree(ret); + } + + if(XGetWindowProperty(dpy, w[i], ATOM("_WMFS_SCREEN"), 0, 32, + False, XA_CARDINAL, &rt, &rf, &ir, &il, &ret) == Success && ret) + { + screen = *ret; + XFree(ret); + } + + if(XGetWindowProperty(dpy, w[i], ATOM("_WMFS_FLAGS"), 0, 32, + False, XA_CARDINAL, &rt, &rf, &ir, &il, &ret) == Success && ret) + { + flags = *ret; + XFree(ret); + } + */ + /*c = */ client_new(w[i], &wa); + + /* + if(tag != -1) + c->tag = tag; + if(screen != -1) + c->screen = screen; + if(flags != -1) + c->flags = flags; + */ + } + } + + XFree(w); +} + +static void +wmfs_loop(void) +{ + XEvent ev; + + while(XPending(W->dpy)) + while(W->running && !XNextEvent(W->dpy, &ev)) + EVENT_HANDLE(&ev); +} + +static inline void +wmfs_init(void) +{ + wmfs_xinit(); + ewmh_init(); + screen_init(); + event_init(); + config_init(); +} + +void +wmfs_quit(void) +{ + struct keybind *k; + struct theme *t; + + /* Will free: + * + * Screens -> tags + * -> Infobars -> Elements + */ + screen_free(); + + XCloseDisplay(W->dpy); + + /* Conf stuffs */ + while(!SLIST_EMPTY(&W->h.keybind)) + { + k = SLIST_FIRST(&W->h.keybind); + SLIST_REMOVE_HEAD(&W->h.keybind, next); + free((void*)k->cmd); + free(k); + } + + while(!SLIST_EMPTY(&W->h.theme)) + { + t = SLIST_FIRST(&W->h.theme); + SLIST_REMOVE_HEAD(&W->h.theme, next); + XFreeFontSet(W->dpy, t->font.fontset); + free(t); + } + + free(W->net_atom); + free(W); + + W->running = false; +} + +/** Reload WMFS binary +*/ +void +uicb_reload(Uicb cmd) +{ + (void)cmd; + /* TODO */ +} + +void +uicb_quit(Uicb cmd) +{ + (void)cmd; + W->running = false; +} + +int +main(int argc, char **argv) +{ + W = (struct wmfs*)xcalloc(1, sizeof(struct wmfs)); + + + /* Get X display */ + if(!(W->dpy = XOpenDisplay(NULL))) + { + fprintf(stderr, "%s: Can't open X server\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Opt */ + /* + int i; + while((i = getopt(argc, argv, "hviC:")) != -1) + { + switch(i) + { + case 'h': + break; + case 'v': + break; + case 'C': + break; + } + } + */ + + /* Core */ + wmfs_init(); + wmfs_scan(); + + wmfs_loop(); + + wmfs_quit(); + + return 1; +} diff --git a/src/wmfs.h b/src/wmfs.h new file mode 100644 index 0000000..be82658 --- /dev/null +++ b/src/wmfs.h @@ -0,0 +1,210 @@ +/* + * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } + * For license, see COPYING. + */ + +#ifndef WMFS_H +#define WMFS_H + +/* Standard */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Xlib */ +#include +#include + +/* Local */ + +#define ButtonMask (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask) +#define MouseMask (ButtonMask | PointerMotionMask) +#define KeyMask (KeyPressMask | KeyReleaseMask) + +typedef unsigned int Flags; +typedef unsigned int Color; +typedef const char* Uicb; +typedef enum { BarTop = 0, BarBottom, BarHide, BarLast } Barpos; +typedef enum { Right = 0, Left, Top, Bottom, Center, PositionLast } Position; + +/* + * Structures + */ + +struct geo +{ + int x, y, w, h; +}; + +struct barwin +{ + struct geo geo; + Window win; + Drawable dr; + Color fg, bg; + void *ptr; /* Special cases */ + SLIST_HEAD(, mousebind) mousebinds; + SLIST_ENTRY(barwin) next; /* global barwin */ + SLIST_ENTRY(barwin) enext; /* element barwin */ +}; + +struct element +{ + struct geo geo; + struct infobar *infobar; + int type; + void (*func_init)(struct element *e); + void (*func_update)(struct element *e); + SLIST_HEAD(, barwin) bars; + TAILQ_ENTRY(element) next; +}; + +struct infobar +{ + struct barwin *bar; + struct geo geo; + struct screen *screen; + struct theme *theme; + char *elemorder; + Barpos pos; + TAILQ_HEAD(esub, element) elements; + SLIST_ENTRY(infobar) next; +}; + +struct screen +{ + struct geo geo, ugeo; + struct tag *seltag; + int id; + Flags elemupdate; + TAILQ_HEAD(tsub, tag) tags; + SLIST_HEAD(, infobar) infobars; + SLIST_ENTRY(screen) next; +}; + +struct tag +{ + struct screen *screen; + struct client *sel; + char *name; + Flags flags; + Window frame; + SLIST_HEAD(, client) clients; + TAILQ_ENTRY(tag) next; +}; + +struct client +{ + struct tag *tag; + struct screen *screen; + struct barwin *titlebar; + struct geo geo, tgeo, wgeo; + char *title; + Flags flags; + Window win; + SLIST_ENTRY(client) next; /* Global list */ + SLIST_ENTRY(client) tnext; /* struct tag list */ +}; + +struct keybind +{ + unsigned int mod; + void (*func)(Uicb); + Uicb cmd; + KeySym keysym; + SLIST_ENTRY(keybind) next; +}; + +struct mousebind +{ + struct geo area; + unsigned int button; + bool use_area; + void (*func)(Uicb); + Uicb cmd; + SLIST_ENTRY(mousebind) next; +}; + +struct colpair +{ + Color fg, bg; +}; + +struct theme +{ + char *name; + + /* Font */ + struct + { + int as, de, width, height; + XFontSet fontset; + } font; + + /* Bars */ + struct colpair bars; + int bars_width; + + /* struct elements */ + struct colpair tags_n, tags_s; /* normal / selected */ + int tags_border_width; + Color tags_border_col; + + /* client / frame */ + struct colpair client_n, client_s; + Color frame_bg; + int client_titlebar_width; + int client_border_width; + + SLIST_ENTRY(theme) next; +}; + +struct wmfs +{ + /* X11 stuffs */ + Display *dpy; + Window root; + int xscreen, xdepth; + Flags numlockmask; + GC gc; + Atom *net_atom; + bool running; + + /* Lists heads */ + struct + { + SLIST_HEAD(, screen) screen; + SLIST_HEAD(, client) client; + SLIST_HEAD(, keybind) keybind; + SLIST_HEAD(, barwin) barwin; + SLIST_HEAD(, theme) theme; + } h; + + /* + * Selected screen, client + */ + struct screen *screen; + struct client *client; + +}; + +int wmfs_error_handler(Display *d, XErrorEvent *event); +int wmfs_error_handler_dummy(Display *d, XErrorEvent *event); +void wmfs_grab_keys(void); +void wmfs_numlockmask(void); +void wmfs_init_font(char *font, struct theme *t); +void wmfs_quit(void); +void uicb_reload(Uicb cmd); +void uicb_quit(Uicb cmd); + + +/* Single global variable */ +struct wmfs *W; + +#endif /* WMFS_H */ diff --git a/src/wmfs.o b/src/wmfs.o new file mode 100644 index 0000000..4fdeb6b Binary files /dev/null and b/src/wmfs.o differ diff --git a/wmfsrc2 b/wmfsrc2 new file mode 100644 index 0000000..7ee90d2 --- /dev/null +++ b/wmfsrc2 @@ -0,0 +1,149 @@ +# +# WMFS2 configuration file +# + +[themes] + + + [theme] + # name = "default" + + font = "fixed" + + # Bars + bars_width = 14 + bars_fg = "#CCCCCC" + bars_bg = "#222222" + + # Element tags + tags_normal_fg = "#CCCCCC" + tags_normal_bg = "#222222" + tags_sel_fg = "#222222" + tags_sel_bg = "#CCCCCC" + tags_border_color = "#888888" + tags_border_width = 1 + + # Frame / Client + client_normal_fg = "#CCCCCC" + client_normal_bg = "#222222" + client_sel_fg = "#222222" + client_sel_bg = "#CCCCCC" + frame_bg = "#555555" + client_titlebar_width = 12 #useless for now + client_border_width = 1 + + [/theme] + + [theme] + name = "perso" + + font = "-*-fixed-bold" + + bars_width = 20 + bars_fg = "#222222" + bars_bg = "#CCCCCC" + + tags_sel_bg = "#33AA33" + tags_normal_fg = "#AA3333" + + [/theme] + +[/themes] + +[bars] + + # Position: + # 0 Top + # 1 Bottom + # 2 Hide + + # Element type: + # t Tags + # S Statustext + + [bar] + position = 1 + screen = 0 + elements = "t" + theme = "perso" + [/bar] + + [bar] + position = 1 + screen = 0 + elements = "t" + theme = "default" + [/bar] + + [bar] + screen = 1 + elements = "t" + theme = "perso" + [/bar] + [bar] + screen = 1 + elements = "t" + theme = "default" + [/bar] + + +[/bars] + +[tags] + + [tag] screen = 0 name = "one" [/tag] + [tag] screen = 0 name = "two" [/tag] + [tag] screen = 0 name = "three" [/tag] + + [tag] screen = 1 name = "four" [/tag] + [tag] screen = 1 name = "five" [/tag] + + [tag] name = "universal tag" [/tag] + +[/tags] + +[keys] + + [key] mod = {"Super"} key = "Return" func = "spawn" cmd = "xterm" [/key] + [key] mod = {"Control","Alt"} key = "q" func = "quit" [/key] + [key] mod = {"Super"} key = "1" func = "tag_set" cmd = "1" [/key] + [key] mod = {"Super"} key = "2" func = "tag_set" cmd = "2" [/key] + [key] mod = {"Super"} key = "3" func = "tag_set" cmd = "3" [/key] + [key] mod = {"Super"} key = "s" func = "tag_next" [/key] + [key] mod = {"Super"} key = "a" func = "tag_prev" [/key] + [key] mod = {"Super"} key = "z" func = "tag" cmd = "tag2" [/key] + + [key] mod = {"Super"} key = "q" func = "client_close" [/key] + + # Focus next / prev client + [key] mod = { "Alt" } key = "Tab" func = "client_focus_next" [/key][key] mod = { "Alt", "Shift" } key = "Tab" func = "client_focus_prev" [/key] + + # Focus next client with direction + [key] mod = {"Alt"} key = "h" func = "client_focus_left" [/key] + [key] mod = {"Alt"} key = "l" func = "client_focus_right" [/key] + [key] mod = {"Alt"} key = "k" func = "client_focus_top" [/key] + [key] mod = {"Alt"} key = "j" func = "client_focus_bottom" [/key] + + # swap next client with direction: + [key] mod = {"Control", "Shift"} key = "h" func = "client_swap_left" [/key] + [key] mod = {"Control", "Shift"} key = "l" func = "client_swap_right" [/key] + [key] mod = {"Control", "Shift"} key = "k" func = "client_swap_top" [/key] + [key] mod = {"Control", "Shift"} key = "j" func = "client_swap_bottom" [/key] + + # Resize selected tiled client with direction + [key] mod = {"Super"} key = "h" func = "client_resize_left" cmd = "20" [/key] + [key] mod = {"Super"} key = "l" func = "client_resize_left" cmd = "-20" [/key] + [key] mod = {"Super"} key = "k" func = "client_resize_top" cmd = "20" [/key] + [key] mod = {"Super"} key = "j" func = "client_resize_top" cmd = "-20" [/key] + [key] mod = {"Super", "Control"} key = "h" func = "client_resize_right" cmd = "-20" [/key] + [key] mod = {"Super", "Control"} key = "l" func = "client_resize_right" cmd = "20" [/key] + [key] mod = {"Super", "Control"} key = "k" func = "client_resize_bottom" cmd = "-20" [/key] + [key] mod = {"Super", "Control"} key = "j" func = "client_resize_bottom" cmd = "20" [/key] + + # Layout manipulation + [key] mod = {"Super"} key = "m" func = "layout_vmirror" [/key] + [key] mod = {"Super", "Shift"} key = "m" func = "layout_hmirror" [/key] + [key] mod = {"Super"} key = "r" func = "layout_rotate_right" [/key] + [key] mod = {"Super", "Shift"} key = "r" func = "layout_rotate_left" [/key] + +[/keys]