/* * seven terminal emulator and more * * big diagram * * client - pane input * ^v | * shared - script | * ^ v * server - emulate <- pty * */ #include "config.h" #include #include #include #include #include #ifdef __APPLE__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defaults.h" #include "emulator.h" #include "keysym2ucs.h" #include "multigraph.h" #include "pane.h" #include "panic.h" #include "print_event.h" #include "proto.h" #include "pty-outfifo.h" #include "seven_client.h" #include "unicode.h" #include "unix.h" #include "x11.h" #include "xatoms.h" #include "selections.h" #include "keys.h" #include "mod.h" #include "debug.h" static void queue(unsigned what); void sevenprog_1(struct svc_req *rqstp, register SVCXPRT *transp); static bool is_visible; static bool is_drawing; static int draw_pending; static int num_pending = 0; static int num_final = 0; static struct pane *pane; static sc_t *sc; static struct emulate *emulate; static char *svc_socket; static SVCXPRT *svc; static void *dummy_ptr; static char *arg_option; static struct terminfo_data terminfo_data; // IO Channels static int x_channel_watch = -1; #define PEND_QCALLBACK 1 #define PEND_TIORESIZE 2 #define PEND_XFLUSH 4 #define PEND_XEXPOSE (PEND_XFLUSH | 8) static unsigned pending; static fifo_t in_fifo = FIFO_BLANK; static int opt_nofork; static int opt_detached; static int opt_dumpopts; static int opt_version; static struct selection_info selection; static void do_redraw(void); #ifndef NDEBUG bool opt_debug; #endif static const struct poptOption options[] = { { NULL, 0, POPT_ARG_INCLUDE_TABLE, (void *)x11_options, 0, "X Windows options", NULL }, { "option", 'o', POPT_ARG_STRING, &arg_option, 'o', "set any configuration option", "name:value" }, { "exec", 'e', POPT_ARG_STRING, NULL, 'e', "Command to run", "command [ args ..]"}, { "detached", 'd', POPT_ARG_NONE, &opt_detached, 0, "Do not connect to display, start in detatched mode.", NULL}, { "nofork", 0, POPT_ARG_NONE, &opt_nofork, 0, "Stay in the foreground, quit with the child programs exit status.", NULL}, { "dumpopts", 0, POPT_ARG_NONE, &opt_dumpopts, 0, "Dump options to stdout", NULL}, { "version" , 0, POPT_ARG_NONE, &opt_version, 0, "print version number and exit", NULL}, #ifndef NDEBUG { "debug" , 0, POPT_ARG_NONE, &opt_debug, 0, "print out debugging info", NULL}, #endif POPT_AUTOHELP POPT_TABLEEND }; static gboolean event_finalizer(void *null) { num_final++; if(pending & PEND_TIORESIZE) ioctl(pty->fd, TIOCSWINSZ, &pty->winsize); if((pending & PEND_XEXPOSE) == PEND_XEXPOSE ) {draw_pending++; do_redraw(); } // if((pending & PEND_XEXPOSE) == PEND_XEXPOSE ) { // if(!is_visible || is_drawing) { // draw_pending = true; // } else { // XEvent ev; // memset(&ev, 0, sizeof(ev)); // ev.type = ClientMessage; // ev.xclient.data.l[0]=XA_SEVEN_PING; // ev.xany.window = win.w; // ev.xany.display = conn->dpy; // ev.xclient.format = 32; // ev.xclient.message_type = XA_ATOM; // // pane_draw(pane); // XSendEvent(conn->dpy, win.w, false, 0, &ev); // is_drawing = true; // } // } if((pending & PEND_XFLUSH) && conn) XFlush(conn->dpy); pending = 0; //if((num_final % 10) == 0) // fprintf(stderr, "p/f = %i/%i %lf\n", num_pending, num_final, (double)num_pending/(double)num_final); return false; } static void add_timeval(struct timeval *tv, int num) { tv->tv_usec += num; tv->tv_sec += tv->tv_usec / 1000000; tv->tv_usec %= 1000000; } static void do_redraw(void) { if(!conn) return; is_drawing = false; if(!is_visible || is_drawing) { draw_pending++; } else { pane_draw(pane, true, true); is_drawing = true; draw_pending = 0; XFlush(conn->dpy); } } static void queue(unsigned what) { assert(what); if(!pending) { g_idle_add(event_finalizer,NULL); } pending |= what; } static void resize_event(int w, int h) { int fw, fh; int cwidth, cheight; if (w == win.width && h == win.height) return; win.width = w; win.height = h; get_charcell_size(&fw, &fh); if(DB_force80x25) { cheight = 25; cwidth = 80; } else { cheight = h / fh; cwidth = w / fw; } pty->winsize.ws_row = cheight; pty->winsize.ws_col = cwidth; pty->winsize.ws_xpixel = w; pty->winsize.ws_ypixel = h; pane_setup(pane, cwidth, cheight); emulate_set_size(emulate, cwidth, cheight); queue(PEND_TIORESIZE | PEND_XEXPOSE); //ioctl(pty->fd, TIOCSWINSZ, &pty->winsize); do_redraw(); if (!XSyncValueIsZero(win.syncValue)) { XSyncSetCounter(conn->dpy, win.syncCounter,win.syncValue); XSyncIntToValue(&win.syncValue,0); } } //TODO //fix overflow bugs. static void handle_keypress(XKeyEvent kev) { KeySym keysym; char *ptr = NULL; static char mg_buf[64]; static char *mg_state = NULL; char buf[41]; int n = XLookupString(&kev,buf,40,&keysym,NULL); if(n > 0) { buf[n] = '\0'; ptr = buf; } //if(!mg_state && keysym == XK_5 && (kev.state & ControlMask)) { if(xevent_match_key((XEvent *)&kev, &DK_keyDumpStats)) { print_emulator_state(stderr, emulate); return; } //if(!mg_state && keysym == XK_Insert && (kev.state & ShiftMask)) { if(xevent_match_key((XEvent *)&kev, &DK_keyInsertSelection)) { selection_get(&selection, conn->dpy, win.w, XA_PRIMARY, XA_TEXT, kev.time); queue(PEND_XFLUSH); return; } if(!mg_state && keysym == XK_Insert && (kev.state & ControlMask)) { selection_get(&selection, conn->dpy, win.w, XA_PRIMARY, XA_TARGETS, kev.time); queue(PEND_XFLUSH); return; } //if(!mg_state && keysym == XK_2 && (kev.state & ControlMask)) { if(xevent_match_key((XEvent *)&kev, &DK_keyMultigraphStart)) { XSetWindowBorder(conn->dpy, win.w, DC_digraphBorderColor->pixel); queue(PEND_XFLUSH); mg_state = mg_buf; memset(mg_buf, 0, sizeof(mg_buf)); return; } if(mg_state) { struct mgraph *mg; strcpy(mg_state, buf); mg_state += n; mg = bsearch(mg_buf, mgraphs, NUM_MGRAPHS, sizeof(mgraph_t), compare_mgraph_exact); if(mg) { send_wchar( pty, mg->c); mg_state = NULL; XSetWindowBorder(conn->dpy, win.w, DC_borderColor->pixel); queue(PEND_XFLUSH); return; } mg = bsearch(mg_buf, mgraphs, NUM_MGRAPHS, sizeof(mgraph_t), compare_mgraph_substr); if(!mg) { mg_state = NULL; XSetWindowBorder(conn->dpy, win.w, DC_borderColor->pixel); queue(PEND_XFLUSH); } else return; } if(xevent_match_key((XEvent *)&kev, &DK_keyDumpStats) ) { char *s; asprintf(&s, "#num_pending %u\n#num_final %u\n#out_fifo %u %u\n#in_fifo %u %u\n", num_pending, num_final, -1, -1, FIFO_LEN(in_fifo), in_fifo.rb.size); stuff_buf(pty, s, strlen(s)); free(s); return; } char *ptr_ = process_key(keysym,emulate,&terminfo_data); if(ptr_) ptr = ptr_; if(ptr) { for(;*ptr;ptr++) send_wchar(pty, *ptr); } else { unsigned cp = keysym2ucs(keysym); if (cp != -1) send_wchar(pty, cp); } } static void update_timestamp(Display *disp, Window w, Time time) { unsigned long t = (unsigned long)time; XChangeProperty(disp, w, XA__NET_WM_USER_TIME, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&t, 1); } static gboolean on_x_event(GIOChannel *source, GIOCondition event, void *null) { XEvent ev; if(!IS_ATTACHED()) return true; while(XPending(conn->dpy)) { XNextEvent(conn->dpy, &ev); //print_event(conn->dpy, ev); switch(ev.type) { case ClientMessage: if((Atom)ev.xclient.data.l[0]==XA_WM_DELETE_WINDOW) { g_main_loop_quit(mainloop); return true; } if((Atom)ev.xclient.data.l[0]==XA__NET_WM_PING) { ev.xclient.window = DefaultRootWindow(conn->dpy); XSendEvent(conn->dpy, DefaultRootWindow(conn->dpy), False,SubstructureRedirectMask | SubstructureNotifyMask, &ev); } else if((Atom)ev.xclient.data.l[0]==XA__NET_WM_SYNC_REQUEST) { printf("_NET_WM_SYNC_REQUEST: %8.8lx%8.8lx\n", (unsigned long)ev.xclient.data.l[3],(unsigned long)ev.xclient.data.l[2]); XSyncIntsToValue((&win.syncValue), ev.xclient.data.l[2], ev.xclient.data.l[3]); } else if((Atom)ev.xclient.data.l[0]==XA_SEVEN_PING) { is_drawing = false; queue(PEND_XEXPOSE); //if(draw_pending) // do_redraw(); //return NULL; } else print_event( ev); break; case Expose: pane_toucharea(pane, ev.xexpose.x, ev.xexpose.y, ev.xexpose.width, ev.xexpose.height); draw_pending++; //do_redraw(); //queue(PEND_XEXPOSE); break; case KeyPress: update_timestamp(conn->dpy, win.w, ev.xkey.time); pane_scrollback(pane, 0); handle_keypress(ev.xkey); break; case ButtonPress: update_timestamp(conn->dpy, win.w, ev.xbutton.time); if(ev.xbutton.button == 4) { pane_scrollback(pane, DI_wheelScrollAmount); draw_pending++; //do_redraw(); } else if(ev.xbutton.button == 5) { pane_scrollback(pane, -DI_wheelScrollAmount); draw_pending++; //do_redraw(); } else print_event( ev); //queue(PEND_XEXPOSE); break; case MappingNotify: XRefreshKeyboardMapping(&(ev.xmapping)); queue(PEND_XFLUSH); break; case ConfigureNotify: resize_event(ev.xconfigure.width, ev.xconfigure.height); break; case UnmapNotify: is_visible = false; case MapNotify: break; case PropertyNotify: case SelectionNotify: { void *buf = NULL; int len = 0; //print_event( ev); if(selection_process(&selection, &ev, &buf, &len)) { if(selection.type == None) break; printf("got selection: %s\n", XGetAtomName(conn->dpy, selection.type)); if(selection.type != XA_ATOM) stuff_buf(pty, buf, len); else { for(int i = 0; i < len; i++) printf("%s\n", XGetAtomName(conn->dpy, ((Atom *)buf)[i]));; } if(buf) XFree(buf); } break; } case VisibilityNotify: switch (ev.xvisibility.state) { case VisibilityUnobscured: case VisibilityPartiallyObscured: is_visible = true; break; case VisibilityFullyObscured: is_visible = false; break; default: break; } break; default: print_event( ev); } } num_pending++; //pane_draw(pane, true, false); // XFlush(conn->dpy); if(draw_pending) do_redraw(); XFlush(conn->dpy); return true; } #define INCOMING_BUFFER 4096 static gboolean on_incoming_data(GIOChannel *source, GIOCondition event, void *_null) { unsigned char buf[INCOMING_BUFFER]; wchar_t wc; int i,r; int fd = g_io_channel_unix_get_fd(source); while(1) { errno = 0; ssize_t ret = read(fd, buf, INCOMING_BUFFER); if(!ret || errno == EIO) { close(fd); g_main_loop_quit(mainloop); return true; } if(ret == -1) { if(errno == EWOULDBLOCK) break; panicnof("read: %s",pty->name); } //fprintf(stderr, "on_incoming_data %u %i\n", FIFO_LEN(in_fifo), ret); fifo_append(&in_fifo, buf, ret); do { char *p = FIFO_PTR(in_fifo); int len = FIFO_LEN(in_fifo); for(i = 0; i < len && !(p[i]&0x80); i++); if (i) { emulate_data(emulate, p, i, sc, true); //draw_pending++; queue(PEND_XEXPOSE); FIFO_POP(in_fifo, i); len -= i; p += i; } if(len) { r = utf8_mbtowc(&wc,p,len); if(!r) continue; if(r < 0) { r = -r; if(opt_debug) { fprintf(stderr, "invalid bytes:"); for (int x = 0; x < r; x++) fprintf(stderr, " %02x", (unsigned)((unsigned char *)FIFO_PTR(in_fifo))[x]); fprintf(stderr, "\n"); } } else { emulate_data(emulate, &wc, 1, sc, false); draw_pending++; } FIFO_POP(in_fifo, r); } else r = 0; } while(!FIFO_EMPTY(in_fifo) && (i || r)); } num_pending++; //queue(PEND_XEXPOSE); //if(draw_pending) // do_redraw(); return true; } static gboolean on_svc(GIOChannel *source, GIOCondition event, void *null) { if(opt_debug) fprintf(stderr, "on_svc(%p,%i,%p)\n", source, event, null); svc_run(); xprt_register(svc); return true; } static void done(void) { unlink(svc_socket); } struct seven_info * sproto_getinfo_1_svc(struct svc_req *rqstp) { static struct seven_info result; static struct x11_info xi; result.connections.connections_val = ξ if(conn) { result.connections.connections_len = 1; xi.xwindow = win.w; xi.display = DisplayString(conn->dpy); xi.xauthority = (getenv("XAUTHORITY")); xi.server_vendor = ServerVendor(conn->dpy); } else { result.connections.connections_len = 0; } result.name = pty->sty; result.title = ("title"); result.klass = "seven"; svc_exit(); return &result; } char ** sproto_attach_1_svc(struct x11_info x11, struct svc_req *rq) { static char *buf = ""; if(!conn) { x11_attach(&x11); pane_attach(pane,conn); XMapWindow(conn->dpy, win.w); XFlush(conn->dpy); GIOChannel *x_channel = g_io_channel_unix_new(ConnectionNumber(conn->dpy)); x_channel_watch = g_io_add_watch(x_channel, G_IO_IN | G_IO_HUP | G_IO_ERR, on_x_event, NULL); g_io_channel_unref(x_channel); } svc_exit(); return &buf; } void * sproto_detach_1_svc(struct svc_req *rq) { if(conn) { if (x_channel_watch != -1) { g_source_remove(x_channel_watch); x_channel_watch = -1; } pane_detach(pane); x11_detach(); } svc_exit(); return &dummy_ptr; } void * sproto_switchscreen_1_svc(int nscreen, struct svc_req *rq) { if(opt_debug) fprintf(stderr, "switchscreen(%i)\n", nscreen); x11_switchscreen(nscreen); XSelectInput(conn->dpy,win.w,KeyPressMask|ExposureMask|StructureNotifyMask|VisibilityChangeMask|ButtonPressMask|PropertyChangeMask); XMapWindow(conn->dpy,win.w); pane_touchall(pane); queue(PEND_XFLUSH); svc_exit(); return &dummy_ptr; } static void app_color(rb_t *rb, struct color *c) { char buf[32]; sprintf(buf, "#%2.2x%2.2x%2.2x", c->red / 256, c->green / 256, c->blue / 256); rbs_append(rb, buf); } static void rbs_printf(rb_t *rb, char *fmt, ...) { char *s; va_list ap; va_start(ap, fmt); int ret = vasprintf(&s, fmt, ap); if(ret == -1) panicno("rbs_printf"); rbs_append(rb, s); free(s); va_end(ap); } static void app_wchar(rb_t *rb, wchar_t wc) { switch(wc) { case '&': rbs_append(rb, "&"); return; case '<': rbs_append(rb, "<"); return; case '>': rbs_append(rb, ">"); return; case 0: rbs_append(rb, " "); return; } if(wc > 0x7f) rbs_printf(rb, "&#%u;", wc); else RB_APPEND(char, rb, wc); } static void app_attribute(rb_t *rb, struct cell c, bool start) { if(c.fgcolor == DC_foreground && c.bgcolor == DC_background) return; if(start) { rbs_append(rb, ""); } else { rbs_append(rb,""); } } char ** sproto_textshot_1_svc(bool_t get_all, struct svc_req *rqstp) { static rb_t rb = RB_BLANK; static char *rb_ptr = NULL; RB_CLEAR(rb); rbs_append(&rb, "
\n", emulate->cols);
        struct cell oc;
        memset(&oc, 0, sizeof(struct cell));
        //int height = get_all ? RBL(struct line, &p->lines) : pane->height;
        int start = get_all ? (pane->height - RBL(struct line, &pane->lines)) : 0;

        for(int j = start; j < pane->height; j++) {
                for(int i = 0; i < pane->width; i++) {
                        struct cell *c = cell(pane,i,j, false);
                        if(!c) {
                                if(oc.fgcolor)
                                                app_attribute(&rb, oc, false);
                                rbs_append(&rb, " ");
                                oc.fgcolor = NULL;
                        } else {
                                struct cell nc = *c;
                                nc.ch = 0;
                                if(memcmp(&nc, &oc, sizeof(struct cell))) {
                                        if(!(i == 0 && j == 0))
                                                app_attribute(&rb, oc, false);
                                        app_attribute(&rb, nc, true);
                                }
                                //app_attribute(&rb, *c, true);
                                app_wchar(&rb, c->ch);
                                //app_attribute(&rb, *c, false);
                                oc = nc;
                        }
                }
                rbs_append(&rb, "\n");
        }


        app_attribute(&rb, oc, false);
        rbs_append(&rb,"
"); rb_stringize(&rb); svc_exit(); rb_ptr = RB_PTR(&rb); return (char **)&rb_ptr; } static const char * const d_dargv[] = { "/bin/sh", NULL }; int main(int argc, const char *argv[]) { char *buf; int rc; setlocale(LC_ALL, ""); poptContext popt = poptGetContext(PACKAGE, argc, argv, options, POPT_CONTEXT_POSIXMEHARDER); char **dargv = (char **)d_dargv; while ((rc = poptGetNextOpt(popt)) > 0) { switch(rc) { case 'o': { char *opt = strchr(arg_option, ':'); if(!opt) fprintf(stderr, "Invalid option string: %s\nIt should be of form 'key:value'.\n", arg_option); *opt++ = '\0'; defaults_option(arg_option, opt); break; } // -e is tricky since it is not a normal option. case 'e': { const char **av = argv; int ac = argc; while (!(!strcmp(*av, "-e") || !strcmp(*av, "--exec")) ) { int l = strlen(*av); if(l > 1 && (*av)[0] == '-' && (*av)[1] != '-' && strchr((*av) + 1, 'e')) { fprintf(stderr, "-e may not occur in a compound option.\n"); goto opt_error; } av++; ac--; } if(!ac) goto opt_error; av++; dargv = malloc(sizeof(char *) * (ac + 1)); memcpy(dargv, av, sizeof(char *) * ac); dargv[ac] = NULL; goto done; } } } if (rc < -1) { opt_error: /* an error occurred during option processing */ fprintf(stderr, "%s: %s\n", poptBadOption(popt, POPT_BADOPTION_NOALIAS), poptStrerror(rc)); exit(1); } if(poptPeekArg(popt)) { const char **av = argv; int ac = argc; while (*av != poptPeekArg(popt)) { av++; ac--; } poptFreeContext(popt); do_client_call( ac, av); return 0; } done: poptFreeContext(popt); if(opt_version) { puts(PACKAGE_STRING); return 0; } // store arguments so they can be passed to screens x11_init(argc, (char **)argv); if(!opt_detached) { struct x11_info x; x11_get_info(&x); x11_attach(&x); } else { defaults_finalize(); } get_terminfo_data(&terminfo_data); unsetenv("LINES"); unsetenv("COLUMNS"); unsetenv("TERMCAP"); asprintf(&buf, "TERM=%s", D_terminalToEmulate); putenv(buf); if(!opt_detached) { asprintf(&buf, "WINDOWID=%lu", win.w); putenv(buf); } char *shell; if (dargv == (char **)d_dargv && (shell = getenv("SHELL"))) { dargv = malloc(sizeof(char *) * 2); dargv[0] = shell; dargv[1] = NULL; } char **cs = dargv; if(opt_debug) { while(*cs) fprintf(stderr, "%s ", *(cs++)); fprintf(stderr, "\n"); } if(opt_dumpopts) { defaults_print(); return 0; } if(terminfo_data.kbs[0] == '\0' || terminfo_data.kbs[1] != '\0') { fprintf(stderr, "Backspace is not single character code. Is your terminfo corrupt?\n"); pty = spawn_p(dargv, 0177); } else pty = spawn_p(dargv, terminfo_data.kbs[0]); if(dargv != (char **)d_dargv) free(dargv); chdir("/"); // to not hold mountpoints open emulate = emulate_create(); emulate->pty = pty; if(opt_debug) { fprintf(stderr, "STY=%s\n", pty->sty); } sc = script_new(); pane = pane_create(sc); mainloop = g_main_loop_new(NULL,true); if(!opt_detached) { pane_attach(pane,conn); XSelectInput(conn->dpy,win.w,KeyPressMask|ExposureMask|StructureNotifyMask|VisibilityChangeMask|ButtonPressMask|PropertyChangeMask); } else { pane_setup(pane,80,25); emulate_set_size(emulate, 80,25); pty->winsize.ws_row = 80; pty->winsize.ws_col = 25; pty->winsize.ws_xpixel = 80; pty->winsize.ws_ypixel = 25; } // set the pty to be in non-blocking mode int fl = fcntl(pty->fd, F_GETFL); if(fl == -1) panicno("fcntl"); fl |= O_NONBLOCK; fl = fcntl(pty->fd, F_SETFL, fl); if(fl == -1) panicno("fcntl"); if(!opt_detached) { win.width++; resize_event(win.width - 1,win.height); XSync(conn->dpy, False); on_x_event(NULL,0,NULL); GIOChannel *x_channel = g_io_channel_unix_new(ConnectionNumber(conn->dpy)); x_channel_watch = g_io_add_watch_full(x_channel, G_PRIORITY_DEFAULT, G_IO_IN | G_IO_HUP | G_IO_ERR, on_x_event, NULL, NULL); g_io_channel_unref(x_channel); } if(!pty_channel) pty_channel = g_io_channel_unix_new(pty->fd); g_io_add_watch(pty_channel, G_IO_IN | G_IO_HUP | G_IO_ERR, on_incoming_data, NULL); mkdir("/tmp/seven", 01777); asprintf(&buf, "/tmp/seven/%u", getuid()); mkdir(buf, 0700); free(buf); asprintf(&svc_socket, "/tmp/seven/%u/%s", getuid(), pty->sty); svc = svcunix_create(RPC_ANYSOCK,0,0,svc_socket); if (svc == NULL) { fprintf (stderr, "%s", "cannot create unix service.\n"); exit(1); } atexit(done); if(!svc_register(svc, SEVENPROG, SEVENVERS, sevenprog_1, 0)) { fprintf (stderr, "%s", "unable to register (SEVENPROG, SEVENVERS, unix).\n"); exit(1); } GIOChannel *svc_channel = g_io_channel_unix_new(svc->xp_sock); g_io_add_watch(svc_channel, G_IO_IN | G_IO_HUP | G_IO_ERR, on_svc, NULL); g_io_channel_unref(svc_channel); if(!opt_detached) XMapWindow(conn->dpy, win.w); g_main_loop_run(mainloop); x11_close(); return 0; }