Change stupid array initialization
[dvtm.git] / dvtm.c
blob9b554724ad66822092e20a21dd9f15c4cd6f721e
1 /*
2 * The initial "port" of dwm to curses was done by
4 * © 2007-2014 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 #include <sys/select.h>
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 #include <pwd.h>
31 #if defined __CYGWIN__ || defined __sun
32 # include <termios.h>
33 #endif
34 #include "vt.h"
36 #ifdef PDCURSES
37 int ESCDELAY;
38 #endif
40 #ifndef NCURSES_REENTRANT
41 # define set_escdelay(d) (ESCDELAY = (d))
42 #endif
44 typedef struct {
45 float mfact;
46 int history;
47 int w;
48 int h;
49 bool need_resize;
50 } Screen;
52 typedef struct {
53 const char *symbol;
54 void (*arrange)(void);
55 } Layout;
57 typedef struct Client Client;
58 struct Client {
59 WINDOW *window;
60 Vt *term;
61 Vt *editor, *app;
62 int editor_fds[2];
63 volatile sig_atomic_t editor_died;
64 const char *cmd;
65 char title[255];
66 int order;
67 pid_t pid;
68 unsigned short int id;
69 unsigned short int x;
70 unsigned short int y;
71 unsigned short int w;
72 unsigned short int h;
73 bool has_title_line;
74 bool minimized;
75 volatile sig_atomic_t died;
76 Client *next;
77 Client *prev;
80 typedef struct {
81 short fg;
82 short bg;
83 short fg256;
84 short bg256;
85 short pair;
86 } Color;
88 typedef struct {
89 const char *title;
90 attr_t attrs;
91 Color *color;
92 } ColorRule;
94 #define ALT(k) ((k) + (161 - 'a'))
95 #if defined CTRL && defined _AIX
96 #undef CTRL
97 #endif
98 #ifndef CTRL
99 #define CTRL(k) ((k) & 0x1F)
100 #endif
101 #define CTRL_ALT(k) ((k) + (129 - 'a'))
103 #define MAX_ARGS 3
105 typedef struct {
106 void (*cmd)(const char *args[]);
107 /* needed to avoid an error about initialization
108 * of nested flexible array members */
109 const char *args[MAX_ARGS];
110 } Action;
112 #define MAX_KEYS 3
114 typedef unsigned int KeyCombo[MAX_KEYS];
116 typedef struct {
117 KeyCombo keys;
118 Action action;
119 } KeyBinding;
121 typedef struct {
122 mmask_t mask;
123 Action action;
124 } Button;
126 typedef struct {
127 const char *name;
128 Action action;
129 } Cmd;
131 enum { BAR_TOP, BAR_BOTTOM, BAR_OFF };
132 enum { ALIGN_LEFT, ALIGN_RIGHT };
134 typedef struct {
135 int fd;
136 int pos;
137 unsigned short int h;
138 unsigned short int y;
139 char text[512];
140 const char *file;
141 } StatusBar;
143 typedef struct {
144 int fd;
145 const char *file;
146 unsigned short int id;
147 } CmdFifo;
149 typedef struct {
150 char *data;
151 size_t len;
152 size_t size;
153 } Register;
155 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
156 #define sstrlen(str) (sizeof(str) - 1)
157 #define max(x, y) ((x) > (y) ? (x) : (y))
159 #ifdef NDEBUG
160 #define debug(format, args...)
161 #else
162 #define debug eprint
163 #endif
165 /* commands for use by keybindings */
166 static void create(const char *args[]);
167 static void copymode(const char *args[]);
168 static void focusn(const char *args[]);
169 static void focusnext(const char *args[]);
170 static void focusnextnm(const char *args[]);
171 static void focusprev(const char *args[]);
172 static void focusprevnm(const char *args[]);
173 static void focuslast(const char *args[]);
174 static void killclient(const char *args[]);
175 static void paste(const char *args[]);
176 static void quit(const char *args[]);
177 static void redraw(const char *args[]);
178 static void scrollback(const char *args[]);
179 static void send(const char *args[]);
180 static void setlayout(const char *args[]);
181 static void setmfact(const char *args[]);
182 static void startup(const char *args[]);
183 static void togglebar(const char *args[]);
184 static void togglebell(const char *key[]);
185 static void toggleminimize(const char *args[]);
186 static void togglemouse(const char *args[]);
187 static void togglerunall(const char *args[]);
188 static void zoom(const char *args[]);
190 /* commands for use by mouse bindings */
191 static void mouse_focus(const char *args[]);
192 static void mouse_fullscreen(const char *args[]);
193 static void mouse_minimize(const char *args[]);
194 static void mouse_zoom(const char *args[]);
196 /* functions and variables available to layouts via config.h */
197 static void resize(Client *c, int x, int y, int w, int h);
198 extern Screen screen;
199 static unsigned int waw, wah, wax, way;
200 static Client *clients = NULL;
201 static char *title;
202 #define COLOR(c) COLOR_PAIR(colors[c].pair)
203 #define NOMOD ERR
205 #include "config.h"
207 /* global variables */
208 static const char *dvtm_name = "dvtm";
209 Screen screen = { MFACT, SCROLL_HISTORY };
210 static Client *sel = NULL;
211 static Client *lastsel = NULL;
212 static Client *msel = NULL;
213 static bool mouse_events_enabled = ENABLE_MOUSE;
214 static Layout *layout = layouts;
215 static StatusBar bar = { -1, BAR_POS, 1 };
216 static CmdFifo cmdfifo = { -1 };
217 static const char *shell;
218 static Register copyreg;
219 static volatile sig_atomic_t running = true;
220 static bool runinall = false;
222 static void
223 eprint(const char *errstr, ...) {
224 va_list ap;
225 va_start(ap, errstr);
226 vfprintf(stderr, errstr, ap);
227 va_end(ap);
230 static void
231 error(const char *errstr, ...) {
232 va_list ap;
233 va_start(ap, errstr);
234 vfprintf(stderr, errstr, ap);
235 va_end(ap);
236 exit(EXIT_FAILURE);
239 static bool
240 isarrange(void (*func)()) {
241 return func == layout->arrange;
244 static bool
245 is_content_visible(Client *c) {
246 if (!c)
247 return false;
248 if (isarrange(fullscreen))
249 return sel == c;
250 return !c->minimized;
253 static void
254 drawbar(void) {
255 wchar_t wbuf[sizeof bar.text];
256 int x, y, w, maxwidth = screen.w - 2;
257 if (bar.pos == BAR_OFF)
258 return;
259 getyx(stdscr, y, x);
260 attrset(BAR_ATTR);
261 mvaddch(bar.y, 0, '[');
262 if (mbstowcs(wbuf, bar.text, sizeof bar.text) == (size_t)-1)
263 return;
264 if ((w = wcswidth(wbuf, maxwidth)) == -1)
265 return;
266 if (BAR_ALIGN == ALIGN_RIGHT) {
267 for (int i = 0; i + w < maxwidth; i++)
268 addch(' ');
270 addstr(bar.text);
271 if (BAR_ALIGN == ALIGN_LEFT) {
272 for (; w < maxwidth; w++)
273 addch(' ');
275 mvaddch(bar.y, screen.w - 1, ']');
276 attrset(NORMAL_ATTR);
277 move(y, x);
278 wnoutrefresh(stdscr);
281 static int
282 show_border(void) {
283 return (bar.fd != -1 && bar.pos != BAR_OFF) || (clients && clients->next);
286 static void
287 draw_border(Client *c) {
288 if (!show_border())
289 return;
290 char t = '\0';
291 int x, y, maxlen;
293 wattrset(c->window, (sel == c || (runinall && !c->minimized)) ? SELECTED_ATTR : NORMAL_ATTR);
294 getyx(c->window, y, x);
295 mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
296 maxlen = c->w - (2 + sstrlen(TITLE) - sstrlen("%s%sd") + sstrlen(SEPARATOR) + 2);
297 if (maxlen < 0)
298 maxlen = 0;
299 if ((size_t)maxlen < sizeof(c->title)) {
300 t = c->title[maxlen];
301 c->title[maxlen] = '\0';
304 mvwprintw(c->window, 0, 2, TITLE,
305 *c->title ? c->title : "",
306 *c->title ? SEPARATOR : "",
307 c->order);
308 if (t)
309 c->title[maxlen] = t;
310 wmove(c->window, y, x);
313 static void
314 draw_content(Client *c) {
315 vt_draw(c->term, c->window, c->has_title_line, 0);
318 static void
319 draw(Client *c) {
320 if (is_content_visible(c)) {
321 redrawwin(c->window);
322 draw_content(c);
324 if (!isarrange(fullscreen) || sel == c)
325 draw_border(c);
326 wnoutrefresh(c->window);
329 static void
330 draw_all(void) {
331 if (!isarrange(fullscreen)) {
332 for (Client *c = clients; c; c = c->next) {
333 if (c == sel)
334 continue;
335 draw(c);
338 /* as a last step the selected window is redrawn,
339 * this has the effect that the cursor position is
340 * accurate
342 if (sel)
343 draw(sel);
346 static void
347 arrange(void) {
348 int m = 0;
349 for (Client *c = clients; c; c = c->next)
350 if (c->minimized)
351 m++;
352 erase();
353 drawbar();
354 attrset(NORMAL_ATTR);
355 if (m && !isarrange(fullscreen))
356 wah--;
357 layout->arrange();
358 if (m && !isarrange(fullscreen)) {
359 int nw = waw / m, nx = wax;
360 for (Client *c = clients; c; c = c->next) {
361 if (c->minimized) {
362 resize(c, nx, way+wah, nw, 1);
363 nx += nw;
366 wah++;
368 wnoutrefresh(stdscr);
369 draw_all();
372 static void
373 attach(Client *c) {
374 if (clients)
375 clients->prev = c;
376 c->next = clients;
377 c->prev = NULL;
378 clients = c;
379 for (int o = 1; c; c = c->next, o++)
380 c->order = o;
383 static void
384 attachafter(Client *c, Client *a) { /* attach c after a */
385 if (c == a)
386 return;
387 if (!a)
388 for (a = clients; a && a->next; a = a->next);
390 if (a) {
391 if (a->next)
392 a->next->prev = c;
393 c->next = a->next;
394 c->prev = a;
395 a->next = c;
396 for (int o = a->order; c; c = c->next)
397 c->order = ++o;
401 static void
402 detach(Client *c) {
403 Client *d;
404 if (c->prev)
405 c->prev->next = c->next;
406 if (c->next) {
407 c->next->prev = c->prev;
408 for (d = c->next; d; d = d->next)
409 --d->order;
411 if (c == clients)
412 clients = c->next;
413 c->next = c->prev = NULL;
416 static void
417 settitle(Client *c) {
418 char *term, *t = title;
419 if (!t && sel == c && *c->title)
420 t = c->title;
421 if (t && (term = getenv("TERM")) && !strstr(term, "linux"))
422 printf("\033]0;%s\007", t);
425 static void
426 focus(Client *c) {
427 Client *tmp = sel;
428 if (sel == c)
429 return;
430 lastsel = sel;
431 sel = c;
432 settitle(c);
433 if (tmp && !isarrange(fullscreen)) {
434 draw_border(tmp);
435 wnoutrefresh(tmp->window);
437 if (isarrange(fullscreen)) {
438 draw(c);
439 } else {
440 draw_border(c);
441 wnoutrefresh(c->window);
443 curs_set(!c->minimized && vt_cursor(c->term));
446 static void
447 applycolorrules(Client *c) {
448 const ColorRule *r = colorrules;
449 short fg = r->color->fg, bg = r->color->bg;
450 attr_t attrs = r->attrs;
452 for (unsigned int i = 1; i < countof(colorrules); i++) {
453 r = &colorrules[i];
454 if (strstr(c->title, r->title)) {
455 attrs = r->attrs;
456 fg = r->color->fg;
457 bg = r->color->bg;
458 break;
462 vt_set_default_colors(c->term, attrs, fg, bg);
465 static void
466 term_event_handler(Vt *term, int event, void *event_data) {
467 Client *c = (Client *)vt_get_data(term);
468 switch (event) {
469 case VT_EVENT_TITLE:
470 if (event_data)
471 strncpy(c->title, event_data, sizeof(c->title) - 1);
472 c->title[event_data ? sizeof(c->title) - 1 : 0] = '\0';
473 settitle(c);
474 if (!isarrange(fullscreen) || sel == c)
475 draw_border(c);
476 applycolorrules(c);
477 break;
481 static void
482 move_client(Client *c, int x, int y) {
483 if (c->x == x && c->y == y)
484 return;
485 debug("moving, x: %d y: %d\n", x, y);
486 if (mvwin(c->window, y, x) == ERR) {
487 eprint("error moving, x: %d y: %d\n", x, y);
488 } else {
489 c->x = x;
490 c->y = y;
494 static void
495 resize_client(Client *c, int w, int h) {
496 bool has_title_line = show_border();
497 bool resize_window = c->w != w || c->h != h;
498 if (resize_window) {
499 debug("resizing, w: %d h: %d\n", w, h);
500 if (wresize(c->window, h, w) == ERR) {
501 eprint("error resizing, w: %d h: %d\n", w, h);
502 } else {
503 c->w = w;
504 c->h = h;
507 if (resize_window || c->has_title_line != has_title_line) {
508 c->has_title_line = has_title_line;
509 vt_resize(c->app, h - has_title_line, w);
510 if (c->editor)
511 vt_resize(c->editor, h - has_title_line, w);
515 static void
516 resize(Client *c, int x, int y, int w, int h) {
517 resize_client(c, w, h);
518 move_client(c, x, y);
521 static Client*
522 get_client_by_coord(unsigned int x, unsigned int y) {
523 if (y < way || y >= wah)
524 return NULL;
525 if (isarrange(fullscreen))
526 return sel;
527 for (Client *c = clients; c; c = c->next) {
528 if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
529 debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
530 return c;
533 return NULL;
536 static void
537 sigchld_handler(int sig) {
538 int errsv = errno;
539 int status;
540 pid_t pid;
542 while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
543 if (pid == -1) {
544 if (errno == ECHILD) {
545 /* no more child processes */
546 break;
548 eprint("waitpid: %s\n", strerror(errno));
549 break;
552 debug("child with pid %d died\n", pid);
554 for (Client *c = clients; c; c = c->next) {
555 if (c->pid == pid) {
556 c->died = true;
557 break;
559 if (c->editor && vt_pid_get(c->editor) == pid) {
560 c->editor_died = true;
561 break;
566 errno = errsv;
569 static void
570 sigwinch_handler(int sig) {
571 screen.need_resize = true;
574 static void
575 sigterm_handler(int sig) {
576 running = false;
579 static void
580 updatebarpos(void) {
581 bar.y = 0;
582 wax = 0;
583 way = 0;
584 wah = screen.h;
585 waw = screen.w;
586 if (bar.fd == -1)
587 return;
588 if (bar.pos == BAR_TOP) {
589 wah -= bar.h;
590 way += bar.h;
591 } else if (bar.pos == BAR_BOTTOM) {
592 wah -= bar.h;
593 bar.y = wah;
597 static void
598 resize_screen(void) {
599 struct winsize ws;
601 if (ioctl(0, TIOCGWINSZ, &ws) == -1) {
602 getmaxyx(stdscr, screen.h, screen.w);
603 } else {
604 screen.w = ws.ws_col;
605 screen.h = ws.ws_row;
608 debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
610 resizeterm(screen.h, screen.w);
611 wresize(stdscr, screen.h, screen.w);
612 updatebarpos();
613 clear();
614 arrange();
617 static KeyBinding*
618 keybinding(KeyCombo keys) {
619 unsigned int keycount = 0;
620 while (keycount < MAX_KEYS && keys[keycount])
621 keycount++;
622 for (unsigned int b = 0; b < countof(bindings); b++) {
623 for (unsigned int k = 0; k < keycount; k++) {
624 if (keys[k] != bindings[b].keys[k])
625 break;
626 if (k == keycount - 1)
627 return &bindings[b];
630 return NULL;
633 static void
634 keypress(int code) {
635 unsigned int len = 1;
636 char buf[8] = { '\e' };
638 if (code == '\e') {
639 /* pass characters following escape to the underlying app */
640 nodelay(stdscr, TRUE);
641 for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++)
642 buf[len] = t;
643 nodelay(stdscr, FALSE);
646 for (Client *c = runinall ? clients : sel; c; c = c->next) {
647 if (is_content_visible(c)) {
648 if (code == '\e')
649 vt_write(c->term, buf, len);
650 else
651 vt_keypress(c->term, code);
653 if (!runinall)
654 break;
658 static void
659 mouse_setup(void) {
660 #ifdef CONFIG_MOUSE
661 mmask_t mask = 0;
663 if (mouse_events_enabled) {
664 mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
665 for (unsigned int i = 0; i < countof(buttons); i++)
666 mask |= buttons[i].mask;
668 mousemask(mask, NULL);
669 #endif /* CONFIG_MOUSE */
672 static bool
673 checkshell(const char *shell) {
674 if (shell == NULL || *shell == '\0' || *shell != '/')
675 return false;
676 fprintf(stderr, "%s == %s\n", strrchr(shell, '/')+1, dvtm_name);
677 if (!strcmp(strrchr(shell, '/')+1, dvtm_name))
678 return false;
679 if (access(shell, X_OK))
680 return false;
681 return true;
684 static const char *
685 getshell(void) {
686 const char *shell = getenv("SHELL");
687 struct passwd *pw;
689 if (checkshell(shell))
690 return shell;
691 if ((pw = getpwuid(getuid())) && checkshell(pw->pw_shell))
692 return pw->pw_shell;
693 return "/bin/sh";
696 static void
697 setup(void) {
698 shell = getshell();
699 setlocale(LC_CTYPE, "");
700 initscr();
701 start_color();
702 noecho();
703 keypad(stdscr, TRUE);
704 mouse_setup();
705 raw();
706 vt_init();
707 vt_set_keytable(keytable, countof(keytable));
708 for (unsigned int i = 0; i < countof(colors); i++) {
709 if (COLORS == 256) {
710 if (colors[i].fg256)
711 colors[i].fg = colors[i].fg256;
712 if (colors[i].bg256)
713 colors[i].bg = colors[i].bg256;
715 colors[i].pair = vt_color_reserve(colors[i].fg, colors[i].bg);
717 resize_screen();
718 struct sigaction sa;
719 sa.sa_flags = 0;
720 sigemptyset(&sa.sa_mask);
721 sa.sa_handler = sigwinch_handler;
722 sigaction(SIGWINCH, &sa, NULL);
723 sa.sa_handler = sigchld_handler;
724 sigaction(SIGCHLD, &sa, NULL);
725 sa.sa_handler = sigterm_handler;
726 sigaction(SIGTERM, &sa, NULL);
727 sa.sa_handler = SIG_IGN;
728 sigaction(SIGPIPE, &sa, NULL);
731 static void
732 destroy(Client *c) {
733 if (sel == c)
734 focusnextnm(NULL);
735 detach(c);
736 if (sel == c) {
737 if (clients) {
738 focus(clients);
739 toggleminimize(NULL);
740 } else {
741 sel = NULL;
744 if (lastsel == c)
745 lastsel = NULL;
746 werase(c->window);
747 wnoutrefresh(c->window);
748 vt_destroy(c->term);
749 delwin(c->window);
750 if (!clients && countof(actions)) {
751 if (!strcmp(c->cmd, shell))
752 quit(NULL);
753 else
754 create(NULL);
756 free(c);
757 arrange();
760 static void
761 cleanup(void) {
762 while (clients)
763 destroy(clients);
764 vt_shutdown();
765 endwin();
766 free(copyreg.data);
767 if (bar.fd > 0)
768 close(bar.fd);
769 if (bar.file)
770 unlink(bar.file);
771 if (cmdfifo.fd > 0)
772 close(cmdfifo.fd);
773 if (cmdfifo.file)
774 unlink(cmdfifo.file);
777 static char *getcwd_by_pid(Client *c) {
778 if (!c)
779 return NULL;
780 char buf[32];
781 snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid);
782 return realpath(buf, NULL);
785 /* commands for use by keybindings */
786 static void
787 create(const char *args[]) {
788 Client *c = calloc(1, sizeof(Client));
789 if (!c)
790 return;
791 const char *cmd = (args && args[0]) ? args[0] : shell;
792 const char *pargs[] = { "/bin/sh", "-c", cmd, NULL };
793 c->id = ++cmdfifo.id;
794 char buf[8], *cwd = NULL;
795 snprintf(buf, sizeof buf, "%d", c->id);
796 const char *env[] = {
797 "DVTM", VERSION,
798 "DVTM_WINDOW_ID", buf,
799 NULL
802 if (!(c->window = newwin(wah, waw, way, wax))) {
803 free(c);
804 return;
807 c->has_title_line = show_border();
808 c->term = c->app = vt_create(screen.h - c->has_title_line, screen.w, screen.history);
809 if (!c->term) {
810 delwin(c->window);
811 free(c);
812 return;
815 c->cmd = cmd;
816 if (args && args[1]) {
817 strncpy(c->title, args[1], sizeof(c->title) - 1);
818 c->title[sizeof(c->title) - 1] = '\0';
820 if (args && args[2])
821 cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2];
822 c->pid = vt_forkpty(c->term, "/bin/sh", pargs, cwd, env, NULL, NULL);
823 if (args && args[2] && !strcmp(args[2], "$CWD"))
824 free(cwd);
825 vt_set_data(c->term, c);
826 vt_set_event_handler(c->term, term_event_handler);
827 c->w = screen.w;
828 c->h = screen.h;
829 c->x = wax;
830 c->y = way;
831 c->order = 0;
832 c->minimized = false;
833 debug("client with pid %d forked\n", c->pid);
834 attach(c);
835 focus(c);
836 arrange();
839 static void
840 copymode(const char *args[]) {
841 if (!sel || sel->editor)
842 return;
843 if (!(sel->editor = vt_create(sel->h, sel->w, 0)))
844 return;
846 char *ed = getenv("DVTM_EDITOR");
847 const char **argv = NULL;
848 if (!ed && !(ed = getenv("EDITOR"))) {
849 ed = editor;
850 argv = editor_args;
852 if (!argv)
853 argv = (const char*[]){ ed, "-", NULL };
855 const char *cwd = NULL;
856 const char *env[] = { "DVTM", VERSION, NULL };
857 int *to = &sel->editor_fds[0], *from = &sel->editor_fds[1];
859 if (vt_forkpty(sel->editor, ed, argv, cwd, env, to, from) < 0) {
860 vt_destroy(sel->editor);
861 sel->editor = NULL;
862 return;
865 sel->term = sel->editor;
867 if (sel->editor_fds[0] != -1) {
868 char *buf = NULL;
869 size_t len = vt_content_get(sel->app, &buf);
870 char *cur = buf;
871 while (len > 0) {
872 ssize_t res = write(sel->editor_fds[0], cur, len);
873 if (res < 0) {
874 if (errno == EAGAIN || errno == EINTR)
875 continue;
876 break;
878 cur += res;
879 len -= res;
881 free(buf);
882 close(sel->editor_fds[0]);
885 if (args[0])
886 vt_write(sel->editor, args[0], strlen(args[0]));
889 static void
890 focusn(const char *args[]) {
891 for (Client *c = clients; c; c = c->next) {
892 if (c->order == atoi(args[0])) {
893 focus(c);
894 if (c->minimized)
895 toggleminimize(NULL);
896 return;
901 static void
902 focusnext(const char *args[]) {
903 if (!sel)
904 return;
905 Client *c = sel->next;
906 if (!c)
907 c = clients;
908 if (c)
909 focus(c);
912 static void
913 focusnextnm(const char *args[]) {
914 if (!sel)
915 return;
916 Client *c = sel;
917 do {
918 c = c->next;
919 if (!c)
920 c = clients;
921 } while (c->minimized && c != sel);
922 focus(c);
925 static void
926 focusprev(const char *args[]) {
927 if (!sel)
928 return;
929 Client *c = sel->prev;
930 if (!c)
931 for (c = clients; c && c->next; c = c->next);
932 if (c)
933 focus(c);
936 static void
937 focusprevnm(const char *args[]) {
938 if (!sel)
939 return;
940 Client *c = sel;
941 do {
942 c = c->prev;
943 if (!c)
944 for (c = clients; c && c->next; c = c->next);
945 } while (c->minimized && c != sel);
946 focus(c);
949 static void
950 focuslast(const char *args[]) {
951 if (lastsel)
952 focus(lastsel);
955 static void
956 killclient(const char *args[]) {
957 if (!sel)
958 return;
959 debug("killing client with pid: %d\n", sel->pid);
960 kill(-sel->pid, SIGKILL);
963 static void
964 paste(const char *args[]) {
965 if (sel && copyreg.data)
966 vt_write(sel->term, copyreg.data, copyreg.len);
969 static void
970 quit(const char *args[]) {
971 cleanup();
972 exit(EXIT_SUCCESS);
975 static void
976 redraw(const char *args[]) {
977 for (Client *c = clients; c; c = c->next) {
978 if (!c->minimized) {
979 vt_dirty(c->term);
980 wclear(c->window);
981 wnoutrefresh(c->window);
984 resize_screen();
987 static void
988 scrollback(const char *args[]) {
989 if (!is_content_visible(sel))
990 return;
992 if (!args[0] || atoi(args[0]) < 0)
993 vt_scroll(sel->term, -sel->h/2);
994 else
995 vt_scroll(sel->term, sel->h/2);
997 draw(sel);
998 curs_set(vt_cursor(sel->term));
1001 static void
1002 send(const char *args[]) {
1003 if (sel && args && args[0])
1004 vt_write(sel->term, args[0], strlen(args[0]));
1007 static void
1008 setlayout(const char *args[]) {
1009 unsigned int i;
1011 if (!args || !args[0]) {
1012 if (++layout == &layouts[countof(layouts)])
1013 layout = &layouts[0];
1014 } else {
1015 for (i = 0; i < countof(layouts); i++)
1016 if (!strcmp(args[0], layouts[i].symbol))
1017 break;
1018 if (i == countof(layouts))
1019 return;
1020 layout = &layouts[i];
1022 arrange();
1025 static void
1026 setmfact(const char *args[]) {
1027 float delta;
1029 if (isarrange(fullscreen) || isarrange(grid))
1030 return;
1031 /* arg handling, manipulate mfact */
1032 if (args[0] == NULL) {
1033 screen.mfact = MFACT;
1034 } else if (1 == sscanf(args[0], "%f", &delta)) {
1035 if (args[0][0] == '+' || args[0][0] == '-')
1036 screen.mfact += delta;
1037 else
1038 screen.mfact = delta;
1039 if (screen.mfact < 0.1)
1040 screen.mfact = 0.1;
1041 else if (screen.mfact > 0.9)
1042 screen.mfact = 0.9;
1044 arrange();
1047 static void
1048 startup(const char *args[]) {
1049 for (unsigned int i = 0; i < countof(actions); i++)
1050 actions[i].cmd(actions[i].args);
1053 static void
1054 togglebar(const char *args[]) {
1055 if (bar.pos == BAR_OFF)
1056 bar.pos = (BAR_POS == BAR_OFF) ? BAR_TOP : BAR_POS;
1057 else
1058 bar.pos = BAR_OFF;
1059 updatebarpos();
1060 redraw(NULL);
1063 static void
1064 togglebell(const char *args[]) {
1065 vt_togglebell(sel->term);
1068 static void
1069 toggleminimize(const char *args[]) {
1070 Client *c, *m;
1071 unsigned int n;
1072 if (!sel)
1073 return;
1074 /* the last window can't be minimized */
1075 if (!sel->minimized) {
1076 for (n = 0, c = clients; c; c = c->next)
1077 if (!c->minimized)
1078 n++;
1079 if (n == 1)
1080 return;
1082 sel->minimized = !sel->minimized;
1083 m = sel;
1084 /* check whether the master client was minimized */
1085 if (sel == clients && sel->minimized) {
1086 c = sel->next;
1087 detach(c);
1088 attach(c);
1089 focus(c);
1090 detach(m);
1091 for (; c && c->next && !c->next->minimized; c = c->next);
1092 attachafter(m, c);
1093 } else if (m->minimized) {
1094 /* non master window got minimized move it above all other
1095 * minimized ones */
1096 focusnextnm(NULL);
1097 detach(m);
1098 for (c = clients; c && c->next && !c->next->minimized; c = c->next);
1099 attachafter(m, c);
1100 } else { /* window is no longer minimized, move it to the master area */
1101 vt_dirty(m->term);
1102 detach(m);
1103 attach(m);
1105 arrange();
1108 static void
1109 togglemouse(const char *args[]) {
1110 mouse_events_enabled = !mouse_events_enabled;
1111 mouse_setup();
1114 static void
1115 togglerunall(const char *args[]) {
1116 runinall = !runinall;
1117 draw_all();
1120 static void
1121 zoom(const char *args[]) {
1122 Client *c;
1124 if (!sel)
1125 return;
1126 if (args && args[0])
1127 focusn(args);
1128 if ((c = sel) == clients)
1129 if (!(c = c->next))
1130 return;
1131 detach(c);
1132 attach(c);
1133 focus(c);
1134 if (c->minimized)
1135 toggleminimize(NULL);
1136 arrange();
1139 /* commands for use by mouse bindings */
1140 static void
1141 mouse_focus(const char *args[]) {
1142 focus(msel);
1143 if (msel->minimized)
1144 toggleminimize(NULL);
1147 static void
1148 mouse_fullscreen(const char *args[]) {
1149 mouse_focus(NULL);
1150 if (isarrange(fullscreen))
1151 setlayout(NULL);
1152 else
1153 setlayout(args);
1156 static void
1157 mouse_minimize(const char *args[]) {
1158 focus(msel);
1159 toggleminimize(NULL);
1162 static void
1163 mouse_zoom(const char *args[]) {
1164 focus(msel);
1165 zoom(NULL);
1168 static Cmd *
1169 get_cmd_by_name(const char *name) {
1170 for (unsigned int i = 0; i < countof(commands); i++) {
1171 if (!strcmp(name, commands[i].name))
1172 return &commands[i];
1174 return NULL;
1177 static void
1178 handle_cmdfifo(void) {
1179 int r;
1180 char *p, *s, cmdbuf[512], c;
1181 Cmd *cmd;
1182 switch (r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1)) {
1183 case -1:
1184 case 0:
1185 cmdfifo.fd = -1;
1186 break;
1187 default:
1188 cmdbuf[r] = '\0';
1189 p = cmdbuf;
1190 while (*p) {
1191 /* find the command name */
1192 for (; *p == ' ' || *p == '\n'; p++);
1193 for (s = p; *p && *p != ' ' && *p != '\n'; p++);
1194 if ((c = *p))
1195 *p++ = '\0';
1196 if (*s && (cmd = get_cmd_by_name(s)) != NULL) {
1197 bool quote = false;
1198 int argc = 0;
1199 const char *args[MAX_ARGS], *arg;
1200 memset(args, 0, sizeof(args));
1201 /* if arguments were specified in config.h ignore the one given via
1202 * the named pipe and thus skip everything until we find a new line
1204 if (cmd->action.args[0] || c == '\n') {
1205 debug("execute %s", s);
1206 cmd->action.cmd(cmd->action.args);
1207 while (*p && *p != '\n')
1208 p++;
1209 continue;
1211 /* no arguments were given in config.h so we parse the command line */
1212 while (*p == ' ')
1213 p++;
1214 arg = p;
1215 for (; (c = *p); p++) {
1216 switch (*p) {
1217 case '\\':
1218 /* remove the escape character '\\' move every
1219 * following character to the left by one position
1221 switch (p[1]) {
1222 case '\\':
1223 case '\'':
1224 case '\"': {
1225 char *t = p+1;
1226 do {
1227 t[-1] = *t;
1228 } while (*t++);
1231 break;
1232 case '\'':
1233 case '\"':
1234 quote = !quote;
1235 break;
1236 case ' ':
1237 if (!quote) {
1238 case '\n':
1239 /* remove trailing quote if there is one */
1240 if (*(p - 1) == '\'' || *(p - 1) == '\"')
1241 *(p - 1) = '\0';
1242 *p++ = '\0';
1243 /* remove leading quote if there is one */
1244 if (*arg == '\'' || *arg == '\"')
1245 arg++;
1246 if (argc < MAX_ARGS)
1247 args[argc++] = arg;
1249 while (*p == ' ')
1250 ++p;
1251 arg = p--;
1253 break;
1256 if (c == '\n' || *p == '\n') {
1257 if (!*p)
1258 p++;
1259 debug("execute %s", s);
1260 for(int i = 0; i < argc; i++)
1261 debug(" %s", args[i]);
1262 debug("\n");
1263 cmd->action.cmd(args);
1264 break;
1272 static void
1273 handle_mouse(void) {
1274 #ifdef CONFIG_MOUSE
1275 MEVENT event;
1276 unsigned int i;
1277 if (getmouse(&event) != OK)
1278 return;
1279 msel = get_client_by_coord(event.x, event.y);
1281 if (!msel)
1282 return;
1284 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);
1286 vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
1288 for (i = 0; i < countof(buttons); i++) {
1289 if (event.bstate & buttons[i].mask)
1290 buttons[i].action.cmd(buttons[i].action.args);
1293 msel = NULL;
1294 #endif /* CONFIG_MOUSE */
1297 static void
1298 handle_statusbar(void) {
1299 char *p;
1300 int r;
1301 switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
1302 case -1:
1303 strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
1304 bar.text[sizeof bar.text - 1] = '\0';
1305 bar.fd = -1;
1306 break;
1307 case 0:
1308 bar.fd = -1;
1309 break;
1310 default:
1311 bar.text[r] = '\0';
1312 p = bar.text + r - 1;
1313 for (; p >= bar.text && *p == '\n'; *p-- = '\0');
1314 for (; p >= bar.text && *p != '\n'; --p);
1315 if (p >= bar.text)
1316 memmove(bar.text, p + 1, strlen(p));
1317 drawbar();
1321 static void
1322 handle_editor(Client *c) {
1323 if (!copyreg.data && (copyreg.data = malloc(screen.history)))
1324 copyreg.size = screen.history;
1325 copyreg.len = 0;
1326 while (copyreg.len < copyreg.size) {
1327 ssize_t len = read(c->editor_fds[1], copyreg.data + copyreg.len, copyreg.size - copyreg.len);
1328 if (len == -1 && errno == EINTR)
1329 continue;
1330 if (len == 0)
1331 break;
1332 copyreg.len += len;
1333 if (copyreg.len == copyreg.size) {
1334 copyreg.size *= 2;
1335 if (!(copyreg.data = realloc(copyreg.data, copyreg.size))) {
1336 copyreg.size = 0;
1337 copyreg.len = 0;
1341 c->editor_died = false;
1342 vt_destroy(c->editor);
1343 c->editor = NULL;
1344 c->term = c->app;
1345 vt_dirty(c->term);
1346 draw_content(c);
1347 wnoutrefresh(c->window);
1350 static int
1351 open_or_create_fifo(const char *name, const char **name_created) {
1352 struct stat info;
1353 int fd;
1355 do {
1356 if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
1357 if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
1358 *name_created = name;
1359 continue;
1361 error("%s\n", strerror(errno));
1363 } while (fd == -1);
1365 if (fstat(fd, &info) == -1)
1366 error("%s\n", strerror(errno));
1367 if (!S_ISFIFO(info.st_mode))
1368 error("%s is not a named pipe\n", name);
1369 return fd;
1372 static void
1373 usage(void) {
1374 cleanup();
1375 eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
1376 "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
1377 exit(EXIT_FAILURE);
1380 static bool
1381 parse_args(int argc, char *argv[]) {
1382 bool init = false;
1383 const char *name = argv[0];
1385 if (name && (name = strrchr(name, '/')))
1386 dvtm_name = name + 1;
1387 if (!getenv("ESCDELAY"))
1388 set_escdelay(100);
1389 for (int arg = 1; arg < argc; arg++) {
1390 if (argv[arg][0] != '-') {
1391 const char *args[] = { argv[arg], NULL, NULL };
1392 if (!init) {
1393 setup();
1394 init = true;
1396 create(args);
1397 continue;
1399 if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc)
1400 usage();
1401 switch (argv[arg][1]) {
1402 case 'v':
1403 puts("dvtm-"VERSION" © 2007-2014 Marc André Tanner");
1404 exit(EXIT_SUCCESS);
1405 case 'M':
1406 mouse_events_enabled = !mouse_events_enabled;
1407 break;
1408 case 'm': {
1409 char *mod = argv[++arg];
1410 if (mod[0] == '^' && mod[1])
1411 *mod = CTRL(mod[1]);
1412 for (unsigned int b = 0; b < countof(bindings); b++)
1413 if (bindings[b].keys[0] == MOD)
1414 bindings[b].keys[0] = *mod;
1415 break;
1417 case 'd':
1418 set_escdelay(atoi(argv[++arg]));
1419 if (ESCDELAY < 50)
1420 set_escdelay(50);
1421 else if (ESCDELAY > 1000)
1422 set_escdelay(1000);
1423 break;
1424 case 'h':
1425 screen.history = atoi(argv[++arg]);
1426 break;
1427 case 't':
1428 title = argv[++arg];
1429 break;
1430 case 's':
1431 bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
1432 updatebarpos();
1433 break;
1434 case 'c': {
1435 const char *fifo;
1436 cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
1437 if (!(fifo = realpath(argv[arg], NULL)))
1438 error("%s\n", strerror(errno));
1439 setenv("DVTM_CMD_FIFO", fifo, 1);
1440 break;
1442 default:
1443 usage();
1446 return init;
1450 main(int argc, char *argv[]) {
1451 KeyCombo keys;
1452 unsigned int key_index = 0;
1453 memset(keys, 0, sizeof(keys));
1454 sigset_t emptyset, blockset;
1456 if (!parse_args(argc, argv)) {
1457 setup();
1458 startup(NULL);
1461 sigemptyset(&emptyset);
1462 sigemptyset(&blockset);
1463 sigaddset(&blockset, SIGWINCH);
1464 sigaddset(&blockset, SIGCHLD);
1465 sigprocmask(SIG_BLOCK, &blockset, NULL);
1467 while (running) {
1468 int r, nfds = 0;
1469 fd_set rd;
1471 if (screen.need_resize) {
1472 resize_screen();
1473 screen.need_resize = false;
1476 FD_ZERO(&rd);
1477 FD_SET(STDIN_FILENO, &rd);
1479 if (cmdfifo.fd != -1) {
1480 FD_SET(cmdfifo.fd, &rd);
1481 nfds = cmdfifo.fd;
1484 if (bar.fd != -1) {
1485 FD_SET(bar.fd, &rd);
1486 nfds = max(nfds, bar.fd);
1489 for (Client *c = clients; c; ) {
1490 if (c->editor && c->editor_died)
1491 handle_editor(c);
1492 if (!c->editor && c->died) {
1493 Client *t = c->next;
1494 destroy(c);
1495 c = t;
1496 continue;
1498 int pty = c->editor ? vt_getpty(c->editor) : vt_getpty(c->app);
1499 FD_SET(pty, &rd);
1500 nfds = max(nfds, pty);
1501 c = c->next;
1504 doupdate();
1505 r = pselect(nfds + 1, &rd, NULL, NULL, NULL, &emptyset);
1507 if (r == -1 && errno == EINTR)
1508 continue;
1510 if (r < 0) {
1511 perror("select()");
1512 exit(EXIT_FAILURE);
1515 if (FD_ISSET(STDIN_FILENO, &rd)) {
1516 int code = getch();
1517 if (code >= 0) {
1518 keys[key_index++] = code;
1519 KeyBinding *binding = NULL;
1520 if (code == KEY_MOUSE) {
1521 handle_mouse();
1522 } else if ((binding = keybinding(keys))) {
1523 unsigned int key_length = 0;
1524 while (key_length < MAX_KEYS && binding->keys[key_length])
1525 key_length++;
1526 if (key_index == key_length) {
1527 binding->action.cmd(binding->action.args);
1528 key_index = 0;
1529 memset(keys, 0, sizeof(keys));
1531 } else {
1532 key_index = 0;
1533 memset(keys, 0, sizeof(keys));
1534 keypress(code);
1537 if (r == 1) /* no data available on pty's */
1538 continue;
1541 if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
1542 handle_cmdfifo();
1544 if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
1545 handle_statusbar();
1547 for (Client *c = clients; c; c = c->next) {
1548 bool ed = c->editor && FD_ISSET(vt_getpty(c->editor), &rd);
1549 bool vt = FD_ISSET(vt_getpty(c->app), &rd);
1551 if (ed && vt_process(c->editor) < 0 && errno == EIO) {
1552 c->editor_died = true;
1553 continue;
1554 } else if (vt && vt_process(c->term) < 0 && errno == EIO) {
1555 c->died = true;
1556 continue;
1559 if ((ed || vt) && c != sel && is_content_visible(c)) {
1560 draw_content(c);
1561 wnoutrefresh(c->window);
1565 if (is_content_visible(sel)) {
1566 draw_content(sel);
1567 curs_set(vt_cursor(sel->term));
1568 wnoutrefresh(sel->window);
1572 cleanup();
1573 return 0;