Zoom to specific window
[dvtm.git] / dvtm.c
bloba70477e422362ea97410c030d9f14bb2fbd36035
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 focusn(const char *args[]);
149 static void focusnext(const char *args[]);
150 static void focusnextnm(const char *args[]);
151 static void focusprev(const char *args[]);
152 static void focusprevnm(const char *args[]);
153 static void focuslast(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 send(const char *args[]);
161 static void setlayout(const char *args[]);
162 static void setmfact(const char *args[]);
163 static void startup(const char *args[]);
164 static void togglebar(const char *args[]);
165 static void togglebell(const char *key[]);
166 static void toggleminimize(const char *args[]);
167 static void togglemouse(const char *args[]);
168 static void togglerunall(const char *args[]);
169 static void zoom(const char *args[]);
171 /* commands for use by mouse bindings */
172 static void mouse_focus(const char *args[]);
173 static void mouse_fullscreen(const char *args[]);
174 static void mouse_minimize(const char *args[]);
175 static void mouse_zoom(const char *args[]);
177 /* functions and variables available to layouts via config.h */
178 static void resize(Client *c, int x, int y, int w, int h);
179 extern Screen screen;
180 static unsigned int waw, wah, wax, way;
181 static Client *clients = NULL;
182 static char *title;
183 #define COLOR(fg, bg) COLOR_PAIR(vt_color_reserve(fg, bg))
184 #define NOMOD ERR
186 #include "config.h"
188 /* global variables */
189 Screen screen = { MFACT, SCROLL_HISTORY };
190 static Client *sel = NULL;
191 static Client *lastsel = NULL;
192 static Client *msel = NULL;
193 static bool mouse_events_enabled = ENABLE_MOUSE;
194 static Layout *layout = layouts;
195 static StatusBar bar = { -1, BAR_POS, 1 };
196 static CmdFifo cmdfifo = { -1 };
197 static const char *shell;
198 static char *copybuf;
199 static volatile sig_atomic_t running = true;
200 static bool runinall = false;
202 static void
203 eprint(const char *errstr, ...) {
204 va_list ap;
205 va_start(ap, errstr);
206 vfprintf(stderr, errstr, ap);
207 va_end(ap);
210 static void
211 error(const char *errstr, ...) {
212 va_list ap;
213 va_start(ap, errstr);
214 vfprintf(stderr, errstr, ap);
215 va_end(ap);
216 exit(EXIT_FAILURE);
219 static bool
220 isarrange(void (*func)()) {
221 return func == layout->arrange;
224 static bool
225 is_content_visible(Client *c) {
226 if (!c)
227 return false;
228 if (isarrange(fullscreen))
229 return sel == c;
230 return !c->minimized;
233 static void
234 drawbar() {
235 wchar_t wbuf[sizeof bar.text];
236 int x, y, w, maxwidth = screen.w - 2;
237 if (bar.pos == BAR_OFF)
238 return;
239 getyx(stdscr, y, x);
240 attrset(BAR_ATTR);
241 mvaddch(bar.y, 0, '[');
242 if (mbstowcs(wbuf, bar.text, sizeof bar.text) == (size_t)-1)
243 return;
244 if ((w = wcswidth(wbuf, maxwidth)) == -1)
245 return;
246 if (BAR_ALIGN == ALIGN_RIGHT) {
247 for (int i = 0; i + w < maxwidth; i++)
248 addch(' ');
250 addstr(bar.text);
251 if (BAR_ALIGN == ALIGN_LEFT) {
252 for (; w < maxwidth; w++)
253 addch(' ');
255 mvaddch(bar.y, screen.w - 1, ']');
256 attrset(NORMAL_ATTR);
257 move(y, x);
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 mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
269 maxlen = c->w - (2 + sstrlen(TITLE) - sstrlen("%s%sd") + sstrlen(SEPARATOR) + 2);
270 if (maxlen < 0)
271 maxlen = 0;
272 if ((size_t)maxlen < sizeof(c->title)) {
273 t = c->title[maxlen];
274 c->title[maxlen] = '\0';
277 mvwprintw(c->window, 0, 2, TITLE,
278 *c->title ? c->title : "",
279 *c->title ? SEPARATOR : "",
280 c->order);
281 if (t)
282 c->title[maxlen] = t;
283 wmove(c->window, y, x);
286 static void
287 draw_content(Client *c) {
288 vt_draw(c->term, c->window, 1, 0);
291 static void
292 draw(Client *c) {
293 if (is_content_visible(c)) {
294 redrawwin(c->window);
295 draw_content(c);
297 if (!isarrange(fullscreen) || sel == c)
298 draw_border(c);
299 wnoutrefresh(c->window);
302 static void
303 draw_all() {
304 if (!isarrange(fullscreen)) {
305 for (Client *c = clients; c; c = c->next) {
306 if (c == sel)
307 continue;
308 draw(c);
311 /* as a last step the selected window is redrawn,
312 * this has the effect that the cursor position is
313 * accurate
315 if (sel)
316 draw(sel);
319 static void
320 arrange() {
321 erase();
322 drawbar();
323 attrset(NORMAL_ATTR);
324 layout->arrange();
325 wnoutrefresh(stdscr);
326 draw_all();
329 static void
330 attach(Client *c) {
331 if (clients)
332 clients->prev = c;
333 c->next = clients;
334 c->prev = NULL;
335 clients = c;
336 for (int o = 1; c; c = c->next, o++)
337 c->order = o;
340 static void
341 attachafter(Client *c, Client *a) { /* attach c after a */
342 if (c == a)
343 return;
344 if (!a)
345 for (a = clients; a && a->next; a = a->next);
347 if (a) {
348 if (a->next)
349 a->next->prev = c;
350 c->next = a->next;
351 c->prev = a;
352 a->next = c;
353 for (int o = a->order; c; c = c->next)
354 c->order = ++o;
358 static void
359 detach(Client *c) {
360 Client *d;
361 if (c->prev)
362 c->prev->next = c->next;
363 if (c->next) {
364 c->next->prev = c->prev;
365 for (d = c->next; d; d = d->next)
366 --d->order;
368 if (c == clients)
369 clients = c->next;
370 c->next = c->prev = NULL;
373 static void
374 settitle(Client *c) {
375 char *term, *t = title;
376 if (!t && sel == c && *c->title)
377 t = c->title;
378 if (t && (term = getenv("TERM")) && !strstr(term, "linux"))
379 printf("\033]0;%s\007", t);
382 static void
383 focus(Client *c) {
384 Client *tmp = sel;
385 if (sel == c)
386 return;
387 lastsel = sel;
388 sel = c;
389 settitle(c);
390 if (tmp && !isarrange(fullscreen)) {
391 draw_border(tmp);
392 wnoutrefresh(tmp->window);
394 if (isarrange(fullscreen)) {
395 draw(c);
396 } else {
397 draw_border(c);
398 wnoutrefresh(c->window);
400 curs_set(!c->minimized && vt_cursor(c->term));
403 static void
404 applycolorrules(Client *c) {
405 const ColorRule *r = colorrules;
406 short fg = r->fg, bg = r->bg;
407 unsigned attrs = r->attrs;
409 for (unsigned int i = 1; i < countof(colorrules); i++) {
410 r = &colorrules[i];
411 if (strstr(c->title, r->title)) {
412 attrs = r->attrs;
413 fg = r->fg;
414 bg = r->bg;
415 break;
419 vt_set_default_colors(c->term, attrs, fg, bg);
422 static void
423 term_event_handler(Vt *term, int event, void *event_data) {
424 Client *c = (Client *)vt_get_data(term);
425 switch (event) {
426 case VT_EVENT_TITLE:
427 if (event_data)
428 strncpy(c->title, event_data, sizeof(c->title) - 1);
429 c->title[event_data ? sizeof(c->title) - 1 : 0] = '\0';
430 settitle(c);
431 if (!isarrange(fullscreen) || sel == c)
432 draw_border(c);
433 applycolorrules(c);
434 break;
435 case VT_EVENT_COPY_TEXT:
436 if (event_data) {
437 free(copybuf);
438 copybuf = event_data;
440 break;
444 static void
445 move_client(Client *c, int x, int y) {
446 if (c->x == x && c->y == y)
447 return;
448 debug("moving, x: %d y: %d\n", x, y);
449 if (mvwin(c->window, y, x) == ERR) {
450 eprint("error moving, x: %d y: %d\n", x, y);
451 } else {
452 c->x = x;
453 c->y = y;
457 static void
458 resize_client(Client *c, int w, int h) {
459 if (c->w == w && c->h == h)
460 return;
461 debug("resizing, w: %d h: %d\n", w, h);
462 if (wresize(c->window, h, w) == ERR) {
463 eprint("error resizing, w: %d h: %d\n", w, h);
464 } else {
465 c->w = w;
466 c->h = h;
468 vt_resize(c->term, h - 1, w);
471 static void
472 resize(Client *c, int x, int y, int w, int h) {
473 resize_client(c, w, h);
474 move_client(c, x, y);
477 static Client*
478 get_client_by_pid(pid_t pid) {
479 for (Client *c = clients; c; c = c->next) {
480 if (c->pid == pid)
481 return c;
483 return NULL;
486 static Client*
487 get_client_by_coord(unsigned int x, unsigned int y) {
488 if (y < way || y >= wah)
489 return NULL;
490 if (isarrange(fullscreen))
491 return sel;
492 for (Client *c = clients; c; c = c->next) {
493 if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
494 debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
495 return c;
498 return NULL;
501 static void
502 sigchld_handler(int sig) {
503 int errsv = errno;
504 int status;
505 pid_t pid;
507 while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
508 if (pid == -1) {
509 if (errno == ECHILD) {
510 /* no more child processes */
511 break;
513 eprint("waitpid: %s\n", strerror(errno));
514 break;
516 debug("child with pid %d died\n", pid);
517 Client *c = get_client_by_pid(pid);
518 if (c)
519 c->died = true;
522 errno = errsv;
525 static void
526 sigwinch_handler(int sig) {
527 screen.need_resize = true;
530 static void
531 sigterm_handler(int sig) {
532 running = false;
535 static void
536 updatebarpos(void) {
537 bar.y = 0;
538 wax = 0;
539 way = 0;
540 wah = screen.h;
541 if (bar.fd == -1)
542 return;
543 if (bar.pos == BAR_TOP) {
544 wah -= bar.h;
545 way += bar.h;
546 } else if (bar.pos == BAR_BOTTOM) {
547 wah -= bar.h;
548 bar.y = wah;
552 static void
553 resize_screen() {
554 struct winsize ws;
556 if (ioctl(0, TIOCGWINSZ, &ws) == -1) {
557 getmaxyx(stdscr, screen.h, screen.w);
558 } else {
559 screen.w = ws.ws_col;
560 screen.h = ws.ws_row;
563 debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
565 resizeterm(screen.h, screen.w);
566 wresize(stdscr, screen.h, screen.w);
568 waw = screen.w;
569 wah = screen.h;
570 updatebarpos();
571 clear();
572 arrange();
575 static bool
576 is_modifier(unsigned int mod) {
577 for (unsigned int i = 0; i < countof(keys); i++) {
578 if (keys[i].mod == mod)
579 return true;
581 return false;
584 static Key*
585 keybinding(unsigned int mod, unsigned int code) {
586 for (unsigned int i = 0; i < countof(keys); i++) {
587 if (keys[i].mod == mod && keys[i].code == code)
588 return &keys[i];
590 return NULL;
593 static void
594 keypress(int code) {
595 unsigned int len = 1;
596 char buf[8] = { '\e' };
598 if (code == '\e') {
599 /* pass characters following escape to the underlying app */
600 nodelay(stdscr, TRUE);
601 for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++)
602 buf[len] = t;
603 nodelay(stdscr, FALSE);
606 for (Client *c = runinall ? clients : sel; c; c = c->next) {
607 if (is_content_visible(c)) {
608 if (code == '\e')
609 vt_write(c->term, buf, len);
610 else
611 vt_keypress(c->term, code);
613 if (!runinall)
614 break;
618 static void
619 mouse_setup() {
620 #ifdef CONFIG_MOUSE
621 mmask_t mask = 0;
623 if (mouse_events_enabled) {
624 mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
625 for (unsigned int i = 0; i < countof(buttons); i++)
626 mask |= buttons[i].mask;
628 mousemask(mask, NULL);
629 #endif /* CONFIG_MOUSE */
632 static void
633 setup() {
634 if (!(shell = getenv("SHELL")))
635 shell = "/bin/sh";
636 setlocale(LC_CTYPE, "");
637 initscr();
638 start_color();
639 noecho();
640 keypad(stdscr, TRUE);
641 mouse_setup();
642 raw();
643 vt_init();
644 vt_set_keytable(keytable, countof(keytable));
645 resize_screen();
646 struct sigaction sa;
647 sa.sa_flags = 0;
648 sigemptyset(&sa.sa_mask);
649 sa.sa_handler = sigwinch_handler;
650 sigaction(SIGWINCH, &sa, NULL);
651 sa.sa_handler = sigchld_handler;
652 sigaction(SIGCHLD, &sa, NULL);
653 sa.sa_handler = sigterm_handler;
654 sigaction(SIGTERM, &sa, NULL);
657 static void
658 destroy(Client *c) {
659 if (sel == c)
660 focusnextnm(NULL);
661 detach(c);
662 if (sel == c) {
663 if (clients) {
664 focus(clients);
665 toggleminimize(NULL);
666 } else {
667 sel = NULL;
670 if (lastsel == c)
671 lastsel = NULL;
672 werase(c->window);
673 wnoutrefresh(c->window);
674 vt_destroy(c->term);
675 delwin(c->window);
676 if (!clients && countof(actions)) {
677 if (!strcmp(c->cmd, shell))
678 quit(NULL);
679 else
680 create(NULL);
682 free(c);
683 arrange();
686 static void
687 cleanup() {
688 while (clients)
689 destroy(clients);
690 vt_shutdown();
691 endwin();
692 free(copybuf);
693 if (bar.fd > 0)
694 close(bar.fd);
695 if (bar.file)
696 unlink(bar.file);
697 if (cmdfifo.fd > 0)
698 close(cmdfifo.fd);
699 if (cmdfifo.file)
700 unlink(cmdfifo.file);
703 static char *getcwd_by_pid(Client *c) {
704 if (!c)
705 return NULL;
706 char buf[32];
707 snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid);
708 return realpath(buf, NULL);
711 /* commands for use by keybindings */
712 static void
713 create(const char *args[]) {
714 Client *c = calloc(1, sizeof(Client));
715 if (!c)
716 return;
717 const char *cmd = (args && args[0]) ? args[0] : shell;
718 const char *pargs[] = { "/bin/sh", "-c", cmd, NULL };
719 c->id = ++cmdfifo.id;
720 char buf[8], *cwd = NULL;
721 snprintf(buf, sizeof buf, "%d", c->id);
722 const char *env[] = {
723 "DVTM", VERSION,
724 "DVTM_WINDOW_ID", buf,
725 NULL
728 if (!(c->window = newwin(wah, waw, way, wax))) {
729 free(c);
730 return;
733 if (!(c->term = vt_create(screen.h - 1, screen.w, screen.history))) {
734 delwin(c->window);
735 free(c);
736 return;
739 c->cmd = cmd;
740 if (args && args[1]) {
741 strncpy(c->title, args[1], sizeof(c->title) - 1);
742 c->title[sizeof(c->title) - 1] = '\0';
744 if (args && args[2])
745 cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2];
746 c->pid = vt_forkpty(c->term, "/bin/sh", pargs, cwd, env, &c->pty);
747 if (args && args[2] && !strcmp(args[2], "$CWD"))
748 free(cwd);
749 vt_set_data(c->term, c);
750 vt_set_event_handler(c->term, term_event_handler);
751 c->w = screen.w;
752 c->h = screen.h;
753 c->x = wax;
754 c->y = way;
755 c->order = 0;
756 c->minimized = false;
757 debug("client with pid %d forked\n", c->pid);
758 attach(c);
759 focus(c);
760 arrange();
763 static void
764 copymode(const char *args[]) {
765 if (!sel)
766 return;
767 vt_copymode_enter(sel->term);
768 if (args[0]) {
769 vt_copymode_keypress(sel->term, args[0][0]);
770 draw(sel);
774 static void
775 focusn(const char *args[]) {
776 for (Client *c = clients; c; c = c->next) {
777 if (c->order == atoi(args[0])) {
778 focus(c);
779 if (c->minimized)
780 toggleminimize(NULL);
781 return;
786 static void
787 focusnext(const char *args[]) {
788 if (!sel)
789 return;
790 Client *c = sel->next;
791 if (!c)
792 c = clients;
793 if (c)
794 focus(c);
797 static void
798 focusnextnm(const char *args[]) {
799 if (!sel)
800 return;
801 Client *c = sel;
802 do {
803 c = c->next;
804 if (!c)
805 c = clients;
806 } while (c->minimized && c != sel);
807 focus(c);
810 static void
811 focusprev(const char *args[]) {
812 if (!sel)
813 return;
814 Client *c = sel->prev;
815 if (!c)
816 for (c = clients; c && c->next; c = c->next);
817 if (c)
818 focus(c);
821 static void
822 focusprevnm(const char *args[]) {
823 if (!sel)
824 return;
825 Client *c = sel;
826 do {
827 c = c->prev;
828 if (!c)
829 for (c = clients; c && c->next; c = c->next);
830 } while (c->minimized && c != sel);
831 focus(c);
834 static void
835 focuslast(const char *args[]) {
836 if (lastsel)
837 focus(lastsel);
840 static void
841 killclient(const char *args[]) {
842 if (!sel)
843 return;
844 debug("killing client with pid: %d\n", sel->pid);
845 kill(-sel->pid, SIGKILL);
848 static void
849 lock(const char *args[]) {
850 size_t len = 0, i = 0;
851 char buf[16], *pass = buf;
852 int c;
854 erase();
855 curs_set(0);
857 if (args && args[0]) {
858 len = strlen(args[0]);
859 pass = (char *)args[0];
860 } else {
861 mvprintw(LINES / 2, COLS / 2 - 7, "Enter password");
862 while (len < sizeof buf && (c = getch()) != '\n')
863 if (c != ERR)
864 buf[len++] = c;
867 mvprintw(LINES / 2, COLS / 2 - 7, "Screen locked!");
869 while (i != len) {
870 for(i = 0; i < len; i++) {
871 if (getch() != pass[i])
872 break;
876 arrange();
879 static void
880 paste(const char *args[]) {
881 if (sel && copybuf)
882 vt_write(sel->term, copybuf, strlen(copybuf));
885 static void
886 quit(const char *args[]) {
887 cleanup();
888 exit(EXIT_SUCCESS);
891 static void
892 redraw(const char *args[]) {
893 for (Client *c = clients; c; c = c->next) {
894 if (!c->minimized) {
895 vt_dirty(c->term);
896 wclear(c->window);
897 wnoutrefresh(c->window);
900 resize_screen();
903 static void
904 scrollback(const char *args[]) {
905 if (!sel)
906 return;
908 if (!args[0] || atoi(args[0]) < 0)
909 vt_scroll(sel->term, -sel->h/2);
910 else
911 vt_scroll(sel->term, sel->h/2);
913 draw(sel);
916 static void
917 send(const char *args[]) {
918 if (sel && args && args[0])
919 vt_write(sel->term, args[0], strlen(args[0]));
922 static void
923 setlayout(const char *args[]) {
924 unsigned int i;
926 if (!args || !args[0]) {
927 if (++layout == &layouts[countof(layouts)])
928 layout = &layouts[0];
929 } else {
930 for (i = 0; i < countof(layouts); i++)
931 if (!strcmp(args[0], layouts[i].symbol))
932 break;
933 if (i == countof(layouts))
934 return;
935 layout = &layouts[i];
937 arrange();
940 static void
941 setmfact(const char *args[]) {
942 float delta;
944 if (isarrange(fullscreen) || isarrange(grid))
945 return;
946 /* arg handling, manipulate mfact */
947 if (args[0] == NULL) {
948 screen.mfact = MFACT;
949 } else if (1 == sscanf(args[0], "%f", &delta)) {
950 if (args[0][0] == '+' || args[0][0] == '-')
951 screen.mfact += delta;
952 else
953 screen.mfact = delta;
954 if (screen.mfact < 0.1)
955 screen.mfact = 0.1;
956 else if (screen.mfact > 0.9)
957 screen.mfact = 0.9;
959 arrange();
962 static void
963 startup(const char *args[]) {
964 for (unsigned int i = 0; i < countof(actions); i++)
965 actions[i].cmd(actions[i].args);
968 static void
969 togglebar(const char *args[]) {
970 if (bar.pos == BAR_OFF)
971 bar.pos = (BAR_POS == BAR_OFF) ? BAR_TOP : BAR_POS;
972 else
973 bar.pos = BAR_OFF;
974 updatebarpos();
975 arrange();
978 static void
979 togglebell(const char *args[]) {
980 vt_togglebell(sel->term);
983 static void
984 toggleminimize(const char *args[]) {
985 Client *c, *m;
986 unsigned int n;
987 if (!sel)
988 return;
989 /* the last window can't be minimized */
990 if (!sel->minimized) {
991 for (n = 0, c = clients; c; c = c->next)
992 if (!c->minimized)
993 n++;
994 if (n == 1)
995 return;
997 sel->minimized = !sel->minimized;
998 m = sel;
999 /* check whether the master client was minimized */
1000 if (sel == clients && sel->minimized) {
1001 c = sel->next;
1002 detach(c);
1003 attach(c);
1004 focus(c);
1005 detach(m);
1006 for (; c && c->next && !c->next->minimized; c = c->next);
1007 attachafter(m, c);
1008 } else if (m->minimized) {
1009 /* non master window got minimized move it above all other
1010 * minimized ones */
1011 focusnextnm(NULL);
1012 detach(m);
1013 for (c = clients; c && c->next && !c->next->minimized; c = c->next);
1014 attachafter(m, c);
1015 } else { /* window is no longer minimized, move it to the master area */
1016 vt_dirty(m->term);
1017 detach(m);
1018 attach(m);
1020 arrange();
1023 static void
1024 togglemouse(const char *args[]) {
1025 mouse_events_enabled = !mouse_events_enabled;
1026 mouse_setup();
1029 static void
1030 togglerunall(const char *args[]) {
1031 runinall = !runinall;
1032 draw_all();
1035 static void
1036 zoom(const char *args[]) {
1037 Client *c;
1039 if (!sel)
1040 return;
1041 if (args && args[0])
1042 focusn(args);
1043 if ((c = sel) == clients)
1044 if (!(c = c->next))
1045 return;
1046 detach(c);
1047 attach(c);
1048 focus(c);
1049 if (c->minimized)
1050 toggleminimize(NULL);
1051 arrange();
1054 /* commands for use by mouse bindings */
1055 static void
1056 mouse_focus(const char *args[]) {
1057 focus(msel);
1058 if (msel->minimized)
1059 toggleminimize(NULL);
1062 static void
1063 mouse_fullscreen(const char *args[]) {
1064 mouse_focus(NULL);
1065 if (isarrange(fullscreen))
1066 setlayout(NULL);
1067 else
1068 setlayout(args);
1071 static void
1072 mouse_minimize(const char *args[]) {
1073 focus(msel);
1074 toggleminimize(NULL);
1077 static void
1078 mouse_zoom(const char *args[]) {
1079 focus(msel);
1080 zoom(NULL);
1083 static Cmd *
1084 get_cmd_by_name(const char *name) {
1085 for (unsigned int i = 0; i < countof(commands); i++) {
1086 if (!strcmp(name, commands[i].name))
1087 return &commands[i];
1089 return NULL;
1092 static void
1093 handle_cmdfifo() {
1094 int r;
1095 char *p, *s, cmdbuf[512], c;
1096 Cmd *cmd;
1097 switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) {
1098 case -1:
1099 case 0:
1100 cmdfifo.fd = -1;
1101 break;
1102 default:
1103 cmdbuf[r] = '\0';
1104 p = cmdbuf;
1105 while (*p) {
1106 /* find the command name */
1107 for (; *p == ' ' || *p == '\n'; p++);
1108 for (s = p; *p && *p != ' ' && *p != '\n'; p++);
1109 if ((c = *p))
1110 *p++ = '\0';
1111 if (*s && (cmd = get_cmd_by_name(s)) != NULL) {
1112 bool quote = false;
1113 int argc = 0;
1114 /* XXX: initializer assumes MAX_ARGS == 2 use a initialization loop? */
1115 const char *args[MAX_ARGS] = { NULL, NULL, NULL}, *arg;
1116 /* if arguments were specified in config.h ignore the one given via
1117 * the named pipe and thus skip everything until we find a new line
1119 if (cmd->action.args[0] || c == '\n') {
1120 debug("execute %s", s);
1121 cmd->action.cmd(cmd->action.args);
1122 while (*p && *p != '\n')
1123 p++;
1124 continue;
1126 /* no arguments were given in config.h so we parse the command line */
1127 while (*p == ' ')
1128 p++;
1129 arg = p;
1130 for (; (c = *p); p++) {
1131 switch (*p) {
1132 case '\\':
1133 /* remove the escape character '\\' move every
1134 * following character to the left by one position
1136 switch (p[1]) {
1137 case '\\':
1138 case '\'':
1139 case '\"': {
1140 char *t = p+1;
1141 do {
1142 t[-1] = *t;
1143 } while (*t++);
1146 break;
1147 case '\'':
1148 case '\"':
1149 quote = !quote;
1150 break;
1151 case ' ':
1152 if (!quote) {
1153 case '\n':
1154 /* remove trailing quote if there is one */
1155 if (*(p - 1) == '\'' || *(p - 1) == '\"')
1156 *(p - 1) = '\0';
1157 *p++ = '\0';
1158 /* remove leading quote if there is one */
1159 if (*arg == '\'' || *arg == '\"')
1160 arg++;
1161 if (argc < MAX_ARGS)
1162 args[argc++] = arg;
1164 while (*p == ' ')
1165 ++p;
1166 arg = p--;
1168 break;
1171 if (c == '\n' || *p == '\n') {
1172 if (!*p)
1173 p++;
1174 debug("execute %s", s);
1175 for(int i = 0; i < argc; i++)
1176 debug(" %s", args[i]);
1177 debug("\n");
1178 cmd->action.cmd(args);
1179 break;
1187 static void
1188 handle_mouse() {
1189 #ifdef CONFIG_MOUSE
1190 MEVENT event;
1191 unsigned int i;
1192 if (getmouse(&event) != OK)
1193 return;
1194 msel = get_client_by_coord(event.x, event.y);
1196 if (!msel)
1197 return;
1199 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);
1201 vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
1203 for (i = 0; i < countof(buttons); i++) {
1204 if (event.bstate & buttons[i].mask)
1205 buttons[i].action.cmd(buttons[i].action.args);
1208 msel = NULL;
1209 #endif /* CONFIG_MOUSE */
1212 static void
1213 handle_statusbar() {
1214 char *p;
1215 int r;
1216 switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
1217 case -1:
1218 strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
1219 bar.text[sizeof bar.text - 1] = '\0';
1220 bar.fd = -1;
1221 break;
1222 case 0:
1223 bar.fd = -1;
1224 break;
1225 default:
1226 bar.text[r] = '\0';
1227 p = bar.text + r - 1;
1228 for (; p >= bar.text && *p == '\n'; *p-- = '\0');
1229 for (; p >= bar.text && *p != '\n'; --p);
1230 if (p >= bar.text)
1231 memmove(bar.text, p + 1, strlen(p));
1232 drawbar();
1236 static int
1237 open_or_create_fifo(const char *name, const char **name_created) {
1238 struct stat info;
1239 int fd;
1241 do {
1242 if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
1243 if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
1244 *name_created = name;
1245 continue;
1247 error("%s\n", strerror(errno));
1249 } while (fd == -1);
1251 if (fstat(fd, &info) == -1)
1252 error("%s\n", strerror(errno));
1253 if (!S_ISFIFO(info.st_mode))
1254 error("%s is not a named pipe\n", name);
1255 return fd;
1258 static void
1259 usage() {
1260 cleanup();
1261 eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
1262 "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
1263 exit(EXIT_FAILURE);
1266 static bool
1267 parse_args(int argc, char *argv[]) {
1268 bool init = false;
1270 if (!getenv("ESCDELAY"))
1271 set_escdelay(100);
1272 for (int arg = 1; arg < argc; arg++) {
1273 if (argv[arg][0] != '-') {
1274 const char *args[] = { argv[arg], NULL, NULL };
1275 if (!init) {
1276 setup();
1277 init = true;
1279 create(args);
1280 continue;
1282 if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc)
1283 usage();
1284 switch (argv[arg][1]) {
1285 case 'v':
1286 puts("dvtm-"VERSION" © 2007-2013 Marc André Tanner");
1287 exit(EXIT_SUCCESS);
1288 case 'M':
1289 mouse_events_enabled = !mouse_events_enabled;
1290 break;
1291 case 'm': {
1292 char *mod = argv[++arg];
1293 if (mod[0] == '^' && mod[1])
1294 *mod = CTRL(mod[1]);
1295 for (unsigned int i = 0; i < countof(keys); i++)
1296 keys[i].mod = *mod;
1297 break;
1299 case 'd':
1300 set_escdelay(atoi(argv[++arg]));
1301 if (ESCDELAY < 50)
1302 set_escdelay(50);
1303 else if (ESCDELAY > 1000)
1304 set_escdelay(1000);
1305 break;
1306 case 'h':
1307 screen.history = atoi(argv[++arg]);
1308 break;
1309 case 't':
1310 title = argv[++arg];
1311 break;
1312 case 's':
1313 bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
1314 updatebarpos();
1315 break;
1316 case 'c': {
1317 const char *fifo;
1318 cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
1319 if (!(fifo = realpath(argv[arg], NULL)))
1320 error("%s\n", strerror(errno));
1321 setenv("DVTM_CMD_FIFO", fifo, 1);
1322 break;
1324 default:
1325 usage();
1328 return init;
1332 main(int argc, char *argv[]) {
1333 int mod = ERR;
1335 if (!parse_args(argc, argv)) {
1336 setup();
1337 startup(NULL);
1340 while (running) {
1341 int r, nfds = 0;
1342 fd_set rd;
1344 if (screen.need_resize) {
1345 resize_screen();
1346 screen.need_resize = false;
1349 FD_ZERO(&rd);
1350 FD_SET(STDIN_FILENO, &rd);
1352 if (cmdfifo.fd != -1) {
1353 FD_SET(cmdfifo.fd, &rd);
1354 nfds = cmdfifo.fd;
1357 if (bar.fd != -1) {
1358 FD_SET(bar.fd, &rd);
1359 nfds = max(nfds, bar.fd);
1362 for (Client *c = clients; c; ) {
1363 if (c->died) {
1364 Client *t = c->next;
1365 destroy(c);
1366 c = t;
1367 continue;
1369 FD_SET(c->pty, &rd);
1370 nfds = max(nfds, c->pty);
1371 c = c->next;
1374 doupdate();
1375 r = select(nfds + 1, &rd, NULL, NULL, NULL);
1377 if (r == -1 && errno == EINTR)
1378 continue;
1380 if (r < 0) {
1381 perror("select()");
1382 exit(EXIT_FAILURE);
1385 if (FD_ISSET(STDIN_FILENO, &rd)) {
1386 int code = getch();
1387 Key *key;
1388 if (code >= 0) {
1389 if (mod >= 0) {
1390 if ((key = keybinding(mod, code)))
1391 key->action.cmd(key->action.args);
1392 mod = ERR;
1393 } else if (code == KEY_MOUSE) {
1394 handle_mouse();
1395 } else if (is_modifier(code)) {
1396 mod = code;
1397 } else if ((key = keybinding(ERR, code))) {
1398 key->action.cmd(key->action.args);
1399 } else if (sel && vt_copymode(sel->term)) {
1400 vt_copymode_keypress(sel->term, code);
1401 draw(sel);
1402 } else {
1403 keypress(code);
1406 if (r == 1) /* no data available on pty's */
1407 continue;
1410 if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
1411 handle_cmdfifo();
1413 if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
1414 handle_statusbar();
1416 for (Client *c = clients; c; ) {
1417 if (FD_ISSET(c->pty, &rd) && !vt_copymode(c->term)) {
1418 if (vt_process(c->term) < 0 && errno == EIO) {
1419 /* client probably terminated */
1420 Client *t = c->next;
1421 destroy(c);
1422 c = t;
1423 continue;
1425 if (c != sel && is_content_visible(c)) {
1426 draw_content(c);
1427 wnoutrefresh(c->window);
1430 c = c->next;
1433 if (is_content_visible(sel)) {
1434 draw_content(sel);
1435 curs_set(vt_cursor(sel->term));
1436 wnoutrefresh(sel->window);
1440 cleanup();
1441 return 0;