/* * wmfs2 by Martin Duquesnoy { for(i = 2011; i < 2111; ++i) ©(i); } * For license, see COPYING. */ #include #include "layout.h" #include "config.h" #include "client.h" #include "draw.h" #include "event.h" #include "util.h" /* Shift in client split to keep clients's parent at close arrange */ static int shiftv = 1, shifth = 1; void layout_save_set(struct tag *t) { struct client *c; struct layout_set *l; struct geo_list *g, *gp; int n = 1; l = xcalloc(1, sizeof(struct layout_set)); SLIST_INIT(&l->geos); FOREACH_NFCLIENT(c, &t->clients, tnext) { g = xcalloc(1, sizeof(struct geo_list)); g->geo = c->geo; if(!SLIST_FIRST(&l->geos)) SLIST_INSERT_HEAD(&l->geos, g, next); else SLIST_INSERT_AFTER(gp, g, next); ++n; gp = g; } l->n = n; TAILQ_INSERT_TAIL(&t->sets, l, next); } static void layout_apply_set(struct tag *t, struct layout_set *l) { struct geo_list *g; struct client *c; int nc = 1; FOREACH_NFCLIENT(c, &t->clients, tnext) ++nc; /* TODO: Adapt different client number case */ if(l->n != nc) return; for(g = SLIST_FIRST(&l->geos), c = SLIST_FIRST(&t->clients); c; c = SLIST_NEXT(c, tnext)) { if(g) { client_moveresize(c, &g->geo); g = SLIST_NEXT(g, next); } /* TODO * Not enough geos in the set; * then integrate remains of client * else layout_split_integrate(c, SLIST_FIRST(&t->clients)); */ } /* TODO * Not enough clients for geos in set; * arrange clients with not set geo. * if((g = SLIST_NEXT(g, next))) for(cc.tag = t; g; g = SLIST_NEXT(g, next)) { cc.geo = g->geo; layout_split_arrange_closed(&cc); } */ /* Re-insert set in historic */ layout_save_set(t); } void layout_free_set(struct tag *t) { struct layout_set *l; while(!TAILQ_EMPTY(&t->sets)) { l = TAILQ_FIRST(&t->sets); TAILQ_REMOVE(&t->sets, l, next); FREE_LIST(geo_list, l->geos); free(l); } } #define _REV_BORDER() \ SLIST_FOREACH(g, &l->geos, next) { \ cd.geo = g->geo; \ draw_reversed_rect(W->root, &cd, false); \ } static void _historic_set(struct tag *t, bool prev) { struct keybind *k; struct layout_set *l; struct geo_list *g; struct client cd = { .screen = t->screen, .theme = THEME_DEFAULT }; bool b = true; XEvent ev; KeySym keysym; if(TAILQ_EMPTY(&t->sets)) return; l = TAILQ_LAST(&t->sets, ssub); if(prev) l = TAILQ_PREV(l, ssub, next); if(!l) return; /* TODO if(option_simple_manual_resize) { layout_set_apply(l); return; */ XGrabKeyboard(W->dpy, W->root, True, GrabModeAsync, GrabModeAsync, CurrentTime); _REV_BORDER(); do { XMaskEvent(W->dpy, KeyPressMask, &ev); if(ev.type == KeyPress) { XKeyPressedEvent *ke = &ev.xkey; keysym = XKeycodeToKeysym(W->dpy, (KeyCode)ke->keycode, 0); _REV_BORDER(); SLIST_FOREACH(k, &W->h.keybind, next) if(k->keysym == keysym && KEYPRESS_MASK(k->mod) == KEYPRESS_MASK(ke->state) && k->func) { if(k->func == uicb_layout_prev_set) { if(!(l = TAILQ_PREV(l, ssub, next))) l = TAILQ_LAST(&t->sets, ssub); } else if(k->func == uicb_layout_next_set) { if(!(l = TAILQ_NEXT(l, next))) l = TAILQ_FIRST(&t->sets); } else { k->func(k->cmd); keysym = XK_Escape; } } if(!l) l = TAILQ_LAST(&t->sets, ssub); _REV_BORDER(); /* Gtfo of this loop */ if(keysym == XK_Return) break; else if(keysym == XK_Escape) { b = false; break; } XSync(W->dpy, False); } XNextEvent(W->dpy, &ev); } while(ev.type != KeyPress); _REV_BORDER(); if(b) layout_apply_set(t, l); XUngrabServer(W->dpy); XUngrabKeyboard(W->dpy, CurrentTime); } void uicb_layout_prev_set(Uicb cmd) { (void)cmd; _historic_set(W->screen->seltag, true); } void uicb_layout_next_set(Uicb cmd) { (void)cmd; _historic_set(W->screen->seltag, false); } static struct geo layout_split(struct client *c, bool vertical) { struct geo og, geo; geo = og = c->geo; if(vertical) { c->geo.w >>= 1; c->geo.w += shiftv; geo.x = c->geo.x + c->geo.w; geo.w >>= 1; /* Remainder */ geo.w += (og.x + og.w) - (geo.x + geo.w); shiftv = -shiftv; } else { c->geo.h >>= 1; c->geo.h += shifth; geo.y = c->geo.y + c->geo.h; geo.h >>= 1; /* Remainder */ geo.h += (og.y + og.h) - (geo.y + geo.h); shifth = -shifth; } return geo; } static inline void layout_split_arrange_size(struct geo *g, struct client *c, enum 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, enum position p) { struct geo cgeo = c->geo; struct client *cc; int s = 0, cs = (LDIR(p) ? g->geo.h : g->geo.w); FOREACH_NFCLIENT(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 _ (____ ~ * | |__.| .--'-==~ ~ * |>>'---\ '. ~ , ~ *__|__| '. '-.___.-'/ ~ * '-.__ _.' ~ * ````` ~ */ #define _ARRANGE_SINGLE_PARENT(p) \ do { \ if((c = client_next_with_pos(ghost, p))) \ if(GEO_CHECK2(ghost->geo, c->geo, p)) \ { \ layout_split_arrange_size(&ghost->geo, c, p); \ layout_save_set(ghost->tag); \ return; \ } \ } while(/* CONSTCOND */ 0); void layout_split_arrange_closed(struct client *ghost) { struct client *c, *cc; struct geo g; bool b = false; enum position p; if(!(ghost->flags & CLIENT_TILED)) return; /* Search for single parent for easy resize * Example case: * ___________ ___________ * | | B | -> -> | | | * | A |_____| -> Close -> | A | B | * | | C | -> C -> | |v v v| * |_____|_____| -> -> |_____|_____| */ _ARRANGE_SINGLE_PARENT(Right); _ARRANGE_SINGLE_PARENT(Left); _ARRANGE_SINGLE_PARENT(Top); _ARRANGE_SINGLE_PARENT(Bottom); /* 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; FOREACH_NFCLIENT(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; } } } layout_save_set(ghost->tag); } /* * Integrate a client in split layout: * - Check if there is no sc * - Check if sc is on a different tag than c * - Check if sc is not in free mode * * So from there, sc is not compatible, so we will integrate * c in the larger tiled client of c tag: * - Check if the larger client is correct * * Checks all failed? Get first tiled client of the tag to integrate in. * Still no client, it means that c is the first tiled client of the tag, then maximize it. */ void layout_split_integrate(struct client *c, struct client *sc) { struct geo g; bool f = false; /* No sc or not compatible, get the largest of the tag */ if(!sc || sc == c || sc->tag != c->tag || (sc->flags & CLIENT_FREE) || !COMPCLIENT(c, sc)) sc = client_get_larger(c->tag, c->flags & CLIENT_IGNORE_TAG); /* Largest not correct */ if(!sc || sc == c) { FOREACH_NFCLIENT(sc, &c->tag->clients, tnext) if(sc != c && !(sc->flags & CLIENT_TABBED)) { f = true; break; } /* Ok there is no client to integrate in */ if(!f) { client_maximize(c); c->flags |= CLIENT_TILED; W->flags &= ~WMFS_TABNOC; return; } } /* Tab Next Opened Client option */ if(W->flags & WMFS_TABNOC && COMPCLIENT(c, sc)) { W->flags ^= WMFS_TABNOC; _client_tab(c, sc); return; } /* If there are clients but we can tab with them, split the screen. */ c->flags |= CLIENT_TILED; g = layout_split(sc, (sc->geo.h < sc->geo.w)); client_moveresize(c, &g); client_moveresize(sc, &sc->geo); client_fac_hint(c); client_fac_hint(sc); layout_save_set(c->tag); W->flags &= ~WMFS_TABNOC; } /* Arrange inter-clients holes: * ___________ ___________ * | || | -> | | | * | A || B | -> | A >| B | * | || | -> | >| | * |_____||____| -> |______|____| * ^ void * * and client-screen edge holes * ___________ ___________ * | | || -> | | | * | A | B || -> | A | B >| * | | || -> | | >| * |_____|----'| -> |_____|__v__| * ^^^ void */ 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 * * Right rotation * ____________ ____________ * | | B | -> | | A | * | A |_______| -> |__|_________| * |____| C | D | -> |_____| B | * |____|___|___| -> |_____|______| * * Left 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, void (*pfunc)(struct geo*, struct geo*, struct geo*)) { struct client *c; struct geo g, *ug = &t->screen->ugeo; float f1 = (float)t->screen->ugeo.w / (float)t->screen->ugeo.h; float f2 = 1 / f1; FOREACH_NFCLIENT(c, &t->clients, tnext) { pfunc(&g, ug, &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.. */ FOREACH_NFCLIENT(c, &t->clients, tnext) layout_fix_hole(c); layout_save_set(t); } void uicb_layout_rotate_left(Uicb cmd) { (void)cmd; layout_rotate(W->screen->seltag, _pos_rotate_left); } void uicb_layout_rotate_right(Uicb cmd) { (void)cmd; layout_rotate(W->screen->seltag, _pos_rotate_right); } /* * 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; FOREACH_NFCLIENT(c, &W->screen->seltag->clients, tnext) { c->geo.x = W->screen->ugeo.w - (c->geo.x + c->geo.w); client_moveresize(c, &c->geo); } layout_save_set(W->screen->seltag); } void uicb_layout_hmirror(Uicb cmd) { (void)cmd; struct client *c; FOREACH_NFCLIENT(c, &W->screen->seltag->clients, tnext) { c->geo.y = W->screen->ugeo.h - (c->geo.y + c->geo.h); client_moveresize(c, &c->geo); } layout_save_set(W->screen->seltag); } #define LAYOUT_INTEGRATE_DIR(D) \ void uicb_layout_integrate_##D(Uicb cmd) \ { \ (void)cmd; \ if(W->client) \ layout_integrate(W->client, D); \ } static void layout_integrate(struct client *c, enum position p) { struct client *n; struct client ghost = *c; if(!(c->flags & CLIENT_TILED)) return; if((n = client_next_with_pos(c, p)) && (n->flags & CLIENT_TILED)) { layout_split_integrate(c, n); layout_split_arrange_closed(&ghost); } } LAYOUT_INTEGRATE_DIR(Left); LAYOUT_INTEGRATE_DIR(Right); LAYOUT_INTEGRATE_DIR(Top); LAYOUT_INTEGRATE_DIR(Bottom); void layout_client(struct client *c) { if(c->flags & (CLIENT_IGNORE_LAYOUT | CLIENT_FULLSCREEN)) { c->flags &= ~CLIENT_IGNORE_LAYOUT; return; } if(c->flags & (CLIENT_FREE | CLIENT_STICKY)) { layout_split_arrange_closed(c); c->flags ^= CLIENT_TILED; client_moveresize(c, &c->geo); XRaiseWindow(W->dpy, c->frame); } else if(!(c->flags & CLIENT_TABBED)) layout_split_integrate(c, c->tag->sel); }