Optimize redraws, reduce cursor flickering
[dvtm.git] / dvtm.c
blobfd7081acb585c3637394ccbb0f0ab22270d64f98
1 /*
2 * The initial "port" of dwm to curses was done by
4 * © 2007-2013 Marc André Tanner <mat at brain-dump dot org>
6 * It is highly inspired by the original X11 dwm and
7 * reuses some code of it which is mostly
9 * © 2006-2007 Anselm R. Garbe <garbeam at gmail dot com>
11 * See LICENSE for details.
14 #define _GNU_SOURCE
15 #include <sys/stat.h>
16 #include <sys/ioctl.h>
17 #include <sys/wait.h>
18 #include <sys/time.h>
19 #include <sys/types.h>
20 #include <fcntl.h>
21 #include <curses.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <signal.h>
25 #include <locale.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <stdbool.h>
29 #include <errno.h>
30 #ifdef __CYGWIN__
31 # include <termios.h>
32 #endif
33 #include "vt.h"
35 #ifdef PDCURSES
36 int ESCDELAY;
37 #endif
39 #ifndef NCURSES_REENTRANT
40 # define set_escdelay(d) (ESCDELAY = (d))
41 #endif
43 typedef struct {
44 float mfact;
45 int history;
46 int w;
47 int h;
48 bool need_resize;
49 } Screen;
51 typedef struct {
52 const char *symbol;
53 void (*arrange)(void);
54 } Layout;
56 typedef struct Client Client;
57 struct Client {
58 WINDOW *window;
59 Vt *term;
60 const char *cmd;
61 char title[255];
62 int order;
63 pid_t pid;
64 int pty;
65 unsigned short int id;
66 unsigned short int x;
67 unsigned short int y;
68 unsigned short int w;
69 unsigned short int h;
70 bool minimized;
71 bool died;
72 Client *next;
73 Client *prev;
76 typedef struct {
77 const char *title;
78 unsigned attrs;
79 short fg;
80 short bg;
81 } ColorRule;
83 #define ALT(k) ((k) + (161 - 'a'))
84 #if defined CTRL && defined _AIX
85 #undef CTRL
86 #endif
87 #ifndef CTRL
88 #define CTRL(k) ((k) & 0x1F)
89 #endif
90 #define CTRL_ALT(k) ((k) + (129 - 'a'))
92 #define MAX_ARGS 3
94 typedef struct {
95 void (*cmd)(const char *args[]);
96 /* needed to avoid an error about initialization
97 * of nested flexible array members */
98 const char *args[MAX_ARGS];
99 } Action;
101 typedef struct {
102 unsigned int mod;
103 unsigned int code;
104 Action action;
105 } Key;
107 typedef struct {
108 mmask_t mask;
109 Action action;
110 } Button;
112 typedef struct {
113 const char *name;
114 Action action;
115 } Cmd;
117 enum { BAR_TOP, BAR_BOTTOM, BAR_OFF };
118 enum { ALIGN_LEFT, ALIGN_RIGHT };
120 typedef struct {
121 int fd;
122 int pos;
123 unsigned short int h;
124 unsigned short int y;
125 char text[512];
126 const char *file;
127 } StatusBar;
129 typedef struct {
130 int fd;
131 const char *file;
132 unsigned short int id;
133 } CmdFifo;
135 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
136 #define sstrlen(str) (sizeof(str) - 1)
137 #define max(x, y) ((x) > (y) ? (x) : (y))
139 #ifdef NDEBUG
140 #define debug(format, args...)
141 #else
142 #define debug eprint
143 #endif
145 /* commands for use by keybindings */
146 static void create(const char *args[]);
147 static void copymode(const char *args[]);
148 static void escapekey(const char *args[]);
149 static void focusn(const char *args[]);
150 static void focusnext(const char *args[]);
151 static void focusnextnm(const char *args[]);
152 static void focusprev(const char *args[]);
153 static void focusprevnm(const char *args[]);
154 static void killclient(const char *args[]);
155 static void lock(const char *key[]);
156 static void paste(const char *args[]);
157 static void quit(const char *args[]);
158 static void redraw(const char *args[]);
159 static void scrollback(const char *args[]);
160 static void setlayout(const char *args[]);
161 static void setmfact(const char *args[]);
162 static void startup(const char *args[]);
163 static void togglebar(const char *args[]);
164 static void togglebell(const char *key[]);
165 static void toggleminimize(const char *args[]);
166 static void togglemouse(const char *args[]);
167 static void togglerunall(const char *args[]);
168 static void zoom(const char *args[]);
170 /* commands for use by mouse bindings */
171 static void mouse_focus(const char *args[]);
172 static void mouse_fullscreen(const char *args[]);
173 static void mouse_minimize(const char *args[]);
174 static void mouse_zoom(const char *args[]);
176 /* functions and variables available to layouts via config.h */
177 static void resize(Client *c, int x, int y, int w, int h);
178 extern Screen screen;
179 static unsigned int waw, wah, wax, way;
180 static Client *clients = NULL;
181 static char *title;
182 #define COLOR(fg, bg) COLOR_PAIR(vt_color_reserve(fg, bg))
184 #include "config.h"
186 /* global variables */
187 Screen screen = { MFACT, SCROLL_HISTORY };
188 static Client *sel = NULL;
189 static Client *msel = NULL;
190 static bool mouse_events_enabled = ENABLE_MOUSE;
191 static Layout *layout = layouts;
192 static StatusBar bar = { -1, BAR_POS, 1 };
193 static CmdFifo cmdfifo = { -1 };
194 static const char *shell;
195 static char *copybuf;
196 static volatile sig_atomic_t running = true;
197 static bool runinall = false;
199 static void
200 eprint(const char *errstr, ...) {
201 va_list ap;
202 va_start(ap, errstr);
203 vfprintf(stderr, errstr, ap);
204 va_end(ap);
207 static void
208 error(const char *errstr, ...) {
209 va_list ap;
210 va_start(ap, errstr);
211 vfprintf(stderr, errstr, ap);
212 va_end(ap);
213 exit(EXIT_FAILURE);
216 static bool
217 isarrange(void (*func)()) {
218 return func == layout->arrange;
221 static bool
222 is_visible(Client *c) {
223 if (!c)
224 return false;
225 if (isarrange(fullscreen))
226 return sel == c;
227 return !c->minimized;
230 static void
231 drawbar() {
232 wchar_t wbuf[sizeof bar.text];
233 int x, y, w, maxwidth = screen.w - 2;
234 if (bar.pos == BAR_OFF || !bar.text[0])
235 return;
236 getyx(stdscr, y, x);
237 curs_set(0);
238 attrset(BAR_ATTR);
239 mvaddch(bar.y, 0, '[');
240 if (mbstowcs(wbuf, bar.text, sizeof bar.text) == (size_t)-1)
241 return;
242 if ((w = wcswidth(wbuf, maxwidth)) == -1)
243 return;
244 if (BAR_ALIGN == ALIGN_RIGHT) {
245 for (int i = 0; i + w < maxwidth; i++)
246 addch(' ');
248 addstr(bar.text);
249 if (BAR_ALIGN == ALIGN_LEFT) {
250 for (; w < maxwidth; w++)
251 addch(' ');
253 mvaddch(bar.y, screen.w - 1, ']');
254 attrset(NORMAL_ATTR);
255 move(y, x);
256 if (is_visible(sel))
257 curs_set(vt_cursor(sel->term));
258 wnoutrefresh(stdscr);
261 static void
262 draw_border(Client *c) {
263 char t = '\0';
264 int x, y, maxlen;
266 wattrset(c->window, (sel == c || (runinall && !c->minimized)) ? SELECTED_ATTR : NORMAL_ATTR);
267 getyx(c->window, y, x);
268 curs_set(0);
269 mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
270 maxlen = c->w - (2 + sstrlen(TITLE) - sstrlen("%s%sd") + sstrlen(SEPARATOR) + 2);
271 if (maxlen < 0)
272 maxlen = 0;
273 if ((size_t)maxlen < sizeof(c->title)) {
274 t = c->title[maxlen];
275 c->title[maxlen] = '\0';
278 mvwprintw(c->window, 0, 2, TITLE,
279 *c->title ? c->title : "",
280 *c->title ? SEPARATOR : "",
281 c->order);
282 if (t)
283 c->title[maxlen] = t;
284 wmove(c->window, y, x);
285 if (is_visible(sel))
286 curs_set(vt_cursor(sel->term));
289 static void
290 draw_content(Client *c) {
291 vt_draw(c->term, c->window, 1, 0);
292 if (c == sel)
293 curs_set(vt_cursor(sel->term));
296 static void
297 draw(Client *c) {
298 if (is_visible(c)) {
299 redrawwin(c->window);
300 draw_content(c);
302 if (!isarrange(fullscreen) || sel == c)
303 draw_border(c);
304 wnoutrefresh(c->window);
307 static void
308 draw_all() {
309 Client *c;
310 curs_set(0);
311 if (!isarrange(fullscreen)) {
312 for (c = clients; c; c = c->next) {
313 if (c == sel)
314 continue;
315 draw(c);
318 /* as a last step the selected window is redrawn,
319 * this has the effect that the cursor position is
320 * accurate
322 if (sel)
323 draw(sel);
326 static void
327 arrange() {
328 erase();
329 drawbar();
330 attrset(NORMAL_ATTR);
331 layout->arrange();
332 wnoutrefresh(stdscr);
333 draw_all();
336 static void
337 attach(Client *c) {
338 if (clients)
339 clients->prev = c;
340 c->next = clients;
341 c->prev = NULL;
342 clients = c;
343 for (int o = 1; c; c = c->next, o++)
344 c->order = o;
347 static void
348 attachafter(Client *c, Client *a) { /* attach c after a */
349 if (c == a)
350 return;
351 if (!a)
352 for (a = clients; a && a->next; a = a->next);
354 if (a) {
355 if (a->next)
356 a->next->prev = c;
357 c->next = a->next;
358 c->prev = a;
359 a->next = c;
360 for (int o = a->order; c; c = c->next)
361 c->order = ++o;
365 static void
366 detach(Client *c) {
367 Client *d;
368 if (c->prev)
369 c->prev->next = c->next;
370 if (c->next) {
371 c->next->prev = c->prev;
372 for (d = c->next; d; d = d->next)
373 --d->order;
375 if (c == clients)
376 clients = c->next;
377 c->next = c->prev = NULL;
380 static void
381 settitle(Client *c) {
382 char *t = title;
383 if (!t && sel == c && *c->title)
384 t = c->title;
385 if (t)
386 printf("\033]0;%s\007", t);
389 static void
390 focus(Client *c) {
391 Client *tmp = sel;
392 if (sel == c)
393 return;
394 sel = c;
395 settitle(c);
396 if (tmp && !isarrange(fullscreen)) {
397 draw_border(tmp);
398 wnoutrefresh(tmp->window);
400 if (isarrange(fullscreen)) {
401 draw(c);
402 } else {
403 draw_border(c);
404 wnoutrefresh(c->window);
408 static void
409 applycolorrules(Client *c) {
410 const ColorRule *r = colorrules;
411 short fg = r->fg, bg = r->bg;
412 unsigned attrs = r->attrs;
414 for (unsigned int i = 1; i < countof(colorrules); i++) {
415 r = &colorrules[i];
416 if (strstr(c->title, r->title)) {
417 attrs = r->attrs;
418 fg = r->fg;
419 bg = r->bg;
420 break;
424 vt_set_default_colors(c->term, attrs, fg, bg);
427 static void
428 term_event_handler(Vt *term, int event, void *event_data) {
429 Client *c = (Client *)vt_get_data(term);
430 switch (event) {
431 case VT_EVENT_TITLE:
432 if (event_data)
433 strncpy(c->title, event_data, sizeof(c->title) - 1);
434 c->title[event_data ? sizeof(c->title) - 1 : 0] = '\0';
435 settitle(c);
436 if (!isarrange(fullscreen) || sel == c)
437 draw_border(c);
438 applycolorrules(c);
439 break;
440 case VT_EVENT_COPY_TEXT:
441 if (event_data) {
442 free(copybuf);
443 copybuf = event_data;
445 break;
449 static void
450 move_client(Client *c, int x, int y) {
451 if (c->x == x && c->y == y)
452 return;
453 debug("moving, x: %d y: %d\n", x, y);
454 if (mvwin(c->window, y, x) == ERR)
455 eprint("error moving, x: %d y: %d\n", x, y);
456 else {
457 c->x = x;
458 c->y = y;
462 static void
463 resize_client(Client *c, int w, int h) {
464 if (c->w == w && c->h == h)
465 return;
466 debug("resizing, w: %d h: %d\n", w, h);
467 if (wresize(c->window, h, w) == ERR)
468 eprint("error resizing, w: %d h: %d\n", w, h);
469 else {
470 c->w = w;
471 c->h = h;
473 vt_resize(c->term, h - 1, w);
476 static void
477 resize(Client *c, int x, int y, int w, int h) {
478 resize_client(c, w, h);
479 move_client(c, x, y);
482 static Client*
483 get_client_by_pid(pid_t pid) {
484 Client *c;
485 for (c = clients; c; c = c->next) {
486 if (c->pid == pid)
487 return c;
489 return NULL;
492 static Client*
493 get_client_by_coord(unsigned int x, unsigned int y) {
494 Client *c;
495 if (y < way || y >= wah)
496 return NULL;
497 if (isarrange(fullscreen))
498 return sel;
499 for (c = clients; c; c = c->next) {
500 if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
501 debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
502 return c;
505 return NULL;
508 static void
509 sigchld_handler(int sig) {
510 int errsv = errno;
511 int status;
512 pid_t pid;
513 Client *c;
515 while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
516 if (pid == -1) {
517 if (errno == ECHILD) {
518 /* no more child processes */
519 break;
521 eprint("waitpid: %s\n", strerror(errno));
522 break;
524 debug("child with pid %d died\n", pid);
525 if ((c = get_client_by_pid(pid)))
526 c->died = true;
529 errno = errsv;
532 static void
533 sigwinch_handler(int sig) {
534 screen.need_resize = true;
537 static void
538 sigterm_handler(int sig) {
539 running = false;
542 static void
543 updatebarpos(void) {
544 bar.y = 0;
545 wax = 0;
546 way = 0;
547 wah = screen.h;
548 if (bar.fd == -1)
549 return;
550 if (bar.pos == BAR_TOP) {
551 wah -= bar.h;
552 way += bar.h;
553 } else if (bar.pos == BAR_BOTTOM) {
554 wah -= bar.h;
555 bar.y = wah;
559 static void
560 resize_screen() {
561 struct winsize ws;
563 if (ioctl(0, TIOCGWINSZ, &ws) == -1) {
564 getmaxyx(stdscr, screen.h, screen.w);
565 } else {
566 screen.w = ws.ws_col;
567 screen.h = ws.ws_row;
570 debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
572 resizeterm(screen.h, screen.w);
573 wresize(stdscr, screen.h, screen.w);
575 waw = screen.w;
576 wah = screen.h;
577 updatebarpos();
578 arrange();
581 static bool
582 is_modifier(unsigned int mod) {
583 unsigned int i;
584 for (i = 0; i < countof(keys); i++) {
585 if (keys[i].mod == mod)
586 return true;
588 return false;
591 static Key*
592 keybinding(unsigned int mod, unsigned int code) {
593 unsigned int i;
594 for (i = 0; i < countof(keys); i++) {
595 if (keys[i].mod == mod && keys[i].code == code)
596 return &keys[i];
598 return NULL;
601 static void
602 keypress(int code) {
603 Client *c;
604 unsigned int len = 1;
605 char buf[8] = { '\e' };
607 if (code == '\e') {
608 /* pass characters following escape to the underlying app */
609 nodelay(stdscr, TRUE);
610 for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++)
611 buf[len] = t;
612 nodelay(stdscr, FALSE);
615 for (c = runinall ? clients : sel; c; c = c->next) {
616 if (!c->minimized || isarrange(fullscreen)) {
617 if (code == '\e')
618 vt_write(c->term, buf, len);
619 else
620 vt_keypress(c->term, code);
622 if (!runinall)
623 break;
627 static void
628 mouse_setup() {
629 #ifdef CONFIG_MOUSE
630 mmask_t mask = 0;
632 if (mouse_events_enabled) {
633 mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
634 for (unsigned int i = 0; i < countof(buttons); i++)
635 mask |= buttons[i].mask;
637 mousemask(mask, NULL);
638 #endif /* CONFIG_MOUSE */
641 static void
642 setup() {
643 if (!(shell = getenv("SHELL")))
644 shell = "/bin/sh";
645 setlocale(LC_CTYPE, "");
646 initscr();
647 start_color();
648 noecho();
649 keypad(stdscr, TRUE);
650 mouse_setup();
651 raw();
652 vt_init();
653 vt_set_keytable(keytable, countof(keytable));
654 resize_screen();
655 struct sigaction sa;
656 sa.sa_flags = 0;
657 sigemptyset(&sa.sa_mask);
658 sa.sa_handler = sigwinch_handler;
659 sigaction(SIGWINCH, &sa, NULL);
660 sa.sa_handler = sigchld_handler;
661 sigaction(SIGCHLD, &sa, NULL);
662 sa.sa_handler = sigterm_handler;
663 sigaction(SIGTERM, &sa, NULL);
666 static void
667 destroy(Client *c) {
668 if (sel == c)
669 focusnextnm(NULL);
670 detach(c);
671 if (sel == c) {
672 if (clients) {
673 focus(clients);
674 toggleminimize(NULL);
675 } else
676 sel = NULL;
678 werase(c->window);
679 wnoutrefresh(c->window);
680 vt_destroy(c->term);
681 delwin(c->window);
682 if (!clients && countof(actions)) {
683 if (!strcmp(c->cmd, shell))
684 quit(NULL);
685 else
686 create(NULL);
688 free(c);
689 arrange();
692 static void
693 cleanup() {
694 while (clients)
695 destroy(clients);
696 vt_shutdown();
697 endwin();
698 free(copybuf);
699 if (bar.fd > 0)
700 close(bar.fd);
701 if (bar.file)
702 unlink(bar.file);
703 if (cmdfifo.fd > 0)
704 close(cmdfifo.fd);
705 if (cmdfifo.file)
706 unlink(cmdfifo.file);
709 static char *getcwd_by_pid(Client *c) {
710 if (!c)
711 return NULL;
712 char buf[32];
713 snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid);
714 return realpath(buf, NULL);
717 /* commands for use by keybindings */
718 static void
719 create(const char *args[]) {
720 Client *c = calloc(1, sizeof(Client));
721 if (!c)
722 return;
723 const char *cmd = (args && args[0]) ? args[0] : shell;
724 const char *pargs[] = { "/bin/sh", "-c", cmd, NULL };
725 c->id = ++cmdfifo.id;
726 char buf[8], *cwd = NULL;
727 snprintf(buf, sizeof buf, "%d", c->id);
728 const char *env[] = {
729 "DVTM", VERSION,
730 "DVTM_WINDOW_ID", buf,
731 NULL
734 if (!(c->window = newwin(wah, waw, way, wax))) {
735 free(c);
736 return;
739 if (!(c->term = vt_create(screen.h - 1, screen.w, screen.history))) {
740 delwin(c->window);
741 free(c);
742 return;
745 c->cmd = cmd;
746 if (args && args[1]) {
747 strncpy(c->title, args[1], sizeof(c->title) - 1);
748 c->title[sizeof(c->title) - 1] = '\0';
750 if (args && args[2])
751 cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2];
752 c->pid = vt_forkpty(c->term, "/bin/sh", pargs, cwd, env, &c->pty);
753 if (args && args[2] && !strcmp(args[2], "$CWD"))
754 free(cwd);
755 vt_set_data(c->term, c);
756 vt_set_event_handler(c->term, term_event_handler);
757 c->w = screen.w;
758 c->h = screen.h;
759 c->x = wax;
760 c->y = way;
761 c->order = 0;
762 c->minimized = false;
763 debug("client with pid %d forked\n", c->pid);
764 attach(c);
765 focus(c);
766 arrange();
769 static void
770 copymode(const char *args[]) {
771 if (!sel)
772 return;
773 vt_copymode_enter(sel->term);
774 if (args[0]) {
775 vt_copymode_keypress(sel->term, args[0][0]);
776 draw(sel);
780 static void
781 escapekey(const char *args[]) {
782 int key;
783 if ((key = getch()) >= 0) {
784 debug("escaping key `%c'\n", key);
785 keypress(CTRL(key));
789 static void
790 focusn(const char *args[]) {
791 Client *c;
793 for (c = clients; c; c = c->next) {
794 if (c->order == atoi(args[0])) {
795 focus(c);
796 if (c->minimized)
797 toggleminimize(NULL);
798 return;
803 static void
804 focusnext(const char *args[]) {
805 Client *c;
807 if (!sel)
808 return;
810 c = sel->next;
811 if (!c)
812 c = clients;
813 if (c)
814 focus(c);
817 static void
818 focusnextnm(const char *args[]) {
819 Client *c;
821 if (!sel)
822 return;
823 c = sel;
824 do {
825 c = c->next;
826 if (!c)
827 c = clients;
828 } while (c->minimized && c != sel);
829 focus(c);
832 static void
833 focusprev(const char *args[]) {
834 Client *c;
836 if (!sel)
837 return;
838 c = sel->prev;
839 if (!c)
840 for (c = clients; c && c->next; c = c->next);
841 if (c)
842 focus(c);
845 static void
846 focusprevnm(const char *args[]) {
847 Client *c;
849 if (!sel)
850 return;
851 c = sel;
852 do {
853 c = c->prev;
854 if (!c)
855 for (c = clients; c && c->next; c = c->next);
856 } while (c->minimized && c != sel);
857 focus(c);
860 static void
861 killclient(const char *args[]) {
862 if (!sel)
863 return;
864 debug("killing client with pid: %d\n", sel->pid);
865 kill(-sel->pid, SIGKILL);
868 static void
869 lock(const char *args[]) {
870 size_t len = 0, i = 0;
871 char buf[16], *pass = buf;
872 int c;
874 erase();
875 curs_set(0);
877 if (args && args[0]) {
878 len = strlen(args[0]);
879 pass = (char *)args[0];
880 } else {
881 mvprintw(LINES / 2, COLS / 2 - 7, "Enter password");
882 while (len < sizeof buf && (c = getch()) != '\n')
883 if (c != ERR)
884 buf[len++] = c;
887 mvprintw(LINES / 2, COLS / 2 - 7, "Screen locked!");
889 while (i != len) {
890 for(i = 0; i < len; i++) {
891 if (getch() != pass[i])
892 break;
896 arrange();
899 static void
900 paste(const char *args[]) {
901 if (sel && copybuf)
902 vt_write(sel->term, copybuf, strlen(copybuf));
905 static void
906 quit(const char *args[]) {
907 cleanup();
908 exit(EXIT_SUCCESS);
911 static void
912 redraw(const char *args[]) {
913 for (Client *c = clients; c; c = c->next) {
914 if (!c->minimized) {
915 vt_dirty(c->term);
916 wclear(c->window);
917 wnoutrefresh(c->window);
920 wclear(stdscr);
921 wnoutrefresh(stdscr);
922 doupdate();
923 resize_screen();
924 draw_all();
927 static void
928 scrollback(const char *args[]) {
929 if (!sel) return;
931 if (!args[0] || atoi(args[0]) < 0)
932 vt_scroll(sel->term, -sel->h/2);
933 else
934 vt_scroll(sel->term, sel->h/2);
936 draw(sel);
939 static void
940 setlayout(const char *args[]) {
941 unsigned int i;
943 if (!args || !args[0]) {
944 if (++layout == &layouts[countof(layouts)])
945 layout = &layouts[0];
946 } else {
947 for (i = 0; i < countof(layouts); i++)
948 if (!strcmp(args[0], layouts[i].symbol))
949 break;
950 if (i == countof(layouts))
951 return;
952 layout = &layouts[i];
954 arrange();
957 static void
958 setmfact(const char *args[]) {
959 float delta;
961 if (isarrange(fullscreen) || isarrange(grid))
962 return;
963 /* arg handling, manipulate mfact */
964 if (args[0] == NULL)
965 screen.mfact = MFACT;
966 else if (1 == sscanf(args[0], "%f", &delta)) {
967 if (args[0][0] == '+' || args[0][0] == '-')
968 screen.mfact += delta;
969 else
970 screen.mfact = delta;
971 if (screen.mfact < 0.1)
972 screen.mfact = 0.1;
973 else if (screen.mfact > 0.9)
974 screen.mfact = 0.9;
976 arrange();
979 static void
980 startup(const char *args[]) {
981 for (unsigned int i = 0; i < countof(actions); i++)
982 actions[i].cmd(actions[i].args);
985 static void
986 togglebar(const char *args[]) {
987 if (bar.pos == BAR_OFF)
988 bar.pos = (BAR_POS == BAR_OFF) ? BAR_TOP : BAR_POS;
989 else
990 bar.pos = BAR_OFF;
991 updatebarpos();
992 arrange();
995 static void
996 togglebell(const char *args[]) {
997 vt_togglebell(sel->term);
1000 static void
1001 toggleminimize(const char *args[]) {
1002 Client *c, *m;
1003 unsigned int n;
1004 if (!sel)
1005 return;
1006 /* the last window can't be minimized */
1007 if (!sel->minimized) {
1008 for (n = 0, c = clients; c; c = c->next)
1009 if (!c->minimized)
1010 n++;
1011 if (n == 1)
1012 return;
1014 sel->minimized = !sel->minimized;
1015 m = sel;
1016 /* check whether the master client was minimized */
1017 if (sel == clients && sel->minimized) {
1018 c = sel->next;
1019 detach(c);
1020 attach(c);
1021 focus(c);
1022 detach(m);
1023 for (; c && c->next && !c->next->minimized; c = c->next);
1024 attachafter(m, c);
1025 } else if (m->minimized) {
1026 /* non master window got minimized move it above all other
1027 * minimized ones */
1028 focusnextnm(NULL);
1029 detach(m);
1030 for (c = clients; c && c->next && !c->next->minimized; c = c->next);
1031 attachafter(m, c);
1032 } else { /* window is no longer minimized, move it to the master area */
1033 vt_dirty(m->term);
1034 detach(m);
1035 attach(m);
1037 arrange();
1040 static void
1041 togglemouse(const char *args[]) {
1042 mouse_events_enabled = !mouse_events_enabled;
1043 mouse_setup();
1046 static void
1047 togglerunall(const char *args[]) {
1048 runinall = !runinall;
1049 draw_all();
1052 static void
1053 zoom(const char *args[]) {
1054 Client *c;
1056 if (!sel)
1057 return;
1058 if ((c = sel) == clients)
1059 if (!(c = c->next))
1060 return;
1061 detach(c);
1062 attach(c);
1063 focus(c);
1064 if (c->minimized)
1065 toggleminimize(NULL);
1066 arrange();
1069 /* commands for use by mouse bindings */
1070 static void
1071 mouse_focus(const char *args[]) {
1072 focus(msel);
1073 if (msel->minimized)
1074 toggleminimize(NULL);
1077 static void
1078 mouse_fullscreen(const char *args[]) {
1079 mouse_focus(NULL);
1080 if (isarrange(fullscreen))
1081 setlayout(NULL);
1082 else
1083 setlayout(args);
1086 static void
1087 mouse_minimize(const char *args[]) {
1088 focus(msel);
1089 toggleminimize(NULL);
1092 static void
1093 mouse_zoom(const char *args[]) {
1094 focus(msel);
1095 zoom(NULL);
1098 static Cmd *
1099 get_cmd_by_name(const char *name) {
1100 for (unsigned int i = 0; i < countof(commands); i++) {
1101 if (!strcmp(name, commands[i].name))
1102 return &commands[i];
1104 return NULL;
1107 static void
1108 handle_cmdfifo() {
1109 int r;
1110 char *p, *s, cmdbuf[512], c;
1111 Cmd *cmd;
1112 switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) {
1113 case -1:
1114 case 0:
1115 cmdfifo.fd = -1;
1116 break;
1117 default:
1118 cmdbuf[r] = '\0';
1119 p = cmdbuf;
1120 while (*p) {
1121 /* find the command name */
1122 for (; *p == ' ' || *p == '\n'; p++);
1123 for (s = p; *p && *p != ' ' && *p != '\n'; p++);
1124 if ((c = *p))
1125 *p++ = '\0';
1126 if (*s && (cmd = get_cmd_by_name(s)) != NULL) {
1127 bool quote = false;
1128 int argc = 0;
1129 /* XXX: initializer assumes MAX_ARGS == 2 use a initialization loop? */
1130 const char *args[MAX_ARGS] = { NULL, NULL, NULL}, *arg;
1131 /* if arguments were specified in config.h ignore the one given via
1132 * the named pipe and thus skip everything until we find a new line
1134 if (cmd->action.args[0] || c == '\n') {
1135 debug("execute %s", s);
1136 cmd->action.cmd(cmd->action.args);
1137 while (*p && *p != '\n')
1138 p++;
1139 continue;
1141 /* no arguments were given in config.h so we parse the command line */
1142 while (*p == ' ')
1143 p++;
1144 arg = p;
1145 for (; (c = *p); p++) {
1146 switch (*p) {
1147 case '\\':
1148 /* remove the escape character '\\' move every
1149 * following character to the left by one position
1151 switch (p[1]) {
1152 case '\\':
1153 case '\'':
1154 case '\"': {
1155 char *t = p+1;
1156 do {
1157 t[-1] = *t;
1158 } while (*t++);
1161 break;
1162 case '\'':
1163 case '\"':
1164 quote = !quote;
1165 break;
1166 case ' ':
1167 if (!quote) {
1168 case '\n':
1169 /* remove trailing quote if there is one */
1170 if (*(p - 1) == '\'' || *(p - 1) == '\"')
1171 *(p - 1) = '\0';
1172 *p++ = '\0';
1173 /* remove leading quote if there is one */
1174 if (*arg == '\'' || *arg == '\"')
1175 arg++;
1176 if (argc < MAX_ARGS)
1177 args[argc++] = arg;
1179 while (*p == ' ')
1180 ++p;
1181 arg = p--;
1183 break;
1186 if (c == '\n' || *p == '\n') {
1187 if (!*p)
1188 p++;
1189 debug("execute %s", s);
1190 for(int i = 0; i < argc; i++)
1191 debug(" %s", args[i]);
1192 debug("\n");
1193 cmd->action.cmd(args);
1194 break;
1202 static void
1203 handle_mouse() {
1204 #ifdef CONFIG_MOUSE
1205 MEVENT event;
1206 unsigned int i;
1207 if (getmouse(&event) != OK)
1208 return;
1209 msel = get_client_by_coord(event.x, event.y);
1211 if (!msel)
1212 return;
1214 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);
1216 vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
1218 for (i = 0; i < countof(buttons); i++) {
1219 if (event.bstate & buttons[i].mask)
1220 buttons[i].action.cmd(buttons[i].action.args);
1223 msel = NULL;
1224 #endif /* CONFIG_MOUSE */
1227 static void
1228 handle_statusbar() {
1229 char *p;
1230 int r;
1231 switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
1232 case -1:
1233 strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
1234 bar.text[sizeof bar.text - 1] = '\0';
1235 bar.fd = -1;
1236 break;
1237 case 0:
1238 bar.fd = -1;
1239 break;
1240 default:
1241 bar.text[r] = '\0'; p = bar.text + strlen(bar.text) - 1;
1242 for (; p >= bar.text && *p == '\n'; *p-- = '\0');
1243 for (; p >= bar.text && *p != '\n'; --p);
1244 if (p > bar.text)
1245 strncpy(bar.text, p + 1, sizeof bar.text);
1246 drawbar();
1250 static int
1251 open_or_create_fifo(const char *name, const char **name_created) {
1252 struct stat info;
1253 int fd;
1255 do {
1256 if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
1257 if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
1258 *name_created = name;
1259 continue;
1261 error("%s\n", strerror(errno));
1263 } while (fd == -1);
1265 if (fstat(fd, &info) == -1)
1266 error("%s\n", strerror(errno));
1267 if (!S_ISFIFO(info.st_mode))
1268 error("%s is not a named pipe\n", name);
1269 return fd;
1272 static void
1273 usage() {
1274 cleanup();
1275 eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
1276 "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
1277 exit(EXIT_FAILURE);
1280 static bool
1281 parse_args(int argc, char *argv[]) {
1282 int arg;
1283 bool init = false;
1285 if (!getenv("ESCDELAY"))
1286 set_escdelay(100);
1287 for (arg = 1; arg < argc; arg++) {
1288 if (argv[arg][0] != '-') {
1289 const char *args[] = { argv[arg], NULL, NULL };
1290 if (!init) {
1291 setup();
1292 init = true;
1294 create(args);
1295 continue;
1297 if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc)
1298 usage();
1299 switch (argv[arg][1]) {
1300 case 'v':
1301 puts("dvtm-"VERSION" © 2007-2013 Marc André Tanner");
1302 exit(EXIT_SUCCESS);
1303 case 'M':
1304 mouse_events_enabled = !mouse_events_enabled;
1305 break;
1306 case 'm': {
1307 char *mod = argv[++arg];
1308 if (mod[0] == '^' && mod[1])
1309 *mod = CTRL(mod[1]);
1310 for (unsigned int i = 0; i < countof(keys); i++)
1311 keys[i].mod = *mod;
1312 break;
1314 case 'd':
1315 set_escdelay(atoi(argv[++arg]));
1316 if (ESCDELAY < 50)
1317 set_escdelay(50);
1318 else if (ESCDELAY > 1000)
1319 set_escdelay(1000);
1320 break;
1321 case 'h':
1322 screen.history = atoi(argv[++arg]);
1323 break;
1324 case 't':
1325 title = argv[++arg];
1326 break;
1327 case 's':
1328 bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
1329 updatebarpos();
1330 break;
1331 case 'c': {
1332 const char *fifo;
1333 cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
1334 if (!(fifo = realpath(argv[arg], NULL)))
1335 error("%s\n", strerror(errno));
1336 setenv("DVTM_CMD_FIFO", fifo, 1);
1337 break;
1339 default:
1340 usage();
1343 return init;
1347 main(int argc, char *argv[]) {
1348 if (!parse_args(argc, argv)) {
1349 setup();
1350 startup(NULL);
1353 while (running) {
1354 Client *c, *t;
1355 int r, nfds = 0;
1356 fd_set rd;
1358 if (screen.need_resize) {
1359 resize_screen();
1360 screen.need_resize = false;
1363 FD_ZERO(&rd);
1364 FD_SET(STDIN_FILENO, &rd);
1366 if (cmdfifo.fd != -1) {
1367 FD_SET(cmdfifo.fd, &rd);
1368 nfds = cmdfifo.fd;
1371 if (bar.fd != -1) {
1372 FD_SET(bar.fd, &rd);
1373 nfds = max(nfds, bar.fd);
1376 for (c = clients; c; ) {
1377 if (c->died) {
1378 t = c->next;
1379 destroy(c);
1380 c = t;
1381 continue;
1383 FD_SET(c->pty, &rd);
1384 nfds = max(nfds, c->pty);
1385 c = c->next;
1387 doupdate();
1388 r = select(nfds + 1, &rd, NULL, NULL, NULL);
1390 if (r == -1 && errno == EINTR)
1391 continue;
1393 if (r < 0) {
1394 perror("select()");
1395 exit(EXIT_FAILURE);
1398 if (FD_ISSET(STDIN_FILENO, &rd)) {
1399 int code = getch();
1400 Key *key;
1401 if (code >= 0) {
1402 if (code == KEY_MOUSE) {
1403 handle_mouse();
1404 } else if (is_modifier(code)) {
1405 int mod = code;
1406 code = getch();
1407 if (code >= 0) {
1408 if (code == mod)
1409 keypress(code);
1410 else if ((key = keybinding(mod, code)))
1411 key->action.cmd(key->action.args);
1413 } else if ((key = keybinding(0, code))) {
1414 key->action.cmd(key->action.args);
1415 } else if (sel && vt_copymode(sel->term)) {
1416 vt_copymode_keypress(sel->term, code);
1417 draw(sel);
1418 } else {
1419 keypress(code);
1422 if (r == 1) /* no data available on pty's */
1423 continue;
1426 if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
1427 handle_cmdfifo();
1429 if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
1430 handle_statusbar();
1432 for (c = clients; c; ) {
1433 if (FD_ISSET(c->pty, &rd) && !vt_copymode(c->term)) {
1434 if (vt_process(c->term) < 0 && errno == EIO) {
1435 /* client probably terminated */
1436 t = c->next;
1437 destroy(c);
1438 c = t;
1439 continue;
1441 if (c != sel && is_visible(c)) {
1442 draw_content(c);
1443 wnoutrefresh(c->window);
1446 c = c->next;
1449 if (is_visible(sel)) {
1450 draw_content(sel);
1451 wnoutrefresh(sel->window);
1455 cleanup();
1456 return 0;