2 * The initial "port" of dwm to curses was done by
3 * (c) 2007-2011 Marc Andre Tanner <mat at brain-dump dot org>
5 * It is highly inspired by the original X11 dwm and
6 * reuses some code of it which is mostly
8 * (c) 2006-2007 Anselm R. Garbe <garbeam at gmail dot com>
10 * See LICENSE for details.
15 #include <sys/ioctl.h>
19 # include <sys/fcntl.h>
23 #include <sys/types.h>
47 void (*arrange
)(void);
50 typedef struct Client Client
;
59 unsigned short int id
;
77 #define ALT(k) ((k) + (161 - 'a'))
79 #define CTRL(k) ((k) & 0x1F)
81 #define CTRL_ALT(k) ((k) + (129 - 'a'))
86 void (*cmd
)(const char *args
[]);
87 /* needed to avoid an error about initialization
88 * of nested flexible array members */
89 const char *args
[MAX_ARGS
+ 1];
108 enum BarPos
{ BarTop
, BarBot
, BarOff
};
109 enum { ALIGN_LEFT
, ALIGN_RIGHT
};
114 unsigned short int h
;
115 unsigned short int y
;
123 unsigned short int id
;
126 #define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
127 #define sstrlen(str) (sizeof(str) - 1)
128 #define max(x, y) ((x) > (y) ? (x) : (y))
131 #define debug(format, args...)
136 /* commands for use by keybindings */
137 static void quit(const char *args
[]);
138 static void create(const char *args
[]);
139 static void startup(const char *args
[]);
140 static void escapekey(const char *args
[]);
141 static void killclient(const char *args
[]);
142 static void focusn(const char *args
[]);
143 static void focusnext(const char *args
[]);
144 static void focusnextnm(const char *args
[]);
145 static void focusprev(const char *args
[]);
146 static void focusprevnm(const char *args
[]);
147 static void togglebell(const char *key
[]);
148 static void toggleminimize(const char *args
[]);
149 static void setmfact(const char *args
[]);
150 static void setlayout(const char *args
[]);
151 static void scrollback(const char *args
[]);
152 static void redraw(const char *args
[]);
153 static void zoom(const char *args
[]);
154 static void lock(const char *key
[]);
155 static void togglerunall(const char *args
[]);
157 static void togglebar(const char *args
[]);
159 static void mouse_focus(const char *args
[]);
160 static void mouse_fullscreen(const char *args
[]);
161 static void mouse_minimize(const char *args
[]);
162 static void mouse_zoom(const char *args
[]);
163 static void mouse_toggle();
165 static void clear_workspace();
166 static void draw(Client
*c
);
167 static void draw_all(bool border
);
168 static void draw_border(Client
*c
);
169 static void resize(Client
*c
, int x
, int y
, int w
, int h
);
170 static void resize_screen();
171 static void eprint(const char *errstr
, ...);
172 static bool isarrange(void (*func
)());
173 static void arrange();
174 static void focus(Client
*c
);
175 static void keypress(int code
);
177 static unsigned int waw
, wah
, wax
, way
;
178 static Client
*clients
= NULL
;
179 extern Screen screen
;
183 Screen screen
= { MFACT
, SCROLL_HISTORY
};
184 static Client
*sel
= NULL
;
185 static Layout
*layout
= layouts
;
186 static StatusBar bar
= { -1, BARPOS
, 1 };
187 static CmdFifo cmdfifo
= { -1 };
188 static const char *shell
;
189 static bool running
= true;
190 static bool runinall
= false;
194 #include "statusbar.c"
197 eprint(const char *errstr
, ...) {
199 va_start(ap
, errstr
);
200 vfprintf(stderr
, errstr
, ap
);
205 error(const char *errstr
, ...) {
207 va_start(ap
, errstr
);
208 vfprintf(stderr
, errstr
, ap
);
221 for (order
= 1; c
; c
= c
->next
, order
++)
226 attachafter(Client
*c
, Client
*a
) { /* attach c after a */
231 for (a
= clients
; a
&& a
->next
; a
= a
->next
);
239 for (o
= a
->order
; c
; c
= c
->next
)
248 c
->prev
->next
= c
->next
;
250 c
->next
->prev
= c
->prev
;
251 for (d
= c
->next
; d
; d
= d
->next
)
256 c
->next
= c
->prev
= NULL
;
262 attrset(NORMAL_ATTR
);
263 color_set(madtty_color_get(NORMAL_FG
, NORMAL_BG
), NULL
);
265 wnoutrefresh(stdscr
);
270 isarrange(void (*func
)()) {
271 return func
== layout
->arrange
;
282 wrefresh(tmp
->window
);
284 if (isarrange(fullscreen
))
285 redrawwin(c
->window
);
291 focusn(const char *args
[]) {
294 for (c
= clients
; c
; c
= c
->next
) {
295 if (c
->order
== atoi(args
[0])) {
298 toggleminimize(NULL
);
305 focusnext(const char *args
[]) {
319 focusnextnm(const char *args
[]) {
329 } while (c
->minimized
&& c
!= sel
);
334 focusprev(const char *args
[]) {
341 for (c
= clients
; c
&& c
->next
; c
= c
->next
);
347 focusprevnm(const char *args
[]) {
356 for (c
= clients
; c
&& c
->next
; c
= c
->next
);
357 } while (c
->minimized
&& c
!= sel
);
362 zoom(const char *args
[]) {
367 if ((c
= sel
) == clients
)
374 toggleminimize(NULL
);
379 togglebell(const char *args
[]) {
380 madtty_togglebell(sel
->term
);
384 toggleminimize(const char *args
[]) {
389 /* the last window can't be minimized */
390 if (!sel
->minimized
) {
391 for (n
= 0, c
= clients
; c
; c
= c
->next
)
397 sel
->minimized
= !sel
->minimized
;
399 /* check whether the master client was minimized */
400 if (sel
== clients
&& sel
->minimized
) {
406 for (; c
&& c
->next
&& !c
->next
->minimized
; c
= c
->next
);
408 } else if (m
->minimized
) {
409 /* non master window got minimized move it above all other
413 for (c
= clients
; c
&& c
->next
&& !c
->next
->minimized
; c
= c
->next
);
415 } else { /* window is no longer minimized, move it to the master area */
416 madtty_dirty(m
->term
);
424 setlayout(const char *args
[]) {
427 if (!args
|| !args
[0]) {
428 if (++layout
== &layouts
[countof(layouts
)])
429 layout
= &layouts
[0];
431 for (i
= 0; i
< countof(layouts
); i
++)
432 if (!strcmp(args
[0], layouts
[i
].symbol
))
434 if (i
== countof(layouts
))
436 layout
= &layouts
[i
];
442 setmfact(const char *args
[]) {
445 if (isarrange(fullscreen
) || isarrange(grid
))
447 /* arg handling, manipulate mfact */
449 screen
.mfact
= MFACT
;
450 else if (1 == sscanf(args
[0], "%lf", &delta
)) {
451 if (args
[0][0] == '+' || args
[0][0] == '-')
452 screen
.mfact
+= delta
;
454 screen
.mfact
= delta
;
455 if (screen
.mfact
< 0.1)
457 else if (screen
.mfact
> 0.9)
464 scrollback(const char *args
[]) {
467 if (!args
[0] || atoi(args
[0]) < 0)
468 madtty_scroll(sel
->term
, -sel
->h
/2);
470 madtty_scroll(sel
->term
, sel
->h
/2);
476 redraw(const char *args
[]) {
483 draw_border(Client
*c
) {
487 wattrset(c
->window
, SELECTED_ATTR
);
488 wcolor_set(c
->window
, madtty_color_get(SELECTED_FG
, SELECTED_BG
), NULL
);
490 wattrset(c
->window
, NORMAL_ATTR
);
491 wcolor_set(c
->window
, madtty_color_get(NORMAL_FG
, NORMAL_BG
), NULL
);
493 getyx(c
->window
, y
, x
);
495 mvwhline(c
->window
, 0, 0, ACS_HLINE
, c
->w
);
496 o
= c
->w
- (4 + sstrlen(TITLE
) - 5 + sstrlen(SEPARATOR
));
499 if ((size_t)o
< sizeof(c
->title
)) {
500 t
= *(s
= &c
->title
[o
]);
503 mvwprintw(c
->window
, 0, 2, TITLE
,
504 *c
->title
? c
->title
: "",
505 *c
->title
? SEPARATOR
: "",
509 wmove(c
->window
, y
, x
);
511 curs_set(madtty_cursor(c
->term
));
515 draw_content(Client
*c
) {
516 if (!c
->minimized
|| isarrange(fullscreen
)) {
517 madtty_draw(c
->term
, c
->window
, 1, 0);
532 for (unsigned int y
= 0; y
< wah
; y
++)
533 mvhline(way
+ y
, 0, ' ', waw
);
534 wnoutrefresh(stdscr
);
538 draw_all(bool border
) {
541 for (c
= clients
; c
; c
= c
->next
) {
542 redrawwin(c
->window
);
548 wnoutrefresh(c
->window
);
550 /* as a last step the selected window is redrawn,
551 * this has the effect that the cursor position is
559 wrefresh(sel
->window
);
564 escapekey(const char *args
[]) {
566 if ((key
= getch()) >= 0) {
567 debug("escaping key `%c'\n", key
);
573 * Lock the screen until the correct password is entered.
574 * The password can either be specified in config.h which is
575 * not recommended because `strings dvtm` will contain it. If
576 * no password is specified in the configuration file it is read
577 * from the keyboard before the screen is locked.
579 * NOTE: this function doesn't handle the input from clients. All
580 * foreground operations are temporarily suspended since the
581 * function doesn't return.
584 lock(const char *args
[]) {
585 size_t len
= 0, i
= 0;
586 char buf
[16], *pass
= buf
;
592 if (args
&& args
[0]) {
593 len
= strlen(args
[0]);
594 pass
= (char *)args
[0];
596 mvprintw(LINES
/ 2, COLS
/ 2 - 7, "Enter password");
597 while (len
< sizeof buf
&& (c
= getch()) != '\n')
602 mvprintw(LINES
/ 2, COLS
/ 2 - 7, "Screen locked!");
605 for(i
= 0; i
< len
; i
++) {
606 if (getch() != pass
[i
])
615 togglerunall(const char *args
[]) {
616 runinall
= !runinall
;
620 killclient(const char *args
[]) {
623 debug("killing client with pid: %d\n", sel
->pid
);
624 kill(-sel
->pid
, SIGKILL
);
628 applycolorrules(madtty_t
*term
, char *title
) {
630 unsigned attrs
= A_NORMAL
;
631 short fg
= -1, bg
= -1;
634 for (i
= 0; i
< countof(colorrules
); i
++) {
636 if (strstr(title
, r
->title
)) {
643 madtty_set_default_colors(term
, attrs
, fg
, bg
);
647 title_escape_seq_handler(madtty_t
*term
, char *es
) {
650 if (es
[0] != ']' || (es
[1] && (es
[1] < '0' || es
[1] > '9')) || (es
[2] && es
[2] != ';'))
651 return MADTTY_HANDLER_NOWAY
;
652 if ((l
= strlen(es
)) < 3 || es
[l
- 1] != '\07')
653 return MADTTY_HANDLER_NOTYET
;
655 c
= (Client
*)madtty_get_data(term
);
656 strncpy(c
->title
, es
+ 3, sizeof(c
->title
));
658 debug("window title: %s\n", c
->title
);
659 applycolorrules(term
, c
->title
);
660 return MADTTY_HANDLER_OK
;
664 create(const char *args
[]) {
665 Client
*c
= calloc(sizeof(Client
), 1);
668 const char *cmd
= (args
&& args
[0]) ? args
[0] : shell
;
669 const char *pargs
[] = { "/bin/sh", "-c", cmd
, NULL
};
670 c
->id
= ++cmdfifo
.id
;
672 snprintf(buf
, sizeof buf
, "%d", c
->id
);
673 const char *env
[] = {
675 "DVTM_WINDOW_ID", buf
,
679 c
->window
= newwin(wah
, waw
, way
, wax
);
680 c
->term
= madtty_create(screen
.h
- 1, screen
.w
, screen
.history
);
683 strncpy(c
->title
, args
[1], sizeof(c
->title
));
684 c
->pid
= madtty_forkpty(c
->term
, "/bin/sh", pargs
, env
, &c
->pty
);
685 madtty_set_data(c
->term
, c
);
686 madtty_set_handler(c
->term
, title_escape_seq_handler
);
692 c
->minimized
= false;
693 debug("client with pid %d forked\n", c
->pid
);
707 toggleminimize(NULL
);
713 madtty_destroy(c
->term
);
715 if (!clients
&& countof(actions
)) {
716 if (!strcmp(c
->cmd
, shell
))
726 move_client(Client
*c
, int x
, int y
) {
727 if (c
->x
== x
&& c
->y
== y
)
729 debug("moving, x: %d y: %d\n", x
, y
);
730 if (mvwin(c
->window
, y
, x
) == ERR
)
731 eprint("error moving, x: %d y: %d\n", x
, y
);
739 resize_client(Client
*c
, int w
, int h
) {
740 if (c
->w
== w
&& c
->h
== h
)
742 debug("resizing, w: %d h: %d\n", w
, h
);
743 if (wresize(c
->window
, h
, w
) == ERR
)
744 eprint("error resizing, w: %d h: %d\n", w
, h
);
749 madtty_resize(c
->term
, h
- 1, w
);
753 resize(Client
*c
, int x
, int y
, int w
, int h
) {
754 resize_client(c
, w
, h
);
755 move_client(c
, x
, y
);
759 is_modifier(unsigned int mod
) {
761 for (i
= 0; i
< countof(keys
); i
++) {
762 if (keys
[i
].mod
== mod
)
769 keybinding(unsigned int mod
, unsigned int code
) {
771 for (i
= 0; i
< countof(keys
); i
++) {
772 if (keys
[i
].mod
== mod
&& keys
[i
].code
== code
)
779 get_client_by_pid(pid_t pid
) {
781 for (c
= clients
; c
; c
= c
->next
) {
789 sigchld_handler(int sig
) {
795 while ((pid
= waitpid(-1, &status
, WNOHANG
)) != 0) {
797 if (errno
== ECHILD
) {
798 /* no more child processes */
801 eprint("waitpid: %s\n", strerror(errno
));
804 debug("child with pid %d died\n", pid
);
805 if ((c
= get_client_by_pid(pid
)))
809 signal(SIGCHLD
, sigchld_handler
);
815 sigwinch_handler(int sig
) {
816 signal(SIGWINCH
, sigwinch_handler
);
817 screen
.need_resize
= true;
821 sigterm_handler(int sig
) {
829 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1)
832 screen
.w
= ws
.ws_col
;
833 screen
.h
= ws
.ws_row
;
835 debug("resize_screen(), w: %d h: %d\n", screen
.w
, screen
.h
);
837 #if defined(__OpenBSD__) || defined(__NetBSD__)
838 resizeterm(screen
.h
, screen
.w
);
840 resize_term(screen
.h
, screen
.w
);
842 wresize(stdscr
, screen
.h
, screen
.w
);
854 startup(const char *args
[]) {
855 for (unsigned int i
= 0; i
< countof(actions
); i
++)
856 actions
[i
].cmd(actions
[i
].args
);
861 if (!(shell
= getenv("SHELL")))
863 setlocale(LC_CTYPE
, "");
867 keypad(stdscr
, TRUE
);
871 getmaxyx(stdscr
, screen
.h
, screen
.w
);
873 signal(SIGWINCH
, sigwinch_handler
);
874 signal(SIGCHLD
, sigchld_handler
);
875 signal(SIGTERM
, sigterm_handler
);
888 unlink(cmdfifo
.file
);
892 quit(const char *args
[]) {
900 eprint("usage: dvtm [-v] [-m mod] [-d escdelay] [-h n] "
908 open_or_create_fifo(const char *name
, const char **name_created
) {
913 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
)) == -1) {
914 if (errno
== ENOENT
&& !mkfifo(name
, S_IRUSR
|S_IWUSR
)) {
915 *name_created
= name
;
918 error("%s\n", strerror(errno
));
922 if (fstat(fd
, &info
) == -1)
923 error("%s\n", strerror(errno
));
924 if (!S_ISFIFO(info
.st_mode
))
925 error("%s is not a named pipe\n", name
);
930 parse_args(int argc
, char *argv
[]) {
934 if (!getenv("ESCDELAY"))
936 for (arg
= 1; arg
< argc
; arg
++) {
937 if (argv
[arg
][0] != '-') {
938 const char *args
[] = { argv
[arg
], NULL
};
946 if (argv
[arg
][1] != 'v' && (arg
+ 1) >= argc
)
948 switch (argv
[arg
][1]) {
950 puts("dvtm-"VERSION
" (c) 2007-2011 Marc Andre Tanner");
953 char *mod
= argv
[++arg
];
954 if (mod
[0] == '^' && mod
[1])
956 for (unsigned int i
= 0; i
< countof(keys
); i
++)
961 ESCDELAY
= atoi(argv
[++arg
]);
964 else if (ESCDELAY
> 1000)
968 screen
.history
= atoi(argv
[++arg
]);
971 bar
.fd
= open_or_create_fifo(argv
[++arg
], &bar
.file
);
976 cmdfifo
.fd
= open_or_create_fifo(argv
[++arg
], &cmdfifo
.file
);
977 if (!(fifo
= get_realpath(argv
[arg
])))
978 error("%s\n", strerror(errno
));
979 setenv("DVTM_CMD_FIFO", fifo
, 1);
992 unsigned int len
= 1;
993 char buf
[8] = { '\e' };
996 /* pass characters following escape to the underlying app */
997 nodelay(stdscr
, TRUE
);
998 for (int t
; len
< sizeof(buf
) && (t
= getch()) != ERR
; len
++)
1000 nodelay(stdscr
, FALSE
);
1003 for (c
= runinall
? clients
: sel
; c
; c
= c
->next
) {
1004 if (!c
->minimized
|| isarrange(fullscreen
)) {
1006 madtty_write(c
->term
, buf
, len
);
1008 madtty_keypress(c
->term
, code
);
1016 main(int argc
, char *argv
[]) {
1017 if (!parse_args(argc
, argv
)) {
1027 if (screen
.need_resize
) {
1029 screen
.need_resize
= false;
1033 FD_SET(STDIN_FILENO
, &rd
);
1035 if (cmdfifo
.fd
!= -1) {
1036 FD_SET(cmdfifo
.fd
, &rd
);
1041 FD_SET(bar
.fd
, &rd
);
1042 nfds
= max(nfds
, bar
.fd
);
1045 for (c
= clients
; c
; ) {
1052 FD_SET(c
->pty
, &rd
);
1053 nfds
= max(nfds
, c
->pty
);
1056 r
= select(nfds
+ 1, &rd
, NULL
, NULL
, NULL
);
1058 if (r
== -1 && errno
== EINTR
)
1066 if (FD_ISSET(STDIN_FILENO
, &rd
)) {
1070 if (code
== KEY_MOUSE
) {
1072 } else if (is_modifier(code
)) {
1078 else if ((key
= keybinding(mod
, code
)))
1079 key
->action
.cmd(key
->action
.args
);
1081 } else if ((key
= keybinding(0, code
))) {
1082 key
->action
.cmd(key
->action
.args
);
1087 if (r
== 1) /* no data available on pty's */
1091 if (cmdfifo
.fd
!= -1 && FD_ISSET(cmdfifo
.fd
, &rd
))
1094 if (bar
.fd
!= -1 && FD_ISSET(bar
.fd
, &rd
))
1097 for (c
= clients
; c
; ) {
1098 if (FD_ISSET(c
->pty
, &rd
)) {
1099 if (madtty_process(c
->term
) < 0 && errno
== EIO
) {
1100 /* client probably terminated */
1108 if (!isarrange(fullscreen
))
1109 wnoutrefresh(c
->window
);
1117 wnoutrefresh(sel
->window
);