2 * The initial "port" of dwm to curses was done by
4 * © 2007-2016 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.
19 #include <sys/select.h>
21 #include <sys/ioctl.h>
24 #include <sys/types.h>
36 #if defined __CYGWIN__ || defined __sun
45 #ifndef NCURSES_REENTRANT
46 # define set_escdelay(d) (ESCDELAY = (d))
55 volatile sig_atomic_t need_resize
;
60 void (*arrange
)(void);
63 typedef struct Client Client
;
69 volatile sig_atomic_t editor_died
;
74 unsigned short int id
;
82 volatile sig_atomic_t died
;
103 #define ALT(k) ((k) + (161 - 'a'))
104 #if defined CTRL && defined _AIX
108 #define CTRL(k) ((k) & 0x1F)
110 #define CTRL_ALT(k) ((k) + (129 - 'a'))
115 void (*cmd
)(const char *args
[]);
121 typedef unsigned int KeyCombo
[MAX_KEYS
];
138 enum { BAR_TOP
, BAR_BOTTOM
, BAR_OFF
};
144 unsigned short int h
;
145 unsigned short int y
;
153 unsigned short int id
;
169 #define LENGTH(arr) (sizeof(arr) / sizeof((arr)[0]))
170 #define MAX(x, y) ((x) > (y) ? (x) : (y))
171 #define MIN(x, y) ((x) < (y) ? (x) : (y))
172 #define TAGMASK ((1 << LENGTH(tags)) - 1)
175 #define debug(format, args...)
180 /* commands for use by keybindings */
181 static void create(const char *args
[]);
182 static void copymode(const char *args
[]);
183 static void focusn(const char *args
[]);
184 static void focusid(const char *args
[]);
185 static void focusnext(const char *args
[]);
186 static void focusnextnm(const char *args
[]);
187 static void focusprev(const char *args
[]);
188 static void focusprevnm(const char *args
[]);
189 static void focuslast(const char *args
[]);
190 static void focusup(const char *args
[]);
191 static void focusdown(const char *args
[]);
192 static void focusleft(const char *args
[]);
193 static void focusright(const char *args
[]);
194 static void killclient(const char *args
[]);
195 static void paste(const char *args
[]);
196 static void quit(const char *args
[]);
197 static void redraw(const char *args
[]);
198 static void scrollback(const char *args
[]);
199 static void send(const char *args
[]);
200 static void setlayout(const char *args
[]);
201 static void incnmaster(const char *args
[]);
202 static void setmfact(const char *args
[]);
203 static void startup(const char *args
[]);
204 static void tag(const char *args
[]);
205 static void tagid(const char *args
[]);
206 static void togglebar(const char *args
[]);
207 static void togglebarpos(const char *args
[]);
208 static void toggleminimize(const char *args
[]);
209 static void togglemouse(const char *args
[]);
210 static void togglerunall(const char *args
[]);
211 static void toggletag(const char *args
[]);
212 static void toggleview(const char *args
[]);
213 static void viewprevtag(const char *args
[]);
214 static void view(const char *args
[]);
215 static void zoom(const char *args
[]);
217 /* commands for use by mouse bindings */
218 static void mouse_focus(const char *args
[]);
219 static void mouse_fullscreen(const char *args
[]);
220 static void mouse_minimize(const char *args
[]);
221 static void mouse_zoom(const char *args
[]);
223 /* functions and variables available to layouts via config.h */
224 static Client
* nextvisible(Client
*c
);
225 static void focus(Client
*c
);
226 static void resize(Client
*c
, int x
, int y
, int w
, int h
);
227 extern Screen screen
;
228 static unsigned int waw
, wah
, wax
, way
;
229 static Client
*clients
= NULL
;
231 static KeyCombo keys
;
235 /* global variables */
236 static const char *dvtm_name
= "dvtm";
237 Screen screen
= { .mfact
= MFACT
, .nmaster
= NMASTER
, .history
= SCROLL_HISTORY
};
238 static Client
*stack
= NULL
;
239 static Client
*sel
= NULL
;
240 static Client
*lastsel
= NULL
;
241 static Client
*msel
= NULL
;
242 static unsigned int seltags
;
243 static unsigned int tagset
[2] = { 1, 1 };
244 static bool mouse_events_enabled
= ENABLE_MOUSE
;
245 static Layout
*layout
= layouts
;
246 static StatusBar bar
= { .fd
= -1, .lastpos
= BAR_POS
, .pos
= BAR_POS
, .autohide
= BAR_AUTOHIDE
, .h
= 1 };
247 static CmdFifo cmdfifo
= { .fd
= -1 };
248 static const char *shell
;
249 static Register copyreg
;
250 static volatile sig_atomic_t running
= true;
251 static bool runinall
= false;
254 eprint(const char *errstr
, ...) {
256 va_start(ap
, errstr
);
257 vfprintf(stderr
, errstr
, ap
);
262 error(const char *errstr
, ...) {
264 va_start(ap
, errstr
);
265 vfprintf(stderr
, errstr
, ap
);
271 isarrange(void (*func
)()) {
272 return func
== layout
->arrange
;
276 isvisible(Client
*c
) {
277 return c
->tags
& tagset
[seltags
];
281 is_content_visible(Client
*c
) {
284 if (isarrange(fullscreen
))
286 return isvisible(c
) && !c
->minimized
;
290 nextvisible(Client
*c
) {
291 for (; c
&& !isvisible(c
); c
= c
->next
);
302 if (bar
.pos
== BAR_TOP
) {
305 } else if (bar
.pos
== BAR_BOTTOM
) {
313 if (bar
.pos
!= BAR_OFF
) {
314 bar
.lastpos
= bar
.pos
;
321 if (bar
.pos
== BAR_OFF
)
322 bar
.pos
= bar
.lastpos
;
327 int sx
, sy
, x
, y
, width
;
328 unsigned int occupied
= 0, urgent
= 0;
329 if (bar
.pos
== BAR_OFF
)
332 for (Client
*c
= clients
; c
; c
= c
->next
) {
338 getyx(stdscr
, sy
, sx
);
342 for (unsigned int i
= 0; i
< LENGTH(tags
); i
++){
343 if (tagset
[seltags
] & (1 << i
))
345 else if (urgent
& (1 << i
))
347 else if (occupied
& (1 << i
))
348 attrset(TAG_OCCUPIED
);
351 printw(TAG_SYMBOL
, tags
[i
]);
354 attrset(runinall
? TAG_SEL
: TAG_NORMAL
);
355 addstr(layout
->symbol
);
358 for (unsigned int i
= 0; i
< MAX_KEYS
&& keys
[i
]; i
++) {
360 printw("^%c", 'A' - 1 + keys
[i
]);
362 printw("%c", keys
[i
]);
367 int maxwidth
= screen
.w
- x
- 2;
372 wchar_t wbuf
[sizeof bar
.text
];
373 size_t numchars
= mbstowcs(wbuf
, bar
.text
, sizeof bar
.text
);
375 if (numchars
!= (size_t)-1 && (width
= wcswidth(wbuf
, maxwidth
)) != -1) {
377 for (pos
= 0; pos
+ width
< maxwidth
; pos
++)
380 for (size_t i
= 0; i
< numchars
; i
++) {
381 pos
+= wcwidth(wbuf
[i
]);
391 mvaddch(bar
.y
, screen
.w
- 1, BAR_END
);
392 attrset(NORMAL_ATTR
);
394 wnoutrefresh(stdscr
);
399 return (bar
.pos
!= BAR_OFF
) || (clients
&& clients
->next
);
403 draw_border(Client
*c
) {
405 int x
, y
, maxlen
, attrs
= NORMAL_ATTR
;
409 if (sel
!= c
&& c
->urgent
)
411 if (sel
== c
|| (runinall
&& !c
->minimized
))
412 attrs
= SELECTED_ATTR
;
414 wattrset(c
->window
, attrs
);
415 getyx(c
->window
, y
, x
);
416 mvwhline(c
->window
, 0, 0, ACS_HLINE
, c
->w
);
420 if ((size_t)maxlen
< sizeof(c
->title
)) {
421 t
= c
->title
[maxlen
];
422 c
->title
[maxlen
] = '\0';
425 mvwprintw(c
->window
, 0, 2, "[%s%s#%d]",
426 *c
->title
? c
->title
: "",
427 *c
->title
? " | " : "",
430 c
->title
[maxlen
] = t
;
431 wmove(c
->window
, y
, x
);
435 draw_content(Client
*c
) {
436 vt_draw(c
->term
, c
->window
, c
->has_title_line
, 0);
441 if (is_content_visible(c
)) {
442 redrawwin(c
->window
);
445 if (!isarrange(fullscreen
) || sel
== c
)
447 wnoutrefresh(c
->window
);
452 if (!nextvisible(clients
)) {
461 if (!isarrange(fullscreen
)) {
462 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
467 /* as a last step the selected window is redrawn,
468 * this has the effect that the cursor position is
477 unsigned int m
= 0, n
= 0;
478 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
484 attrset(NORMAL_ATTR
);
485 if (bar
.fd
== -1 && bar
.autohide
) {
486 if ((!clients
|| !clients
->next
) && n
== 1)
492 if (m
&& !isarrange(fullscreen
))
495 if (m
&& !isarrange(fullscreen
)) {
496 unsigned int i
= 0, nw
= waw
/ m
, nx
= wax
;
497 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
499 resize(c
, nx
, way
+wah
, ++i
== m
? waw
- nx
: nw
, 1);
506 wnoutrefresh(stdscr
);
518 for (int o
= 1; c
; c
= nextvisible(c
->next
), o
++)
523 attachafter(Client
*c
, Client
*a
) { /* attach c after a */
527 for (a
= clients
; a
&& a
->next
; a
= a
->next
);
535 for (int o
= a
->order
; c
; c
= nextvisible(c
->next
))
541 attachstack(Client
*c
) {
550 c
->prev
->next
= c
->next
;
552 c
->next
->prev
= c
->prev
;
553 for (d
= nextvisible(c
->next
); d
; d
= nextvisible(d
->next
))
558 c
->next
= c
->prev
= NULL
;
562 settitle(Client
*c
) {
563 char *term
, *t
= title
;
564 if (!t
&& sel
== c
&& *c
->title
)
566 if (t
&& (term
= getenv("TERM")) && !strstr(term
, "linux")) {
567 printf("\033]0;%s\007", t
);
569 wnoutrefresh(c
->window
);
574 detachstack(Client
*c
) {
576 for (tc
= &stack
; *tc
&& *tc
!= c
; tc
= &(*tc
)->snext
);
583 for (c
= stack
; c
&& !isvisible(c
); c
= c
->snext
);
589 lastsel
->urgent
= false;
590 if (!isarrange(fullscreen
)) {
591 draw_border(lastsel
);
592 wnoutrefresh(lastsel
->window
);
601 if (isarrange(fullscreen
)) {
605 wnoutrefresh(c
->window
);
608 curs_set(c
&& !c
->minimized
&& vt_cursor_visible(c
->term
));
612 applycolorrules(Client
*c
) {
613 const ColorRule
*r
= colorrules
;
614 short fg
= r
->color
->fg
, bg
= r
->color
->bg
;
615 attr_t attrs
= r
->attrs
;
617 for (unsigned int i
= 1; i
< LENGTH(colorrules
); i
++) {
619 if (strstr(c
->title
, r
->title
)) {
627 vt_default_colors_set(c
->term
, attrs
, fg
, bg
);
631 term_title_handler(Vt
*term
, const char *title
) {
632 Client
*c
= (Client
*)vt_data_get(term
);
634 strncpy(c
->title
, title
, sizeof(c
->title
) - 1);
635 c
->title
[title
? sizeof(c
->title
) - 1 : 0] = '\0';
637 if (!isarrange(fullscreen
) || sel
== c
)
643 term_urgent_handler(Vt
*term
) {
644 Client
*c
= (Client
*)vt_data_get(term
);
649 if (!isarrange(fullscreen
) && sel
!= c
&& isvisible(c
))
654 move_client(Client
*c
, int x
, int y
) {
655 if (c
->x
== x
&& c
->y
== y
)
657 debug("moving, x: %d y: %d\n", x
, y
);
658 if (mvwin(c
->window
, y
, x
) == ERR
) {
659 eprint("error moving, x: %d y: %d\n", x
, y
);
667 resize_client(Client
*c
, int w
, int h
) {
668 bool has_title_line
= show_border();
669 bool resize_window
= c
->w
!= w
|| c
->h
!= h
;
671 debug("resizing, w: %d h: %d\n", w
, h
);
672 if (wresize(c
->window
, h
, w
) == ERR
) {
673 eprint("error resizing, w: %d h: %d\n", w
, h
);
679 if (resize_window
|| c
->has_title_line
!= has_title_line
) {
680 c
->has_title_line
= has_title_line
;
681 vt_resize(c
->app
, h
- has_title_line
, w
);
683 vt_resize(c
->editor
, h
- has_title_line
, w
);
688 resize(Client
*c
, int x
, int y
, int w
, int h
) {
689 resize_client(c
, w
, h
);
690 move_client(c
, x
, y
);
694 get_client_by_coord(unsigned int x
, unsigned int y
) {
695 if (y
< way
|| y
>= way
+wah
)
697 if (isarrange(fullscreen
))
699 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
700 if (x
>= c
->x
&& x
< c
->x
+ c
->w
&& y
>= c
->y
&& y
< c
->y
+ c
->h
) {
701 debug("mouse event, x: %d y: %d client: %d\n", x
, y
, c
->order
);
709 sigchld_handler(int sig
) {
714 while ((pid
= waitpid(-1, &status
, WNOHANG
)) != 0) {
716 if (errno
== ECHILD
) {
717 /* no more child processes */
720 eprint("waitpid: %s\n", strerror(errno
));
724 debug("child with pid %d died\n", pid
);
726 for (Client
*c
= clients
; c
; c
= c
->next
) {
731 if (c
->editor
&& vt_pid_get(c
->editor
) == pid
) {
732 c
->editor_died
= true;
742 sigwinch_handler(int sig
) {
743 screen
.need_resize
= true;
747 sigterm_handler(int sig
) {
752 resize_screen(void) {
755 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1) {
756 getmaxyx(stdscr
, screen
.h
, screen
.w
);
758 screen
.w
= ws
.ws_col
;
759 screen
.h
= ws
.ws_row
;
762 debug("resize_screen(), w: %d h: %d\n", screen
.w
, screen
.h
);
764 resizeterm(screen
.h
, screen
.w
);
765 wresize(stdscr
, screen
.h
, screen
.w
);
772 keybinding(KeyCombo keys
, unsigned int keycount
) {
773 for (unsigned int b
= 0; b
< LENGTH(bindings
); b
++) {
774 for (unsigned int k
= 0; k
< keycount
; k
++) {
775 if (keys
[k
] != bindings
[b
].keys
[k
])
777 if (k
== keycount
- 1)
785 bitoftag(const char *tag
) {
789 for (i
= 0; (i
< LENGTH(tags
)) && strcmp(tags
[i
], tag
); i
++);
790 return (i
< LENGTH(tags
)) ? (1 << i
) : 0;
795 bool allminimized
= true;
796 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
798 allminimized
= false;
802 if (allminimized
&& nextvisible(clients
)) {
804 toggleminimize(NULL
);
810 tag(const char *args
[]) {
813 sel
->tags
= bitoftag(args
[0]) & TAGMASK
;
818 tagid(const char *args
[]) {
819 if (!args
[0] || !args
[1])
822 const int win_id
= atoi(args
[0]);
823 for (Client
*c
= clients
; c
; c
= c
->next
) {
824 if (c
->id
== win_id
) {
825 unsigned int ntags
= c
->tags
;
826 for (unsigned int i
= 1; i
< MAX_ARGS
&& args
[i
]; i
++) {
827 if (args
[i
][0] == '+')
828 ntags
|= bitoftag(args
[i
]+1);
829 else if (args
[i
][0] == '-')
830 ntags
&= ~bitoftag(args
[i
]+1);
832 ntags
= bitoftag(args
[i
]);
845 toggletag(const char *args
[]) {
848 unsigned int newtags
= sel
->tags
^ (bitoftag(args
[0]) & TAGMASK
);
856 toggleview(const char *args
[]) {
857 unsigned int newtagset
= tagset
[seltags
] ^ (bitoftag(args
[0]) & TAGMASK
);
859 tagset
[seltags
] = newtagset
;
865 view(const char *args
[]) {
866 unsigned int newtagset
= bitoftag(args
[0]) & TAGMASK
;
867 if (tagset
[seltags
] != newtagset
&& newtagset
) {
868 seltags
^= 1; /* toggle sel tagset */
869 tagset
[seltags
] = newtagset
;
875 viewprevtag(const char *args
[]) {
883 unsigned int len
= 1;
884 char buf
[8] = { '\e' };
887 /* pass characters following escape to the underlying app */
888 nodelay(stdscr
, TRUE
);
889 for (int t
; len
< sizeof(buf
) && (t
= getch()) != ERR
; len
++) {
896 nodelay(stdscr
, FALSE
);
899 for (Client
*c
= runinall
? nextvisible(clients
) : sel
; c
; c
= nextvisible(c
->next
)) {
900 if (is_content_visible(c
)) {
903 vt_write(c
->term
, buf
, len
);
905 vt_keypress(c
->term
, code
);
907 vt_keypress(c
->term
, key
);
919 if (mouse_events_enabled
) {
920 mask
= BUTTON1_CLICKED
| BUTTON2_CLICKED
;
921 for (unsigned int i
= 0; i
< LENGTH(buttons
); i
++)
922 mask
|= buttons
[i
].mask
;
924 mousemask(mask
, NULL
);
925 #endif /* CONFIG_MOUSE */
929 checkshell(const char *shell
) {
930 if (shell
== NULL
|| *shell
== '\0' || *shell
!= '/')
932 if (!strcmp(strrchr(shell
, '/')+1, dvtm_name
))
934 if (access(shell
, X_OK
))
941 const char *shell
= getenv("SHELL");
944 if (checkshell(shell
))
946 if ((pw
= getpwuid(getuid())) && checkshell(pw
->pw_shell
))
954 setlocale(LC_CTYPE
, "");
959 keypad(stdscr
, TRUE
);
963 vt_keytable_set(keytable
, LENGTH(keytable
));
964 for (unsigned int i
= 0; i
< LENGTH(colors
); i
++) {
967 colors
[i
].fg
= colors
[i
].fg256
;
969 colors
[i
].bg
= colors
[i
].bg256
;
971 colors
[i
].pair
= vt_color_reserve(colors
[i
].fg
, colors
[i
].bg
);
975 memset(&sa
, 0, sizeof sa
);
977 sigemptyset(&sa
.sa_mask
);
978 sa
.sa_handler
= sigwinch_handler
;
979 sigaction(SIGWINCH
, &sa
, NULL
);
980 sa
.sa_handler
= sigchld_handler
;
981 sigaction(SIGCHLD
, &sa
, NULL
);
982 sa
.sa_handler
= sigterm_handler
;
983 sigaction(SIGTERM
, &sa
, NULL
);
984 sa
.sa_handler
= SIG_IGN
;
985 sigaction(SIGPIPE
, &sa
, NULL
);
995 Client
*next
= nextvisible(clients
);
998 toggleminimize(NULL
);
1006 wnoutrefresh(c
->window
);
1007 vt_destroy(c
->term
);
1009 if (!clients
&& LENGTH(actions
)) {
1010 if (!strcmp(c
->cmd
, shell
))
1033 unlink(cmdfifo
.file
);
1036 static char *getcwd_by_pid(Client
*c
) {
1040 snprintf(buf
, sizeof buf
, "/proc/%d/cwd", c
->pid
);
1041 return realpath(buf
, NULL
);
1045 create(const char *args
[]) {
1046 const char *pargs
[4] = { shell
, NULL
};
1047 char buf
[8], *cwd
= NULL
;
1048 const char *env
[] = {
1049 "DVTM_WINDOW_ID", buf
,
1053 if (args
&& args
[0]) {
1058 Client
*c
= calloc(1, sizeof(Client
));
1061 c
->tags
= tagset
[seltags
];
1062 c
->id
= ++cmdfifo
.id
;
1063 snprintf(buf
, sizeof buf
, "%d", c
->id
);
1065 if (!(c
->window
= newwin(wah
, waw
, way
, wax
))) {
1070 c
->term
= c
->app
= vt_create(screen
.h
, screen
.w
, screen
.history
);
1077 if (args
&& args
[0]) {
1079 char name
[PATH_MAX
];
1080 strncpy(name
, args
[0], sizeof(name
));
1081 name
[sizeof(name
)-1] = '\0';
1082 strncpy(c
->title
, basename(name
), sizeof(c
->title
));
1087 if (args
&& args
[1])
1088 strncpy(c
->title
, args
[1], sizeof(c
->title
));
1089 c
->title
[sizeof(c
->title
)-1] = '\0';
1091 if (args
&& args
[2])
1092 cwd
= !strcmp(args
[2], "$CWD") ? getcwd_by_pid(sel
) : (char*)args
[2];
1093 c
->pid
= vt_forkpty(c
->term
, shell
, pargs
, cwd
, env
, NULL
, NULL
);
1094 if (args
&& args
[2] && !strcmp(args
[2], "$CWD"))
1096 vt_data_set(c
->term
, c
);
1097 vt_title_handler_set(c
->term
, term_title_handler
);
1098 vt_urgent_handler_set(c
->term
, term_urgent_handler
);
1102 debug("client with pid %d forked\n", c
->pid
);
1109 copymode(const char *args
[]) {
1110 if (!args
|| !args
[0] || !sel
|| sel
->editor
)
1113 bool colored
= strstr(args
[0], "pager") != NULL
;
1115 if (!(sel
->editor
= vt_create(sel
->h
- sel
->has_title_line
, sel
->w
, 0)))
1118 int *to
= &sel
->editor_fds
[0];
1119 int *from
= strstr(args
[0], "editor") ? &sel
->editor_fds
[1] : NULL
;
1120 sel
->editor_fds
[0] = sel
->editor_fds
[1] = -1;
1122 const char *argv
[3] = { args
[0], NULL
, NULL
};
1124 int line
= vt_content_start(sel
->app
);
1125 snprintf(argline
, sizeof(argline
), "+%d", line
);
1128 char *cwd
= getcwd_by_pid(sel
);
1129 if (vt_forkpty(sel
->editor
, args
[0], argv
, cwd
, NULL
, to
, from
) < 0) {
1130 vt_destroy(sel
->editor
);
1135 sel
->term
= sel
->editor
;
1137 if (sel
->editor_fds
[0] != -1) {
1139 size_t len
= vt_content_get(sel
->app
, &buf
, colored
);
1142 ssize_t res
= write(sel
->editor_fds
[0], cur
, len
);
1144 if (errno
== EAGAIN
|| errno
== EINTR
)
1152 close(sel
->editor_fds
[0]);
1153 sel
->editor_fds
[0] = -1;
1157 vt_write(sel
->editor
, args
[1], strlen(args
[1]));
1161 focusn(const char *args
[]) {
1162 for (Client
*c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
)) {
1163 if (c
->order
== atoi(args
[0])) {
1166 toggleminimize(NULL
);
1173 focusid(const char *args
[]) {
1177 const int win_id
= atoi(args
[0]);
1178 for (Client
*c
= clients
; c
; c
= c
->next
) {
1179 if (c
->id
== win_id
) {
1182 toggleminimize(NULL
);
1183 if (!isvisible(c
)) {
1184 c
->tags
|= tagset
[seltags
];
1193 focusnext(const char *args
[]) {
1197 for (c
= sel
->next
; c
&& !isvisible(c
); c
= c
->next
);
1199 for (c
= clients
; c
&& !isvisible(c
); c
= c
->next
);
1205 focusnextnm(const char *args
[]) {
1210 c
= nextvisible(c
->next
);
1212 c
= nextvisible(clients
);
1213 } while (c
->minimized
&& c
!= sel
);
1218 focusprev(const char *args
[]) {
1222 for (c
= sel
->prev
; c
&& !isvisible(c
); c
= c
->prev
);
1224 for (c
= clients
; c
&& c
->next
; c
= c
->next
);
1225 for (; c
&& !isvisible(c
); c
= c
->prev
);
1232 focusprevnm(const char *args
[]) {
1237 for (c
= c
->prev
; c
&& !isvisible(c
); c
= c
->prev
);
1239 for (c
= clients
; c
&& c
->next
; c
= c
->next
);
1240 for (; c
&& !isvisible(c
); c
= c
->prev
);
1242 } while (c
&& c
!= sel
&& c
->minimized
);
1247 focuslast(const char *args
[]) {
1253 focusup(const char *args
[]) {
1256 /* avoid vertical separator, hence +1 in x direction */
1257 Client
*c
= get_client_by_coord(sel
->x
+ 1, sel
->y
- 1);
1265 focusdown(const char *args
[]) {
1268 Client
*c
= get_client_by_coord(sel
->x
, sel
->y
+ sel
->h
);
1276 focusleft(const char *args
[]) {
1279 Client
*c
= get_client_by_coord(sel
->x
- 2, sel
->y
);
1287 focusright(const char *args
[]) {
1290 Client
*c
= get_client_by_coord(sel
->x
+ sel
->w
+ 1, sel
->y
);
1298 killclient(const char *args
[]) {
1301 debug("killing client with pid: %d\n", sel
->pid
);
1302 kill(-sel
->pid
, SIGKILL
);
1306 paste(const char *args
[]) {
1307 if (sel
&& copyreg
.data
)
1308 vt_write(sel
->term
, copyreg
.data
, copyreg
.len
);
1312 quit(const char *args
[]) {
1318 redraw(const char *args
[]) {
1319 for (Client
*c
= clients
; c
; c
= c
->next
) {
1320 if (!c
->minimized
) {
1323 wnoutrefresh(c
->window
);
1330 scrollback(const char *args
[]) {
1331 if (!is_content_visible(sel
))
1334 if (!args
[0] || atoi(args
[0]) < 0)
1335 vt_scroll(sel
->term
, -sel
->h
/2);
1337 vt_scroll(sel
->term
, sel
->h
/2);
1340 curs_set(vt_cursor_visible(sel
->term
));
1344 send(const char *args
[]) {
1345 if (sel
&& args
&& args
[0])
1346 vt_write(sel
->term
, args
[0], strlen(args
[0]));
1350 setlayout(const char *args
[]) {
1353 if (!args
|| !args
[0]) {
1354 if (++layout
== &layouts
[LENGTH(layouts
)])
1355 layout
= &layouts
[0];
1357 for (i
= 0; i
< LENGTH(layouts
); i
++)
1358 if (!strcmp(args
[0], layouts
[i
].symbol
))
1360 if (i
== LENGTH(layouts
))
1362 layout
= &layouts
[i
];
1368 incnmaster(const char *args
[]) {
1371 if (isarrange(fullscreen
) || isarrange(grid
))
1373 /* arg handling, manipulate nmaster */
1374 if (args
[0] == NULL
) {
1375 screen
.nmaster
= NMASTER
;
1376 } else if (sscanf(args
[0], "%d", &delta
) == 1) {
1377 if (args
[0][0] == '+' || args
[0][0] == '-')
1378 screen
.nmaster
+= delta
;
1380 screen
.nmaster
= delta
;
1381 if (screen
.nmaster
< 1)
1388 setmfact(const char *args
[]) {
1391 if (isarrange(fullscreen
) || isarrange(grid
))
1393 /* arg handling, manipulate mfact */
1394 if (args
[0] == NULL
) {
1395 screen
.mfact
= MFACT
;
1396 } else if (sscanf(args
[0], "%f", &delta
) == 1) {
1397 if (args
[0][0] == '+' || args
[0][0] == '-')
1398 screen
.mfact
+= delta
;
1400 screen
.mfact
= delta
;
1401 if (screen
.mfact
< 0.1)
1403 else if (screen
.mfact
> 0.9)
1410 startup(const char *args
[]) {
1411 for (unsigned int i
= 0; i
< LENGTH(actions
); i
++)
1412 actions
[i
].cmd(actions
[i
].args
);
1416 togglebar(const char *args
[]) {
1417 if (bar
.pos
== BAR_OFF
)
1421 bar
.autohide
= false;
1427 togglebarpos(const char *args
[]) {
1428 switch (bar
.pos
== BAR_OFF
? bar
.lastpos
: bar
.pos
) {
1430 bar
.pos
= BAR_BOTTOM
;
1441 toggleminimize(const char *args
[]) {
1446 /* the last window can't be minimized */
1447 if (!sel
->minimized
) {
1448 for (n
= 0, c
= nextvisible(clients
); c
; c
= nextvisible(c
->next
))
1454 sel
->minimized
= !sel
->minimized
;
1456 /* check whether the master client was minimized */
1457 if (sel
== nextvisible(clients
) && sel
->minimized
) {
1458 c
= nextvisible(sel
->next
);
1463 for (; c
&& (t
= nextvisible(c
->next
)) && !t
->minimized
; c
= t
);
1465 } else if (m
->minimized
) {
1466 /* non master window got minimized move it above all other
1470 for (c
= nextvisible(clients
); c
&& (t
= nextvisible(c
->next
)) && !t
->minimized
; c
= t
);
1472 } else { /* window is no longer minimized, move it to the master area */
1481 togglemouse(const char *args
[]) {
1482 mouse_events_enabled
= !mouse_events_enabled
;
1487 togglerunall(const char *args
[]) {
1488 runinall
= !runinall
;
1494 zoom(const char *args
[]) {
1499 if (args
&& args
[0])
1501 if ((c
= sel
) == nextvisible(clients
))
1502 if (!(c
= nextvisible(c
->next
)))
1508 toggleminimize(NULL
);
1512 /* commands for use by mouse bindings */
1514 mouse_focus(const char *args
[]) {
1516 if (msel
->minimized
)
1517 toggleminimize(NULL
);
1521 mouse_fullscreen(const char *args
[]) {
1523 setlayout(isarrange(fullscreen
) ? NULL
: args
);
1527 mouse_minimize(const char *args
[]) {
1529 toggleminimize(NULL
);
1533 mouse_zoom(const char *args
[]) {
1539 get_cmd_by_name(const char *name
) {
1540 for (unsigned int i
= 0; i
< LENGTH(commands
); i
++) {
1541 if (!strcmp(name
, commands
[i
].name
))
1542 return &commands
[i
];
1548 handle_cmdfifo(void) {
1550 char *p
, *s
, cmdbuf
[512], c
;
1553 r
= read(cmdfifo
.fd
, cmdbuf
, sizeof cmdbuf
- 1);
1562 /* find the command name */
1563 for (; *p
== ' ' || *p
== '\n'; p
++);
1564 for (s
= p
; *p
&& *p
!= ' ' && *p
!= '\n'; p
++);
1567 if (*s
&& (cmd
= get_cmd_by_name(s
)) != NULL
) {
1570 const char *args
[MAX_ARGS
], *arg
;
1571 memset(args
, 0, sizeof(args
));
1572 /* if arguments were specified in config.h ignore the one given via
1573 * the named pipe and thus skip everything until we find a new line
1575 if (cmd
->action
.args
[0] || c
== '\n') {
1576 debug("execute %s", s
);
1577 cmd
->action
.cmd(cmd
->action
.args
);
1578 while (*p
&& *p
!= '\n')
1582 /* no arguments were given in config.h so we parse the command line */
1586 for (; (c
= *p
); p
++) {
1589 /* remove the escape character '\\' move every
1590 * following character to the left by one position
1610 /* remove trailing quote if there is one */
1611 if (*(p
- 1) == '\'' || *(p
- 1) == '\"')
1614 /* remove leading quote if there is one */
1615 if (*arg
== '\'' || *arg
== '\"')
1617 if (argc
< MAX_ARGS
)
1627 if (c
== '\n' || *p
== '\n') {
1630 debug("execute %s", s
);
1631 for(int i
= 0; i
< argc
; i
++)
1632 debug(" %s", args
[i
]);
1634 cmd
->action
.cmd(args
);
1643 handle_mouse(void) {
1647 if (getmouse(&event
) != OK
)
1649 msel
= get_client_by_coord(event
.x
, event
.y
);
1654 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
);
1656 vt_mouse(msel
->term
, event
.x
- msel
->x
, event
.y
- msel
->y
, event
.bstate
);
1658 for (i
= 0; i
< LENGTH(buttons
); i
++) {
1659 if (event
.bstate
& buttons
[i
].mask
)
1660 buttons
[i
].action
.cmd(buttons
[i
].action
.args
);
1664 #endif /* CONFIG_MOUSE */
1668 handle_statusbar(void) {
1671 switch (r
= read(bar
.fd
, bar
.text
, sizeof bar
.text
- 1)) {
1673 strncpy(bar
.text
, strerror(errno
), sizeof bar
.text
- 1);
1674 bar
.text
[sizeof bar
.text
- 1] = '\0';
1682 p
= bar
.text
+ r
- 1;
1683 for (; p
>= bar
.text
&& *p
== '\n'; *p
-- = '\0');
1684 for (; p
>= bar
.text
&& *p
!= '\n'; --p
);
1686 memmove(bar
.text
, p
+ 1, strlen(p
));
1692 handle_editor(Client
*c
) {
1693 if (!copyreg
.data
&& (copyreg
.data
= malloc(screen
.history
)))
1694 copyreg
.size
= screen
.history
;
1696 while (c
->editor_fds
[1] != -1 && copyreg
.len
< copyreg
.size
) {
1697 ssize_t len
= read(c
->editor_fds
[1], copyreg
.data
+ copyreg
.len
, copyreg
.size
- copyreg
.len
);
1706 if (copyreg
.len
== copyreg
.size
) {
1708 if (!(copyreg
.data
= realloc(copyreg
.data
, copyreg
.size
))) {
1714 c
->editor_died
= false;
1715 c
->editor_fds
[1] = -1;
1716 vt_destroy(c
->editor
);
1721 wnoutrefresh(c
->window
);
1725 open_or_create_fifo(const char *name
, const char **name_created
) {
1730 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
)) == -1) {
1731 if (errno
== ENOENT
&& !mkfifo(name
, S_IRUSR
|S_IWUSR
)) {
1732 *name_created
= name
;
1735 error("%s\n", strerror(errno
));
1739 if (fstat(fd
, &info
) == -1)
1740 error("%s\n", strerror(errno
));
1741 if (!S_ISFIFO(info
.st_mode
))
1742 error("%s is not a named pipe\n", name
);
1749 eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
1750 "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
1755 parse_args(int argc
, char *argv
[]) {
1757 const char *name
= argv
[0];
1759 if (name
&& (name
= strrchr(name
, '/')))
1760 dvtm_name
= name
+ 1;
1761 if (!getenv("ESCDELAY"))
1763 for (int arg
= 1; arg
< argc
; arg
++) {
1764 if (argv
[arg
][0] != '-') {
1765 const char *args
[] = { argv
[arg
], NULL
, NULL
};
1773 if (argv
[arg
][1] != 'v' && argv
[arg
][1] != 'M' && (arg
+ 1) >= argc
)
1775 switch (argv
[arg
][1]) {
1777 puts("dvtm-"VERSION
" © 2007-2016 Marc André Tanner");
1780 mouse_events_enabled
= !mouse_events_enabled
;
1783 char *mod
= argv
[++arg
];
1784 if (mod
[0] == '^' && mod
[1])
1785 *mod
= CTRL(mod
[1]);
1786 for (unsigned int b
= 0; b
< LENGTH(bindings
); b
++)
1787 if (bindings
[b
].keys
[0] == MOD
)
1788 bindings
[b
].keys
[0] = *mod
;
1792 set_escdelay(atoi(argv
[++arg
]));
1795 else if (ESCDELAY
> 1000)
1799 screen
.history
= atoi(argv
[++arg
]);
1802 title
= argv
[++arg
];
1805 bar
.fd
= open_or_create_fifo(argv
[++arg
], &bar
.file
);
1810 cmdfifo
.fd
= open_or_create_fifo(argv
[++arg
], &cmdfifo
.file
);
1811 if (!(fifo
= realpath(argv
[arg
], NULL
)))
1812 error("%s\n", strerror(errno
));
1813 setenv("DVTM_CMD_FIFO", fifo
, 1);
1825 main(int argc
, char *argv
[]) {
1826 unsigned int key_index
= 0;
1827 memset(keys
, 0, sizeof(keys
));
1828 sigset_t emptyset
, blockset
;
1830 setenv("DVTM", VERSION
, 1);
1831 if (!parse_args(argc
, argv
)) {
1836 sigemptyset(&emptyset
);
1837 sigemptyset(&blockset
);
1838 sigaddset(&blockset
, SIGWINCH
);
1839 sigaddset(&blockset
, SIGCHLD
);
1840 sigprocmask(SIG_BLOCK
, &blockset
, NULL
);
1846 if (screen
.need_resize
) {
1848 screen
.need_resize
= false;
1852 FD_SET(STDIN_FILENO
, &rd
);
1854 if (cmdfifo
.fd
!= -1) {
1855 FD_SET(cmdfifo
.fd
, &rd
);
1860 FD_SET(bar
.fd
, &rd
);
1861 nfds
= MAX(nfds
, bar
.fd
);
1864 for (Client
*c
= clients
; c
; ) {
1865 if (c
->editor
&& c
->editor_died
)
1867 if (!c
->editor
&& c
->died
) {
1868 Client
*t
= c
->next
;
1873 int pty
= c
->editor
? vt_pty_get(c
->editor
) : vt_pty_get(c
->app
);
1875 nfds
= MAX(nfds
, pty
);
1880 r
= pselect(nfds
+ 1, &rd
, NULL
, NULL
, NULL
, &emptyset
);
1889 if (FD_ISSET(STDIN_FILENO
, &rd
)) {
1892 keys
[key_index
++] = code
;
1893 KeyBinding
*binding
= NULL
;
1894 if (code
== KEY_MOUSE
) {
1897 } else if ((binding
= keybinding(keys
, key_index
))) {
1898 unsigned int key_length
= MAX_KEYS
;
1899 while (key_length
> 1 && !binding
->keys
[key_length
-1])
1901 if (key_index
== key_length
) {
1902 binding
->action
.cmd(binding
->action
.args
);
1904 memset(keys
, 0, sizeof(keys
));
1908 memset(keys
, 0, sizeof(keys
));
1912 if (is_content_visible(sel
))
1913 wnoutrefresh(sel
->window
);
1915 if (r
== 1) /* no data available on pty's */
1919 if (cmdfifo
.fd
!= -1 && FD_ISSET(cmdfifo
.fd
, &rd
))
1922 if (bar
.fd
!= -1 && FD_ISSET(bar
.fd
, &rd
))
1925 for (Client
*c
= clients
; c
; c
= c
->next
) {
1926 if (FD_ISSET(vt_pty_get(c
->term
), &rd
)) {
1927 if (vt_process(c
->term
) < 0 && errno
== EIO
) {
1929 c
->editor_died
= true;
1936 if (c
!= sel
&& is_content_visible(c
)) {
1938 wnoutrefresh(c
->window
);
1942 if (is_content_visible(sel
)) {
1944 curs_set(vt_cursor_visible(sel
->term
));
1945 wnoutrefresh(sel
->window
);