Compilation fixes for pdcurses
[dvtm.git] / dvtm.c
blob48f14950d2feb800ef049ece564769f968f16d69
1 /*
2 * The initial "port" of dwm to curses was done by
3 * (c) 2007-2011 Marc Andre Tanner <mat at brain-dump dot org>
5 * It is highly inspired by the original X11 dwm and
6 * reuses some code of it which is mostly
8 * (c) 2006-2007 Anselm R. Garbe <garbeam at gmail dot com>
10 * See LICENSE for details.
13 #define _GNU_SOURCE
14 #include <sys/stat.h>
15 #include <sys/ioctl.h>
16 #ifdef _AIX
17 # include <fcntl.h>
18 #else
19 # include <sys/fcntl.h>
20 #endif
21 #include <sys/wait.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <curses.h>
25 #include <stdio.h>
26 #include <stdarg.h>
27 #include <signal.h>
28 #include <locale.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <stdbool.h>
32 #include <errno.h>
33 #ifdef __CYGWIN__
34 # include <termios.h>
35 #endif
36 #include "madtty.h"
38 #ifdef PDCURSES
39 int ESCDELAY;
40 #endif
42 typedef struct {
43 double mfact;
44 int history;
45 int w;
46 int h;
47 bool need_resize;
48 } Screen;
50 typedef struct {
51 const char *symbol;
52 void (*arrange)(void);
53 } Layout;
55 typedef struct Client Client;
56 struct Client {
57 WINDOW *window;
58 madtty_t *term;
59 const char *cmd;
60 char title[256];
61 uint8_t order;
62 pid_t pid;
63 int pty;
64 unsigned short int id;
65 unsigned short int x;
66 unsigned short int y;
67 unsigned short int w;
68 unsigned short int h;
69 bool minimized;
70 bool died;
71 Client *next;
72 Client *prev;
75 typedef struct {
76 const char *title;
77 unsigned attrs;
78 short fg;
79 short bg;
80 } ColorRule;
82 #define ALT(k) ((k) + (161 - 'a'))
83 #ifndef CTRL
84 #define CTRL(k) ((k) & 0x1F)
85 #endif
86 #define CTRL_ALT(k) ((k) + (129 - 'a'))
88 #define MAX_ARGS 2
90 typedef struct {
91 void (*cmd)(const char *args[]);
92 /* needed to avoid an error about initialization
93 * of nested flexible array members */
94 const char *args[MAX_ARGS + 1];
95 } Action;
97 typedef struct {
98 unsigned int mod;
99 unsigned int code;
100 Action action;
101 } Key;
103 typedef struct {
104 mmask_t mask;
105 Action action;
106 } Button;
108 typedef struct {
109 const char *name;
110 Action action;
111 } Cmd;
113 enum { BAR_TOP, BAR_BOTTOM, BAR_OFF };
114 enum { ALIGN_LEFT, ALIGN_RIGHT };
116 typedef struct {
117 int fd;
118 int pos;
119 unsigned short int h;
120 unsigned short int y;
121 char text[512];
122 const char *file;
123 } StatusBar;
125 typedef struct {
126 int fd;
127 const char *file;
128 unsigned short int id;
129 } CmdFifo;
131 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
132 #define sstrlen(str) (sizeof(str) - 1)
133 #define max(x, y) ((x) > (y) ? (x) : (y))
135 #ifdef NDEBUG
136 #define debug(format, args...)
137 #else
138 #define debug eprint
139 #endif
141 /* commands for use by keybindings */
142 static void create(const char *args[]);
143 static void escapekey(const char *args[]);
144 static void focusn(const char *args[]);
145 static void focusnext(const char *args[]);
146 static void focusnextnm(const char *args[]);
147 static void focusprev(const char *args[]);
148 static void focusprevnm(const char *args[]);
149 static void killclient(const char *args[]);
150 static void lock(const char *key[]);
151 static void quit(const char *args[]);
152 static void redraw(const char *args[]);
153 static void scrollback(const char *args[]);
154 static void setlayout(const char *args[]);
155 static void setmfact(const char *args[]);
156 static void startup(const char *args[]);
157 static void togglebar(const char *args[]);
158 static void togglebell(const char *key[]);
159 static void toggleminimize(const char *args[]);
160 static void togglemouse(const char *args[]);
161 static void togglerunall(const char *args[]);
162 static void zoom(const char *args[]);
164 /* commands for use by mouse bindings */
165 static void mouse_focus(const char *args[]);
166 static void mouse_fullscreen(const char *args[]);
167 static void mouse_minimize(const char *args[]);
168 static void mouse_zoom(const char *args[]);
170 /* functions and variables available to layouts via config.h */
171 static void resize(Client *c, int x, int y, int w, int h);
172 extern Screen screen;
173 static unsigned int waw, wah, wax, way;
174 static Client *clients = NULL;
176 #include "config.h"
178 /* global variables */
179 Screen screen = { MFACT, SCROLL_HISTORY };
180 static Client *sel = NULL;
181 static Client *msel = NULL;
182 static bool mouse_events_enabled = ENABLE_MOUSE;
183 static Layout *layout = layouts;
184 static StatusBar bar = { -1, BAR_POS, 1 };
185 static CmdFifo cmdfifo = { -1 };
186 static const char *shell;
187 static bool running = true;
188 static bool runinall = false;
190 static void
191 eprint(const char *errstr, ...) {
192 va_list ap;
193 va_start(ap, errstr);
194 vfprintf(stderr, errstr, ap);
195 va_end(ap);
198 static void
199 error(const char *errstr, ...) {
200 va_list ap;
201 va_start(ap, errstr);
202 vfprintf(stderr, errstr, ap);
203 va_end(ap);
204 exit(EXIT_FAILURE);
207 static bool
208 isarrange(void (*func)()) {
209 return func == layout->arrange;
212 static void
213 clear_workspace() {
214 for (unsigned int y = 0; y < wah; y++)
215 mvhline(way + y, 0, ' ', waw);
216 wnoutrefresh(stdscr);
219 static void
220 drawbar() {
221 wchar_t wbuf[sizeof bar.text];
222 int w, maxwidth = screen.w - 2;
223 if (bar.pos == BAR_OFF || !bar.text[0])
224 return;
225 curs_set(0);
226 attrset(BAR_ATTR);
227 wcolor_set(stdscr, madtty_color_get(BAR_FG, BAR_BG), NULL);
228 mvaddch(bar.y, 0, '[');
229 if (mbstowcs(wbuf, bar.text, sizeof bar.text) == (size_t)-1)
230 return;
231 if ((w = wcswidth(wbuf, maxwidth)) == -1)
232 return;
233 if (BAR_ALIGN == ALIGN_RIGHT) {
234 for (int i = 0; i + w < maxwidth; i++)
235 addch(' ');
237 addstr(bar.text);
238 if (BAR_ALIGN == ALIGN_LEFT) {
239 for (; w < maxwidth; w++)
240 addch(' ');
242 mvaddch(bar.y, screen.w - 1, ']');
243 attrset(NORMAL_ATTR);
244 if (sel)
245 curs_set(madtty_cursor(sel->term));
246 refresh();
249 static void
250 draw_border(Client *c) {
251 char *s, t = '\0';
252 int x, y, o;
253 if (sel == c) {
254 wattrset(c->window, SELECTED_ATTR);
255 wcolor_set(c->window, madtty_color_get(SELECTED_FG, SELECTED_BG), NULL);
256 } else {
257 wattrset(c->window, NORMAL_ATTR);
258 wcolor_set(c->window, madtty_color_get(NORMAL_FG, NORMAL_BG), NULL);
260 getyx(c->window, y, x);
261 curs_set(0);
262 mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
263 o = c->w - (4 + sstrlen(TITLE) - 5 + sstrlen(SEPARATOR));
264 if (o < 0)
265 o = 0;
266 if ((size_t)o < sizeof(c->title)) {
267 t = *(s = &c->title[o]);
268 *s = '\0';
270 mvwprintw(c->window, 0, 2, TITLE,
271 *c->title ? c->title : "",
272 *c->title ? SEPARATOR : "",
273 c->order);
274 if (t)
275 *s = t;
276 wmove(c->window, y, x);
277 if (!c->minimized)
278 curs_set(madtty_cursor(c->term));
281 static void
282 draw_content(Client *c) {
283 if (!c->minimized || isarrange(fullscreen)) {
284 madtty_draw(c->term, c->window, 1, 0);
285 if (c != sel)
286 curs_set(0);
290 static void
291 draw(Client *c) {
292 draw_content(c);
293 draw_border(c);
294 wrefresh(c->window);
297 static void
298 draw_all(bool border) {
299 Client *c;
300 curs_set(0);
301 for (c = clients; c; c = c->next) {
302 redrawwin(c->window);
303 if (c == sel)
304 continue;
305 draw_content(c);
306 if (border)
307 draw_border(c);
308 wnoutrefresh(c->window);
310 /* as a last step the selected window is redrawn,
311 * this has the effect that the cursor position is
312 * accurate
314 refresh();
315 if (sel) {
316 draw_content(sel);
317 if (border)
318 draw_border(sel);
319 wrefresh(sel->window);
323 static void
324 arrange() {
325 clear_workspace();
326 attrset(NORMAL_ATTR);
327 color_set(madtty_color_get(NORMAL_FG, NORMAL_BG), NULL);
328 layout->arrange();
329 wnoutrefresh(stdscr);
330 draw_all(true);
333 static void
334 attach(Client *c) {
335 uint8_t order;
336 if (clients)
337 clients->prev = c;
338 c->next = clients;
339 c->prev = NULL;
340 clients = c;
341 for (order = 1; c; c = c->next, order++)
342 c->order = order;
345 static void
346 attachafter(Client *c, Client *a) { /* attach c after a */
347 uint8_t o;
348 if (c == a)
349 return;
350 if (!a)
351 for (a = clients; a && a->next; a = a->next);
353 if (a) {
354 if (a->next)
355 a->next->prev = c;
356 c->next = a->next;
357 c->prev = a;
358 a->next = c;
359 for (o = a->order; c; c = c->next)
360 c->order = ++o;
364 static void
365 detach(Client *c) {
366 Client *d;
367 if (c->prev)
368 c->prev->next = c->next;
369 if (c->next) {
370 c->next->prev = c->prev;
371 for (d = c->next; d; d = d->next)
372 --d->order;
374 if (c == clients)
375 clients = c->next;
376 c->next = c->prev = NULL;
379 static void
380 focus(Client *c) {
381 Client *tmp = sel;
382 if (sel == c)
383 return;
384 sel = c;
385 if (tmp) {
386 draw_border(tmp);
387 wrefresh(tmp->window);
389 if (isarrange(fullscreen))
390 redrawwin(c->window);
391 draw_border(c);
392 wrefresh(c->window);
395 static void
396 applycolorrules(madtty_t *term, char *title) {
397 unsigned int i;
398 unsigned attrs = A_NORMAL;
399 short fg = -1, bg = -1;
400 const ColorRule *r;
402 for (i = 0; i < countof(colorrules); i++) {
403 r = &colorrules[i];
404 if (strstr(title, r->title)) {
405 attrs = r->attrs;
406 fg = r->fg;
407 bg = r->bg;
408 break;
411 madtty_set_default_colors(term, attrs, fg, bg);
414 static int
415 title_escape_seq_handler(madtty_t *term, char *es) {
416 Client *c;
417 unsigned int l;
418 if (es[0] != ']' || (es[1] && (es[1] < '0' || es[1] > '9')) || (es[2] && es[2] != ';'))
419 return MADTTY_HANDLER_NOWAY;
420 if ((l = strlen(es)) < 3 || es[l - 1] != '\07')
421 return MADTTY_HANDLER_NOTYET;
422 es[l - 1] = '\0';
423 c = (Client *)madtty_get_data(term);
424 strncpy(c->title, es + 3, sizeof(c->title));
425 draw_border(c);
426 debug("window title: %s\n", c->title);
427 applycolorrules(term, c->title);
428 return MADTTY_HANDLER_OK;
431 static void
432 move_client(Client *c, int x, int y) {
433 if (c->x == x && c->y == y)
434 return;
435 debug("moving, x: %d y: %d\n", x, y);
436 if (mvwin(c->window, y, x) == ERR)
437 eprint("error moving, x: %d y: %d\n", x, y);
438 else {
439 c->x = x;
440 c->y = y;
444 static void
445 resize_client(Client *c, int w, int h) {
446 if (c->w == w && c->h == h)
447 return;
448 debug("resizing, w: %d h: %d\n", w, h);
449 if (wresize(c->window, h, w) == ERR)
450 eprint("error resizing, w: %d h: %d\n", w, h);
451 else {
452 c->w = w;
453 c->h = h;
455 madtty_resize(c->term, h - 1, w);
458 static void
459 resize(Client *c, int x, int y, int w, int h) {
460 resize_client(c, w, h);
461 move_client(c, x, y);
464 static Client*
465 get_client_by_pid(pid_t pid) {
466 Client *c;
467 for (c = clients; c; c = c->next) {
468 if (c->pid == pid)
469 return c;
471 return NULL;
474 static Client*
475 get_client_by_coord(unsigned int x, unsigned int y) {
476 Client *c;
477 if (y < way || y >= wah)
478 return NULL;
479 if (isarrange(fullscreen))
480 return sel;
481 for (c = clients; c; c = c->next) {
482 if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
483 debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
484 return c;
487 return NULL;
490 static void
491 sigchld_handler(int sig) {
492 int errsv = errno;
493 int status;
494 pid_t pid;
495 Client *c;
497 while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
498 if (pid == -1) {
499 if (errno == ECHILD) {
500 /* no more child processes */
501 break;
503 eprint("waitpid: %s\n", strerror(errno));
504 break;
506 debug("child with pid %d died\n", pid);
507 if ((c = get_client_by_pid(pid)))
508 c->died = true;
511 signal(SIGCHLD, sigchld_handler);
513 errno = errsv;
516 static void
517 sigwinch_handler(int sig) {
518 signal(SIGWINCH, sigwinch_handler);
519 screen.need_resize = true;
522 static void
523 sigterm_handler(int sig) {
524 running = false;
527 static void
528 updatebarpos(void) {
529 bar.y = 0;
530 wax = 0;
531 way = 0;
532 wah = screen.h;
533 if (bar.fd == -1)
534 return;
535 if (bar.pos == BAR_TOP) {
536 wah -= bar.h;
537 way += bar.h;
538 } else if (bar.pos == BAR_BOTTOM) {
539 wah -= bar.h;
540 bar.y = wah;
544 static void
545 resize_screen() {
546 struct winsize ws;
548 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
549 return;
551 screen.w = ws.ws_col;
552 screen.h = ws.ws_row;
554 debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
556 resizeterm(screen.h, screen.w);
557 wresize(stdscr, screen.h, screen.w);
558 wrefresh(curscr);
559 refresh();
561 waw = screen.w;
562 wah = screen.h;
563 updatebarpos();
564 drawbar();
565 arrange();
568 static bool
569 is_modifier(unsigned int mod) {
570 unsigned int i;
571 for (i = 0; i < countof(keys); i++) {
572 if (keys[i].mod == mod)
573 return true;
575 return false;
578 static Key*
579 keybinding(unsigned int mod, unsigned int code) {
580 unsigned int i;
581 for (i = 0; i < countof(keys); i++) {
582 if (keys[i].mod == mod && keys[i].code == code)
583 return &keys[i];
585 return NULL;
588 static void
589 keypress(int code) {
590 Client *c;
591 unsigned int len = 1;
592 char buf[8] = { '\e' };
594 if (code == '\e') {
595 /* pass characters following escape to the underlying app */
596 nodelay(stdscr, TRUE);
597 for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++)
598 buf[len] = t;
599 nodelay(stdscr, FALSE);
602 for (c = runinall ? clients : sel; c; c = c->next) {
603 if (!c->minimized || isarrange(fullscreen)) {
604 if (code == '\e')
605 madtty_write(c->term, buf, len);
606 else
607 madtty_keypress(c->term, code);
609 if (!runinall)
610 break;
614 static void
615 mouse_setup() {
616 #ifdef CONFIG_MOUSE
617 mmask_t mask = 0;
619 if (mouse_events_enabled) {
620 mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
621 for (unsigned int i = 0; i < countof(buttons); i++)
622 mask |= buttons[i].mask;
624 mousemask(mask, NULL);
625 #endif /* CONFIG_MOUSE */
628 static void
629 setup() {
630 if (!(shell = getenv("SHELL")))
631 shell = "/bin/sh";
632 setlocale(LC_CTYPE, "");
633 initscr();
634 start_color();
635 noecho();
636 keypad(stdscr, TRUE);
637 mouse_setup();
638 raw();
639 madtty_init();
640 getmaxyx(stdscr, screen.h, screen.w);
641 resize_screen();
642 signal(SIGWINCH, sigwinch_handler);
643 signal(SIGCHLD, sigchld_handler);
644 signal(SIGTERM, sigterm_handler);
647 static void
648 destroy(Client *c) {
649 if (sel == c)
650 focusnextnm(NULL);
651 detach(c);
652 if (sel == c) {
653 if (clients) {
654 focus(clients);
655 toggleminimize(NULL);
656 } else
657 sel = NULL;
659 werase(c->window);
660 wrefresh(c->window);
661 madtty_destroy(c->term);
662 delwin(c->window);
663 if (!clients && countof(actions)) {
664 if (!strcmp(c->cmd, shell))
665 quit(NULL);
666 else
667 create(NULL);
669 free(c);
670 arrange();
673 static void
674 cleanup() {
675 endwin();
676 if (bar.fd > 0)
677 close(bar.fd);
678 if (bar.file)
679 unlink(bar.file);
680 if (cmdfifo.fd > 0)
681 close(cmdfifo.fd);
682 if (cmdfifo.file)
683 unlink(cmdfifo.file);
686 /* commands for use by keybindings */
687 static void
688 create(const char *args[]) {
689 Client *c = calloc(sizeof(Client), 1);
690 if (!c)
691 return;
692 const char *cmd = (args && args[0]) ? args[0] : shell;
693 const char *pargs[] = { "/bin/sh", "-c", cmd, NULL };
694 c->id = ++cmdfifo.id;
695 char buf[8];
696 snprintf(buf, sizeof buf, "%d", c->id);
697 const char *env[] = {
698 "DVTM", VERSION,
699 "DVTM_WINDOW_ID", buf,
700 NULL
703 c->window = newwin(wah, waw, way, wax);
704 c->term = madtty_create(screen.h - 1, screen.w, screen.history);
705 c->cmd = cmd;
706 if (args && args[1])
707 strncpy(c->title, args[1], sizeof(c->title));
708 c->pid = madtty_forkpty(c->term, "/bin/sh", pargs, env, &c->pty);
709 madtty_set_data(c->term, c);
710 madtty_set_handler(c->term, title_escape_seq_handler);
711 c->w = screen.w;
712 c->h = screen.h;
713 c->x = wax;
714 c->y = way;
715 c->order = 0;
716 c->minimized = false;
717 debug("client with pid %d forked\n", c->pid);
718 attach(c);
719 focus(c);
720 arrange();
723 static void
724 escapekey(const char *args[]) {
725 int key;
726 if ((key = getch()) >= 0) {
727 debug("escaping key `%c'\n", key);
728 keypress(CTRL(key));
732 static void
733 focusn(const char *args[]) {
734 Client *c;
736 for (c = clients; c; c = c->next) {
737 if (c->order == atoi(args[0])) {
738 focus(c);
739 if (c->minimized)
740 toggleminimize(NULL);
741 return;
746 static void
747 focusnext(const char *args[]) {
748 Client *c;
750 if (!sel)
751 return;
753 c = sel->next;
754 if (!c)
755 c = clients;
756 if (c)
757 focus(c);
760 static void
761 focusnextnm(const char *args[]) {
762 Client *c;
764 if (!sel)
765 return;
766 c = sel;
767 do {
768 c = c->next;
769 if (!c)
770 c = clients;
771 } while (c->minimized && c != sel);
772 focus(c);
775 static void
776 focusprev(const char *args[]) {
777 Client *c;
779 if (!sel)
780 return;
781 c = sel->prev;
782 if (!c)
783 for (c = clients; c && c->next; c = c->next);
784 if (c)
785 focus(c);
788 static void
789 focusprevnm(const char *args[]) {
790 Client *c;
792 if (!sel)
793 return;
794 c = sel;
795 do {
796 c = c->prev;
797 if (!c)
798 for (c = clients; c && c->next; c = c->next);
799 } while (c->minimized && c != sel);
800 focus(c);
803 static void
804 killclient(const char *args[]) {
805 if (!sel)
806 return;
807 debug("killing client with pid: %d\n", sel->pid);
808 kill(-sel->pid, SIGKILL);
811 static void
812 lock(const char *args[]) {
813 size_t len = 0, i = 0;
814 char buf[16], *pass = buf;
815 int c;
817 erase();
818 curs_set(0);
820 if (args && args[0]) {
821 len = strlen(args[0]);
822 pass = (char *)args[0];
823 } else {
824 mvprintw(LINES / 2, COLS / 2 - 7, "Enter password");
825 while (len < sizeof buf && (c = getch()) != '\n')
826 if (c != ERR)
827 buf[len++] = c;
830 mvprintw(LINES / 2, COLS / 2 - 7, "Screen locked!");
832 while (i != len) {
833 for(i = 0; i < len; i++) {
834 if (getch() != pass[i])
835 break;
839 arrange();
842 static void
843 quit(const char *args[]) {
844 cleanup();
845 exit(EXIT_SUCCESS);
848 static void
849 redraw(const char *args[]) {
850 wrefresh(curscr);
851 resize_screen();
852 draw_all(true);
855 static void
856 scrollback(const char *args[]) {
857 if (!sel) return;
859 if (!args[0] || atoi(args[0]) < 0)
860 madtty_scroll(sel->term, -sel->h/2);
861 else
862 madtty_scroll(sel->term, sel->h/2);
864 draw(sel);
867 static void
868 setlayout(const char *args[]) {
869 unsigned int i;
871 if (!args || !args[0]) {
872 if (++layout == &layouts[countof(layouts)])
873 layout = &layouts[0];
874 } else {
875 for (i = 0; i < countof(layouts); i++)
876 if (!strcmp(args[0], layouts[i].symbol))
877 break;
878 if (i == countof(layouts))
879 return;
880 layout = &layouts[i];
882 arrange();
885 static void
886 setmfact(const char *args[]) {
887 double delta;
889 if (isarrange(fullscreen) || isarrange(grid))
890 return;
891 /* arg handling, manipulate mfact */
892 if (args[0] == NULL)
893 screen.mfact = MFACT;
894 else if (1 == sscanf(args[0], "%lf", &delta)) {
895 if (args[0][0] == '+' || args[0][0] == '-')
896 screen.mfact += delta;
897 else
898 screen.mfact = delta;
899 if (screen.mfact < 0.1)
900 screen.mfact = 0.1;
901 else if (screen.mfact > 0.9)
902 screen.mfact = 0.9;
904 arrange();
907 static void
908 startup(const char *args[]) {
909 for (unsigned int i = 0; i < countof(actions); i++)
910 actions[i].cmd(actions[i].args);
913 static void
914 togglebar(const char *args[]) {
915 if (bar.pos == BAR_OFF)
916 bar.pos = (BAR_POS == BAR_OFF) ? BAR_TOP : BAR_POS;
917 else
918 bar.pos = BAR_OFF;
919 updatebarpos();
920 arrange();
921 drawbar();
924 static void
925 togglebell(const char *args[]) {
926 madtty_togglebell(sel->term);
929 static void
930 toggleminimize(const char *args[]) {
931 Client *c, *m;
932 unsigned int n;
933 if (!sel)
934 return;
935 /* the last window can't be minimized */
936 if (!sel->minimized) {
937 for (n = 0, c = clients; c; c = c->next)
938 if (!c->minimized)
939 n++;
940 if (n == 1)
941 return;
943 sel->minimized = !sel->minimized;
944 m = sel;
945 /* check whether the master client was minimized */
946 if (sel == clients && sel->minimized) {
947 c = sel->next;
948 detach(c);
949 attach(c);
950 focus(c);
951 detach(m);
952 for (; c && c->next && !c->next->minimized; c = c->next);
953 attachafter(m, c);
954 } else if (m->minimized) {
955 /* non master window got minimized move it above all other
956 * minimized ones */
957 focusnextnm(NULL);
958 detach(m);
959 for (c = clients; c && c->next && !c->next->minimized; c = c->next);
960 attachafter(m, c);
961 } else { /* window is no longer minimized, move it to the master area */
962 madtty_dirty(m->term);
963 detach(m);
964 attach(m);
966 arrange();
969 static void
970 togglemouse(const char *args[]) {
971 mouse_events_enabled = !mouse_events_enabled;
972 mouse_setup();
975 static void
976 togglerunall(const char *args[]) {
977 runinall = !runinall;
980 static void
981 zoom(const char *args[]) {
982 Client *c;
984 if (!sel)
985 return;
986 if ((c = sel) == clients)
987 if (!(c = c->next))
988 return;
989 detach(c);
990 attach(c);
991 focus(c);
992 if (c->minimized)
993 toggleminimize(NULL);
994 arrange();
997 /* commands for use by mouse bindings */
998 static void
999 mouse_focus(const char *args[]) {
1000 focus(msel);
1001 if (msel->minimized)
1002 toggleminimize(NULL);
1005 static void
1006 mouse_fullscreen(const char *args[]) {
1007 mouse_focus(NULL);
1008 if (isarrange(fullscreen))
1009 setlayout(NULL);
1010 else
1011 setlayout(args);
1014 static void
1015 mouse_minimize(const char *args[]) {
1016 focus(msel);
1017 toggleminimize(NULL);
1020 static void
1021 mouse_zoom(const char *args[]) {
1022 focus(msel);
1023 zoom(NULL);
1026 static Cmd *
1027 get_cmd_by_name(const char *name) {
1028 for (unsigned int i = 0; i < countof(commands); i++) {
1029 if (!strcmp(name, commands[i].name))
1030 return &commands[i];
1032 return NULL;
1035 static void
1036 handle_cmdfifo() {
1037 int r;
1038 char *p, *s, cmdbuf[512], c;
1039 Cmd *cmd;
1040 switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) {
1041 case -1:
1042 case 0:
1043 cmdfifo.fd = -1;
1044 break;
1045 default:
1046 cmdbuf[r] = '\0';
1047 p = cmdbuf;
1048 while (*p) {
1049 /* find the command name */
1050 for (; *p == ' ' || *p == '\n'; p++);
1051 for (s = p; *p && *p != ' ' && *p != '\n'; p++);
1052 if ((c = *p))
1053 *p++ = '\0';
1054 if (*s && (cmd = get_cmd_by_name(s)) != NULL) {
1055 bool quote = false;
1056 int argc = 0;
1057 /* XXX: initializer assumes MAX_ARGS == 2 use a initialization loop? */
1058 const char *args[MAX_ARGS] = { NULL, NULL}, *arg;
1059 /* if arguments were specified in config.h ignore the one given via
1060 * the named pipe and thus skip everything until we find a new line
1062 if (cmd->action.args[0] || c == '\n') {
1063 debug("execute %s", s);
1064 cmd->action.cmd(cmd->action.args);
1065 while (*p && *p != '\n')
1066 p++;
1067 continue;
1069 /* no arguments were given in config.h so we parse the command line */
1070 while (*p == ' ')
1071 p++;
1072 arg = p;
1073 for (; (c = *p); p++) {
1074 switch (*p) {
1075 case '\\':
1076 /* remove the escape character '\\' move every
1077 * following character to the left by one position
1079 switch (*(++p)) {
1080 case '\\':
1081 case '\'':
1082 case '\"': {
1083 char *t = p;
1084 for (;;) {
1085 *(t - 1) = *t;
1086 if (*t++ == '\0')
1087 break;
1089 p -= 2;
1092 break;
1093 case '\'':
1094 case '\"':
1095 quote = !quote;
1096 break;
1097 case ' ':
1098 if (!quote) {
1099 case '\n':
1100 /* remove trailing quote if there is one */
1101 if (*(p - 1) == '\'' || *(p - 1) == '\"')
1102 *(p - 1) = '\0';
1103 *p++ = '\0';
1104 /* remove leading quote if there is one */
1105 if (*arg == '\'' || *arg == '\"')
1106 arg++;
1107 if (argc < MAX_ARGS)
1108 args[argc++] = arg;
1110 while (*p == ' ')
1111 ++p;
1112 arg = p;
1114 break;
1117 if (c == '\n' || *p == '\n') {
1118 debug("execute %s", s);
1119 for(int i = 0; i < argc; i++)
1120 debug(" %s", args[i]);
1121 debug("\n");
1122 cmd->action.cmd(args);
1123 break;
1131 static void
1132 handle_mouse() {
1133 #ifdef CONFIG_MOUSE
1134 MEVENT event;
1135 unsigned int i;
1136 if (getmouse(&event) != OK)
1137 return;
1138 msel = get_client_by_coord(event.x, event.y);
1140 if (!msel)
1141 return;
1143 debug("mouse x:%d y:%d cx:%d cy:%d mask:%d\n", event.x, event.y, event.x - msel->x, event.y - msel->y, event.bstate);
1145 madtty_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
1147 for (i = 0; i < countof(buttons); i++) {
1148 if (event.bstate & buttons[i].mask)
1149 buttons[i].action.cmd(buttons[i].action.args);
1152 msel = NULL;
1153 #endif /* CONFIG_MOUSE */
1156 static void
1157 handle_statusbar() {
1158 char *p;
1159 int r;
1160 switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
1161 case -1:
1162 strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
1163 bar.text[sizeof bar.text - 1] = '\0';
1164 bar.fd = -1;
1165 break;
1166 case 0:
1167 bar.fd = -1;
1168 break;
1169 default:
1170 bar.text[r] = '\0'; p = bar.text + strlen(bar.text) - 1;
1171 for (; p >= bar.text && *p == '\n'; *p-- = '\0');
1172 for (; p >= bar.text && *p != '\n'; --p);
1173 if (p > bar.text)
1174 strncpy(bar.text, p + 1, sizeof bar.text);
1175 drawbar();
1179 static int
1180 open_or_create_fifo(const char *name, const char **name_created) {
1181 struct stat info;
1182 int fd;
1184 do {
1185 if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
1186 if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
1187 *name_created = name;
1188 continue;
1190 error("%s\n", strerror(errno));
1192 } while (fd == -1);
1194 if (fstat(fd, &info) == -1)
1195 error("%s\n", strerror(errno));
1196 if (!S_ISFIFO(info.st_mode))
1197 error("%s is not a named pipe\n", name);
1198 return fd;
1201 static void
1202 usage() {
1203 cleanup();
1204 eprint("usage: dvtm [-v] [-m mod] [-d escdelay] [-h n] "
1205 "[-s status-fifo] "
1206 "[-c cmd-fifo] "
1207 "[cmd...]\n");
1208 exit(EXIT_FAILURE);
1211 static bool
1212 parse_args(int argc, char *argv[]) {
1213 int arg;
1214 bool init = false;
1216 if (!getenv("ESCDELAY"))
1217 ESCDELAY = 100;
1218 for (arg = 1; arg < argc; arg++) {
1219 if (argv[arg][0] != '-') {
1220 const char *args[] = { argv[arg], NULL };
1221 if (!init) {
1222 setup();
1223 init = true;
1225 create(args);
1226 continue;
1228 if (argv[arg][1] != 'v' && (arg + 1) >= argc)
1229 usage();
1230 switch (argv[arg][1]) {
1231 case 'v':
1232 puts("dvtm-"VERSION" (c) 2007-2011 Marc Andre Tanner");
1233 exit(EXIT_SUCCESS);
1234 case 'm': {
1235 char *mod = argv[++arg];
1236 if (mod[0] == '^' && mod[1])
1237 *mod = CTRL(mod[1]);
1238 for (unsigned int i = 0; i < countof(keys); i++)
1239 keys[i].mod = *mod;
1240 break;
1242 case 'd':
1243 ESCDELAY = atoi(argv[++arg]);
1244 if (ESCDELAY < 50)
1245 ESCDELAY = 50;
1246 else if (ESCDELAY > 1000)
1247 ESCDELAY = 1000;
1248 break;
1249 case 'h':
1250 screen.history = atoi(argv[++arg]);
1251 break;
1252 case 's':
1253 bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
1254 updatebarpos();
1255 break;
1256 case 'c': {
1257 const char *fifo;
1258 cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
1259 if (!(fifo = realpath(argv[arg], NULL)))
1260 error("%s\n", strerror(errno));
1261 setenv("DVTM_CMD_FIFO", fifo, 1);
1262 break;
1264 default:
1265 usage();
1268 return init;
1272 main(int argc, char *argv[]) {
1273 if (!parse_args(argc, argv)) {
1274 setup();
1275 startup(NULL);
1278 while (running) {
1279 Client *c, *t;
1280 int r, nfds = 0;
1281 fd_set rd;
1283 if (screen.need_resize) {
1284 resize_screen();
1285 screen.need_resize = false;
1288 FD_ZERO(&rd);
1289 FD_SET(STDIN_FILENO, &rd);
1291 if (cmdfifo.fd != -1) {
1292 FD_SET(cmdfifo.fd, &rd);
1293 nfds = cmdfifo.fd;
1296 if (bar.fd != -1) {
1297 FD_SET(bar.fd, &rd);
1298 nfds = max(nfds, bar.fd);
1301 for (c = clients; c; ) {
1302 if (c->died) {
1303 t = c->next;
1304 destroy(c);
1305 c = t;
1306 continue;
1308 FD_SET(c->pty, &rd);
1309 nfds = max(nfds, c->pty);
1310 c = c->next;
1312 r = select(nfds + 1, &rd, NULL, NULL, NULL);
1314 if (r == -1 && errno == EINTR)
1315 continue;
1317 if (r < 0) {
1318 perror("select()");
1319 exit(EXIT_FAILURE);
1322 if (FD_ISSET(STDIN_FILENO, &rd)) {
1323 int code = getch();
1324 Key *key;
1325 if (code >= 0) {
1326 if (code == KEY_MOUSE) {
1327 handle_mouse();
1328 } else if (is_modifier(code)) {
1329 int mod = code;
1330 code = getch();
1331 if (code >= 0) {
1332 if (code == mod)
1333 keypress(code);
1334 else if ((key = keybinding(mod, code)))
1335 key->action.cmd(key->action.args);
1337 } else if ((key = keybinding(0, code))) {
1338 key->action.cmd(key->action.args);
1339 } else {
1340 keypress(code);
1343 if (r == 1) /* no data available on pty's */
1344 continue;
1347 if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
1348 handle_cmdfifo();
1350 if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
1351 handle_statusbar();
1353 for (c = clients; c; ) {
1354 if (FD_ISSET(c->pty, &rd)) {
1355 if (madtty_process(c->term) < 0 && errno == EIO) {
1356 /* client probably terminated */
1357 t = c->next;
1358 destroy(c);
1359 c = t;
1360 continue;
1362 if (c != sel) {
1363 draw_content(c);
1364 if (!isarrange(fullscreen))
1365 wnoutrefresh(c->window);
1368 c = c->next;
1371 if (sel) {
1372 draw_content(sel);
1373 wnoutrefresh(sel->window);
1375 doupdate();
1378 cleanup();
1379 return 0;