#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; short 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)) { 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; }