aboutsummaryrefslogtreecommitdiff
path: root/sys/cmd/dvtm/dvtm.c.old
diff options
context:
space:
mode:
Diffstat (limited to 'sys/cmd/dvtm/dvtm.c.old')
-rw-r--r--sys/cmd/dvtm/dvtm.c.old1810
1 files changed, 1810 insertions, 0 deletions
diff --git a/sys/cmd/dvtm/dvtm.c.old b/sys/cmd/dvtm/dvtm.c.old
new file mode 100644
index 0000000..749ca1d
--- /dev/null
+++ b/sys/cmd/dvtm/dvtm.c.old
@@ -0,0 +1,1810 @@
+#include "dvtm.h"
+
+/* global variables */
+uint waw, wah, wax, way;
+Client *clients = nil;
+char *title;
+const char *dvtm_name = "dvtm";
+
+Screen screen = { .mfact = MFACT, .nmaster = NMASTER, .history = SCROLL_HISTORY };
+static Client *stack = nil;
+static Client *sel = nil;
+static Client *lastsel = nil;
+static Client *msel = nil;
+
+static uint seltags;
+static uint tagset[2] = { 1, 1 };
+static bool mouse_events_enabled = ENABLE_MOUSE;
+static Layout *layout = layouts;
+
+static StatusBar bar = { .fd = -1, .lastpos = BAR_POS, .pos = BAR_POS, .autohide = BAR_AUTOHIDE, .h = 1 };
+static CmdFifo cmdfifo = { .fd = -1 };
+static const char *shell;
+static Register copyreg;
+static volatile sig_atomic_t running = true;
+static bool runinall = false;
+
+/* function implementations */
+static
+void
+eprint(const char *errstr, ...)
+{
+ va_list ap;
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+}
+
+static
+void
+fatal(const char *errstr, ...)
+{
+ va_list ap;
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static
+bool
+isarrange(void (*func)())
+{
+ return func == layout->arrange;
+}
+
+static
+bool
+isvisible(Client *c)
+{
+ return c->tags & tagset[seltags];
+}
+
+static
+bool
+is_content_visible(Client *c)
+{
+ if (!c)
+ return false;
+ if (isarrange(fullscreen))
+ return sel == c;
+ return isvisible(c) && !c->minimized;
+}
+
+Client*
+nextvisible(Client *c)
+{
+ for (; c && !isvisible(c); c = c->next);
+ return c;
+}
+
+static
+void
+updatebarpos(void)
+{
+ bar.y = 0;
+ wax = 0;
+ way = 0;
+ wah = screen.h;
+ waw = screen.w;
+ if (bar.pos == BAR_TOP) {
+ wah -= bar.h;
+ way += bar.h;
+ } else if (bar.pos == BAR_BOTTOM) {
+ wah -= bar.h;
+ bar.y = wah;
+ }
+}
+
+static
+void
+hidebar(void)
+{
+ if (bar.pos != BAR_OFF) {
+ bar.lastpos = bar.pos;
+ bar.pos = BAR_OFF;
+ }
+}
+
+static
+void
+showbar(void)
+{
+ if (bar.pos == BAR_OFF)
+ bar.pos = bar.lastpos;
+}
+
+static
+void
+drawbar(void)
+{
+ int sx, sy, x, y, width;
+ uint occupied = 0, urgent = 0;
+ if (bar.pos == BAR_OFF)
+ return;
+
+ for (Client *c = clients; c; c = c->next) {
+ occupied |= c->tags;
+ if (c->urgent)
+ urgent |= c->tags;
+ }
+
+ getyx(stdscr, sy, sx);
+ attrset(BAR_ATTR);
+ move(bar.y, 0);
+
+ for (uint i = 0; i < arrlen(tags); i++){
+ if (tagset[seltags] & (1 << i))
+ attrset(TAG_SEL);
+ else if (urgent & (1 << i))
+ attrset(TAG_URGENT);
+ else if (occupied & (1 << i))
+ attrset(TAG_OCCUPIED);
+ else
+ attrset(TAG_NORMAL);
+ printw(TAG_SYMBOL, tags[i]);
+ }
+
+ attrset(runinall ? TAG_SEL : TAG_NORMAL);
+ addstr(layout->symbol);
+ attrset(TAG_NORMAL);
+
+ getyx(stdscr, y, x);
+ (void)y;
+ int maxwidth = screen.w - x - 2;
+
+ addch(BAR_BEGIN);
+ attrset(BAR_ATTR);
+
+ wchar_t wbuf[sizeof bar.text];
+ size_t numchars = mbstowcs(wbuf, bar.text, sizeof bar.text);
+
+ if (numchars != (size_t)-1 && (width = wcswidth(wbuf, maxwidth)) != -1) {
+ int pos;
+ for (pos = 0; pos + width < maxwidth; pos++)
+ addch(' ');
+
+ for (size_t i = 0; i < numchars; i++) {
+ pos += wcwidth(wbuf[i]);
+ if (pos > maxwidth)
+ break;
+ addnwstr(wbuf+i, 1);
+ }
+
+ clrtoeol();
+ }
+
+ attrset(TAG_NORMAL);
+ mvaddch(bar.y, screen.w - 1, BAR_END);
+ attrset(NORMAL_ATTR);
+ move(sy, sx);
+ wnoutrefresh(stdscr);
+}
+
+static
+int
+show_border(void) {
+ return (bar.pos != BAR_OFF) || (clients && clients->next);
+}
+
+static void
+draw_border(Client *c) {
+ char t = '\0';
+ int x, y, maxlen, attrs = NORMAL_ATTR;
+
+ if (!show_border())
+ return;
+ if (sel != c && c->urgent)
+ attrs = URGENT_ATTR;
+ if (sel == c || (runinall && !c->minimized))
+ attrs = SELECTED_ATTR;
+
+ wattrset(c->window, attrs);
+ getyx(c->window, y, x);
+ mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
+ maxlen = c->w - 10;
+ if (maxlen < 0)
+ maxlen = 0;
+ if ((size_t)maxlen < sizeof(c->title)) {
+ t = c->title[maxlen];
+ c->title[maxlen] = '\0';
+ }
+
+ mvwprintw(c->window, 0, 2, "[%s%s#%d]",
+ *c->title ? c->title : "",
+ *c->title ? " | " : "",
+ c->order);
+ if (t)
+ c->title[maxlen] = t;
+ wmove(c->window, y, x);
+}
+
+static void
+draw_content(Client *c) {
+ vt_draw(c->term, c->window, c->has_title_line, 0);
+}
+
+static void
+draw(Client *c) {
+ if (is_content_visible(c)) {
+ redrawwin(c->window);
+ draw_content(c);
+ }
+ if (!isarrange(fullscreen) || sel == c)
+ draw_border(c);
+ wnoutrefresh(c->window);
+}
+
+static void
+draw_all(void) {
+ if (!nextvisible(clients)) {
+ sel = nil;
+ curs_set(0);
+ erase();
+ drawbar();
+ doupdate();
+ return;
+ }
+
+ if (!isarrange(fullscreen)) {
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c != sel)
+ draw(c);
+ }
+ }
+ /* as a last step the selected window is redrawn,
+ * this has the effect that the cursor position is
+ * accurate
+ */
+ if (sel)
+ draw(sel);
+}
+
+static void
+arrange(void) {
+ uint m = 0, n = 0;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ c->order = ++n;
+ if (c->minimized)
+ m++;
+ }
+ erase();
+ attrset(NORMAL_ATTR);
+ if (bar.fd == -1 && bar.autohide) {
+ if ((!clients || !clients->next) && n == 1)
+ hidebar();
+ else
+ showbar();
+ updatebarpos();
+ }
+ if (m && !isarrange(fullscreen))
+ wah--;
+ layout->arrange();
+ if (m && !isarrange(fullscreen)) {
+ uint i = 0, nw = waw / m, nx = wax;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c->minimized) {
+ resize(c, nx, way+wah, ++i == m ? waw - nx : nw, 1);
+ nx += nw;
+ }
+ }
+ wah++;
+ }
+ focus(nil);
+ wnoutrefresh(stdscr);
+ drawbar();
+ draw_all();
+}
+
+static void
+attach(Client *c) {
+ if (clients)
+ clients->prev = c;
+ c->next = clients;
+ c->prev = nil;
+ clients = c;
+ for (int o = 1; c; c = nextvisible(c->next), o++)
+ c->order = o;
+}
+
+static void
+attachafter(Client *c, Client *a) { /* attach c after a */
+ if (c == a)
+ return;
+ if (!a)
+ for (a = clients; a && a->next; a = a->next);
+
+ if (a) {
+ if (a->next)
+ a->next->prev = c;
+ c->next = a->next;
+ c->prev = a;
+ a->next = c;
+ for (int o = a->order; c; c = nextvisible(c->next))
+ c->order = ++o;
+ }
+}
+
+static void
+attachstack(Client *c) {
+ c->snext = stack;
+ stack = c;
+}
+
+static void
+detach(Client *c) {
+ Client *d;
+ if (c->prev)
+ c->prev->next = c->next;
+ if (c->next) {
+ c->next->prev = c->prev;
+ for (d = nextvisible(c->next); d; d = nextvisible(d->next))
+ --d->order;
+ }
+ if (c == clients)
+ clients = c->next;
+ c->next = c->prev = nil;
+}
+
+static void
+settitle(Client *c) {
+ char *term, *t = title;
+ if (!t && sel == c && *c->title)
+ t = c->title;
+ if (t && (term = getenv("TERM")) && !strstr(term, "linux")) {
+ printf("\033]0;%s\007", t);
+ fflush(stdout);
+ }
+}
+
+static void
+detachstack(Client *c) {
+ Client **tc;
+ for (tc = &stack; *tc && *tc != c; tc = &(*tc)->snext);
+ *tc = c->snext;
+}
+
+void
+focus(Client *c) {
+ if (!c)
+ for (c = stack; c && !isvisible(c); c = c->snext);
+ if (sel == c)
+ return;
+ lastsel = sel;
+ sel = c;
+ if (lastsel) {
+ lastsel->urgent = false;
+ if (!isarrange(fullscreen)) {
+ draw_border(lastsel);
+ wnoutrefresh(lastsel->window);
+ }
+ }
+
+ if (c) {
+ detachstack(c);
+ attachstack(c);
+ settitle(c);
+ c->urgent = false;
+ if (isarrange(fullscreen)) {
+ draw(c);
+ } else {
+ draw_border(c);
+ wnoutrefresh(c->window);
+ }
+ }
+ curs_set(c && !c->minimized && vt_cursor_visible(c->term));
+}
+
+static
+void
+applycolorrules(Client *c)
+{
+ const ColorRule *r = colorrules;
+ int fg = r->color->fg, bg = r->color->bg;
+ attr_t attrs = r->attrs;
+
+ for (uint i = 1; i < arrlen(colorrules); i++) {
+ r = &colorrules[i];
+ if (strstr(c->title, r->title)) {
+ attrs = r->attrs;
+ fg = r->color->fg;
+ bg = r->color->bg;
+ break;
+ }
+ }
+
+ vt_default_colors_set(c->term, attrs, fg, bg);
+}
+
+static
+void
+term_title_handler(Vt *term, const char *title) {
+ Client *c = (Client *)vt_data_get(term);
+ if (title)
+ strncpy(c->title, title, sizeof(c->title) - 1);
+ c->title[title ? sizeof(c->title) - 1 : 0] = '\0';
+ settitle(c);
+ if (!isarrange(fullscreen) || sel == c)
+ draw_border(c);
+ applycolorrules(c);
+}
+
+static
+void
+term_urgent_handler(Vt *term) {
+ Client *c = (Client *)vt_data_get(term);
+ c->urgent = true;
+ printf("\a");
+ fflush(stdout);
+ drawbar();
+ if (!isarrange(fullscreen) && sel != c && isvisible(c))
+ draw_border(c);
+}
+
+static
+void
+move_client(Client *c, int x, int y)
+{
+ if (c->x == x && c->y == y)
+ return;
+ debug("moving, x: %d y: %d\n", x, y);
+ if (mvwin(c->window, y, x) == ERR) {
+ eprint("error moving, x: %d y: %d\n", x, y);
+ } else {
+ c->x = x;
+ c->y = y;
+ }
+}
+
+static
+void
+resize_client(Client *c, int w, int h)
+{
+ bool has_title_line = show_border();
+ bool resize_window = c->w != w || c->h != h;
+ if (resize_window) {
+ debug("resizing, w: %d h: %d\n", w, h);
+ if (wresize(c->window, h, w) == ERR) {
+ eprint("error resizing, w: %d h: %d\n", w, h);
+ } else {
+ c->w = w;
+ c->h = h;
+ }
+ }
+ if (resize_window || c->has_title_line != has_title_line) {
+ c->has_title_line = has_title_line;
+ vt_resize(c->app, h - has_title_line, w);
+ if (c->editor)
+ vt_resize(c->editor, h - has_title_line, w);
+ }
+}
+
+void
+resize(Client *c, int x, int y, int w, int h)
+{
+ resize_client(c, w, h);
+ move_client(c, x, y);
+}
+
+static
+Client*
+get_client_by_coord(uint x, unsigned int y) {
+ if (y < way || y >= way+wah)
+ return nil;
+ if (isarrange(fullscreen))
+ return sel;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
+ debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
+ return c;
+ }
+ }
+ return nil;
+}
+
+static
+void
+sigchld_handler(int sig) {
+ int errsv = errno;
+ int status;
+ pid_t pid;
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
+ if (pid == -1) {
+ if (errno == ECHILD) {
+ /* no more child processes */
+ break;
+ }
+ eprint("waitpid: %s\n", strerror(errno));
+ break;
+ }
+
+ debug("child with pid %d died\n", pid);
+
+ for (Client *c = clients; c; c = c->next) {
+ if (c->pid == pid) {
+ c->died = true;
+ break;
+ }
+ if (c->editor && vt_pid_get(c->editor) == pid) {
+ c->editor_died = true;
+ break;
+ }
+ }
+ }
+
+ errno = errsv;
+}
+
+static
+void
+sigwinch_handler(int sig) {
+ screen.need_resize = true;
+}
+
+static
+void
+sigterm_handler(int sig) {
+ running = false;
+}
+
+static
+void
+resize_screen(void)
+{
+ struct winsize ws;
+
+ if (ioctl(0, TIOCGWINSZ, &ws) == -1) {
+ getmaxyx(stdscr, screen.h, screen.w);
+ } else {
+ screen.w = ws.ws_col;
+ screen.h = ws.ws_row;
+ }
+
+ debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
+
+ resizeterm(screen.h, screen.w);
+ wresize(stdscr, screen.h, screen.w);
+ updatebarpos();
+ clear();
+ arrange();
+}
+
+static
+KeyBinding*
+keybinding(KeyCombo keys, uint keycount)
+{
+ for (uint b = 0; b < arrlen(bindings); b++) {
+ for (uint k = 0; k < keycount; k++) {
+ if (keys[k] != bindings[b].keys[k])
+ break;
+ if (k == keycount - 1)
+ return &bindings[b];
+ }
+ }
+ return nil;
+}
+
+static
+uint
+bitoftag(const char *tag)
+{
+ uint i;
+ if (!tag)
+ return ~0;
+ for (i = 0; (i < arrlen(tags)) && strcmp(tags[i], tag); i++);
+ return (i < arrlen(tags)) ? (1 << i) : 0;
+}
+
+static void
+tagschanged() {
+ bool allminimized = true;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (!c->minimized) {
+ allminimized = false;
+ break;
+ }
+ }
+ if (allminimized && nextvisible(clients)) {
+ focus(nil);
+ toggleminimize(nil);
+ }
+ arrange();
+}
+
+void
+tag(const char *args[])
+{
+ if (!sel)
+ return;
+ sel->tags = bitoftag(args[0]) & TAGMASK;
+ tagschanged();
+}
+
+void
+tagid(const char *args[])
+{
+ if (!args[0] || !args[1])
+ return;
+
+ const int win_id = atoi(args[0]);
+ for (Client *c = clients; c; c = c->next) {
+ if (c->id == win_id) {
+ uint ntags = c->tags;
+ for (uint i = 1; i < MAX_ARGS && args[i]; i++) {
+ if (args[i][0] == '+')
+ ntags |= bitoftag(args[i]+1);
+ else if (args[i][0] == '-')
+ ntags &= ~bitoftag(args[i]+1);
+ else
+ ntags = bitoftag(args[i]);
+ }
+ ntags &= TAGMASK;
+ if (ntags) {
+ c->tags = ntags;
+ tagschanged();
+ }
+ return;
+ }
+ }
+}
+
+void
+toggletag(const char *args[])
+{
+ if (!sel)
+ return;
+ uint newtags = sel->tags ^ (bitoftag(args[0]) & TAGMASK);
+ if (newtags) {
+ sel->tags = newtags;
+ tagschanged();
+ }
+}
+
+void
+toggleview(const char *args[])
+{
+ uint newtagset = tagset[seltags] ^ (bitoftag(args[0]) & TAGMASK);
+ if (newtagset) {
+ tagset[seltags] = newtagset;
+ tagschanged();
+ }
+}
+
+void
+view(const char *args[])
+{
+ uint newtagset = bitoftag(args[0]) & TAGMASK;
+ if (tagset[seltags] != newtagset && newtagset) {
+ seltags ^= 1; /* toggle sel tagset */
+ tagset[seltags] = newtagset;
+ tagschanged();
+ }
+}
+
+void
+viewprevtag(const char *args[])
+{
+ seltags ^= 1;
+ tagschanged();
+}
+
+static
+void
+keypress(int code)
+{
+ int key = -1;
+ uint len = 1;
+ char buf[8] = { '\e' };
+
+ if (code == '\e') {
+ /* pass characters following escape to the underlying app */
+ nodelay(stdscr, TRUE);
+ for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++) {
+ if (t > 255) {
+ key = t;
+ break;
+ }
+ buf[len] = t;
+ }
+ nodelay(stdscr, FALSE);
+ }
+
+ for (Client *c = runinall ? nextvisible(clients) : sel; c; c = nextvisible(c->next)) {
+ if (is_content_visible(c)) {
+ c->urgent = false;
+ if (code == '\e')
+ vt_write(c->term, buf, len);
+ else
+ vt_keypress(c->term, code);
+
+ if (key != -1)
+ vt_keypress(c->term, key);
+ }
+ if (!runinall)
+ break;
+ }
+}
+
+static
+void
+mouse_setup(void)
+{
+#ifdef CONFIG_MOUSE
+ mmask_t mask = 0;
+
+ if (mouse_events_enabled) {
+ mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
+ for (uint i = 0; i < arrlen(buttons); i++)
+ mask |= buttons[i].mask;
+ }
+ mousemask(mask, nil);
+#endif /* CONFIG_MOUSE */
+}
+
+static bool
+checkshell(const char *shell) {
+ if (shell == nil || *shell == '\0' || *shell != '/')
+ return false;
+ if (!strcmp(strrchr(shell, '/')+1, dvtm_name))
+ return false;
+ if (access(shell, X_OK))
+ return false;
+ return true;
+}
+
+static const char *
+getshell(void) {
+ const char *shell = getenv("SHELL");
+ struct passwd *pw;
+
+ if (checkshell(shell))
+ return shell;
+ if ((pw = getpwuid(getuid())) && checkshell(pw->pw_shell))
+ return pw->pw_shell;
+ return "/bin/sh";
+}
+
+static
+void
+setup(void)
+{
+ shell = getshell();
+ setlocale(LC_CTYPE, "");
+ initscr();
+ start_color();
+ noecho();
+ nonl();
+ keypad(stdscr, TRUE);
+ mouse_setup();
+ raw();
+ vt_init();
+ vt_keytable_set(keytable, arrlen(keytable));
+ for (uint i = 0; i < arrlen(colors); i++) {
+ if (COLORS == 256) {
+ if (colors[i].fg256)
+ colors[i].fg = colors[i].fg256;
+ if (colors[i].bg256)
+ colors[i].bg = colors[i].bg256;
+ }
+ colors[i].pair = vt_color_reserve(colors[i].fg, colors[i].bg);
+ }
+ resize_screen();
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof sa);
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigwinch_handler;
+ sigaction(SIGWINCH, &sa, nil);
+ sa.sa_handler = sigchld_handler;
+ sigaction(SIGCHLD, &sa, nil);
+ sa.sa_handler = sigterm_handler;
+ sigaction(SIGTERM, &sa, nil);
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, nil);
+}
+
+static
+void
+destroy(Client *c) {
+ if (sel == c)
+ focusnextnm(nil);
+ detach(c);
+ detachstack(c);
+ if (sel == c) {
+ Client *next = nextvisible(clients);
+ if (next) {
+ focus(next);
+ toggleminimize(nil);
+ } else {
+ sel = nil;
+ }
+ }
+ if (lastsel == c)
+ lastsel = nil;
+ werase(c->window);
+ wnoutrefresh(c->window);
+ vt_destroy(c->term);
+ delwin(c->window);
+ if (!clients && arrlen(actions)) {
+ if (!strcmp(c->cmd, shell))
+ quit(nil);
+ else
+ create(nil);
+ }
+ free(c);
+ arrange();
+}
+
+static
+void
+cleanup(void) {
+ while (clients)
+ destroy(clients);
+ vt_shutdown();
+ endwin();
+ free(copyreg.data);
+ if (bar.fd > 0)
+ close(bar.fd);
+ if (bar.file)
+ unlink(bar.file);
+ if (cmdfifo.fd > 0)
+ close(cmdfifo.fd);
+ if (cmdfifo.file)
+ unlink(cmdfifo.file);
+}
+
+static
+char *getcwd_by_pid(Client *c) {
+ if (!c)
+ return nil;
+ char buf[32];
+ snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid);
+ return realpath(buf, nil);
+}
+
+void
+create(const char *args[])
+{
+ const char *pargs[4] = { shell, nil };
+ char buf[8], *cwd = nil;
+ const char *env[] = {
+ "DVTM_WINDOW_ID", buf,
+ nil
+ };
+
+ if (args && args[0]) {
+ pargs[1] = "-c";
+ pargs[2] = args[0];
+ pargs[3] = nil;
+ }
+ Client *c = calloc(1, sizeof(Client));
+ if (!c)
+ return;
+ c->tags = tagset[seltags];
+ c->id = ++cmdfifo.id;
+ snprintf(buf, sizeof buf, "%d", c->id);
+
+ if (!(c->window = newwin(wah, waw, way, wax))) {
+ free(c);
+ return;
+ }
+
+ c->term = c->app = vt_create(screen.h, screen.w, screen.history);
+ if (!c->term) {
+ delwin(c->window);
+ free(c);
+ return;
+ }
+
+ if (args && args[0]) {
+ c->cmd = args[0];
+ char name[PATH_MAX];
+ strncpy(name, args[0], sizeof(name));
+ name[sizeof(name)-1] = '\0';
+ strncpy(c->title, basename(name), sizeof(c->title));
+ } else {
+ c->cmd = shell;
+ }
+
+ if (args && args[1])
+ strncpy(c->title, args[1], sizeof(c->title));
+ c->title[sizeof(c->title)-1] = '\0';
+
+ if (args && args[2])
+ cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2];
+ c->pid = vt_forkpty(c->term, shell, pargs, cwd, env, nil, nil);
+ if (args && args[2] && !strcmp(args[2], "$CWD"))
+ free(cwd);
+ vt_data_set(c->term, c);
+ vt_title_handler_set(c->term, term_title_handler);
+ vt_urgent_handler_set(c->term, term_urgent_handler);
+ applycolorrules(c);
+ c->x = wax;
+ c->y = way;
+ debug("client with pid %d forked\n", c->pid);
+ attach(c);
+ focus(c);
+ arrange();
+}
+
+void
+copymode(const char *args[])
+{
+ if (!args || !args[0] || !sel || sel->editor)
+ return;
+
+ bool colored = strstr(args[0], "pager") != nil;
+
+ if (!(sel->editor = vt_create(sel->h - sel->has_title_line, sel->w, 0)))
+ return;
+
+ int *to = &sel->editor_fds[0];
+ int *from = strstr(args[0], "editor") ? &sel->editor_fds[1] : nil;
+ sel->editor_fds[0] = sel->editor_fds[1] = -1;
+
+ const char *argv[3] = { args[0], nil, nil };
+ char argline[32];
+ int line = vt_content_start(sel->app);
+ snprintf(argline, sizeof(argline), "+%d", line);
+ argv[1] = argline;
+
+ if (vt_forkpty(sel->editor, args[0], argv, nil, nil, to, from) < 0) {
+ vt_destroy(sel->editor);
+ sel->editor = nil;
+ return;
+ }
+
+ sel->term = sel->editor;
+
+ if (sel->editor_fds[0] != -1) {
+ char *buf = nil;
+ size_t len = vt_content_get(sel->app, &buf, colored);
+ char *cur = buf;
+ while (len > 0) {
+ ssize_t res = write(sel->editor_fds[0], cur, len);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ cur += res;
+ len -= res;
+ }
+ free(buf);
+ close(sel->editor_fds[0]);
+ sel->editor_fds[0] = -1;
+ }
+
+ if (args[1])
+ vt_write(sel->editor, args[1], strlen(args[1]));
+}
+
+void
+focusn(const char *args[])
+{
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c->order == atoi(args[0])) {
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ return;
+ }
+ }
+}
+
+void
+focusid(const char *args[])
+{
+ if (!args[0])
+ return;
+
+ const int win_id = atoi(args[0]);
+ for (Client *c = clients; c; c = c->next) {
+ if (c->id == win_id) {
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ if (!isvisible(c)) {
+ c->tags |= tagset[seltags];
+ tagschanged();
+ }
+ return;
+ }
+ }
+}
+
+void
+focusnext(const char *args[])
+{
+ Client *c;
+ if (!sel)
+ return;
+ for (c = sel->next; c && !isvisible(c); c = c->next);
+ if (!c)
+ for (c = clients; c && !isvisible(c); c = c->next);
+ if (c)
+ focus(c);
+}
+
+void
+focusnextnm(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = sel;
+ do {
+ c = nextvisible(c->next);
+ if (!c)
+ c = nextvisible(clients);
+ } while (c->minimized && c != sel);
+ focus(c);
+}
+
+void
+focusprev(const char *args[])
+{
+ Client *c;
+ if (!sel)
+ return;
+ for (c = sel->prev; c && !isvisible(c); c = c->prev);
+ if (!c) {
+ for (c = clients; c && c->next; c = c->next);
+ for (; c && !isvisible(c); c = c->prev);
+ }
+ if (c)
+ focus(c);
+}
+
+void
+focusprevnm(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = sel;
+ do {
+ for (c = c->prev; c && !isvisible(c); c = c->prev);
+ if (!c) {
+ for (c = clients; c && c->next; c = c->next);
+ for (; c && !isvisible(c); c = c->prev);
+ }
+ } while (c && c != sel && c->minimized);
+ focus(c);
+}
+
+void
+focuslast(const char *args[])
+{
+ if (lastsel)
+ focus(lastsel);
+}
+
+void
+focusup(const char *args[])
+{
+ if (!sel)
+ return;
+ /* avoid vertical separator, hence +1 in x direction */
+ Client *c = get_client_by_coord(sel->x + 1, sel->y - 1);
+ if (c)
+ focus(c);
+ else
+ focusprev(args);
+}
+
+void
+focusdown(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x, sel->y + sel->h);
+ if (c)
+ focus(c);
+ else
+ focusnext(args);
+}
+
+void
+focusleft(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x - 2, sel->y);
+ if (c)
+ focus(c);
+ else
+ focusprev(args);
+}
+
+void
+focusright(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x + sel->w + 1, sel->y);
+ if (c)
+ focus(c);
+ else
+ focusnext(args);
+}
+
+void
+killclient(const char *args[])
+{
+ if (!sel)
+ return;
+ debug("killing client with pid: %d\n", sel->pid);
+ kill(-sel->pid, SIGKILL);
+}
+
+void
+paste(const char *args[])
+{
+ if (sel && copyreg.data)
+ vt_write(sel->term, copyreg.data, copyreg.len);
+}
+
+void
+quit(const char *args[])
+{
+ cleanup();
+ exit(EXIT_SUCCESS);
+}
+
+void
+redraw(const char *args[])
+{
+ for (Client *c = clients; c; c = c->next) {
+ if (!c->minimized) {
+ vt_dirty(c->term);
+ wclear(c->window);
+ wnoutrefresh(c->window);
+ }
+ }
+ resize_screen();
+}
+
+void
+scrollback(const char *args[])
+{
+ if (!is_content_visible(sel))
+ return;
+
+ if (!args[0] || atoi(args[0]) < 0)
+ vt_scroll(sel->term, -sel->h/2);
+ else
+ vt_scroll(sel->term, sel->h/2);
+
+ draw(sel);
+ curs_set(vt_cursor_visible(sel->term));
+}
+
+void
+send(const char *args[])
+{
+ if (sel && args && args[0])
+ vt_write(sel->term, args[0], strlen(args[0]));
+}
+
+void
+setlayout(const char *args[])
+{
+ uint i;
+
+ if (!args || !args[0]) {
+ if (++layout == &layouts[arrlen(layouts)])
+ layout = &layouts[0];
+ } else {
+ for (i = 0; i < arrlen(layouts); i++)
+ if (!strcmp(args[0], layouts[i].symbol))
+ break;
+ if (i == arrlen(layouts))
+ return;
+ layout = &layouts[i];
+ }
+ arrange();
+}
+
+void
+incnmaster(const char *args[])
+{
+ int delta;
+
+ if (isarrange(fullscreen) || isarrange(grid))
+ return;
+ /* arg handling, manipulate nmaster */
+ if (args[0] == nil) {
+ screen.nmaster = NMASTER;
+ } else if (sscanf(args[0], "%d", &delta) == 1) {
+ if (args[0][0] == '+' || args[0][0] == '-')
+ screen.nmaster += delta;
+ else
+ screen.nmaster = delta;
+ if (screen.nmaster < 1)
+ screen.nmaster = 1;
+ }
+ arrange();
+}
+
+void
+setmfact(const char *args[])
+{
+ float delta;
+
+ if (isarrange(fullscreen) || isarrange(grid))
+ return;
+ /* arg handling, manipulate mfact */
+ if (args[0] == nil) {
+ screen.mfact = MFACT;
+ } else if (sscanf(args[0], "%f", &delta) == 1) {
+ if (args[0][0] == '+' || args[0][0] == '-')
+ screen.mfact += delta;
+ else
+ screen.mfact = delta;
+ if (screen.mfact < 0.1)
+ screen.mfact = 0.1;
+ else if (screen.mfact > 0.9)
+ screen.mfact = 0.9;
+ }
+ arrange();
+}
+
+void
+startup(const char *args[])
+{
+ for (uint i = 0; i < arrlen(actions); i++)
+ actions[i].cmd(actions[i].args);
+}
+
+void
+togglebar(const char *args[])
+{
+ if (bar.pos == BAR_OFF)
+ showbar();
+ else
+ hidebar();
+ bar.autohide = false;
+ updatebarpos();
+ redraw(nil);
+}
+
+void
+togglebarpos(const char *args[])
+{
+ switch (bar.pos == BAR_OFF ? bar.lastpos : bar.pos) {
+ case BAR_TOP:
+ bar.pos = BAR_BOTTOM;
+ break;
+ case BAR_BOTTOM:
+ bar.pos = BAR_TOP;
+ break;
+ }
+ updatebarpos();
+ redraw(nil);
+}
+
+void
+toggleminimize(const char *args[])
+{
+ Client *c, *m, *t;
+ uint n;
+ if (!sel)
+ return;
+ /* the last window can't be minimized */
+ if (!sel->minimized) {
+ for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next))
+ if (!c->minimized)
+ n++;
+ if (n == 1)
+ return;
+ }
+ sel->minimized = !sel->minimized;
+ m = sel;
+ /* check whether the master client was minimized */
+ if (sel == nextvisible(clients) && sel->minimized) {
+ c = nextvisible(sel->next);
+ detach(c);
+ attach(c);
+ focus(c);
+ detach(m);
+ for (; c && (t = nextvisible(c->next)) && !t->minimized; c = t);
+ attachafter(m, c);
+ } else if (m->minimized) {
+ /* non master window got minimized move it above all other
+ * minimized ones */
+ focusnextnm(nil);
+ detach(m);
+ for (c = nextvisible(clients); c && (t = nextvisible(c->next)) && !t->minimized; c = t);
+ attachafter(m, c);
+ } else { /* window is no longer minimized, move it to the master area */
+ vt_dirty(m->term);
+ detach(m);
+ attach(m);
+ }
+ arrange();
+}
+
+void
+togglemouse(const char *args[])
+{
+ mouse_events_enabled = !mouse_events_enabled;
+ mouse_setup();
+}
+
+void
+togglerunall(const char *args[])
+{
+ runinall = !runinall;
+ drawbar();
+ draw_all();
+}
+
+void
+zoom(const char *args[])
+{
+ Client *c;
+
+ if (!sel)
+ return;
+ if (args && args[0])
+ focusn(args);
+ if ((c = sel) == nextvisible(clients))
+ if (!(c = nextvisible(c->next)))
+ return;
+ detach(c);
+ attach(c);
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ arrange();
+}
+
+/* commands for use by mouse bindings */
+void
+mouse_focus(const char *args[])
+{
+ focus(msel);
+ if (msel->minimized)
+ toggleminimize(nil);
+}
+
+void
+mouse_fullscreen(const char *args[])
+{
+ mouse_focus(nil);
+ setlayout(isarrange(fullscreen) ? nil : args);
+}
+
+void
+mouse_minimize(const char *args[])
+{
+ focus(msel);
+ toggleminimize(nil);
+}
+
+void
+mouse_zoom(const char *args[])
+{
+ focus(msel);
+ zoom(nil);
+}
+
+static
+Cmd *
+get_cmd_by_name(const char *name) {
+ for (uint i = 0; i < arrlen(commands); i++) {
+ if (!strcmp(name, commands[i].name))
+ return &commands[i];
+ }
+ return nil;
+}
+
+static
+void
+handle_cmdfifo(void) {
+ int r;
+ char *p, *s, cmdbuf[512], c;
+ Cmd *cmd;
+
+ r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1);
+ if (r <= 0) {
+ cmdfifo.fd = -1;
+ return;
+ }
+
+ cmdbuf[r] = '\0';
+ p = cmdbuf;
+ while (*p) {
+ /* find the command name */
+ for (; *p == ' ' || *p == '\n'; p++);
+ for (s = p; *p && *p != ' ' && *p != '\n'; p++);
+ if ((c = *p))
+ *p++ = '\0';
+ if (*s && (cmd = get_cmd_by_name(s)) != nil) {
+ bool quote = false;
+ int argc = 0;
+ const char *args[MAX_ARGS], *arg;
+ memset(args, 0, sizeof(args));
+ /* if arguments were specified in config.h ignore the one given via
+ * the named pipe and thus skip everything until we find a new line
+ */
+ if (cmd->action.args[0] || c == '\n') {
+ debug("execute %s", s);
+ cmd->action.cmd(cmd->action.args);
+ while (*p && *p != '\n')
+ p++;
+ continue;
+ }
+ /* no arguments were given in config.h so we parse the command line */
+ while (*p == ' ')
+ p++;
+ arg = p;
+ for (; (c = *p); p++) {
+ switch (*p) {
+ case '\\':
+ /* remove the escape character '\\' move every
+ * following character to the left by one position
+ */
+ switch (p[1]) {
+ case '\\':
+ case '\'':
+ case '\"': {
+ char *t = p+1;
+ do {
+ t[-1] = *t;
+ } while (*t++);
+ }
+ }
+ break;
+ case '\'':
+ case '\"':
+ quote = !quote;
+ break;
+ case ' ':
+ if (!quote) {
+ case '\n':
+ /* remove trailing quote if there is one */
+ if (*(p - 1) == '\'' || *(p - 1) == '\"')
+ *(p - 1) = '\0';
+ *p++ = '\0';
+ /* remove leading quote if there is one */
+ if (*arg == '\'' || *arg == '\"')
+ arg++;
+ if (argc < MAX_ARGS)
+ args[argc++] = arg;
+
+ while (*p == ' ')
+ ++p;
+ arg = p--;
+ }
+ break;
+ }
+
+ if (c == '\n' || *p == '\n') {
+ if (!*p)
+ p++;
+ debug("execute %s", s);
+ for(int i = 0; i < argc; i++)
+ debug(" %s", args[i]);
+ debug("\n");
+ cmd->action.cmd(args);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+handle_mouse(void) {
+#ifdef CONFIG_MOUSE
+ MEVENT event;
+ uint i;
+ if (getmouse(&event) != OK)
+ return;
+ msel = get_client_by_coord(event.x, event.y);
+
+ if (!msel)
+ return;
+
+ 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);
+
+ vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
+
+ for (i = 0; i < arrlen(buttons); i++) {
+ if (event.bstate & buttons[i].mask)
+ buttons[i].action.cmd(buttons[i].action.args);
+ }
+
+ msel = nil;
+#endif /* CONFIG_MOUSE */
+}
+
+static void
+handle_statusbar(void) {
+ char *p;
+ int r;
+ switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
+ case -1:
+ strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
+ bar.text[sizeof bar.text - 1] = '\0';
+ bar.fd = -1;
+ break;
+ case 0:
+ bar.fd = -1;
+ break;
+ default:
+ bar.text[r] = '\0';
+ p = bar.text + r - 1;
+ for (; p >= bar.text && *p == '\n'; *p-- = '\0');
+ for (; p >= bar.text && *p != '\n'; --p);
+ if (p >= bar.text)
+ memmove(bar.text, p + 1, strlen(p));
+ drawbar();
+ }
+}
+
+static void
+handle_editor(Client *c) {
+ if (!copyreg.data && (copyreg.data = malloc(screen.history)))
+ copyreg.size = screen.history;
+ copyreg.len = 0;
+ while (c->editor_fds[1] != -1 && copyreg.len < copyreg.size) {
+ ssize_t len = read(c->editor_fds[1], copyreg.data + copyreg.len, copyreg.size - copyreg.len);
+ if (len == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (len == 0)
+ break;
+ copyreg.len += len;
+ if (copyreg.len == copyreg.size) {
+ copyreg.size *= 2;
+ if (!(copyreg.data = realloc(copyreg.data, copyreg.size))) {
+ copyreg.size = 0;
+ copyreg.len = 0;
+ }
+ }
+ }
+ c->editor_died = false;
+ c->editor_fds[1] = -1;
+ vt_destroy(c->editor);
+ c->editor = nil;
+ c->term = c->app;
+ vt_dirty(c->term);
+ draw_content(c);
+ wnoutrefresh(c->window);
+}
+
+static int
+open_or_create_fifo(const char *name, const char **name_created) {
+ struct stat info;
+ int fd;
+
+ do {
+ if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
+ if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
+ *name_created = name;
+ continue;
+ }
+ fatal("%s\n", strerror(errno));
+ }
+ } while (fd == -1);
+
+ if (fstat(fd, &info) == -1)
+ fatal("%s\n", strerror(errno));
+ if (!S_ISFIFO(info.st_mode))
+ fatal("%s is not a named pipe\n", name);
+ return fd;
+}
+
+static
+void
+usage(void) {
+ cleanup();
+ eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
+ "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
+ exit(EXIT_FAILURE);
+}
+
+static
+bool
+parse_args(int argc, char *argv[]) {
+ bool init = false;
+ const char *name = argv[0];
+
+ if (name && (name = strrchr(name, '/')))
+ dvtm_name = name + 1;
+ if (!getenv("ESCDELAY"))
+ set_escdelay(100);
+ for (int arg = 1; arg < argc; arg++) {
+ if (argv[arg][0] != '-') {
+ const char *args[] = { argv[arg], nil, nil };
+ if (!init) {
+ setup();
+ init = true;
+ }
+ create(args);
+ continue;
+ }
+ if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc)
+ usage();
+ switch (argv[arg][1]) {
+ case 'v':
+ puts("dvtm-"VERSION);
+ exit(EXIT_SUCCESS);
+ case 'M':
+ mouse_events_enabled = !mouse_events_enabled;
+ break;
+ case 'm': {
+ char *mod = argv[++arg];
+ if (mod[0] == '^' && mod[1])
+ *mod = CTRL(mod[1]);
+ for (uint b = 0; b < arrlen(bindings); b++)
+ if (bindings[b].keys[0] == MOD)
+ bindings[b].keys[0] = *mod;
+ break;
+ }
+ case 'd':
+ set_escdelay(atoi(argv[++arg]));
+ if (ESCDELAY < 50)
+ set_escdelay(50);
+ else if (ESCDELAY > 1000)
+ set_escdelay(1000);
+ break;
+ case 'h':
+ screen.history = atoi(argv[++arg]);
+ break;
+ case 't':
+ title = argv[++arg];
+ break;
+ case 's':
+ bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
+ updatebarpos();
+ break;
+ case 'c': {
+ const char *fifo;
+ cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
+ if (!(fifo = realpath(argv[arg], nil)))
+ fatal("%s\n", strerror(errno));
+ setenv("DVTM_CMD_FIFO", fifo, 1);
+ break;
+ }
+ default:
+ usage();
+ }
+ }
+ return init;
+}
+
+int
+main(int argc, char *argv[])
+{
+ KeyCombo keys;
+ uint key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ sigset_t emptyset, blockset;
+
+ setenv("DVTM", VERSION, 1);
+ if (!parse_args(argc, argv)) {
+ setup();
+ startup(nil);
+ }
+
+ sigemptyset(&emptyset);
+ sigemptyset(&blockset);
+ sigaddset(&blockset, SIGWINCH);
+ sigaddset(&blockset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &blockset, nil);
+
+ while (running) {
+ int r, nfds = 0;
+ fd_set rd;
+
+ if (screen.need_resize) {
+ resize_screen();
+ screen.need_resize = false;
+ }
+
+ FD_ZERO(&rd);
+ FD_SET(STDIN_FILENO, &rd);
+
+ if (cmdfifo.fd != -1) {
+ FD_SET(cmdfifo.fd, &rd);
+ nfds = cmdfifo.fd;
+ }
+
+ if (bar.fd != -1) {
+ FD_SET(bar.fd, &rd);
+ nfds = MAX(nfds, bar.fd);
+ }
+
+ for (Client *c = clients; c;) {
+ if (c->editor && c->editor_died)
+ handle_editor(c);
+ if (!c->editor && c->died) {
+ Client *t = c->next;
+ destroy(c);
+ c = t;
+ continue;
+ }
+ int pty = c->editor ? vt_pty_get(c->editor) : vt_pty_get(c->app);
+ FD_SET(pty, &rd);
+ nfds = MAX(nfds, pty);
+ c = c->next;
+ }
+
+ doupdate();
+ r = pselect(nfds + 1, &rd, nil, nil, nil, &emptyset);
+
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select()");
+ exit(EXIT_FAILURE);
+ }
+
+ if (FD_ISSET(STDIN_FILENO, &rd)) {
+ /* NOTE: this is the input handling step */
+ int code = getch();
+ if (code >= 0) {
+ keys[key_index++] = code;
+ KeyBinding *binding = nil;
+
+ if (code == KEY_MOUSE) {
+ key_index = 0;
+ handle_mouse();
+ } else if ((binding = keybinding(keys, key_index))) {
+ uint key_length = MAX_KEYS;
+ while (key_length > 1 && !binding->keys[key_length-1])
+ key_length--;
+ if (key_index == key_length) {
+ binding->action.cmd(binding->action.args);
+ key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ }
+ } else {
+ key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ keypress(code);
+ }
+ }
+ if (r == 1) /* no data available on pty's */
+ continue;
+ }
+
+ if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
+ handle_cmdfifo();
+
+ if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
+ handle_statusbar();
+
+ for (Client *c = clients; c; c = c->next) {
+ if (FD_ISSET(vt_pty_get(c->term), &rd)) {
+ if (vt_process(c->term) < 0 && errno == EIO) {
+ if (c->editor)
+ c->editor_died = true;
+ else
+ c->died = true;
+ continue;
+ }
+ }
+
+ if (c != sel && is_content_visible(c)) {
+ draw_content(c);
+ wnoutrefresh(c->window);
+ }
+ }
+
+ if (is_content_visible(sel)) {
+ draw_content(sel);
+ curs_set(vt_cursor_visible(sel->term));
+ wnoutrefresh(sel->window);
+ }
+ }
+
+ cleanup();
+ return 0;
+}