| 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 | |
|---|
| 38 | namespace Captty { |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | namespace { |
|---|
| 42 | |
|---|
| 43 | int 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 | |
|---|
| 61 | inline 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 | |
|---|
| 77 | class scoped_pty_raw { |
|---|
| 78 | public: |
|---|
| 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 | } |
|---|
| 103 | private: |
|---|
| 104 | int m_fd; |
|---|
| 105 | struct termios m_orig; |
|---|
| 106 | private: |
|---|
| 107 | scoped_pty_raw(); |
|---|
| 108 | scoped_pty_raw(const scoped_pty_raw&); |
|---|
| 109 | }; |
|---|
| 110 | |
|---|
| 111 | class scoped_window_save { |
|---|
| 112 | public: |
|---|
| 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 | } |
|---|
| 120 | private: |
|---|
| 121 | int m_fd; |
|---|
| 122 | struct winsize m_ws; |
|---|
| 123 | private: |
|---|
| 124 | scoped_window_save(); |
|---|
| 125 | scoped_window_save(const scoped_window_save&); |
|---|
| 126 | }; |
|---|
| 127 | |
|---|
| 128 | } // noname namespace |
|---|
| 129 | |
|---|
| 130 | |
|---|
| 131 | BlockWriter::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 | |
|---|
| 145 | BlockWriter::~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 | |
|---|
| 164 | void 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 | |
|---|
| 179 | void 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 | |
|---|
| 191 | void BlockWriter::bufcpy(const char* buf, size_t length) |
|---|
| 192 | { |
|---|
| 193 | std::memcpy(m_pos, buf, length); |
|---|
| 194 | m_pos += length; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | void 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 | |
|---|
| 212 | void 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 | |
|---|
| 231 | time_t BlockWriter::tv_diff(struct timeval& now, struct timeval& before) { |
|---|
| 232 | return (now.tv_sec - before.tv_sec); |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | |
|---|
| 236 | |
|---|
| 237 | Recorder::Recorder(std::ostream& output) : |
|---|
| 238 | impl(new RecorderIMPL(output)) {} |
|---|
| 239 | |
|---|
| 240 | RecorderIMPL::RecorderIMPL(std::ostream& output) : |
|---|
| 241 | block_writer(output) |
|---|
| 242 | { |
|---|
| 243 | get_time(&tv_before); |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | Recorder::~Recorder() { delete impl; } |
|---|
| 247 | RecorderIMPL::~RecorderIMPL() {} |
|---|
| 248 | |
|---|
| 249 | |
|---|
| 250 | uint32_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 | |
|---|
| 258 | void Recorder::write(const char* buf, uint16_t len) { impl->write(buf, len); } |
|---|
| 259 | void 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 | |
|---|
| 270 | void Recorder::set_window_size(short row, short col) { impl->set_window_size(row, col); } |
|---|
| 271 | void 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 | |
|---|
| 283 | void 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 | |
|---|
| 340 | PlayerIMPL::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 | |
|---|
| 367 | Player::~Player() { delete impl; } |
|---|
| 368 | PlayerIMPL::~PlayerIMPL() {} |
|---|
| 369 | |
|---|
| 370 | void Player::play() { impl->play(); } |
|---|
| 371 | void 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 | |
|---|
| 425 | bool 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 | |
|---|
| 486 | void Player::set_handler(void (*handler)(int, void*), void* data) |
|---|
| 487 | { impl->set_handler(handler, data); } |
|---|
| 488 | void PlayerIMPL::set_handler(void (*handler)(int, void*), void* data) |
|---|
| 489 | { |
|---|
| 490 | m_handler = handler; |
|---|
| 491 | m_handler_data = data; |
|---|
| 492 | } |
|---|
| 493 | |
|---|
| 494 | void Player::speed_up() { impl->speed_up(); } |
|---|
| 495 | void PlayerIMPL::speed_up() |
|---|
| 496 | { |
|---|
| 497 | if( m_speed < m_speed*2 ) { |
|---|
| 498 | m_speed *= 2; |
|---|
| 499 | } |
|---|
| 500 | } |
|---|
| 501 | |
|---|
| 502 | void Player::speed_down() { impl->speed_down(); } |
|---|
| 503 | void PlayerIMPL::speed_down() |
|---|
| 504 | { |
|---|
| 505 | if( m_speed / 2 != 0 ) { |
|---|
| 506 | m_speed /= 2; |
|---|
| 507 | } |
|---|
| 508 | } |
|---|
| 509 | |
|---|
| 510 | void Player::speed_reset() { impl->speed_reset(); } |
|---|
| 511 | void PlayerIMPL::speed_reset() |
|---|
| 512 | { |
|---|
| 513 | m_speed = 1.0; |
|---|
| 514 | } |
|---|
| 515 | |
|---|
| 516 | void Player::speed_set(double speed) { impl->speed_set(speed); } |
|---|
| 517 | void PlayerIMPL::speed_set(double speed) |
|---|
| 518 | { |
|---|
| 519 | m_speed = speed; |
|---|
| 520 | } |
|---|
| 521 | |
|---|
| 522 | void Player::skip_back() { impl->skip_back(); } |
|---|
| 523 | void 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 | |
|---|
| 536 | void Player::skip_forward() { impl->skip_forward(); } |
|---|
| 537 | void PlayerIMPL::skip_forward() |
|---|
| 538 | { |
|---|
| 539 | m_skip_mode = true; |
|---|
| 540 | } |
|---|
| 541 | |
|---|
| 542 | void Player::pause() { impl->pause(); } |
|---|
| 543 | void PlayerIMPL::pause() |
|---|
| 544 | { |
|---|
| 545 | m_speed = std::numeric_limits<double>::min(); |
|---|
| 546 | m_rewait_frame = true; |
|---|
| 547 | } |
|---|
| 548 | |
|---|
| 549 | void Player::toggle_pause() { impl->toggle_pause(); } |
|---|
| 550 | void 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 | |
|---|
| 560 | void Player::rewind() { impl->rewind(); } |
|---|
| 561 | void 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 | |
|---|
| 569 | void Player::quit() { impl->quit(); } |
|---|
| 570 | void PlayerIMPL::quit() |
|---|
| 571 | { |
|---|
| 572 | m_quit = true; |
|---|
| 573 | m_skip_block = true; |
|---|
| 574 | clear_display(); |
|---|
| 575 | } |
|---|
| 576 | |
|---|
| 577 | void 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 | |
|---|
| 584 | void 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 | |
|---|
| 593 | void 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 | |
|---|
| 623 | void 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 | |
|---|