diff --git a/.gitmodules b/.gitmodules index 3d24582..2348053 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "a2png"] path = a2png url = https://github.com/asciinema/a2png +[submodule "vt"] + path = vt + url = https://github.com/asciinema/vt.git diff --git a/Dockerfile b/Dockerfile index f8173ee..8496d40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,19 +13,16 @@ RUN apt-get update && \ wget --quiet -O - https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | apt-key add - && \ apt-get update && \ apt-get install -y \ - autoconf \ build-essential \ elixir \ esl-erlang \ git-core \ libfontconfig1 \ libpq-dev \ - libtool \ libxml2-dev \ libxslt1-dev \ nginx \ nodejs \ - pkg-config \ ruby2.1 \ ruby2.1-dev \ supervisor \ @@ -33,7 +30,6 @@ RUN apt-get update && \ tzdata # Packages required for: -# autoconf, libtool and pkg-config for libtsm # libfontconfig1 for PhantomJS # ttf-bitstream-vera for a2png @@ -56,18 +52,6 @@ RUN wget --quiet -O /opt/phantomjs.tar.bz2 https://bitbucket.org/ariya/phantomjs rm /opt/phantomjs.tar.bz2 && \ ln -sf /opt/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin/phantomjs /usr/local/bin -# install libtsm - -RUN git clone https://github.com/asciinema/libtsm.git /tmp/libtsm && \ - cd /tmp/libtsm && \ - git checkout asciinema && \ - test -f ./configure || NOCONFIGURE=1 ./autogen.sh && \ - ./configure --prefix=/usr/local && \ - make && \ - make install && \ - ldconfig && \ - rm -rf /tmp/libtsm - # install JDK RUN wget --quiet -O /opt/jdk-8u131-linux-x64.tar.gz --no-check-certificate --no-cookies --header 'Cookie: oraclelicense=accept-securebackup-cookie' http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.tar.gz && \ @@ -106,18 +90,19 @@ RUN cd a2png && npm install COPY a2png /app/a2png RUN cd a2png && lein cljsbuild once main && lein cljsbuild once page +# build vt + +COPY vt/project.clj /app/vt/ +RUN cd vt && lein deps + +COPY vt /app/vt +RUN cd vt && lein cljsbuild once main + # service URLs ENV DATABASE_URL "postgresql://postgres@postgres/postgres" ENV REDIS_URL "redis://redis:6379" -# compile terminal.c - -RUN mkdir -p /app/bin -COPY src/Makefile /app/src/ -COPY src/terminal.c /app/src/ -RUN cd src && make - # add Ruby source files COPY config/*.rb /app/config/ @@ -134,6 +119,7 @@ COPY vendor /app/vendor COPY config.ru /app/ COPY Rakefile /app/ COPY app /app/app +COPY resources/welcome.json /app/resources/welcome.json # compile assets with assets pipeline diff --git a/app/models/terminal.rb b/app/models/terminal.rb index 9b0e408..82d52b3 100644 --- a/app/models/terminal.rb +++ b/app/models/terminal.rb @@ -2,29 +2,27 @@ require 'open3' class Terminal - BINARY_PATH = (Rails.root + "bin" + "terminal").to_s + SCRIPT_PATH = (Rails.root + "vt" + "main.js").to_s def initialize(width, height) - @process = Process.new("#{BINARY_PATH} #{width} #{height}") + @process = Process.new("node #{SCRIPT_PATH}") + send_cmd("new", { width: width, height: height }) end def feed(data) - process.write("d\n#{data.bytesize}\n") - process.write(data) + send_cmd("feed-str", { str: data }) end - def snapshot - process.write("p\n") - lines = Yajl::Parser.new.parse(process.read_line) + def screen + send_cmd("dump-screen") + screen = read_result + lines = screen.fetch("lines") + cursor = screen.fetch("cursor") - Snapshot.build(lines) - end - - def cursor - process.write("c\n") - c = Yajl::Parser.new.parse(process.read_line) - - Cursor.new(c['x'], c['y'], c['visible']) + { + snapshot: Snapshot.build(lines), + cursor: Cursor.new(cursor['x'], cursor['y'], cursor['visible']) + } end def release @@ -33,6 +31,15 @@ class Terminal private + def send_cmd(cmd, data = {}) + json = data.merge({ cmd: cmd }).to_json + process.write("#{json}\n") + end + + def read_result() + Yajl::Parser.new.parse(process.read_line).fetch("result") + end + attr_reader :process class Process diff --git a/app/services/film.rb b/app/services/film.rb index c4f6453..f6fc5c1 100644 --- a/app/services/film.rb +++ b/app/services/film.rb @@ -10,13 +10,14 @@ class Film terminal.feed(data) end - terminal.snapshot + terminal.screen[:snapshot] end def frames frames = stdout.lazy.map do |delay, data| terminal.feed(data) - [delay, Frame.new(terminal.snapshot, terminal.cursor)] + screen = terminal.screen + [delay, Frame.new(screen[:snapshot], screen[:cursor])] end FrameDiffList.new(frames) diff --git a/spec/support/fakes.rb b/spec/support/fakes.rb index d7ada01..b4a433e 100644 --- a/spec/support/fakes.rb +++ b/spec/support/fakes.rb @@ -8,12 +8,7 @@ class FakeTerminal @data << data end - def snapshot - @data + def screen + { snapshot: @data, cursor: @data.size } end - - def cursor - @data.size - end - end diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 6cc474a..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -terminal -*.o diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index ba9e89c..0000000 --- a/src/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -all: ../bin/terminal - -../bin/terminal: terminal - mkdir -p ../bin - cp terminal ../bin/terminal - -terminal: terminal.c - gcc -O3 -o terminal terminal.c -ltsm diff --git a/src/terminal.c b/src/terminal.c deleted file mode 100644 index 5a687c1..0000000 --- a/src/terminal.c +++ /dev/null @@ -1,310 +0,0 @@ -#include -#include -#include - -#define READ_BUF_SIZE 16 * 1024 - -static int RGB_LEVELS[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; - -struct tsm_screen_attr last_attr; -struct tsm_screen_attr *last_attr_ptr = &last_attr; - - -static int u8_wc_to_utf8(char *dest, uint32_t ch) { - if (ch < 0x80) { - dest[0] = (char)ch; - return 1; - } - - if (ch < 0x800) { - dest[0] = (ch >> 6) | 0xC0; - dest[1] = (ch & 0x3F) | 0x80; - return 2; - } - - if (ch < 0x10000) { - dest[0] = (ch >> 12) | 0xE0; - dest[1] = ((ch >> 6) & 0x3F) | 0x80; - dest[2] = (ch & 0x3F) | 0x80; - return 3; - } - - if (ch < 0x110000) { - dest[0] = (ch >> 18) | 0xF0; - dest[1] = ((ch >> 12) & 0x3F) | 0x80; - dest[2] = ((ch >> 6) & 0x3F) | 0x80; - dest[3] = (ch & 0x3F) | 0x80; - return 4; - } - - return 0; -} - -void check_err(int err, const char *message) { - if (err) { - printf("%s\n", message); - exit(1); - } -} - -void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data) {} - -int prepare_cb(struct tsm_screen *con, void *data) { - int *line = (int *)data; - - *line = -1; - printf("["); - - return 0; -} - -void print_char(const uint32_t *ch) { - char str[5]; - int size; - - if (*ch == 0) { - printf(" "); - } else if (*ch == '"') { - printf("\\\""); - } else if (*ch == '\\') { - printf("\\\\"); - } else { - size = u8_wc_to_utf8(str, *ch); - if (size > 0) { - str[size] = 0; - printf("%s", str); - } else { - printf("?"); - } - } -} - -int get_rgb_index(int value) { - int i; - - for (i=0; i<6; i++) { - if (RGB_LEVELS[i] == value) return i; - } - - return -1; -} - -int get_rgb_color(int r, int g, int b) { - if (r == g && g == b && (r - 8) % 10 == 0) { - return 232 + (r - 8) / 10; - } else { - return 16 + get_rgb_index(r) * 36 + get_rgb_index(g) * 6 + get_rgb_index(b); - } -} - -int get_fg(const struct tsm_screen_attr *attr) { - if (attr->fccode == -1) { - return get_rgb_color(attr->fr, attr->fg, attr->fb); - } else if (attr->fccode >= 0 && attr->fccode < 16) { - return attr->fccode; - } else { - return -1; - } -} - -int get_bg(const struct tsm_screen_attr *attr) { - if (attr->bccode == -1) { - return get_rgb_color(attr->br, attr->bg, attr->bb); - } else if (attr->bccode >= 0 && attr->bccode < 16) { - return attr->bccode; - } else { - return -1; - } -} - -void print_attr(const struct tsm_screen_attr *attr) { - bool comma_needed = false; - int color_code; - - printf("{"); - - color_code = get_fg(attr); - if (color_code != -1) { - printf("\"fg\":%d", color_code); - comma_needed = true; - } - - color_code = get_bg(attr); - if (color_code != -1) { - if (comma_needed) printf(","); - printf("\"bg\":%d", color_code); - comma_needed = true; - } - - if (attr->bold) { - if (comma_needed) printf(","); - printf("\"bold\":true"); - comma_needed = true; - } - - if (attr->underline) { - if (comma_needed) printf(","); - printf("\"underline\":true"); - comma_needed = true; - } - - if (attr->inverse) { - if (comma_needed) printf(","); - printf("\"inverse\":true"); - comma_needed = true; - } - - if (attr->blink) { - if (comma_needed) printf(","); - printf("\"blink\":true"); - comma_needed = true; - } - - printf("}"); -} - -bool attr_eq(struct tsm_screen_attr *attr1, const struct tsm_screen_attr *attr2) { - return attr1->fccode == attr2->fccode && - attr1->bccode == attr2->bccode && - attr1->fr == attr2->fr && - attr1->fg == attr2->fg && - attr1->fb == attr2->fb && - attr1->br == attr2->br && - attr1->bg == attr2->bg && - attr1->bb == attr2->bb && - attr1->bold == attr2->bold && - attr1->underline == attr2->underline && - attr1->inverse == attr2->inverse && - attr1->blink == attr2->blink; -} - -void attr_cp(const struct tsm_screen_attr *src, struct tsm_screen_attr *dst) { - memcpy((void *)dst, (const void *)src, sizeof(last_attr)); -} - -void close_cell() { - printf("\","); - print_attr(last_attr_ptr); - printf("]"); -} - -void open_cell(const struct tsm_screen_attr *attr) { - printf("[\""); - attr_cp(attr, last_attr_ptr); -} - -int draw_cb(struct tsm_screen *con, uint32_t id, const uint32_t *ch, size_t len, - unsigned int width, unsigned int posx, unsigned int posy, - const struct tsm_screen_attr *attr, tsm_age_t age, void *data) { - - int *line = (int *)data; - - if (((signed int)posy) > *line) { - if (*line >= 0) { - close_cell(); - printf("],"); // close line - } - printf("["); // open line - open_cell(attr); - } - - *line = posy; - - if (width == 0) return 0; - - if (!(attr_eq(last_attr_ptr, attr))) { - close_cell(); - printf(","); - open_cell(attr); - } - - print_char(ch); - - return 0; -} - -int render_cb(struct tsm_screen *con, void *data) { - int *line = (int *)data; - - if (*line >= 0) { - close_cell(); - printf("]"); // close line - } - - printf("]\n"); - - return 0; -} - -int main(int argc, char *argv[]) { - int err; - int i; - struct tsm_screen *screen; - struct tsm_vte *vte; - int width, height; - char *line = NULL; - size_t size; - unsigned int n; - char *buffer = (char *) malloc(READ_BUF_SIZE); - int line_n, cursor_x, cursor_y; - char *cursor_visible; - unsigned int flags; - int m, read; - - width = atoi(argv[1]); - height = atoi(argv[2]); - - err = tsm_screen_new(&screen, NULL, NULL); - check_err(err, "can't create screen"); - - err = tsm_screen_resize(screen, width, height); - check_err(err, "can't resize screen"); - - err = tsm_vte_new(&vte, screen, write_cb, NULL, NULL, NULL); - check_err(err, "can't create vte"); - - for (;;) { - if (getline(&line, &size, stdin) == -1) break; - - char action = line[0]; - - switch(action) { - case 'd': - if (getline(&line, &size, stdin) == -1) break; - n = atoi(line); - while (n > 0) { - if (n > READ_BUF_SIZE) { - m = READ_BUF_SIZE; - } else { - m = n; - } - read = fread(buffer, 1, m, stdin); - tsm_vte_input(vte, buffer, read); - n = n - read; - } - break; - case 'p': - //tsm_screen_draw(screen, prepare_cb, draw_cb, render_cb, &line_n); - prepare_cb(screen, &line_n); - tsm_screen_draw(screen, draw_cb, &line_n); - render_cb(screen, &line_n); - break; - case 'c': - cursor_x = tsm_screen_get_cursor_x(screen); - cursor_y = tsm_screen_get_cursor_y(screen); - flags = tsm_screen_get_flags(screen); - if (!(flags & TSM_SCREEN_HIDE_CURSOR)) { - cursor_visible = "true"; - } else { - cursor_visible = "false"; - } - printf("{\"x\":%d,\"y\":%d,\"visible\":%s}\n", cursor_x, cursor_y, cursor_visible); - - break; - } - - fflush(stdout); - } - - return 0; -} diff --git a/vt b/vt new file mode 160000 index 0000000..5fde1d8 --- /dev/null +++ b/vt @@ -0,0 +1 @@ +Subproject commit 5fde1d8cb8306d09034709ae5901d7f7b43630cc