]> Pileus Git - wmpus/blob - wm-wmii.c
Make settings configurable
[wmpus] / wm-wmii.c
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #include "util.h"
5 #include "sys.h"
6 #include "wm.h"
7
8 #ifndef MODKEY
9 #define MODKEY alt
10 #endif
11 #ifndef MARGIN
12 #define MARGIN 0
13 #endif
14 #ifndef STACK
15 #define STACK  25
16 #endif
17
18 /* Enums */
19 typedef enum {
20         none, move, resize
21 } drag_t;
22
23 typedef enum {
24         split, stack, max, tab
25 } mode_t;
26
27
28 /* Window structure types */
29 struct win_wm { };
30
31 typedef struct {
32         win_t  *win;
33         int     height;
34 } row_t;
35
36 typedef struct {
37         list_t *rows; // of row_t
38         row_t  *row;
39         int     width;
40         mode_t  mode;
41 } col_t;
42
43 typedef struct {
44         list_t *cols; // of col_t
45         col_t  *col;
46         win_t  *geom;
47 } dpy_t;
48
49 typedef struct {
50         list_t *dpys; // of dpy_t
51         dpy_t  *dpy;
52         int     name;
53 } tag_t;
54
55 typedef struct {
56         list_t *tags; // of tag_t
57         tag_t  *tag;
58         win_t  *root;
59         list_t *screens;
60 } wm_t;
61
62 /* Mouse drag data */
63 static drag_t  move_mode;
64 static list_t *move_lrow;
65 static list_t *move_lcol;
66 static ptr_t   move_prev;
67 static struct { int v, h; } move_dir;
68
69 /* Window management data */
70 static wm_t  *wm;
71 #define wm_win   wm->tag->dpy->col->row->win
72 #define wm_row   wm->tag->dpy->col->row
73 #define wm_col   wm->tag->dpy->col
74 #define wm_dpy   wm->tag->dpy
75 #define wm_tag   wm->tag
76 #define wm_focus (wm_tag && wm_dpy && wm_col && wm_row ? wm_win : NULL)
77
78 #define WIN(l) ((win_t*)(l)->data)
79 #define ROW(l) ((row_t*)(l)->data)
80 #define COL(l) ((col_t*)(l)->data)
81 #define DPY(l) ((dpy_t*)(l)->data)
82 #define TAG(l) ((tag_t*)(l)->data)
83
84 #define tag_foreach(tag, dpy, col, row, win) \
85         for (list_t *dpy =     tag ->dpys; dpy; dpy = dpy->next) \
86         for (list_t *col = DPY(dpy)->cols; col; col = col->next) \
87         for (list_t *row = COL(col)->rows; row; row = row->next) \
88         for (win_t  *win = ROW(row)->win;  win; win = NULL)      \
89
90 /* Helper functions */
91 static int searchl(tag_t *tag, win_t *target,
92                 list_t **_dpy, list_t **_col, list_t **_row)
93 {
94         tag_foreach(tag, dpy, col, row, win) {
95                 if (win == target) {
96                         if (_dpy) *_dpy = dpy;
97                         if (_col) *_col = col;
98                         if (_row) *_row = row;
99                         return 1;
100                 }
101         }
102         return 0;
103 }
104
105 static int search(tag_t *tag, win_t *target,
106                 dpy_t **_dpy, col_t **_col, row_t **_row)
107 {
108         list_t *dpy, *col, *row;
109         if (searchl(tag, target, &dpy, &col, &row)) {
110                 if (_dpy) *_dpy = DPY(dpy);
111                 if (_col) *_col = COL(col);
112                 if (_row) *_row = ROW(row);
113                 return 1;
114         }
115         return 0;
116 }
117
118 static void set_mode(win_t *win, mode_t mode)
119 {
120         col_t *col;
121         if (!search(wm_tag, win, NULL, &col, NULL))
122                 return;
123         printf("set_mode: %p, %d -> %d\n",
124                         col, col->mode, mode);
125         col->mode = mode;
126         if (col->mode == split)
127                 for (list_t *cur = col->rows; cur; cur = cur->next) {
128                         row_t *row = cur->data;
129                         row->height = wm_dpy->geom->h;
130                 }
131         wm_update();
132 }
133
134 static void set_focus(win_t *win)
135 {
136         if (win == NULL || win == wm->root) {
137                 sys_focus(wm->root);
138                 return;
139         }
140
141         /* - Only grab mouse button on unfocused window,
142          *   this prevents stealing all mouse clicks from client windows,
143          * - A better way may be to re-send mouse clicks to client windows
144          *   using the return value from wm_handle_key */
145         for (int i = key_mouse1; i < key_mouse7; i++) {
146                 if (wm_focus)
147                         sys_watch(wm_focus, i, MOD());
148                 sys_unwatch(win, i, MOD());
149         }
150
151         dpy_t *dpy; col_t *col; row_t *row;
152         if (search(wm_tag, win, &dpy, &col, &row)) {
153                 wm_dpy = dpy;
154                 wm_col = col;
155                 wm_row = row;
156         }
157         sys_focus(win);
158 }
159
160 static void set_move(win_t *win, ptr_t ptr, drag_t drag)
161 {
162         printf("set_move: %d - %p@%d,%d\n",
163                         drag, win, ptr.rx, ptr.ry);
164         move_mode = drag;
165         if (drag == move || drag == resize) {
166                 searchl(wm_tag, win, NULL, &move_lcol, &move_lrow);
167                 move_prev = ptr;
168                 int my = win->y + (win->h/2);
169                 int mx = win->x + (win->w/2);
170                 move_dir.v = ptr.ry < my ? -1 : +1;
171                 move_dir.h = ptr.rx < mx ? -1 : +1;
172         }
173 }
174
175 static void print_txt(void)
176 {
177         for (list_t *ltag = wm->tags; ltag; ltag = ltag->next) {
178                 tag_t *tag = ltag->data;
179                 printf("tag:       <%-9p [%p->%p] >%-9p !%-9p -  %d\n",
180                                 ltag->prev, ltag, ltag->data, ltag->next,
181                                 tag->dpy, tag->name);
182         for (list_t *ldpy = tag->dpys; ldpy; ldpy = ldpy->next) {
183                 dpy_t *dpy  = ldpy->data;
184                 win_t *geom = dpy->geom;
185                 printf("  dpy:     <%-9p [%p->%p] >%-9p !%-9p -  %d,%d %dx%d\n",
186                                 ldpy->prev, ldpy, ldpy->data, ldpy->next,
187                                 dpy->col, geom->x, geom->y, geom->h, geom->w);
188         for (list_t *lcol = dpy->cols; lcol; lcol = lcol->next) {
189                 col_t *col = lcol->data;
190                 printf("    col:   <%-9p [%p->%p] >%-9p !%-9p -  %dpx @ %d\n",
191                                 lcol->prev, lcol, lcol->data, lcol->next,
192                                 col->row, col->width, col->mode);
193         for (list_t *lrow = col->rows; lrow; lrow = lrow->next) {
194                 row_t *row = lrow->data;
195                 win_t *win = row->win;
196                 printf("      win: <%-9p [%p>>%p] >%-9p !%-9p -  %4dpx focus=%d%d\n",
197                                 lrow->prev, lrow, win, lrow->next,
198                                 win, win->h, col->row == row, wm_focus == win);
199         } } } }
200 }
201
202 static void cut_win(tag_t *tag, win_t *win)
203 {
204         list_t *ldpy, *lcol, *lrow;
205         if (!searchl(tag, win, &ldpy, &lcol, &lrow))
206                 return;
207         col_t  *col  = COL(lcol);
208         dpy_t  *dpy  = DPY(ldpy);
209
210         col->row  = lrow->prev ? lrow->prev->data :
211                     lrow->next ? lrow->next->data : NULL;
212         col->rows = list_remove(col->rows, lrow);
213
214         if (col->rows == NULL && (lcol->next || lcol->prev)) {
215                 dpy->col  = lcol->prev ? lcol->prev->data :
216                             lcol->next ? lcol->next->data : NULL;
217                 dpy->cols = list_remove(dpy->cols, lcol);
218         }
219 }
220
221 static void put_win(win_t *win, tag_t *tag, dpy_t *dpy, col_t *col)
222 {
223         row_t *row = new0(row_t);
224         row->win = win;
225
226         if (col == NULL) {
227                 col = new0(col_t);
228                 dpy->cols = list_insert(dpy->cols, col);
229         }
230
231         int nrows = list_length(col->rows);
232         if (col->row) {
233                 list_t *prev = list_find(col->rows, col->row);
234                 list_insert_after(prev, row);
235         } else {
236                 col->rows = list_insert(col->rows, row);
237         }
238         tag->dpy           = dpy;
239         tag->dpy->col      = col;
240         tag->dpy->col->row = row;
241
242         row->height = dpy->geom->h / MAX(nrows,1);
243         if (nrows == 0) {
244                 int ncols = list_length(dpy->cols);
245                 col->width = dpy->geom->w / MAX(ncols-1,1);
246         }
247 }
248
249 static void shift_window(win_t *win, int col, int row)
250 {
251         if (!win) return;
252         printf("shift_window: %p - %+d,%+d\n", win, col, row);
253         print_txt();
254         printf("shift_window: >>>\n");
255         list_t *ldpy, *lcol, *lrow;
256         if (!searchl(wm_tag, win, &ldpy, &lcol, &lrow))
257                 return;
258         dpy_t *dpy = ldpy->data;
259         if (row != 0) {
260                 list_t *src = lrow, *dst = NULL;
261                 if (row < 0) dst = src->prev;
262                 if (row > 0) dst = src->next;
263                 if (src && dst) {
264                         printf("swap: %p <-> %p\n", src->data, dst->data);
265                         row_t *tmp = src->data;
266                         src->data = dst->data;
267                         dst->data = tmp;
268                         goto update;
269                 }
270         } else {
271                 int onlyrow = !lrow->prev && !lrow->next;
272                 list_t *src = lcol, *dst = NULL;
273                 if (col < 0) {
274                         if (src->prev) {
275                                 dst = src->prev;
276                         } else if (!onlyrow) {
277                                 dpy->cols = list_insert(dpy->cols, new0(col_t));
278                                 dst = src->prev;
279                         } else if (ldpy->prev) {
280                                 dpy = ldpy->prev->data;
281                                 dst = list_last(dpy->cols);
282                         } else {
283                                 return;
284                         }
285                 }
286                 if (col > 0) {
287                         if (src->next) {
288                                 dst = src->next;
289                         } else if (!onlyrow) {
290                                 dpy->cols = list_append(dpy->cols, new0(col_t));
291                                 dst = src->next;
292                         } else if (ldpy->next) {
293                                 dpy = ldpy->next->data;
294                                 dst = dpy->cols;
295                         } else {
296                                 return;
297                         }
298                 }
299                 cut_win(wm_tag, win);
300                 put_win(win, wm_tag, dpy, dst ? dst->data : NULL);
301                 goto update;
302         }
303 update:
304         print_txt();
305         wm_update();
306 }
307
308 static list_t *get_next(list_t *list, int forward)
309 {
310         list_t *next = forward ? list->next : list->prev;
311         if (next == NULL) {
312                 next = list;
313                 while ((list = forward ? next->prev : next->next))
314                         next = list;
315         }
316         return next;
317 }
318 static void shift_focus(int cols, int rows)
319 {
320         printf("shift_focus: %+d,%+d\n", cols, rows);
321         if (rows != 0 && wm_focus) {
322                 list_t *dpy, *col, *row;
323                 if (!searchl(wm_tag, wm_focus, &dpy, &col, &row))
324                         return;
325                 row_t *next = get_next(row, rows > 0)->data;
326                 set_focus(next->win);
327                 if (COL(col)->mode != split)
328                         wm_update();
329         }
330         if (cols != 0) {
331                 list_t *dpy, *col, *row, *ndpy, *ncol = NULL;
332                 if (wm_focus) {
333                         if (!searchl(wm_tag, wm_focus, &dpy, &col, &row))
334                                 return;
335                         ncol = cols > 0 ? col->next : col->prev;
336                 } else {
337                         dpy = list_find(wm_tag->dpys, wm_dpy);
338                 }
339                 if (ncol == NULL) {
340                         ndpy = get_next(dpy, cols > 0);
341                         ncol = cols > 0 ? DPY(ndpy)->cols :
342                                 list_last(DPY(ndpy)->cols);
343                         wm_dpy = ndpy->data;
344                 }
345                 if (ncol && COL(ncol) && COL(ncol)->row)
346                         set_focus(COL(ncol)->row->win);
347                 else
348                         sys_focus(wm->root);
349         }
350 }
351
352 static tag_t *tag_new(list_t *screens, int name)
353 {
354         tag_t *tag = new0(tag_t);
355         tag->name  = name;
356         for (list_t *cur = screens; cur; cur = cur->next) {
357                 dpy_t *dpy  = new0(dpy_t);
358                 dpy->geom = cur->data;
359                 tag->dpys = list_append(tag->dpys, dpy);
360         }
361         tag->dpy  = tag->dpys->data;
362         return tag;
363 }
364
365 static tag_t *tag_find(int name)
366 {
367         tag_t *tag = NULL;
368         for (list_t *cur = wm->tags; cur; cur = cur->next)
369                 if (name == TAG(cur)->name) {
370                         tag = cur->data;
371                         break;
372                 }
373         if (!tag) {
374                 tag = tag_new(wm->screens, name);
375                 wm->tags = list_append(wm->tags, tag);
376         }
377         return tag;
378 }
379
380 static void tag_set(win_t *win, int name)
381 {
382         printf("tag_set: %p %d\n", win, name);
383         if (wm_tag->name == name)
384                 return;
385         tag_t *tag = tag_find(name);
386         cut_win(wm_tag, win);
387         put_win(win, tag, tag->dpy, tag->dpy->col);
388         set_focus(wm_focus);
389 }
390
391 static void tag_switch(int name)
392 {
393         printf("tag_switch: %d\n", name);
394         if (wm_col == NULL || wm_row == NULL)
395                 wm->tags = list_remove(wm->tags,
396                                 list_find(wm->tags, wm_tag));
397         wm_tag = tag_find(name);
398 }
399
400 /* Window management functions */
401 void wm_update_dpy(dpy_t *dpy)
402 {
403         int  x=0,  y=0; // Current window top-left position
404         int tx=0, ty=0; // Total x/y size
405         int mx=0, my=0; // Maximum x/y size (screen size)
406         int       sy=0; // Size of focused stack window
407
408         /* Scale horizontally */
409         x  = dpy->geom->x;
410         mx = dpy->geom->w - (list_length(dpy->cols)+1)*MARGIN;
411         for (list_t *lcol = dpy->cols; lcol; lcol = lcol->next)
412                 tx += COL(lcol)->width;
413         for (list_t *lcol = dpy->cols; lcol; lcol = lcol->next)
414                 COL(lcol)->width *= (float)mx / tx;
415
416         /* Scale each column vertically */
417         for (list_t *lcol = dpy->cols; lcol; lcol = lcol->next) {
418                 col_t *col = lcol->data;
419                 ty = 0;
420                 for (list_t *lrow = col->rows; lrow; lrow = lrow->next)
421                         ty += ROW(lrow)->height;
422                 y  = dpy->geom->y;
423                 my = dpy->geom->h - (list_length(col->rows)+1)*MARGIN;
424                 sy = my           - (list_length(col->rows)-1)*STACK;
425                 for (list_t *lrow = col->rows; lrow; lrow = lrow->next) {
426                         win_t *win = ROW(lrow)->win;
427                         win->h = ROW(lrow)->height;
428                         int height = 0;
429                         switch (col->mode) {
430                         case split:
431                                 sys_move(win, x+MARGIN, y+MARGIN,
432                                         col->width, win->h * ((float)my / ty));
433                                 height = win->h;
434                                 break;
435                         case stack:
436                                 if (lrow->next && ROW(lrow->next)->win == col->row->win) {
437                                         /* Hack to prevent flashing */
438                                         win_t *next = ROW(lrow->next)->win;
439                                         sys_move(next, x+MARGIN, y+MARGIN+STACK+MARGIN,
440                                                 col->width, sy);
441                                 }
442                                 height = win == col->row->win ? sy : STACK;
443                                 sys_move(win, x+MARGIN, y+MARGIN,
444                                         col->width, height);
445                                 break;
446                         case max:
447                         case tab:
448                                 sys_move(win, x+MARGIN, 0+MARGIN,
449                                         col->width, dpy->geom->h-2*MARGIN);
450                                 if (col->row->win == win)
451                                         sys_raise(win);
452                                 break;
453                         }
454                         y += height + MARGIN;
455                         ROW(lrow)->height = win->h;
456                 }
457                 x += col->width + MARGIN;
458         }
459 }
460
461 void wm_update(void)
462 {
463         /* Show/hide tags */
464         tag_foreach(wm_tag, dpy, col, row, win)
465                 sys_show(win, st_show);
466         for (list_t *tag = wm ->tags; tag; tag = tag->next)
467                 tag_foreach(TAG(tag), dpy, col, row, win)
468                         if (tag->data != wm_tag)
469                                 sys_show(win, st_hide);
470
471         /* Refrsh the display */
472         for (list_t *ldpy = wm_tag->dpys; ldpy; ldpy = ldpy->next)
473                 wm_update_dpy(ldpy->data);
474         if (wm_focus)
475                 set_focus(wm_focus);
476 }
477
478 int wm_handle_key(win_t *win, Key_t key, mod_t mod, ptr_t ptr)
479 {
480         if (!win || win == wm_dpy->geom) return 0;
481         //printf("wm_handle_key: %p - %x %c%c%c%c%c\n", win, key,
482         //      mod.up    ? '^' : 'v',
483         //      mod.alt   ? 'a' : '-',
484         //      mod.ctrl  ? 'c' : '-',
485         //      mod.shift ? 's' : '-',
486         //      mod.win   ? 'w' : '-');
487
488         /* Mouse movement */
489         if (key_mouse0 <= key && key <= key_mouse7 && mod.up)
490                 return set_move(win,ptr,none),   0;
491         else if (key == key_mouse1 && mod.MODKEY)
492                 return set_move(win,ptr,move),   1;
493         else if (key == key_mouse3 && mod.MODKEY)
494                 return set_move(win,ptr,resize), 1;
495
496         /* Only handle key-down */
497         if (mod.up)
498                 return 0;
499
500         /* Misc */
501         if (mod.MODKEY) {
502 #ifdef DEBUG
503                 if (key == key_f1) return sys_raise(win), 1;
504                 if (key == key_f2) return set_focus(win), 1;
505                 if (key == key_f3) return sys_show(win, st_show), 1;
506                 if (key == key_f4) return sys_show(win, st_hide), 1;
507 #endif
508                 if (key == key_f5) return wm_update(),    1;
509                 if (key == key_f6) return print_txt(),    1;
510         }
511         if (key_mouse0 <= key && key <= key_mouse7)
512                 sys_raise(win);
513
514         /* Movement commands */
515         if (mod.MODKEY && mod.shift) {
516                 switch (key) {
517                 case 'h': return shift_window(wm_focus,-1, 0), 1;
518                 case 'j': return shift_window(wm_focus, 0,+1), 1;
519                 case 'k': return shift_window(wm_focus, 0,-1), 1;
520                 case 'l': return shift_window(wm_focus,+1, 0), 1;
521                 default: break;
522                 }
523         }
524         else if (mod.MODKEY) {
525                 switch (key) {
526                 case 'h': return shift_focus(-1, 0), 1;
527                 case 'j': return shift_focus( 0,+1), 1;
528                 case 'k': return shift_focus( 0,-1), 1;
529                 case 'l': return shift_focus(+1, 0), 1;
530                 default: break;
531                 }
532         }
533
534         /* Column mode commands */
535         if (mod.MODKEY) {
536                 switch (key) {
537                 case 'd': return set_mode(win, split), 1;
538                 case 's': return set_mode(win, stack), 1;
539                 case 'm': return set_mode(win, max),   1;
540                 case 't': return set_mode(win, tab),   1;
541                 default: break;
542                 }
543         }
544
545         /* Tag switching */
546         if (mod.MODKEY && '0' <= key && key <= '9') {
547                 int name = key - '0';
548                 if (mod.shift)
549                         tag_set(win, name);
550                 else
551                         tag_switch(name);
552                 wm_update();
553         }
554
555         /* Focus change */
556         if (key == key_enter)
557                 return set_focus(win), 1;
558
559         if (key_mouse0 <= key && key <= key_mouse7)
560                 return set_focus(win), 0;
561
562         /* Reset focus after after focus change,
563          * not sure what is causing the focus change in the first place
564          * but preventing that would be a better solution */
565         if (key == key_focus)
566                 sys_focus(wm_focus ?: wm->root);
567
568         return 0;
569 }
570
571 int wm_handle_ptr(win_t *cwin, ptr_t ptr)
572 {
573         //printf("wm_handle_ptr: %p - %d,%d %d,%d (%d) -- \n",
574         //              cwin, ptr.x, ptr.y, ptr.rx, ptr.ry, move_mode);
575
576         if (move_mode == none)
577                 return 0;
578
579         /* Tiling */
580         int dx = ptr.rx - move_prev.rx;
581         int dy = ptr.ry - move_prev.ry;
582         move_prev = ptr;
583         if (move_mode == resize) {
584                 list_t *vert = move_dir.v < 0 ? move_lrow->prev : move_lrow->next;
585                 list_t *horz = move_dir.h < 0 ? move_lcol->prev : move_lcol->next;
586                 if (vert) {
587                         ROW(move_lrow)->height += move_dir.v * dy;
588                         ROW(vert)->height      -= move_dir.v * dy;
589                 }
590                 if (horz) {
591                         COL(move_lcol)->width  += move_dir.h * dx;
592                         COL(horz)->width       -= move_dir.h * dx;
593                 }
594                 wm_update();
595         }
596
597         /* Floating */
598         //win_t *mwin = move_win;
599         //int dx = ptr.rx - move_prev.rx;
600         //int dy = ptr.ry - move_prev.ry;
601         //move_prev = ptr;
602         //if (move_mode == move)
603         //      sys_move(mwin, mwin->x+dx, mwin->y+dy, mwin->w, mwin->h);
604         //else if (move_mode == resize)
605         //      sys_move(mwin, mwin->x, mwin->y, mwin->w+dx, mwin->h+dy);
606
607         return 0;
608 }
609
610 void wm_insert(win_t *win)
611 {
612         printf("wm_insert: %p\n", win);
613         print_txt();
614
615         /* Initialize window */
616         win->wm = new0(win_wm_t);
617         sys_watch(win, key_enter, MOD());
618         sys_watch(win, key_focus, MOD());
619
620         /* Add to screen */
621         put_win(win, wm_tag, wm_dpy, wm_col);
622
623         /* Arrange */
624         wm_update();
625         set_focus(wm_focus);
626         print_txt();
627 }
628
629 void wm_remove(win_t *win)
630 {
631         printf("wm_remove: %p\n", win);
632         print_txt();
633         for (list_t *tag = wm->tags; tag; tag = tag->next)
634                 cut_win(tag->data, win);
635         set_focus(wm_focus);
636         wm_update();
637         print_txt();
638 }
639
640 void wm_init(win_t *root)
641 {
642         printf("wm_init: %p\n", root);
643
644         /* Hack, fix screen order */
645         list_t *screens = sys_info(root);
646         list_t *left  = screens;
647         list_t *right = screens->next;
648         if (left && right && WIN(left)->x > WIN(right)->x) {
649                 void *tmp   = left->data;
650                 left->data  = right->data;
651                 right->data = tmp;
652         }
653
654         wm          = new0(wm_t);
655         wm->root    = root;
656         wm->screens = screens;
657         wm->tag     = tag_new(wm->screens, 1);
658         wm->tags    = list_insert(NULL, wm->tag);
659
660         Key_t keys_e[] = {key_enter, key_focus};
661         Key_t keys_s[] = {'h', 'j', 'k', 'l',
662                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
663         Key_t keys_m[] = {'h', 'j', 'k', 'l', 'd', 's', 'm', 't',
664                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
665                 key_f1, key_f2, key_f3, key_f4, key_f5, key_f6,
666                 key_mouse1, key_mouse3};
667         for (int i = 0; i < countof(keys_e); i++)
668                 sys_watch(root, keys_e[i],  MOD());
669         for (int i = 0; i < countof(keys_m); i++)
670                 sys_watch(root, keys_m[i], MOD(.MODKEY=1));
671         for (int i = 0; i < countof(keys_s); i++)
672                 sys_watch(root, keys_s[i], MOD(.MODKEY=1,.shift=1));
673 }