root/lang/c/partty/trunk/src/captty.cc @ 7787

Revision 7787, 14.0 kB (checked in by frsyuki, 5 years ago)

lang/c/partty: fixed some compatibility problem

Line 
1/*
2 * This file is part of Captty.
3 *
4 * Copyright (C) 2008-2009 FURUHASHI Sadayuki
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21#include <cstring>
22#include <zlib.h>
23#include <stdio.h>
24#include <fcntl.h>
25#include <fstream>
26#include <algorithm>
27#include <limits>
28#include "captty.h"
29#include "captty_impl.h"
30
31#ifdef HAVE_UTIL_H
32#include <util.h>
33#endif
34#ifdef HAVE_PTY_H
35#include <pty.h>
36#endif
37
38namespace Captty {
39
40
41namespace {
42
43int execute_shell(char* cmd[])
44{
45        if( cmd == NULL ) {
46                char* shell = getenv("SHELL");
47                char fallback_shell[] = "/bin/sh";
48                if( shell == NULL ) { shell = fallback_shell; }
49                char* name = strrchr(shell, '/');
50                if( name == NULL ) { name = shell; } else { name += 1; }
51                execl(shell, name, "-i", NULL);
52                perror(shell);
53                return 127;
54        } else {
55                execvp(cmd[0], cmd);
56                perror(cmd[0]);
57                return 127;
58        }
59}
60
61inline size_t write_all(int fd, const char *buf, size_t count) {
62        const char *p = buf;
63        const char * const endp = p + count;
64        do {
65                const int num_bytes = write(fd, p, endp - p);
66                if( num_bytes < 0 ) {
67                        if( errno != EINTR && errno != EAGAIN ) {
68                                throw io_error("output", errno);
69                        }
70                } else {
71                        p += num_bytes;
72                }
73        } while (p < endp);
74        return count;
75}
76
77class scoped_pty_raw {
78public:
79        scoped_pty_raw(int fd) : m_fd(fd) {
80                tcgetattr(m_fd, &m_orig);
81                struct termios raw = m_orig;
82                //cfmakeraw(&raw);
83                raw.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
84                                | INLCR | IGNCR | ICRNL | IXON);
85                raw.c_oflag &= ~OPOST;
86                raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
87                raw.c_cflag &= ~(CSIZE | PARENB);
88                raw.c_cflag |= CS8;
89                //raw.c_lflag &= ~ECHO;
90                tcsetattr(m_fd, TCSAFLUSH, &raw);
91        }
92        ~scoped_pty_raw() {
93                finish();
94        }
95        void finish(void) {
96                if(m_fd >= 0) {
97                        // XXX: TCSANOW?
98                        tcsetattr(m_fd, TCSADRAIN, &m_orig);
99                        write(m_fd, "\n", 1);
100                        m_fd = -1;
101                }
102        }
103private:
104        int m_fd;
105        struct termios m_orig;
106private:
107        scoped_pty_raw();
108        scoped_pty_raw(const scoped_pty_raw&);
109};
110
111class scoped_window_save {
112public:
113        scoped_window_save(int fd) : m_fd(fd) {
114                get_window_size(m_fd, &m_ws);
115        }
116        ~scoped_window_save() try {
117                set_window_size(m_fd, &m_ws);
118        } catch (...) {
119        }
120private:
121        int m_fd;
122        struct winsize m_ws;
123private:
124        scoped_window_save();
125        scoped_window_save(const scoped_window_save&);
126};
127
128}  // noname namespace
129
130
131BlockWriter::BlockWriter(std::ostream& file) :
132                m_file(file)
133{
134        m_buffer = new char[MAX_BLOCK_SIZE];
135        m_pos = m_buffer;
136        try {
137                m_zbuffer = new char[MAX_BLOCK_SIZE];
138        } catch (...) {
139                delete[] m_buffer;
140                throw std::bad_alloc();
141        }
142        get_time(&tv_before);
143}
144
145BlockWriter::~BlockWriter()
146{
147        struct timeval tv_now;
148        get_time(&tv_now);
149        time_t btdiff = tv_diff(tv_now, tv_before);
150
151        try {
152                write_block((uint16_t)btdiff);
153        } catch (io_error& e) {
154                perror(e.what());
155        } catch (...) {
156                perror("write_block");
157        }
158
159        delete[] m_buffer;
160        delete[] m_zbuffer;
161}
162
163
164void BlockWriter::reserve(size_t length)
165{
166        struct timeval tv_now;
167        get_time(&tv_now);
168        time_t btdiff = tv_diff(tv_now, tv_before);
169
170        if( btdiff > BLOCK_TIME_SPAN ) {
171                tv_before = tv_now;
172                write_block((uint16_t)btdiff);
173        } else if( MAX_BLOCK_SIZE - (m_pos - m_buffer) < length ) {
174                tv_before = tv_now;
175                write_block((uint16_t)btdiff);
176        }
177}
178
179void BlockWriter::append_frame(uint32_t tdiff, const char* data,
180                uint16_t data_length, uint8_t flag)
181{
182        reserve(FRAME::HEADER_SIZE + data_length);
183        uint32_t tdiff_le = htolel(tdiff);
184        uint16_t data_length_le = htoles(data_length);
185        bufcpy((const char*)&tdiff_le, sizeof(uint32_t));
186        bufcpy((const char*)&flag, sizeof(uint8_t));
187        bufcpy((const char*)&data_length_le, sizeof(uint16_t));
188        bufcpy((const char*)data, data_length);
189}
190
191void BlockWriter::bufcpy(const char* buf, size_t length)
192{
193        std::memcpy(m_pos, buf, length);
194        m_pos += length;
195}
196
197void BlockWriter::write_block(uint16_t btdiff)
198{
199        uint32_t length = m_pos - m_buffer;
200        size_t zlength = MAX_BLOCK_SIZE;
201        if( compress( (Bytef*)m_zbuffer,(uLongf*)&zlength, (Bytef*)m_buffer, length) == Z_OK &&
202                        zlength < length ) {
203                write_block_data(BLOCK::COMPRESSED_FRAMES, btdiff,
204                                m_zbuffer, zlength);
205        } else {
206                write_block_data(BLOCK::UNCOMPRESSED_FRAMES, btdiff,
207                                m_buffer, length);
208        }
209        m_pos = m_buffer;
210}
211
212void BlockWriter::write_block_data(uint8_t flag, uint16_t btdiff,
213                const char* data, uint32_t length)
214{
215        uint16_t btdiff_le = htoles(btdiff);
216        uint32_t length_le = htolel(length);
217        if( !m_file.write((char*)&btdiff_le,sizeof(uint16_t)).good() ) {
218                throw io_error("write file btdiff");
219        }
220        if( !m_file.write((char*)&flag,sizeof(uint8_t)).good() ) {
221                throw io_error("write file flag");
222        }
223        if( !m_file.write((char*)&length_le,sizeof(uint32_t)).good() ) {
224                throw io_error("write file length");
225        }
226        if( !m_file.write((char*)data,length).good() ) {
227                throw io_error("write file buffer");
228        }
229}
230
231time_t BlockWriter::tv_diff(struct timeval& now, struct timeval& before) {
232        return (now.tv_sec - before.tv_sec);
233}
234
235
236
237Recorder::Recorder(std::ostream& output) :
238        impl(new RecorderIMPL(output)) {}
239
240RecorderIMPL::RecorderIMPL(std::ostream& output) :
241        block_writer(output)
242{
243        get_time(&tv_before);
244}
245
246Recorder::~Recorder() { delete impl; }
247RecorderIMPL::~RecorderIMPL() {}
248
249
250uint32_t RecorderIMPL::tv_forward(struct timeval& next)
251{
252        uint32_t diff = (next.tv_sec - tv_before.tv_sec)*1000*1000
253                + (next.tv_usec - tv_before.tv_usec);
254        tv_before = next;
255        return diff;
256}
257
258void Recorder::write(const char* buf, uint16_t len) { impl->write(buf, len); }
259void RecorderIMPL::write(const char* buf, uint16_t len)
260{
261        struct timeval tv_now;
262        get_time(&tv_now);
263        block_writer.append_frame(
264                        tv_forward(tv_now),
265                        buf, len,
266                        FRAME::OUTPUT
267                        );
268}
269
270void Recorder::set_window_size(short row, short col) { impl->set_window_size(row, col); }
271void RecorderIMPL::set_window_size(short row, short col)
272{
273        struct timeval tv_now;
274        get_time(&tv_now);
275        tty_size tsz = {row, col};
276        block_writer.append_frame(
277                        tv_forward(tv_now),
278                        (char*)&tsz, sizeof(tsz),
279                        FRAME::WINDOW_SIZE
280                        );
281}
282
283void record(const char* file, char* cmd[])
284{
285        std::ofstream output(file, std::ios::binary | std::ios::trunc);
286
287        // termios, winsizeの引き継ぎ
288        struct termios tios;
289        tcgetattr(STDIN_FILENO, &tios);
290
291        struct winsize ws_before;
292        get_window_size(STDIN_FILENO, &ws_before);
293
294        int master;
295        int slave;
296        if( openpty(&master, &slave, NULL, &tios, &ws_before) == -1 ) {
297                throw captty_error("can't open pty", errno);
298        }
299
300        // fork
301        pid_t pid = fork();
302        if( pid < 0 ) {
303                close(master);
304                close(slave);
305                throw captty_error("can't fork", errno);
306        } else if( pid == 0 ) {
307                // child
308                close(master);
309                setsid();
310                //dup2(slave, 0);
311                dup2(slave, 1);
312                dup2(slave, 2);
313                close(slave);
314                exit( execute_shell(cmd) );
315        }
316        // parent
317        close(slave);
318
319        RecorderIMPL recorder(output);
320
321        ws_before.ws_row = 0;
322        ws_before.ws_col = 0;
323        struct winsize ws_now;
324
325        ssize_t len;
326        char buf[1<<16];
327        while( (len = read(master, buf, sizeof(buf))) > 0 ) {
328                get_window_size(STDIN_FILENO, &ws_now);
329                if( !cmp_window_size(ws_before, ws_now) ) {
330                        set_window_size(master, &ws_now);
331                        recorder.set_window_size(ws_now.ws_row, ws_now.ws_col);
332                        ws_before = ws_now;
333                }
334                write_all(STDOUT_FILENO, buf, len);
335                recorder.write(buf, len);
336        }
337}
338
339
340PlayerIMPL::PlayerIMPL(std::istream& input) :
341        m_file(input),
342        m_block_pos(0),
343        m_speed(1.0),
344        m_skip_mode(false),
345        m_skip_block(false),
346        m_rewait_frame(false),
347        m_quit(false),
348        m_handler(NULL),
349        m_handler_data(NULL)
350{
351        char buf[BLOCK::HEADER_SIZE];
352        while( m_file.read(buf, BLOCK::HEADER_SIZE).good() ) {
353                uint16_t btdiff = letohs( *(uint16_t*)buf );
354                //uint8_t& bflag( *(uint8_t*)(buf + sizeof(uint16_t)) );
355                uint32_t blength = letohl( *(uint32_t*)(buf + sizeof(uint16_t) + sizeof(uint8_t)) );
356                size_t fp = (size_t)m_file.tellg() - BLOCK::HEADER_SIZE;
357                index_info_t info;
358                info.btdiff = btdiff;
359                info.seekpos = fp;
360                m_index.push_back(info);
361                m_file.seekg(blength, std::ios::cur);
362        }
363        m_file.clear();
364        m_file.seekg(0, std::ios::beg);
365}
366
367Player::~Player() { delete impl; }
368PlayerIMPL::~PlayerIMPL() {}
369
370void Player::play() { impl->play(); }
371void PlayerIMPL::play()
372{
373        scoped_pty_raw scoped_raw(STDIN_FILENO);
374        if( fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) < 0 ) {
375                throw io_error("fcntl O_NONBLOCK", errno);
376        }
377
378        scoped_window_save scoped_ws(STDIN_FILENO);
379
380        char buf[MAX_BLOCK_SIZE + BLOCK::HEADER_SIZE];
381        char zbuf[MAX_BLOCK_SIZE];
382        size_t zlen;
383        while( m_file.read(buf, BLOCK::HEADER_SIZE).good() && !m_quit ) {
384                //uint16_t btdiff = letohs( *(uint16_t*)buf );
385                uint8_t& bflag( *(uint8_t*)(buf + sizeof(uint16_t)) );
386                uint32_t blength = letohl( *(uint32_t*)(buf + sizeof(uint8_t) + sizeof(uint16_t)) );
387                if( blength > MAX_BLOCK_SIZE ) {
388                        throw io_error("invalid block length");
389                }
390                if( !m_file.read(buf+BLOCK::HEADER_SIZE, blength).good() ) {
391                        throw io_error("read block");
392                }
393                m_block_pos++;
394
395                const char* pos;
396                const char* endpos;
397                switch(bflag) {
398                case BLOCK::COMPRESSED_FRAMES:
399                        zlen = sizeof(zbuf);
400                        if( uncompress((Bytef*)zbuf, (uLongf*)&zlen,
401                                                (Bytef*)(buf+BLOCK::HEADER_SIZE), blength) != Z_OK ) {
402                                throw io_error("uncompress error");
403                        }
404                        pos = zbuf;
405                        endpos = zbuf + zlen;   
406                        break;
407                case BLOCK::UNCOMPRESSED_FRAMES:
408                        pos = buf + BLOCK::HEADER_SIZE;
409                        endpos = buf + BLOCK::HEADER_SIZE + blength;
410                        break;
411                default:  // unsupported block
412                        continue;  // skip this block
413                }
414
415                if( !play_block(pos, endpos) ) {
416                        m_skip_mode = false;
417                }
418        }
419        if( !m_file.good() && !m_file.eof() ) {
420                throw io_error("read file exit", errno);
421        }
422}
423
424
425bool PlayerIMPL::play_block(const char* pos, const char* const endpos)
426{
427        while(pos < endpos && !m_quit) {
428                uint32_t ftdiff = letohl( *(uint32_t*)pos );
429                uint8_t& fflag( *(uint8_t*)(pos + sizeof(uint32_t)) );
430                uint16_t fdata_length = letohs( *(uint16_t*)(pos + sizeof(uint32_t) + sizeof(uint8_t)) );
431
432                struct timeval tv;
433                if( m_skip_mode ) {
434                        tv.tv_sec  = 0;
435                        tv.tv_usec = 0;
436                } else {
437                        double d = ftdiff / m_speed;
438                        ftdiff = static_cast<uint32_t>( std::min( d, static_cast<double>(std::numeric_limits<uint32_t>::max()) ) );
439                        tv.tv_sec  = ftdiff / (1000 * 1000);
440                        tv.tv_usec = ftdiff % (1000 * 1000);
441                }
442                fd_set read_set;
443                FD_ZERO(&read_set);
444                FD_SET(STDIN_FILENO, &read_set);
445                int numev = select(STDIN_FILENO+1, &read_set, NULL, NULL, &tv);
446
447                if( numev > 0 ) {
448                        char rbuf[512];
449                        ssize_t rlen;
450                        if( (rlen = read(STDIN_FILENO, rbuf, sizeof(rbuf))) < 0 ) {
451                                if( errno == EAGAIN || errno == EINTR ) {
452                                        // do nothing
453                                } else {
454                                        throw io_error("read stdinput", errno);
455                                }
456                        } else if( rlen == 0 ) {
457                                throw io_error("read stdinput ends");
458                        } else {
459                                call_handler(rbuf[0]);
460                                if( m_skip_block ) {
461                                        m_skip_block = false;
462                                        return true;
463                                } else if( m_rewait_frame ) {
464                                        m_rewait_frame = false;
465                                        continue;
466                                }
467                        }
468                }
469
470                switch(fflag) {
471                case FRAME::OUTPUT:
472                        write_all(STDOUT_FILENO, pos+FRAME::HEADER_SIZE, fdata_length);
473                        break;
474                case FRAME::WINDOW_SIZE: {
475                        tty_size& tsz( *(tty_size*)(pos+FRAME::HEADER_SIZE) );
476                        struct winsize ws = {tsz.row, tsz.col};
477                        set_window_size(STDOUT_FILENO, &ws);
478                        } break;
479                }
480
481                pos += FRAME::HEADER_SIZE + fdata_length;
482        }
483        return false;
484}
485
486void Player::set_handler(void (*handler)(int, void*), void* data)
487        { impl->set_handler(handler, data); }
488void PlayerIMPL::set_handler(void (*handler)(int, void*), void* data)
489{
490        m_handler = handler;
491        m_handler_data = data;
492}
493
494void Player::speed_up() { impl->speed_up(); }
495void PlayerIMPL::speed_up()
496{
497        if( m_speed < m_speed*2 ) {
498                m_speed *= 2;
499        }
500}
501
502void Player::speed_down() { impl->speed_down(); }
503void PlayerIMPL::speed_down()
504{
505        if( m_speed / 2 != 0 ) {
506                m_speed /= 2;
507        }
508}
509
510void Player::speed_reset() { impl->speed_reset(); }
511void PlayerIMPL::speed_reset()
512{
513        m_speed = 1.0;
514}
515
516void Player::speed_set(double speed) { impl->speed_set(speed); }
517void PlayerIMPL::speed_set(double speed)
518{
519        m_speed = speed;
520}
521
522void Player::skip_back() { impl->skip_back(); }
523void PlayerIMPL::skip_back()
524{
525        if( m_block_pos < 3 ) {
526                m_block_pos = 0;
527        } else {
528                m_block_pos -= 3;
529                m_skip_mode = true;
530        }
531        m_file.seekg(m_index[m_block_pos].seekpos, std::ios::beg);
532        m_skip_block = true;
533        clear_display();
534}
535
536void Player::skip_forward() { impl->skip_forward(); }
537void PlayerIMPL::skip_forward()
538{
539        m_skip_mode = true;
540}
541
542void Player::pause() { impl->pause(); }
543void PlayerIMPL::pause()
544{
545        m_speed = std::numeric_limits<double>::min();
546        m_rewait_frame = true;
547}
548
549void Player::toggle_pause() { impl->toggle_pause(); }
550void PlayerIMPL::toggle_pause()
551{
552        if( m_speed == std::numeric_limits<double>::min() ) {
553                m_speed = 1.0;
554        } else {
555                m_speed = std::numeric_limits<double>::min();
556                m_rewait_frame = true;
557        }
558}
559
560void Player::rewind() { impl->rewind(); }
561void PlayerIMPL::rewind()
562{
563        m_file.seekg(m_index[0].seekpos, std::ios::beg);
564        m_block_pos = 0;
565        m_skip_block = true;
566        clear_display();
567}
568
569void Player::quit() { impl->quit(); }
570void PlayerIMPL::quit()
571{
572        m_quit = true;
573        m_skip_block = true;
574        clear_display();
575}
576
577void PlayerIMPL::clear_display()
578{
579        static const char sequence[] = {0x1b, 0x5b, 0x48, 0x1b, 0x5b, 0x32, 0x4a};
580        std::cout.write(sequence, sizeof(sequence));
581        std::cout << std::flush;
582}
583
584void PlayerIMPL::call_handler(int c)
585{
586        if(m_handler) {
587                (*m_handler)(c, m_handler_data);
588        } else {
589                default_handler(c);
590        }
591}
592
593void PlayerIMPL::default_handler(int c)
594{
595        switch(c) {
596        case 'h':
597                skip_back();
598                break;
599        case 'l':
600                skip_forward();
601                break;
602        case 'j':
603                speed_down();
604                break;
605        case 'k':
606                speed_up();
607                break;
608        case 'g':
609                rewind();
610                break;
611        case ';':
612                toggle_pause();
613                break;
614        case '=':
615                speed_reset();
616                break;
617        case 'q':
618                quit();
619                break;
620        }
621}
622
623void play(const char* file)
624{
625        std::ifstream input(file, std::ios::binary);
626        PlayerIMPL player(input);
627        player.play();
628}
629
630
631}  // namespace Captty
632
Note: See TracBrowser for help on using the browser.