diff options
author | iroul <iroul@iroul-VirtualBox.(none)> | 2014-04-04 07:35:14 -0700 |
---|---|---|
committer | iroul <iroul@iroul-VirtualBox.(none)> | 2014-04-04 07:35:14 -0700 |
commit | afd34f2893a06a3aecf17e8e83b1df6ed2ae91a2 (patch) | |
tree | 851102abc55d91a1b76e63e9e89f9a7733da95b5 /src/terminal | |
parent | c4b028ad53f7b362a864de24828d7cc39ff67b0a (diff) | |
download | fqterm-afd34f2893a06a3aecf17e8e83b1df6ed2ae91a2.tar.xz |
move to my github.
Diffstat (limited to 'src/terminal')
-rw-r--r-- | src/terminal/CMakeLists.txt | 46 | ||||
-rw-r--r-- | src/terminal/fqterm_buffer.cpp | 998 | ||||
-rw-r--r-- | src/terminal/fqterm_buffer.h | 297 | ||||
-rw-r--r-- | src/terminal/fqterm_session.cpp | 1648 | ||||
-rw-r--r-- | src/terminal/fqterm_session.h | 344 | ||||
-rw-r--r-- | src/terminal/fqterm_text_line.cpp | 541 | ||||
-rw-r--r-- | src/terminal/fqterm_text_line.h | 159 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_decode.cpp | 1279 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_decode.h | 194 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_telnet.cpp | 1041 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_telnet.h | 195 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_zmodem.cpp | 3056 | ||||
-rw-r--r-- | src/terminal/internal/fqterm_zmodem.h | 623 |
13 files changed, 10421 insertions, 0 deletions
diff --git a/src/terminal/CMakeLists.txt b/src/terminal/CMakeLists.txt new file mode 100644 index 0000000..c3cf3db --- /dev/null +++ b/src/terminal/CMakeLists.txt @@ -0,0 +1,46 @@ +set(export_SRCS + fqterm_buffer.h + fqterm_session.h + fqterm_text_line.h + fqterm_session.cpp + fqterm_buffer.cpp + fqterm_text_line.cpp +) + +set(internal_SRCS + internal/fqterm_decode.h + internal/fqterm_telnet.h + internal/fqterm_zmodem.h + internal/fqterm_decode.cpp + internal/fqterm_telnet.cpp + internal/fqterm_zmodem.cpp +) + +include_directories( + ${QT_INCLUDE_DIR} + ${QT_QTCORE_INCLUDE_DIR} + ${QT_QTGUI_INCLUDE_DIR} + ${QT_QTNETWORK_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/internal + ${CMAKE_CURRENT_SOURCE_DIR}/../common + ${CMAKE_CURRENT_SOURCE_DIR}/../utilities + ${CMAKE_CURRENT_SOURCE_DIR}/../protocol +) + +QT4_AUTOMOC( + ${export_SRCS} + ${internal_SRCS} +) + +add_library(fqterm_terminal + ${export_SRCS} + ${internal_SRCS} +) + +add_dependencies(fqterm_terminal + fqterm_common + fqterm_protocol + fqterm_utilities +) diff --git a/src/terminal/fqterm_buffer.cpp b/src/terminal/fqterm_buffer.cpp new file mode 100644 index 0000000..c00e079 --- /dev/null +++ b/src/terminal/fqterm_buffer.cpp @@ -0,0 +1,998 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <algorithm> +#include <map> +#include <vector> + +#include <QString> +#include <QRect> +#include <QRegExp> + +#include "fqterm.h" +#include "common.h" +#include "fqterm_buffer.h" +#include "fqterm_text_line.h" + +namespace FQTerm { + +typedef std::vector<int> TabStops; + +/* staight from linux/drivers/char/consolemap.c, GNU GPL:ed */ +static const UTF16 VT_SPECIAL_GRAPHICS_TABLE[256]={ + /* VT100 graphics mapped to Unicode */ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0, + 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, + 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff +}; + +FQTermBuffer::FQTermBuffer(int column, int row, int max_hist_line, bool is_bbs) { + tab_stops_ = new TabStops; + num_rows_ = row; + num_columns_ = column; + max_num_hist_lines_ = max_hist_line; + is_bbs_ = is_bbs; + + num_hist_lines_ = 0; + + while (text_lines_.count() < num_rows_) { + text_lines_.append(new FQTermTextLine(num_columns_)); + } + + top_row_ = 0; + bottom_row_ = num_rows_ - 1; + + is_g0_used_ = true; + + is_insert_mode_ = false; + is_ansi_mode_ = true; + is_newline_mode_ = false; + is_cursor_mode_ = false; + is_numeric_mode_ = true; + is_origin_mode_ = false; + is_autowrap_mode_ = false; + is_autorepeat_mode_ = true; + is_lightbg_mode_ = false; + + selection_start_ = QPoint(-1, -1); + selection_end_ = QPoint(-1, -1); +} + +FQTermBuffer::~FQTermBuffer(){ + foreach(FQTermTextLine *line, text_lines_) { + delete line; + } + + delete (TabStops *)tab_stops_; +} + +FQTermBuffer::Caret::Caret() + : column_(0), + row_(0), + color_(NO_COLOR), + attr_(NO_ATTR), + G0_(ASCII_SET), + G1_(ASCII_SET) { +} + +void FQTermBuffer::setTermSize(int col, int row) { + FQ_TRACE("term", 3) << "Change term size to " + << col << "x" << row; + + if (num_columns_ == col && num_rows_ == row) { + return; + } + + clearSelect(); + + if (num_rows_ < row) { + for (int i = 0; i < row - num_rows_; i++) { + text_lines_.append(new FQTermTextLine(col)); + } + } else if (num_rows_ > row) { + for (int i = 0; i < num_rows_ - row; i++) { + delete text_lines_.takeLast(); + } + } + + num_columns_ = col; + num_rows_ = row; + + top_row_ = 0; + bottom_row_ = num_rows_ - 1; + + // TODO: how about saved caret column? + // TODO: why make last saved equal to current position? + last_saved_caret_.row_ = caret_.row_ = qMin(caret_.row_, row - 1); + + for (int i = 0; i < text_lines_.size(); ++i) { + text_lines_.at(i)->setMaxCellCount(num_columns_); + } + +// this->clearArea(0, 0, num_columns_, num_rows_, 0, 0); + // this->moveCaretTo(0, 0); + + moveCaretTo(qMin(caret_.column_, col - 1), qMin(caret_.row_, row - 1)); + + emit onSetTermSize(col, row); + emit termSizeChanged(num_columns_, num_rows_); +} + +int FQTermBuffer::getNumColumns() const { + return num_columns_; +} + +int FQTermBuffer::getNumRows() const { + return num_rows_; +} + +int FQTermBuffer::getNumLines() const { + return num_rows_ + num_hist_lines_; +} + +const FQTermTextLine *FQTermBuffer::getTextLineInBuffer(int line_index) const { + return text_lines_.value(line_index, NULL); +} + +const FQTermTextLine *FQTermBuffer::getTextLineInTerm(int line_index) const { + return text_lines_.value(line_index + num_hist_lines_, NULL); +} + +void FQTermBuffer::setCurrentAttr(unsigned char color, unsigned char attr) { + caret_.color_ = color; + caret_.attr_ = attr; +} + +void FQTermBuffer::writeText(const QString &str, int charstate) { + + QString cstr = str; + + FQ_TRACE("term", 8) << "Add text: \"" << cstr << "\""; + +#if 0 + if ((is_g0_used_ && caret_.G0_ == SPECIAL_GRAPHICS) || + (!is_g0_used_ && caret_.G1_ == SPECIAL_GRAPHICS)) { + + // FIXME: will this break the utf-16 sequence? + for (int i = 0; i < cstr.size(); ++i) { + if (cstr[i] >= 0137 && cstr[i] <= 0176) { + cstr[i] = VT_SPECIAL_GRAPHICS_TABLE[cstr.at(i).unicode()]; + } + } + } +#endif + + // Insert the str into the buffer. Different pieces of text might be + // written into different lines. + while (!cstr.isEmpty()) { + FQTermTextLine *line = text_lines_.value(num_hist_lines_ + caret_.row_, NULL); + + if (line == NULL) { + FQ_TRACE("error", 0) << "Error: setBuffer null line"; + return ; + } + + if (charstate & FQTermTextLine::SECONDPART) { + moveCaretOffset(-1, 0); + } + + if (caret_.column_ >= (int)line->getMaxCellCount()) { + // move the caret to the next line. + moveCaretTo(0, caret_.row_ + 1, true); + continue; + } + + if (caret_.column_ > (int)line->getWidth()) { + line->appendWhiteSpace(caret_.column_ - line->getWidth()); + } + + unsigned cell_begin = line->getCellBegin(caret_.column_); + int max_width = line->getMaxCellCount() - cell_begin; + int element_consumed = 0; + int width = get_str_width((const UTF16 *)cstr.data(), cstr.size(), + max_width, element_consumed); + + if (width < 0) { + break; + } + + if (is_insert_mode_) { + // FIXEME: How to move cursor if the entire line is wider than + // line->getMaxCellCount() after insertion? + line->insertText((UTF16 *)cstr.data(), element_consumed, cell_begin, + caret_.color_, caret_.attr_, charstate); + if ((int)line->getWidth() > num_columns_) { + line->deleteText(num_columns_, line->getWidth()); + } + } else { + line->replaceText((UTF16 *)cstr.data(), element_consumed, + cell_begin, qMin(cell_begin + width, line->getWidth()), + caret_.color_, caret_.attr_, charstate); + } + moveCaretOffset(width, 0); + + if (element_consumed == 0) + { + element_consumed = 1; + } + + if (element_consumed == cstr.size()) { + break; + } + + cstr.remove(0, element_consumed); + } +} + +void FQTermBuffer::lineFeed() { + FQ_TRACE("term", 8) << "add a new line"; + if (caret_.row_ == bottom_row_) + scrollLinesInTerm(top_row_, 1); + if (is_newline_mode_) { + moveCaretOffset(-caret_.column_, 1); + } else { + moveCaretOffset(0, 1); + } +} + +void FQTermBuffer::tab() { + FQ_TRACE("term", 8) << "add a tab"; + int x = getTabStop(caret_.column_); + + moveCaretTo(x, caret_.row_); +} + +int FQTermBuffer::getTabStop(int column) { + + std::vector<int> &tabs = *(TabStops *)tab_stops_; + + if (tabs.size() == 0) { + int res = ((caret_.column_) / 8 + 1) * 8; + return (res < num_columns_) ? res : num_columns_ - 1; + } + + std::vector<int>::iterator it = + std::upper_bound(tabs.begin(), tabs.end(), column); + + int res = num_columns_ - 1; + + if (it != tabs.end() && *it < res) { + res = *it; + } + + if (res > num_columns_ - 1) { + res = num_columns_ - 1; + } + + return res; +} + +void FQTermBuffer::addTabStop() { + // FIXME: what if the caret is located beyond the term temporarily. + int x = caret_.column_; + + std::vector<int> &tabs = *(TabStops *)tab_stops_; + + std::vector<int>::iterator it = + std::lower_bound(tabs.begin(), tabs.end(), x); + + if (it == tabs.end() || *it != x) { + tabs.insert(it, x); + } +} + +void FQTermBuffer::clearTabStop(bool clear_all) { + // FIXME: what if the caret is located beyond the term temporarily. + std::vector<int> &tabs = *(TabStops *)tab_stops_; + + if (clear_all) { + tabs.clear(); + return; + } + + int x = caret_.column_; + + std::vector<int>::iterator it = + std::lower_bound(tabs.begin(), tabs.end(), x); + + if (it != tabs.end() && *it == x) { + tabs.erase(it); + } +} + +void FQTermBuffer::setMargins(int top, int bottom) { + FQ_TRACE("term", 3) << "Set margins: [" << top << ", " << bottom << "]"; + + top_row_ = qMax(top, 0); + bottom_row_ = qMin(qMax(bottom, 0), num_rows_ - 1); + + if (is_origin_mode_) { + moveCaretTo(0, top_row_); + } else { + moveCaretTo(0, 0); + } +} + +void FQTermBuffer::moveCaretTo(int column, int row, bool scroll_if_necessary) { + if (row != caret_.row_) + emit caretChangeRow(); + + FQ_TRACE("term", 5) << "Move caret to (" << column << ", " << row << ")"; + + //If th + + // detect index boundary + if (column >= num_columns_) { + column = num_columns_; + } + if (column < 0) { + column = 0; + } + + int scroll_lines = 0; + + int stop = is_origin_mode_ ? top_row_ : 0; + if (row < stop) { + scroll_lines = row - stop; + row = stop; + } + + stop = is_origin_mode_ ? bottom_row_ : num_rows_ - 1; + if (row > stop) { + scroll_lines = row - stop; + row = stop; + } + + // Set dirty flag for cells of the last caret position. + FQTermTextLine *line = text_lines_.value(num_hist_lines_ + caret_.row_, NULL); + if (0 <= caret_.column_ && caret_.column_ < (int)line->getWidth()) { + unsigned cell_begin = line->getCellBegin(caret_.column_); + unsigned cell_end = line->getCellBegin(caret_.column_ + 1); + line->setDirtyFlag(cell_begin, cell_end); + } + + if (scroll_if_necessary) { + scrollLinesInTerm(top_row_, scroll_lines); + } + + caret_.column_ = column; + caret_.row_ = row; +} + +void FQTermBuffer::moveCaretOffset(int column_offset, int row_offset, bool scroll_if_necessary) { + if (caret_.column_ >= num_columns_) { + // it's only allowed that the caret_.column_ >= num_columns_ + // temporarily when a sequence of normal text is received. if we + // found caret_.column_ is out of bounds in other case, we should + // correct it first. + if (is_bbs_) { + // but the BBS (newsmth.net) assumes that the caret could be + // located out of the screen :( + } else { + caret_.column_ = num_columns_ - 1; + } + } + + if (caret_.column_ + column_offset < 0) { + column_offset = -caret_.column_; + } + + moveCaretTo(caret_.column_ + column_offset, caret_.row_ + row_offset, + scroll_if_necessary); +} + +void FQTermBuffer::changeCaretPosition(int coloumn, int row) { + if (is_origin_mode_) { + moveCaretTo(coloumn, row + top_row_); + } else { + moveCaretTo(coloumn, row); + } +} + +void FQTermBuffer::saveCaret() { + FQ_TRACE("term", 5) << "save the caret."; + last_saved_caret_ = caret_; +} + +void FQTermBuffer::restoreCaret() { + FQ_TRACE("term", 5) << "restore the caret."; + moveCaretTo(last_saved_caret_.column_, last_saved_caret_.row_); + caret_ = last_saved_caret_; +} + +void FQTermBuffer::SelectVtCharacterSet(VtCharSet charset, bool G0) { + if (G0) { + caret_.G0_ = charset; + } else { + caret_.G1_ = charset; + } +} + +void FQTermBuffer::invokeCharset(bool G0) { + if (G0) { + is_g0_used_ = true; + } else { + is_g0_used_ = false; + } +} + +void FQTermBuffer::carriageReturn() { + FQ_TRACE("term", 8) << "carrige return"; + moveCaretOffset(-caret_.column_, 0); +} + +int FQTermBuffer::getCaretColumn() const { + return caret_.column_; +} + +int FQTermBuffer::getCaretRow() const { + return caret_.row_; +} + +int FQTermBuffer::getCaretLine() const { + return caret_.row_ + num_hist_lines_; +} + +// erase functions +void FQTermBuffer::eraseText(int cell_count) { + FQ_TRACE("term", 8) << "erase " << cell_count << " cell(s) of text"; + + const FQTermTextLine *line = text_lines_.at(caret_.row_ + num_hist_lines_); + + int x = line->getWidth() - caret_.column_; + + clearArea(caret_.column_, caret_.row_, + qMin(cell_count, x), 1, + caret_.color_, caret_.attr_); +} + +void FQTermBuffer::deleteText(int cell_count) { + FQ_TRACE("term", 8) << "delete " << cell_count << " cell(s) of text"; + + FQTermTextLine *line = text_lines_.at(caret_.row_ + num_hist_lines_); + + int x = line->getWidth() - caret_.column_; + + if (cell_count >= x) { + line->deleteText(caret_.column_, line->getWidth()); + } else { + line->deleteText(caret_.column_, caret_.column_ + cell_count); + } +} + +void FQTermBuffer::fillScreenWith(char c) { + FQ_TRACE("term", 5) << "fill screen with '" << c << "'"; + for (int i = 0; i < num_rows_; i++) { + FQTermTextLine *line = text_lines_[i + num_hist_lines_]; + line->deleteAllText(); + line->appendWhiteSpace(num_columns_, NO_COLOR, NO_ATTR, c); + } +} + +void FQTermBuffer::insertSpaces(int count) { + FQ_TRACE("term", 8) << "insert " << count << " white space(s)"; + + FQTermTextLine *line = text_lines_.at(caret_.row_ + num_hist_lines_); + + int x = line->getWidth() - caret_.column_; + + if (count >= x) { + clearArea(caret_.column_, caret_.row_, x, caret_.row_, + caret_.color_, caret_.attr_); + } else { + line->insertWhiteSpace(count, caret_.column_, caret_.color_, caret_.attr_); + } +} + +void FQTermBuffer::deleteLines(int line_count) { + FQ_TRACE("term", 8) << "delete " << line_count << " line(s)"; + + int y = bottom_row_ - caret_.row_; + + if (line_count >= y) { + clearArea(0, caret_.row_, -1, y, caret_.color_, caret_.attr_); + } else { + scrollLinesInTerm(caret_.row_, line_count); + } +} + +void FQTermBuffer::insertLines(int count) { + FQ_TRACE("term", 8) << "insert " << count << " line(s)"; + + int y = bottom_row_ - caret_.row_; + + if (count >= y) { + clearArea(0, caret_.row_, -1, y, caret_.color_, caret_.attr_); + } else { + scrollLinesInTerm(caret_.row_, -count); + } +} + +void FQTermBuffer::eraseToLineEnd() { + FQ_TRACE("term", 8) << "erase to line end"; + + clearArea(caret_.column_, caret_.row_, -1, 1, caret_.color_, caret_.attr_); +} + +void FQTermBuffer::eraseToLineBegin() { + FQ_TRACE("term", 8) << "erase to line begin"; + clearArea(0, caret_.row_, caret_.column_ + 1, 1, caret_.color_, caret_.attr_); +} + +void FQTermBuffer::eraseEntireLine() { + FQ_TRACE("term", 8) << "erase entire line"; + clearArea(0, caret_.row_, -1, 1, caret_.color_, caret_.attr_); +} + +// Set a line of text on screen to have been changed from start to end. +void FQTermBuffer::setLineChanged(int index, int cell_begin, int cell_end) { + FQ_ASSERT(0 <= index && index < num_rows_ + num_hist_lines_); + text_lines_[index]->setDirtyFlag(cell_begin, cell_end); +} + +void FQTermBuffer::clearLineChanged(int index) { + FQ_ASSERT(0 <= index && index < num_rows_ + num_hist_lines_); + text_lines_[index]->clearDirtyFlag(); +} + +void FQTermBuffer::setLineAllChanged(int index) { + FQ_ASSERT(0 <= index && index < num_rows_ + num_hist_lines_); + text_lines_[index]->setAllDirty(); +} + +void FQTermBuffer::scrollLinesInTerm(int startRow, int numRows) { + if (numRows == 0 || startRow > bottom_row_) { + return ; + } + if (startRow < top_row_) { + startRow = top_row_; + } + + // TODO: performance issue here. Reuse the old text lines. + if (numRows > 0) { + //We are scrolling the whole screen. + if (startRow == 0 && caret_.row_ == num_rows_ - 1) { + addHistoryLine(numRows); + } else { + // delete lines from startRow, insert lines on the bottom_row_. + while (numRows) { + delete text_lines_.takeAt(num_hist_lines_ + startRow); + text_lines_.insert(num_hist_lines_ + bottom_row_, new FQTermTextLine(num_columns_)); + numRows--; + } + } + } + + // TODO: performance issue here. Reuse the old text lines. + if (numRows < 0) { + // delete lines from bottom_row_, insert lines in the startRow. + while (numRows) { + delete text_lines_.takeAt(num_hist_lines_ + bottom_row_); + text_lines_.insert(num_hist_lines_ + startRow, new FQTermTextLine(num_columns_)); + numRows++; + } + } + + for (int i = num_hist_lines_ + startRow; i <= num_hist_lines_ + bottom_row_; i++) { + text_lines_.at(i)->setAllDirty(); + } +} + +void FQTermBuffer::eraseToTermEnd() { + FQ_TRACE("term", 8) << "erase to term end"; + + if (caret_.column_ == 0 && caret_.row_ == 0) { + eraseEntireTerm(); + return ; + } + + clearArea(caret_.column_, caret_.row_, -1, 1, caret_.color_, caret_.attr_); + + if (caret_.row_ < bottom_row_) { + clearArea(0, caret_.row_ + 1, + -1, bottom_row_ - caret_.row_, + caret_.color_, caret_.attr_); + } +} + +void FQTermBuffer::eraseToTermBegin() { + FQ_TRACE("term", 8) << "erase to term begin"; + if (caret_.column_ == num_columns_ - 1 && caret_.row_ == num_rows_ - 1) { + eraseEntireTerm(); + return ; + } + + clearArea(0, caret_.row_, caret_.column_ + 1, 1, caret_.color_, caret_.attr_); + if (caret_.row_ > top_row_) { + clearArea(0, top_row_, -1, caret_.row_, caret_.color_, caret_.attr_); + } +} + +void FQTermBuffer::eraseEntireTerm() { + FQ_TRACE("term", 8) << "erase entire term"; + addHistoryLine(num_rows_); + clearArea(0, 0, num_columns_, num_rows_, caret_.color_, caret_.attr_); +} + +// width = -1 : clear to end +void FQTermBuffer::clearArea(int startColumn, int startRow, + int width, int height, + unsigned char color, unsigned char attr) { + QByteArray cstr; + + FQTermTextLine *line; + + if (startRow + height > num_rows_) { + height = num_rows_ - startRow; + } + + for (int i = startRow; i < height + startRow; i++) { + line = text_lines_[i + num_hist_lines_]; + + int w = width; + + if (startColumn < num_columns_) { + if (w == -1) { + w = num_columns_ - startColumn; + } + + int endX = startColumn + w; + if (endX > (int)line->getWidth()) { + endX = line->getWidth(); + } + + if(startColumn>=endX) + { + continue; + } + + line->replaceWithWhiteSpace(w, startColumn, endX, color, attr); + } + } +} + +void FQTermBuffer::addHistoryLine(int n) { + bool is_full = (num_hist_lines_ == max_num_hist_lines_); + + // TODO: performance issue. Reuse the old lines. + while (n) { + if (num_hist_lines_ == max_num_hist_lines_) { + delete text_lines_.takeFirst(); + } + + text_lines_.append(new FQTermTextLine(num_columns_)); + num_hist_lines_ = qMin(num_hist_lines_ + 1, max_num_hist_lines_); + n--; + } + + for (int i = num_hist_lines_ + 0; i < num_hist_lines_ + bottom_row_; i++) { + text_lines_.at(i)->setAllDirty(); + } + + if (!is_full) { + emit bufferSizeChanged(); + } +} + +void FQTermBuffer::startDecode() { + last_saved_caret_.column_ = caret_.column_; + last_saved_caret_.row_ = caret_.row_; +} + +void FQTermBuffer::endDecode() { + if (last_saved_caret_.row_ < num_rows_) { + FQTermTextLine *line = text_lines_[last_saved_caret_.row_ + num_hist_lines_]; + + line->safelySetDirtyFlag(last_saved_caret_.column_, + last_saved_caret_.column_ + 1); + } + + if (caret_.row_ < num_rows_) { + FQTermTextLine *line = text_lines_.at(caret_.row_ + num_hist_lines_); + + line->safelySetDirtyFlag(caret_.column_, caret_.column_ + 1); + } + + clearSelect(); +} + +void FQTermBuffer::setMode(TermMode mode) { + FQ_TRACE("term", 8) << "set mode " << mode; + + switch (mode) { + case INSERT_MODE: + is_insert_mode_ = true; + break; + case CURSOR_MODE: + is_cursor_mode_ = true; + break; + case ANSI_MODE: + is_ansi_mode_ = true; + case NUMERIC_MODE: + is_numeric_mode_ = true; + break; + case SMOOTH_MODE: + is_smoothscroll_mode_ = true; + break; + case NEWLINE_MODE: + is_newline_mode_ = true; + break; + case ORIGIN_MODE: + is_origin_mode_ = true; + this->moveCaretTo(0, top_row_); + break; + case AUTOWRAP_MODE: + is_autowrap_mode_ = true; + break; + case AUTOREPEAT_MODE: + is_autorepeat_mode_ = true; + break; + case LIGHTBG_MODE: + is_lightbg_mode_ = true; + break; + default: + break; + } +} + +void FQTermBuffer::resetMode(TermMode mode) { + FQ_TRACE("term", 8) << "reset mode " << mode; + + switch (mode) { + case INSERT_MODE: + is_insert_mode_ = false; + break; + case CURSOR_MODE: + is_cursor_mode_ = false; + break; + case ANSI_MODE: + is_ansi_mode_ = false; + case NUMERIC_MODE: + is_numeric_mode_ = false; + break; + case SMOOTH_MODE: + is_smoothscroll_mode_ = false; + break; + case NEWLINE_MODE: + is_newline_mode_ = false; + break; + case ORIGIN_MODE: + is_origin_mode_ = false; + this->moveCaretTo(0, 0); + break; + case AUTOWRAP_MODE: + is_autowrap_mode_ = false; + break; + case AUTOREPEAT_MODE: + is_autorepeat_mode_ = false; + break; + case LIGHTBG_MODE: + is_lightbg_mode_ = false; + break; + default: + break; + } +} + +void FQTermBuffer::setSelect(const QPoint &pt1, const QPoint &pt2) { + QPoint ptSelStart, ptSelEnd; + + if (pt1.y() == pt2.y()) { + ptSelStart = pt1.x() < pt2.x() ? pt1 : pt2; + ptSelEnd = pt1.x() > pt2.x() ? pt1 : pt2; + } else { + ptSelStart = pt1.y() < pt2.y() ? pt1 : pt2; + ptSelEnd = pt1.y() > pt2.y() ? pt1 : pt2; + } + + int y1 = ptSelStart.y(); + int y2 = ptSelEnd.y(); + + if (!(selection_start_ == QPoint(-1, -1) + && selection_end_ == QPoint(-1, -1))) { + y1 = qMin(y1, selection_start_.y()); + y2 = qMax(y2, selection_end_.y()); + } + + for (int i = y1; i <= y2; i++) { + text_lines_.value(i, NULL)->setAllDirty(); + } + + selection_start_ = ptSelStart; + selection_end_ = ptSelEnd; +} + +void FQTermBuffer::clearSelect() { + if (selection_start_ == QPoint(-1, -1) && selection_end_ == QPoint(-1, -1)) { + return; + } + + for (int i = selection_start_.y(); i <= selection_end_.y(); i++) { + text_lines_.value(i, NULL)->setAllDirty(); + } + + selection_start_ = selection_end_ = QPoint(-1, -1); +} + +bool FQTermBuffer::isSelected(int line_index) const { + if (selection_start_ == QPoint(-1, -1) && selection_end_ == QPoint(-1, -1)) { + return false; + } else { + return line_index >= selection_start_.y() && line_index <= selection_end_.y(); + } +} + +bool FQTermBuffer::isSelected(const QPoint &cell, bool is_rect_sel) const { + if (selection_start_ == QPoint(-1, -1) && selection_end_ == QPoint(-1, -1)) { + return false; + } + + if (cell.y() < 0 || cell.y() >= getNumLines()) { + return false; + } + + int x1 = selection_start_.x(); + int y1 = selection_start_.y(); + + int x2 = selection_end_.x(); + int y2 = selection_end_.y(); + + if (cell.y() < y1 || cell.y() > y2) + return false; + + const FQTermTextLine *line = this->getTextLineInBuffer(cell.y()); + + int cell_begin = 0; + int cell_end = cell.x() + 1; + + if (is_rect_sel) { + int minx = qMin(x1, x2); + int maxx = qMax(x1, x2); + cell_begin = line->getCellBegin(qMin(minx, (int)line->getWidth())); + cell_end = line->getCellEnd(qMin(maxx + 1, (int)line->getWidth())); + } else { + if (cell.y() == y1) { + cell_begin = line->getCellBegin(qMin(x1, (int)line->getWidth())); + } + if (cell.y() == y2) { + cell_end = line->getCellEnd(qMin(x2 + 1, (int)line->getWidth())); + } + } + + return cell_begin <= cell.x() && cell.x() < cell_end; +} + +static void removeTrailSpace(QString &line) { + for (int last_non_space = line.size() - 1; + last_non_space >= 0; --last_non_space) { + QChar a = line.at(last_non_space); + if (!a.isSpace()) { + line.resize(last_non_space + 1); + break; + } + + if (last_non_space == 0) { + line.resize(0); + } + } +} + + +QString FQTermBuffer::getTextSelected(bool is_rect_sel, bool is_color_copy, + const QByteArray &escape) const { + QString cstrSelect; + QString strTemp; + + if (selection_start_ == QPoint(-1, -1) && selection_end_ == QPoint(-1, -1)) { + return cstrSelect; + } + + QRect rc; + + for (int i = selection_start_.y(); i <= selection_end_.y(); i++) { + strTemp.clear(); + rc = getSelectRect(i, is_rect_sel); + + FQTermTextLine *line = text_lines_.at(i); + unsigned cell_begin = qMax(rc.left(), 0); + unsigned cell_end = qMin(rc.right() + 1, (int)line->getWidth()); + FQ_ASSERT(rc.left() + rc.width() == rc.right() + 1); + + if (cell_begin < cell_end) { + cell_begin = line->getCellBegin(cell_begin); + cell_end = line->getCellEnd(cell_end); + + if (is_color_copy) { + line->getAnsiText(cell_begin, cell_end, strTemp, escape); + } else { + line->getPlainText(cell_begin, cell_end, strTemp); + } + } + + removeTrailSpace(strTemp); + + cstrSelect += strTemp; + + // add newline except the last line + if (i != selection_end_.y()) { + cstrSelect += OS_NEW_LINE; + } + } + + return cstrSelect; +} + +QRect FQTermBuffer::getSelectRect(int line_index, bool is_rect_sel) const { + FQ_ASSERT(isSelected(line_index)); + + if (is_rect_sel) { + return QRect(qMin(selection_start_.x(), selection_end_.x()), line_index, + abs(selection_end_.x() - selection_start_.x()) + 1, 1); + } else if (selection_start_.y() == selection_end_.y()) { + return QRect(selection_start_.x(), line_index, + qMin(selection_end_.x(), num_columns_) - selection_start_.x() + 1, 1); + } else if (line_index == selection_start_.y()) { + return QRect(selection_start_.x(), line_index, + qMax(0, num_columns_ - selection_start_.x()), 1); + } else if (line_index == selection_end_.y()) { + return QRect(0, line_index, qMin(num_columns_, selection_end_.x() + 1), 1); + } else { + return QRect(0, line_index, num_columns_, 1); + } +} + +void FQTermBuffer::scrollTerm(int numRows) { + scrollLinesInTerm(caret_.row_, numRows); + moveCaretOffset(0, numRows); +} + + +} // namespace FQTerm + +#include "fqterm_buffer.moc" diff --git a/src/terminal/fqterm_buffer.h b/src/terminal/fqterm_buffer.h new file mode 100644 index 0000000..3e0ef6a --- /dev/null +++ b/src/terminal/fqterm_buffer.h @@ -0,0 +1,297 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_BUFFER_H +#define FQTERM_BUFFER_H + +#include <QList> +#include <QObject> +#include <QPoint> + +#include "fqterm_text_line.h" +class QString; +class QByteArray; +class QRect; + +namespace FQTerm { + +class FQTermTextLine; + +/* Text buffer consists of characters in a matrix of cells. + * + * Buffer + * +--------------------------------+ + * ||----> | + * || x | + * || | + * |V Y | + * | | + * . . + * . . + * . histroical lines . + * . . + * . . + * | | + * | | + * | | + * | | + * |+------------------------------+| + * |||----> || + * ||| x || + * ||| terminal || + * ||V Y || + * || || + * || || + * || | (caret) || + * || || + * || || + * |+------------------------------+| + * +--------------------------------+ + * + * Text buffer, to manage lines of characters with colors and + * attributes, caret positions, terminal size/margins, selection + * region, historical text lines, buffer write mode (insert or + * replace), and etc. + * + * Note: There are two coordinates, relative to terminal or entire + * buffer. When we use (column, row) it indicates terminal coordinate, + * while when we use (column, line) it indicates buffer coordinate. + * This rule applies both in variable names and function names. + **/ +class FQTermBuffer: public QObject { + Q_OBJECT; + public: + enum TermMode { + INSERT_MODE, /* otherwise replace mode */ + ANSI_MODE, /* otherwise VT52 mode */ + SMOOTH_MODE, /* otherwse jump mode */ + NEWLINE_MODE, /* otherwise line feed mode */ + CURSOR_MODE, /* otherwise reset cursor mode */ + NUMERIC_MODE, /* otherwise application mode */ + ORIGIN_MODE, /* otherwise absolute mode */ + AUTOWRAP_MODE, /* otherwise non-autowrap mode */ + AUTOREPEAT_MODE, /* otherwise non-autorepeat mode */ + LIGHTBG_MODE, /* otherwise dark background mode */ + }; + + enum VtCharSet { + UNITED_KINGDOM_SET, + ASCII_SET, + SPECIAL_GRAPHICS, + ALTERNATE_CHARACTER_ROM_STANDARD_CHARACTER_SET, + ALTERNATE_CHARACTER_ROM_SPECIAL_GRAPHICS + }; + + FQTermBuffer(int column, int row, int max_hist_line, bool is_bbs); + ~FQTermBuffer(); + + // Get text lines. + // Return null if the line_index is out of bound, return NULL. + const FQTermTextLine *getTextLineInBuffer(int line_index) const; + const FQTermTextLine *getTextLineInTerm(int line_index) const; + + // Get number of columns and rows of the term, or lines of the + // entire buffer. + int getNumColumns() const; + int getNumRows() const; + int getNumLines() const; + + // Set the size of the screen. + void setTermSize(int col, int row); + // set margins in term. (Generally speaking, most operations are + // restrictied in [top_row_, bottom_row_] of terminal.) + void setMargins(int top_row, int bottom_row); + + // the caret's coordinate in term or buffer. + int getCaretColumn() const; + int getCaretRow() const; + int getCaretLine() const; + + // Set current attribute. + void setCurrentAttr(unsigned char color, unsigned char attr); + + // Below are a series of functions to modify the content of buffer. + // These functions will work from the current caret position. + // All the behaviours of these functions may be influenced by the + // mode of this buffer (is_insert_mode_, is_newline_mode_, and + // etc.). + + void writeText(const QString &cstr, int charstate = FQTermTextLine::NORMAL); + + void fillScreenWith(char c); + + void insertSpaces(int count); + void insertLines(int count); + + void deleteText(int cell_count); + void deleteLines(int line_count); + + void eraseText(int cell_count); + + void eraseToLineBegin(); + void eraseToLineEnd(); + void eraseEntireLine(); + + void eraseToTermBegin(); + void eraseToTermEnd(); + void eraseEntireTerm(); + + // Set a line of buffer to have been changed from start to end. + void setLineChanged(int index, int cell_begin, int cell_end); + void clearLineChanged(int index); + void setLineAllChanged(int index); + + // Functions about caret. + // If the caret is moved, the cells of the position of the last caret will + // be marked as changed. + void moveCaretOffset(int coloumn_offset, int row_offset, + bool scroll_if_necessary = false); + void moveCaretTo(int coloumn, int row, bool scroll_if_necessary = false); + void changeCaretPosition(int coloumn, int row); + void saveCaret(); + void restoreCaret(); + + void SelectVtCharacterSet(VtCharSet charset, bool G0); + void invokeCharset(bool G0); + + // non-printing characters + void tab(); + void carriageReturn(); + void lineFeed(); + + void addTabStop(); + void clearTabStop(bool clear_all); + + // Select Mode (SM) and Reset Mode (RM) functions. + // See ANSI X3.64 Mode-Changing Parameters. + void setMode(TermMode); + void resetMode(TermMode); + + bool isCursorMode() const { return is_cursor_mode_; } + bool isAnsiMode() const { return is_ansi_mode_; } + bool isNumericMode() const { return is_numeric_mode_; } + bool isAutoRepeatMode() const { return is_autorepeat_mode_; } + bool isLightBackgroundMode() const { return is_lightbg_mode_; } + bool isNewLineMode() const {return is_newline_mode_;} + + // for test + void startDecode(); + void endDecode(); + + // Functions about selection in buffer. + void setSelect(const QPoint &start_point, const QPoint &end_point); + void clearSelect(); + bool isSelected(const QPoint &cell, bool isRectSel) const; + bool isSelected(int line_index) const; + + QString getTextSelected(bool is_rect_sel, bool is_color_copy, + const QByteArray &escape) const; + + // Get the rectangle of selected text of a certain line in buffer. + // Note: please ensure there exits any cell of the line line_index + // is selected. + QRect getSelectRect(int line_index, bool is_rect_sel) const; + + //Scroll line && adjust caret pos. + //Scroll must occur at the line where caret is + void scrollTerm(int numRows); + + signals: + void bufferSizeChanged(); + void termSizeChanged(int column, int row); + void onSetTermSize(int col, int row); + void caretChangeRow(); + private: + // Scroll lines between startRow and bottom_row_ (see setMargin()). + // num > 0 scroll up. + // num < 0 scroll down. + void scrollLinesInTerm(int startRow, int numRows); + + // Replace a area of cells with spaces. + void clearArea(int startColumn, int startRow, + int width, int height, + unsigned char color, unsigned char attr); + + // Append n empty lines to the buffer and make the first n lines of + // term historical. + void addHistoryLine(int n); + + int getTabStop(int column); + + struct Caret { + int column_, row_; + int color_, attr_; + VtCharSet G0_, G1_; + + Caret(); + }; + + // terminal size. + int num_columns_, num_rows_; + + // term margins. (Generally speaking, most operations are + // restrictied in [top_row_, bottom_row_] of terminal.) + int top_row_, bottom_row_; + + // Historical data length and max length. + int num_hist_lines_, max_num_hist_lines_; + + // All lines of text, including both historical lines and lines in + // current terminal. + // + // Note: the number of historical lines, num_hist_lines, may + // increase from 0 to max_num_hist_lines_. + QList<FQTermTextLine *> text_lines_; + + void *tab_stops_; + + // The caret in terminal + Caret caret_; + Caret last_saved_caret_; + + bool is_g0_used_; // is G0 or G1 charset used; + + bool is_insert_mode_; // Is insert or replace mode. + bool is_ansi_mode_; // Is ansi or vt52 mode. + bool is_smoothscroll_mode_; // Is smooth or jump scrolling mode. + bool is_newline_mode_; // Is newline or linefeed mode. + bool is_cursor_mode_; // Is cursor key mode. + bool is_numeric_mode_; // Is numeric or application mode. + bool is_origin_mode_; // Is origin or absolute mode. + bool is_autowrap_mode_; // Is auto-wrap mode enabled. + bool is_autorepeat_mode_; // Is auto-repeat mode enabled. + bool is_lightbg_mode_; // Is ligth background or dark backgrond mode. + + // Selection in buffer. If section start and end are in the same + // line, start.x should be equal to or less than end.x. Otherwise + // start.y should be less than end.y. + // + // If selection_start_ = selection_start_ = (-1, -1), the selection + // is empty. + QPoint selection_start_; + QPoint selection_end_; + + // whether this buffer is used for a bbs session. + bool is_bbs_; +}; + +} // namespace FQTerm + +#endif // FQTERM_BUFFER_H diff --git a/src/terminal/fqterm_session.cpp b/src/terminal/fqterm_session.cpp new file mode 100644 index 0000000..6c5b513 --- /dev/null +++ b/src/terminal/fqterm_session.cpp @@ -0,0 +1,1648 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include "fqterm.h" +#include "common.h" +#include "fqterm_trace.h" +#include "fqterm_session.h" +#include "fqterm_buffer.h" +#include "fqterm_text_line.h" +#include "fqterm_telnet.h" +#include "fqterm_decode.h" +#include "fqterm_zmodem.h" + +#ifdef HAVE_PYTHON
+#include <Python.h>
+#endif //HAVE_PYTHON + +#ifndef WIN32 +#include <unistd.h> +#else +#include <windows.h> +#endif + + +#include <QString> +#include <QTimer> +#include <QMutex> +#include <QRegExp> +#include <QtAlgorithms> +#include <QChar> +#include <QPair> +#include <QReadLocker> +#include <QWriteLocker> +#include <QReadWriteLock> + +namespace FQTerm { + +const QString FQTermSession::endOfUrl[] = { + "ac","ad","ae","af","ag","ai","al","am","an","ao","aq","ar","as","at", + "au","aw","az","ba","bb","bd","be","bf","bg","bh","bi","bj","bm","bn","bo","br", + "bs","bt","bv","bw","by","bz","ca","cc","cd","cf","cg","ch","ci","ck","cl","cm", + "cn","co","uk","com","cr","cs","cu","cv","cx","cy","cz","de","dj","dk","dm", + "do","dz","ec","edu","ee","eg","eh","er","es","et","fi","fj","fk","fm","fo", + "fr","ga","gd","ge","gf","gg","gh","gi","gl","gm","gn","gov","gp","gq","aero", + "asia","biz","coop","eu","info","museum","name","pro","travel","gr","gs","gt", + "gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il","im","in","int", + "io","iq","ir","is","it","je","jm","jo","jp","ke","kg","kh","ki","km","kn","kp", + "kr","kw","ky","kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma", + "mc","md","mg","mh","mil","mk","ml","mm","mn","mo","mp","mq","mr","ms","mt", + "mu","mv","mw","mx","my","mz","na","nc","ne","net","nf","ng","ni","nl","no", + "np","nr","nt","nu","nz","om","org","pa","pe","pf","pg","ph","pk","pl","pm", + "pn","pr","ps","pt","pw","py","qa","re","ro","ru","rw","sa","sb","sc","sd","se", + "sg","sh","si","sj","sk","sl","sm","sn","so","sr","sv","st","sy","sz","tc","td", + "tf","tg","th","tj","tk","tm","tn","to","tp","tr","tt","tv","tw","tz","ua","ug", + "uk","um","us","uy","uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt", + "yu","za","zm","zw" +}; + +FQTermSession::FQTermSession(FQTermConfig *config, FQTermParam param) { + reconnectRetry_ = 0; + scriptListener_ = NULL; + param_ = param; + termBuffer_ = new FQTermBuffer(param_.numColumns_, + param_.numRows_, + param_.numScrollLines_, + param_.hostType_ == 0); + + if (param.protocolType_ == 0) { + telnet_ = new FQTermTelnet(param_.virtualTermType_.toLatin1(), + param_.numRows_, param_.numColumns_, param.protocolType_, param.hostType_ ); + } else if (param.protocolType_ == 3) { + telnet_ = new FQTermTelnet(param_.virtualTermType_.toLatin1(), + param_.numRows_, param_.numColumns_, param.protocolType_, param.hostType_ ); + } else { +#if defined(_NO_SSH_COMPILED) + QMessageBox::warning(this, "sorry", + "SSH support is not compiled, " + "FQTerm can only use Telnet!"); + telnet_ = new FQTermTelnet(param_.virtualTermType_.toUtf8(), + param_.numRows_, param_.numColumns_, param.protocolType_, param.hostType_ ); +#else + telnet_ = new FQTermTelnet(param_.virtualTermType_.toUtf8(), + param_.numRows_, param_.numColumns_, param.protocolType_ , param.hostType_ , + param_.sshUserName_.toUtf8(), + param_.sshPassword_.toUtf8()); +#endif + } + + zmodem_ = new FQTermZmodem(config, telnet_, param.protocolType_, param.serverEncodingID_); + decoder_ = new FQTermDecode(termBuffer_, param.serverEncodingID_); + FQ_VERIFY(connect(decoder_, SIGNAL(enqReceived()), this, SLOT(onEnqReceived()))); + FQ_VERIFY(connect(decoder_, SIGNAL(onTitleSet(const QString&)), this, SIGNAL(onTitleSet(const QString&)))); + + isConnected_ = false; +#ifndef _NO_SSH_COMPILED + if (param.protocolType_ != 0) { + isSSHLogining_ = true; + } else { + isSSHLogining_ = false; + } +#else + isSSHLogining_ = false; +#endif + isTelnetLogining_ = false; + isIdling_ = false; + isSendingMessage_ = false; + isMouseX11_ = false; + + idleTimer_ = new QTimer; + autoReplyTimer_ = new QTimer; + + acThread_ = new ArticleCopyThread(*this, waitCondition_, bufferWriteLock_); + + FQ_VERIFY(connect(decoder_, SIGNAL(mouseMode(bool)), + this, SLOT(setMouseMode(bool)))); + FQ_VERIFY(connect(telnet_, SIGNAL(readyRead(int, int)), + this, SLOT(readReady(int, int)))); + FQ_VERIFY(connect(telnet_, SIGNAL(TelnetState(int)), + this, SLOT(changeTelnetState(int)))); + FQ_VERIFY(connect(telnet_, SIGNAL(errorMessage(QString)), + this, SIGNAL(errorMessage(QString)))); + FQ_VERIFY(connect(telnet_, SIGNAL(requestUserPwd(QString*, QString*, bool*)), + this, SIGNAL(requestUserPwd(QString*, QString*, bool*)))); + + FQ_VERIFY(connect(telnet_, SIGNAL(onSSHAuthOK()), + this, SLOT(onSSHAuthOK()))); + + + + FQ_VERIFY(connect(termBuffer_, SIGNAL(onSetTermSize(int, int)), + telnet_, SLOT(windowSizeChanged(int, int)))); + + FQ_VERIFY(connect(zmodem_, SIGNAL(ZmodemState(int, int, const char *)), + this, SIGNAL(zmodemStateChanged(int, int, const char *)))); + + FQ_VERIFY(connect(idleTimer_, SIGNAL(timeout()), this, SLOT(onIdle()))); + FQ_VERIFY(connect(autoReplyTimer_, SIGNAL(timeout()), + this, SLOT(onAutoReply()))); + + FQ_VERIFY(connect(acThread_, SIGNAL(articleCopied(int, const QString)), + this, SIGNAL(articleCopied(int, const QString)))); + + setAntiIdle(param_.isAntiIdle_); +} + +FQTermSession::~FQTermSession() { + delete idleTimer_; + delete autoReplyTimer_; + delete acThread_; + delete termBuffer_; + delete telnet_; + delete zmodem_; + delete decoder_; +} + +const QString FQTermSession::protocolPrefix[] = { + "http://", "https://", "mms://", "rstp://", "ftp://", "mailto:", "telnet://" +}; + +void FQTermSession::setScreenStart(int nStart) { + screenStartLineNumber_ = nStart; +} + +bool FQTermSession::setCursorPos(const QPoint &pt, QRect &rc) { + QRect rectOld = getMenuRect(); + + cursorPoint_ = pt; + + detectMenuRect(); + QRect rectNew = getMenuRect(); + + rc = rectOld | rectNew; + + return rectOld != rectNew; +} + +QString FQTermSession::getMessage() { + const FQTermTextLine *line; + LineColorInfo colorInfo; + QString message; + + getLineColorInfo(termBuffer_->getTextLineInTerm(0), &colorInfo); + if (!colorInfo.uniBackgroundColor) { + return message; + } + + int i = 1; + termBuffer_->getTextLineInTerm(0)->getAllPlainText(message); + + line = termBuffer_->getTextLineInTerm(i); + getLineColorInfo(line, &colorInfo); + while (colorInfo.uniBackgroundColor && colorInfo.hasBackgroundColor) { + message += "\n"; + line->getAllPlainText(message); + i++; + line = termBuffer_->getTextLineInTerm(i); + getLineColorInfo(line, &colorInfo); + } + return message; +} + + +void FQTermSession::detectPageState() { + //for smth type bbs. + pageState_ = Undefined; + if (param_.hostType_ != 0) { + return; + } + + const FQTermTextLine *line[4]; + LineColorInfo colorInfo[4]; + int lineIndex[4] = {0, 1, 2}; + lineIndex[3] = termBuffer_->getNumRows() - 1; + for (int i = 0; i < 4; ++i) { + line[i] = termBuffer_->getTextLineInTerm(lineIndex[i]); + getLineColorInfo(line[i], colorInfo + i); + } + + //TODO: Detect PageState in a clearer way. + if (!colorInfo[0].hasBackgroundColor) { + if (colorInfo[3].foregroundColorIndex.count() != 0 && + colorInfo[3].hasBackgroundColor){ + if (colorInfo[3].uniBackgroundColor) { + QString text; + line[3]->getAllPlainText(text); + if (text[0] != L'\x3010') { + pageState_ = Read; + } else { + pageState_ = Edit; + } + } else if (colorInfo[3].backgroundColorIndex.count() == 2 && + colorInfo[3].backgroundColorIndex.at(0) == 4 && + colorInfo[3].backgroundColorIndex.at(1) == 0){ + pageState_ = Read; + } + } + return; + } + + if (colorInfo[0].backgroundColorIndex.at(0) != 4 + || !(colorInfo[3].hasBackgroundColor && colorInfo[3].backgroundColorIndex.at(0) == 4)) { + if (!colorInfo[3].hasBackgroundColor && + colorInfo[0].foregroundColorIndex.count() == 4 && + colorInfo[0].foregroundColorIndex.at(1) == 4 && + colorInfo[0].foregroundColorIndex.at(2) == 7 && + colorInfo[0].foregroundColorIndex.at(3) == 4){ + pageState_ = TOP10; + } + return; + } + + if (colorInfo[1].hasBackgroundColor && colorInfo[1].uniBackgroundColor) { + pageState_ = Message; + return; + } + + if (colorInfo[0].uniBackgroundColor) { + if (!colorInfo[2].hasBackgroundColor || + colorInfo[2].backgroundColorIndex.at(0) != 4 || + colorInfo[2].backgroundColorIndex.count() > 3) { + pageState_ = Menu; + return; + } + } + + if (!colorInfo[0].uniBackgroundColor && + colorInfo[0].backgroundColorIndex.at(0) == 4 && + !colorInfo[3].uniBackgroundColor) { + //announce, elite root + pageState_ = EliteArticleList; + return; + } + + if (colorInfo[2].hasBackgroundColor && + !colorInfo[2].uniBackgroundColor && + colorInfo[2].backgroundColorIndex.at(0) == 4 && + !colorInfo[3].uniBackgroundColor) { + pageState_ = EliteArticleList; + return; + } + + if (colorInfo[0].foregroundColorIndex.indexOf(14) != -1) { + pageState_ = ArticleList; + return; + } + + if (colorInfo[2].foregroundColorIndex.indexOf(7) != -1) { + QString text; + line[2]->getAllPlainText(text); + if (text[0] == ' ') { + pageState_ = BoardList; + } else { + pageState_ = MailMenu; + } + } + +} + +FQTermSession::CursorType FQTermSession::getCursorType(const QPoint &pt) { + if (screenStartLineNumber_ != + (termBuffer_->getNumLines() - termBuffer_->getNumRows())) { + return kNormal; + } + + QRect rc = getMenuRect(); + CursorType nCursorType = kNormal; + switch (pageState_) { + case Undefined: // not recognized + if (rc.contains(pt)) { + //HAND + nCursorType = kRight; + } else { + nCursorType = kNormal; + } + break; + case Menu: + case MailMenu: + // menu + if (pt.x() < 5) { + // LEFT + nCursorType = kLeft; + } else if (rc.contains(pt)) { + // HAND + nCursorType = kRight; + } else { + nCursorType = kNormal; + } + break; + case ArticleList: + case BoardList: + case EliteArticleList: + case FriendMailList: + // list + if (pt.x() < 12) { + // LEFT + nCursorType = kLeft; + } else if (pt.y() - screenStartLineNumber_ < 3) { + // HOME + nCursorType = kHome; + } else if (pt.y() == termBuffer_->getNumLines() - 1) { + // END + nCursorType = kEnd; + } else if (pt.x() > termBuffer_->getNumColumns() - 16 && + pt.y() - screenStartLineNumber_ <= termBuffer_->getNumRows() / 2) { + //PAGEUP + nCursorType = kPageUp; + } else if (pt.x() > termBuffer_->getNumColumns() - 16 + && pt.y() - screenStartLineNumber_ > + termBuffer_->getNumRows() / 2) { + // PAGEDOWN + nCursorType = kPageDown; + } else if (rc.contains(pt)) { + // HAND + nCursorType = kRight; + } else { + nCursorType = kNormal; + } + break; + case Read: + // read + if (pt.x() < 12) { + // LEFT + nCursorType = kLeft; + } else if (pt.x() > (termBuffer_->getNumColumns() - 16) && + (pt.y() - screenStartLineNumber_) + <= termBuffer_->getNumRows() / 2) { + // PAGEUP + nCursorType = kPageUp; + } else if (pt.x() > (termBuffer_->getNumColumns() - 16) && + (pt.y() - screenStartLineNumber_) + > termBuffer_->getNumRows() / 2) { + // PAGEDOWN + nCursorType = kPageDown; + } else if (rc.contains(pt)) { + //HAND + nCursorType = kRight; + } else { + nCursorType = kNormal; + } + break; + case Edit: + // TODO: add action for kEdit state. + if (rc.contains(pt)) { + //HAND + nCursorType = kRight; + } else { + nCursorType = kNormal; + } + break; + case TOP10: + if (pt.x() < 12) { + nCursorType = kLeft; + } else if (rc.contains(pt)){ + nCursorType = kRight; + } + break; + default: + FQ_TRACE("error", 2) << "Error, wrong PageState."; + break; + } + + return nCursorType; +} + +bool FQTermSession::isSelectedMenu(int line) { + // TODO: possible performance issue + QRect rect = getMenuRect(); + + // nothing selected + if (rect.isNull()) { + return false; + } + + return line >= rect.bottom() && line <= rect.top(); +} + +bool FQTermSession::isSelectedMenu(const QPoint &pt) { + // TODO: possible performance issue + QRect rect = getMenuRect(); + + // nothing selected + if (rect.isNull()) { + return false; + } + + return rect.contains(pt); +} + +FQTermSession::PageState FQTermSession::getPageState() { + return pageState_; +} + +char FQTermSession::getMenuChar() { + return menuChar_; +} + +void FQTermSession::setMenuRect(int row, int col, int len) { + menuRect_ = QRect(col, row, len, 1); +} + +QRect FQTermSession::detectMenuRect() { + QRect rect(0, 0, 0, 0); + menuRect_ = rect; + if (scriptListener_) + { + bool res = scriptListener_->postScriptCallback(SFN_DETECT_MENU +#ifdef HAVE_PYTHON + ,Py_BuildValue("l", scriptListener_->windowID()) +#endif //HAVE_PYTHON + ); + if (res) return menuRect_; + } + // current screen scrolled + if (screenStartLineNumber_ != + (termBuffer_->getNumLines() - termBuffer_->getNumRows())) { + return rect; + } + + const FQTermTextLine *line; + int menuStartLine = 8; + switch (pageState_) { + case Undefined: break; + case MailMenu: + menuStartLine = 7; + case Menu: + if (cursorPoint_.y() - screenStartLineNumber_ >= menuStartLine && + cursorPoint_.x() > 5) { + line = termBuffer_->getTextLineInBuffer(cursorPoint_.y()); + QString cstr; + int cell_end = line->getCellEnd(qMin(cursorPoint_.x(), + (int)line->getWidth())); + line->getPlainText(0, cell_end, cstr); + + int base = cstr.lastIndexOf(" "); + if (base == -1) { + base = 0; + } + + QRegExp reg("[a-zA-Z0-9][).\\]]"); + char indexChar = cstr.indexOf(reg, base); + if (indexChar != -1) { + menuChar_ = cstr.at(indexChar).toLatin1(); + + QString strTmp = cstr.left(cstr.indexOf(reg, base)); + int nMenuStart = get_str_width((const UTF16 *)strTmp.data(), + strTmp.size()); + if (cstr[indexChar - 1] == '(' || cstr[indexChar - 1] == '[') { + nMenuStart--; + } + + cstr.clear(); + line->getAllPlainText(cstr); + + + reg = QRegExp("[^ ]"); + + int nMenuBaseLength = 20; + int cell_begin = + line->getCellBegin( + qMin(nMenuStart + nMenuBaseLength, (int)line->getWidth() - 1)); + int char_begin = line->getCharBegin(cell_begin); + //last [^ ] until char_begin + strTmp = cstr.left(cstr.lastIndexOf(reg, char_begin) + 1); + + int nMenuLength = + get_str_width((const UTF16 *)strTmp.data(), strTmp.size()) + - nMenuStart; + if (nMenuLength == nMenuBaseLength + 1) { + strTmp = cstr.left(cstr.indexOf(" ", char_begin) + 1); + nMenuLength = + get_str_width((const UTF16 *)strTmp.data(), strTmp.size()) + - nMenuStart; //base length is not enough + } + + if (cursorPoint_.x() >= nMenuStart && cursorPoint_.x() <= + nMenuStart + nMenuLength) { + rect.setX(nMenuStart); + rect.setY(cursorPoint_.y()); + rect.setWidth(nMenuLength); + rect.setHeight(1); + } + } + } + break; + case ArticleList: + case BoardList: + case FriendMailList: + case EliteArticleList: + if ((cursorPoint_.y() - screenStartLineNumber_) >= 3 + && (cursorPoint_.y() - screenStartLineNumber_) + < termBuffer_->getNumRows() -1 + && cursorPoint_.x() >= 12 + && cursorPoint_.x() <= termBuffer_->getNumColumns() - 16) { + line = termBuffer_->getTextLineInBuffer(cursorPoint_.y()); + //QString str = line->getText(); + QString str; + line->getAllPlainText(str); + + if (str.count(" ") != (int)str.length()) { + rect.setX(0); + rect.setY(cursorPoint_.y()); + rect.setWidth(line->getWidth()); + rect.setHeight(1); + } + } + break; + case Read: + + break; + case Edit: + break; + case TOP10: + { + int ln = cursorPoint_.y() - screenStartLineNumber_; + if (ln >= 3 && (ln & 1) && ln <= 21 && + cursorPoint_.x() >= 12 && + cursorPoint_.x() <= termBuffer_->getNumColumns() - 8){ + line = termBuffer_->getTextLineInBuffer(cursorPoint_.y()); + + QString str; + line->getAllPlainText(str); + + if (str.count(" ") != (int)str.length()) { + if (ln == 21){ + menuChar_ = '0'; + } else { + menuChar_ = ((ln - 1) >> 1) + '0'; + } + + rect.setX(12); + rect.setY(cursorPoint_.y()); + rect.setWidth(line->getWidth() - 20); + rect.setHeight(1); + } + } + } + break; + default: + break; + } + menuRect_ = rect; + return rect; +} + +bool FQTermSession::isIllChar(char ch) { + static char illChars[] = ";'\"[]<>^"; + return ch > '~' || ch < '!' || strchr(illChars, ch) != NULL; +} + +bool FQTermSession::isUrl(QRect &rcUrl, QRect &rcOld) { + return checkUrl(rcUrl, rcOld, false); +} + +bool FQTermSession::isIP(QRect &rcUrl, QRect &rcOld) { + return checkUrl(rcUrl, rcOld, true); +} + +QString FQTermSession::expandUrl(const QPoint& pt, QPair<int, int>& range) +{ + // int at = pt.x(); + range.first = -1; + range.second = -2; + const FQTermTextLine *textLine = termBuffer_->getTextLineInBuffer(pt.y()); + if (textLine == NULL || pt.x() > (int)textLine->getWidth()) { + return ""; + } + + QString text; + textLine->getAllPlainText(text); + + int cell_begin = textLine->getCellBegin(pt.x()); + int at = textLine->getCharBegin(cell_begin); + if (at >= text.length()) { + return ""; + } + + int start; + int end; + for (start = at; start >= 0 && !isIllChar(text.at(start).toLatin1()); + start--) { + } + start++; + for (end = at; end < text.length() && !isIllChar(text.at(end).toLatin1()); + end++) { + } + range.first = get_str_width((const UTF16 *)text.data(), start); + range.second = get_str_width((const UTF16 *)text.data(), end); + return text.mid(start, end - start); +} + + +bool FQTermSession::checkUrl(QRect &rcUrl, QRect &rcOld, bool checkIP) { + + + + QPoint pt = cursorPoint_; + int at = pt.x(); + rcOld = urlRect_; + + if (at > urlRect_.left() && at < urlRect_.right() && urlRect_.y() == pt.y()) { + if ( (checkIP && !ip_.isEmpty()) || + (!checkIP && !url_.isEmpty()) ) { + rcUrl = urlRect_; + return true; + } + + } + + QPoint urlStartPoint; + QPoint urlEndPoint; + urlStartPoint_ = QPoint(); + urlEndPoint_ = QPoint(); + urlRect_ = QRect(0, 0, -1, -1); + if (!checkIP) { + url_.clear(); + } else { + ip_.clear(); + } + + QString urlText; + //phase 1: find all consecutive legal chars + QPair<int, int> range; + urlText = expandUrl(pt, range); + if (range.first < 0 || range.first >= range.second) { + return false; + } + + + QRect urlRect = QRect(range.first, pt.y(), range.second - range.first, 1); + urlStartPoint = QPoint(range.first, pt.y()); + urlEndPoint = QPoint(range.second, pt.y()); + + if (range.second == termBuffer_->getNumColumns()) { + for (int i = pt.y() + 1; i < termBuffer_->getNumLines(); ++i) { + QString lineText = expandUrl(QPoint(0, i), range); + if (range.first == 0) { + urlText += lineText; + urlEndPoint.setY(urlEndPoint.y() + 1); + urlEndPoint.setX(range.second); + if (range.second == termBuffer_->getNumColumns()) { + continue; + } + } + break; + } + } + + //phase 2: find protocol prefix for url + //if none, prefix to add is set to "mailto:" if '@' is found + //otherwise, the prefix is "http://" by default. + QString prefixToAdd; + int protocolIndex; + for (protocolIndex = 0; protocolIndex < ProtocolSupported; ++protocolIndex) { + int begin = urlText.indexOf(protocolPrefix[protocolIndex], + Qt::CaseInsensitive); + if (begin != -1 && begin <= at) { + prefixToAdd = protocolPrefix[protocolIndex]; + urlText = urlText.mid(begin + protocolPrefix[protocolIndex].length()); + urlStartPoint.setX(urlStartPoint.x() + begin); + break; + } + } + if (protocolIndex == ProtocolSupported) { + int begin = urlText.indexOf("://"); + if (begin != -1) { + return false; + } + if (urlText.indexOf('@') != -1) { + prefixToAdd = protocolPrefix[Mailto]; + } else { + prefixToAdd = protocolPrefix[Http]; + } + } + + //no point or duplicated points + if (urlText.indexOf("..") != -1 || urlText.indexOf('.') == -1) { + return false; + } + + //phase 3: (for check ip) if there is a ':' before ip, there must be a '@' + int host_begin = qMax(urlText.lastIndexOf('@') + 1, 0); + int host_end = urlText.indexOf(':', host_begin); + if (host_end == -1) { + host_end = urlText.indexOf('/', host_begin); + } + if (host_end == -1) { + host_end = urlText.length(); + } + if (host_begin >= host_end) { + return false; + } + if (checkIP && urlText[host_end - 1] == '*') { + urlText[host_end - 1] = '1'; + } + + for (int index = 0; index < urlText.length() && urlText.at(index) != '/'; + index++) { + QChar cur(urlText.at(index)); + if (!cur.isLetterOrNumber() && !QString("-_~:@.").contains(cur)) { + return false; + } + } + + QString newIP; + newIP = urlText.mid(host_begin, host_end - host_begin); + QStringList ipv4 = newIP.split(QLatin1String(".")); + bool isIPv4 = true; + if (ipv4.count() != 4){ + isIPv4 = false; + } + foreach(QString domain, ipv4) { + bool ok; + int num = domain.toInt(&ok); + //won't tolerant spaces. + if (!ok || num < 0 || num > 255 || domain.length() > 3) { + isIPv4 = false; + break; + } + } + + if (!checkIP){ + QString lastName = ipv4.back(); + + int lastCharIndex = lastName.lastIndexOf(QRegExp("[a-zA-Z]")); + if (lastCharIndex == -1) { + if (!isIPv4) { + return false; + } else if (urlText == newIP) { + return false; + } + } else { + const QString *begin = endOfUrl; + const QString *end = endOfUrl + sizeof(endOfUrl) / sizeof(QString *); + if (qFind(begin, end, lastName) == end) { + return false; + } + } + url_ = prefixToAdd + urlText; + } else { + if (!isIPv4) { + return false; + } + ip_ = newIP; + } + + urlRect_ = urlRect; + rcUrl = urlRect; + urlStartPoint_ = urlStartPoint; + urlEndPoint_ = urlEndPoint; + return true; +} + +QString FQTermSession::getUrl() { + return url_; +} + +QString FQTermSession::getIP() { + return ip_; +} + +bool FQTermSession::isPageComplete() { + return termBuffer_->getCaretRow() == (termBuffer_->getNumRows() - 1) + && termBuffer_->getCaretColumn() == (termBuffer_->getNumColumns() - 1); +} + +bool FQTermSession::isAntiIdle() { + return param_.isAntiIdle_; +} + +void FQTermSession::setAntiIdle(bool antiIdle) { + param_.isAntiIdle_ = antiIdle; + + // disabled + if (!param_.isAntiIdle_ && idleTimer_->isActive()) { + idleTimer_->stop(); + } + + // enabled + if (param_.isAntiIdle_) { + if (idleTimer_->isActive()) { + idleTimer_->stop(); + } + idleTimer_->start(param_.maxIdleSeconds_ *1000); + } +} + +void FQTermSession::setAutoReconnect(bool autoReconnect) { + param_.isAutoReconnect_ = autoReconnect; + reconnectRetry_ = 0; + if (autoReconnect && !isConnected_) { + reconnectProcess(); + } +} + + +void FQTermSession::autoReplyMessage() { + if (autoReplyTimer_->isActive()) { + autoReplyTimer_->stop(); + } + if (pageState_ != Message) { + return; + } + if (scriptListener_) + { + bool res = scriptListener_->postScriptCallback(SFN_AUTO_REPLY +#ifdef HAVE_PYTHON + ,Py_BuildValue("l", scriptListener_->windowID()) +#endif //HAVE_PYTHON + ); + if (res) return; + } + QByteArray cstrTmp = param_.replyKeyCombination_.toLocal8Bit(); + QByteArray cstr = parseString(cstrTmp.isEmpty() ? QByteArray("^Z"): cstrTmp); + //cstr += m_param.m_strAutoReply.toLocal8Bit(); + cstr += unicode2bbs(param_.autoReplyMessage_); + cstr += '\n'; + telnet_->write(cstr, cstr.length()); + + emit messageAutoReplied(); +} + +QByteArray FQTermSession::parseString(const QByteArray &cstr, int *len) { + QByteArray parsed = ""; + + if (len != 0) { + *len = 0; + } + + for (uint i = 0; (long)i < cstr.length(); i++) { + if (cstr.at(i) == '^') { + i++; + if ((long)i < cstr.length()) { + parsed += FQTERM_CTRL(cstr.at(i)); + if (len != 0) { + *len = *len + 1; + } + } + } else if (cstr.at(i) == '\\') { + i++; + if ((long)i < cstr.length()) { + if (cstr.at(i) == 'n') { + parsed += CHAR_CR; + } else if (cstr.at(i) == 'r') { + parsed += CHAR_LF; + } else if (cstr.at(i) == 't') { + parsed += CHAR_TAB; + } + if (len != 0) { + *len = *len + 1; + } + } + } else { + parsed += cstr.at(i); + if (len != 0) { + *len = *len + 1; + } + } + } + + return parsed; +} + +void FQTermSession::reconnect() { + if (!isConnected_) { + telnet_->connectHost(param_.hostAddress_, param_.port_); + reconnectRetry_++; + } +} + +void FQTermSession::reconnectProcess() { + if (!isConnected_ && param_.isAutoReconnect_ && (reconnectRetry_ < param_.retryTimes_ || param_.retryTimes_ == -1)) { + int interval = param_.reconnectInterval_ <= 0 ? 0 : param_.reconnectInterval_ * 1000; + QTimer::singleShot(interval, this, SLOT(reconnect())); + } +} + +void FQTermSession::setMouseMode(bool on) { + isMouseX11_ = on; +} + +void FQTermSession::doAutoLogin() { + if (!param_.preLoginCommand_.isEmpty()) { + QByteArray temp = parseString(param_.preLoginCommand_.toLatin1()); + telnet_->write((const char*)(temp), temp.length()); + } + if (!param_.userName_.isEmpty()) { + QByteArray temp = param_.userName_.toLocal8Bit(); + telnet_->write((const char*)(temp), temp.length()); + char ch = CHAR_CR; + telnet_->write(&ch, 1); + } + if (!param_.password_.isEmpty()) { + QByteArray temp = param_.password_.toLocal8Bit(); + telnet_->write((const char*)(temp), temp.length()); + char ch = CHAR_CR; + telnet_->write(&ch, 1); + } + + // smth ignore continous input, so sleep 1 sec :) +#if defined(_OS_WIN32_) || defined(Q_OS_WIN32) + Sleep(1000); +#else + sleep(1); +#endif + + if (!param_.postLoginCommand_.isEmpty()) { + QByteArray temp = parseString(param_.postLoginCommand_.toLatin1()); + telnet_->write((const char*)(temp), temp.length()); + } + isTelnetLogining_ = false; +} + +//read slot +void FQTermSession::readReady(int size, int raw_size) { + FQ_ASSERT(size > 0 || raw_size > 0); // maybe raw_size is greater than 0. + + raw_data_.resize(raw_size); + telnet_->read_raw(&raw_data_[0], raw_size); + + // read raw buffer + int zmodem_consumed; + if (param_.enableZmodem_) + zmodem_->ZmodemRcv((uchar*)&raw_data_[0], raw_data_.size(), &(zmodem_->info), + zmodem_consumed); + + if (zmodem_->transferstate == notransfer) { + //decode + if (size > 0) { + int last_size = telnet_data_.size(); + telnet_data_.resize(last_size + size); + telnet_->read(&telnet_data_[0] + last_size, size); + int processed = 0; + { + while (!bufferWriteLock_.tryLockForWrite()) {} + //QWriteLocker locker(&bufferWriteLock_); + processed = decoder_->decode(&telnet_data_[0], telnet_data_.size()); + bufferWriteLock_.unlock(); + } + telnet_data_.erase(telnet_data_.begin(), telnet_data_.begin() + processed); + } + + if (isTelnetLogining_) { + int n = termBuffer_->getCaretRow(); + for (int y = n - 5; y < n + 5; y++) { + y = qMax(0, y); + const FQTermTextLine *pTextLine = termBuffer_->getTextLineInTerm(y); + if (pTextLine == NULL) { + continue; + } + + // QString str = pTextLine->getText(); + QString str; + pTextLine->getAllPlainText(str); + + if (str.indexOf("guest") != -1 /*&& str.indexOf("new") != -1*/) { + doAutoLogin(); + break; + } + } + } + // page complete when caret at the right corner + // this works for most but not for all + const FQTermTextLine *pTextLine = + termBuffer_->getTextLineInTerm(termBuffer_->getNumRows() - 1); + + //QString strText = stripWhitespace(pTextLine->getText()); + // TODO_UTF16: fixme! performance issue! + QString strText; + pTextLine->getAllPlainText(strText); + + // TODO_UTF16: shall we disable trim? + strText = strText.trimmed(); + + if (termBuffer_->getCaretRow() == termBuffer_->getNumRows() - 1 + && termBuffer_->getCaretColumn() >= (strText.length() - 1) / 2) { + waitCondition_.wakeAll(); + } + + //QToolTip::remove(this, screen_->mapToRect(m_rcUrl)); + + // message received + // 03/19/2003. the caret posion removed as a message judgement + // because smth.org changed + if (decoder_->bellReceive()) { + emit startAlert(); + emit bellReceived(); + + bool bellConsumed = false; + if (scriptListener_) { + bellConsumed = scriptListener_->postScriptCallback(SFN_ON_BELL +#ifdef HAVE_PYTHON + ,Py_BuildValue("l", scriptListener_->windowID()) +#endif //HAVE_PYTHON + ); + } + + if (isAutoReply() && !bellConsumed) { + // TODO: save messages + if (isIdling_) { + autoReplyMessage(); + } else { + autoReplyTimer_->start(param_.maxIdleSeconds_ *1000 / 2); + } + } + } + + // set page state + detectPageState(); + if (scriptListener_) { + scriptListener_->postScriptCallback(SFN_DATA_EVENT +#ifdef HAVE_PYTHON + ,Py_BuildValue("l", scriptListener_->windowID()) +#endif //HAVE_PYTHON + ); + } + emit sessionUpdated(); + } + + if (zmodem_->transferstate == transferstop) { + zmodem_->transferstate = notransfer; + } +} + +/* 0-left 1-middle 2-right 3-relsase 4/5-wheel + * + */ +//void FQTermWindow::sendMouseState( int num, +// Qt::ButtonState btnstate, Qt::ButtonState keystate, const QPoint& pt ) +void FQTermSession::sendMouseState(int num, Qt::KeyboardModifier btnstate, + Qt::KeyboardModifier keystate, const QPoint &pt) { + /* + if(!m_bMouseX11) + return; + + QPoint ptc = screen_->mapToChar(pt); + + if(btnstate&Qt::LeftButton) + num = num==0?0:num; + else if(btnstate&Qt::MidButton) + num = num==0?1:num; + else if(btnstate&Qt::RightButton) + num = num==0?2:num; + + int state = 0; + if(keystate&Qt::ShiftModifier) + state |= 0x04; + if(keystate&Qt::MetaModifier) + state |= 0x08; + if(keystate&Qt::ControlModifier) + state |= 0x10; + + // normal buttons are passed as 0x20 + button, + // mouse wheel (buttons 4,5) as 0x5c + button + if(num>3) num += 0x3c; + + char mouse[10]; + sprintf(mouse, "\033[M%c%c%c", + num+state+0x20, + ptc.x()+1+0x20, + ptc.y()+1+0x20); + telnet_->write( mouse, strlen(mouse) ); + */ +} + +void FQTermSession::cancelZmodem() { + zmodem_->zmodemCancel(); +} + +void FQTermSession::disconnect() { + telnet_->close(); + finalizeConnection(); +} + +void FQTermSession::finalizeConnection() { + if (isConnected_) { + QString strMsg = ""; + strMsg += "\n\n\n\r\n\r"; + strMsg += "\x1b[17C\x1b[0m===========================================\n\r"; + strMsg += + "\x1b[17C Connection Closed, Press \x1b[1m\x1b[31;40mEnter\x1b[m\x1b[0m To Connect\n\r"; + strMsg += "\x1b[17C===========================================\n"; + decoder_->decode(strMsg.toLatin1(), strMsg.length()); + } + isConnected_ = false; + + if (autoReplyTimer_->isActive()) { + autoReplyTimer_->stop(); + } + + if (idleTimer_->isActive()) { + idleTimer_->stop(); + } + + emit connectionClosed(); +} + +// telnet state slot +void FQTermSession::changeTelnetState(int state) { + switch (state) { + case TSRESOLVING: + break; + case TSHOSTFOUND: + break; + case TSHOSTNOTFOUND: + finalizeConnection(); + break; + case TSCONNECTING: + break; + case TSHOSTCONNECTED: + isConnected_ = true; + reconnectRetry_ = 0; + if (param_.isAutoLogin_ && param_.protocolType_==0) { + isTelnetLogining_ = true; + } + + break; + case TSPROXYCONNECTED: + break; + case TSPROXYAUTH: + break; + case TSPROXYFAIL: + disconnect(); + break; + case TSREFUSED: + finalizeConnection(); + break; + case TSREADERROR: + disconnect(); + break; + case TSCLOSED: + finalizeConnection(); + if (param_.isAutoReconnect_) { + reconnectProcess(); + } + break; + case TSCLOSEFINISH: + finalizeConnection(); + break; + case TSCONNECTVIAPROXY: + break; + case TSEGETHOSTBYNAME: + finalizeConnection(); + break; + case TSEINIWINSOCK: + finalizeConnection(); + break; + case TSERROR: + disconnect(); + break; + case TSPROXYERROR: + disconnect(); + break; + case TSWRITED: + // restart the idle timer + if (idleTimer_->isActive()) { + idleTimer_->stop(); + } + if (param_.isAntiIdle_) { + idleTimer_->start(param_.maxIdleSeconds_ *1000); + } + isIdling_ = false; + break; + default: + break; + } + + emit telnetStateChanged(state); +} + +void FQTermSession::handleInput(const QString &text) { + if (text.length() > 0) { + QByteArray cstrTmp = unicode2bbs_smart(text); + telnet_->write(cstrTmp, cstrTmp.length()); + } +} + +QString FQTermSession::bbs2unicode(const QByteArray &text) +{ + return encoding2unicode(text, param_.serverEncodingID_); +} + +QByteArray FQTermSession::unicode2bbs(const QString &text) { + return unicode2encoding(text, param_.serverEncodingID_); +} + + +QByteArray FQTermSession::unicode2bbs_smart(const QString &text) { + QByteArray strTmp; + + switch(param_.serverEncodingID_) + { + case FQTERM_ENCODING_GBK: + if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_BIG5) + { + strTmp = U2B(text); + char* tmp = encodingConverter_.B2G(strTmp, strTmp.length()); + strTmp = tmp; + delete []tmp; + } + else + { + strTmp = U2G(text); + } + break; + case FQTERM_ENCODING_BIG5: + if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK) + { + strTmp = U2G(text); + char* tmp = encodingConverter_.G2B(strTmp, strTmp.length()); + strTmp = tmp; + delete []tmp; + } + else + { + strTmp = U2B(text); + } + break; + case FQTERM_ENCODING_HKSCS: + if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK) + { + strTmp = U2G(text); + char* tmp = encodingConverter_.G2B(strTmp, strTmp.length()); + strTmp = tmp; + delete []tmp; + } + else + { + strTmp = U2H(text); + } + break; + case FQTERM_ENCODING_UTF8: + strTmp = U2U8(text); + break; + case FQTERM_ENCODING_UAO: + if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK) + { + strTmp = U2G(text); + char* tmp = encodingConverter_.G2B(strTmp, strTmp.length()); + strTmp = tmp; + delete []tmp; + } + else + { + strTmp = U2A(text); + } + break; + } + + return strTmp; +} + + +void FQTermSession::onIdle() { + // do as autoreply when it is enabled + if (autoReplyTimer_->isActive() && param_.isAutoReply_) { + autoReplyMessage(); + stopAlert(); + return ; + } + + isIdling_ = true; + // system script can handle that + if (scriptListener_) { + bool res = scriptListener_->postScriptCallback(SFN_ANTI_IDLE +#ifdef HAVE_PYTHON + ,Py_BuildValue("l", scriptListener_->windowID()) +#endif //HAVE_PYTHON + ); + if (res) + return; + } + // the default function + int length; + QByteArray cstr = parseString(param_.antiIdleMessage_.toLocal8Bit(), &length); + telnet_->write(cstr, length); +} + +void FQTermSession::onAutoReply() { + // if AutoReply still enabled, then autoreply + if (param_.isAutoReply_) { + autoReplyMessage(); + } else { + // else just stop the timer + autoReplyTimer_->stop(); + } + + stopAlert(); +} +bool FQTermSession::isAutoReply() { + return param_.isAutoReply_; +} + +void FQTermSession::setAutoReply(bool on) { + param_.isAutoReply_ = on; + if (!param_.isAutoReply_ && autoReplyTimer_->isActive()) { + autoReplyTimer_->stop(); + } +} + +int FQTermSession::write(const char *data, int len) { + return telnet_->write(data, len); +} + +int FQTermSession::writeStr(const char *str) { + return write(str, strlen(str)); +} + +void FQTermSession::setProxy(int type, bool needAuth, + const QString &hostName, quint16 portNumber, + const QString &userName, const QString &password) { + telnet_->setProxy(type, needAuth, hostName, portNumber, userName, password); +} + +void FQTermSession::connectHost(const QString &hostName, quint16 portNumber) { + telnet_->connectHost(hostName, portNumber); +} + +void FQTermSession::close() { + telnet_->close(); + finalizeConnection(); +} + +void FQTermSession::clearLineChanged(int index) { + termBuffer_->clearLineChanged(index); +} + +void FQTermSession::setLineAllChanged(int index) { + termBuffer_->setLineAllChanged(index); +} + +void FQTermSession::setTermSize(int col, int row) { + termBuffer_->setTermSize(col, row); + param_.numColumns_ = col; + param_.numRows_ = row; +} + +FQTermBuffer *FQTermSession::getBuffer() const { + return termBuffer_; +} + +void FQTermSession::setSelect(const QPoint &pt1, const QPoint &pt2) { + termBuffer_->setSelect(pt1, pt2); +} + +void FQTermSession::clearSelect() { + termBuffer_->clearSelect(); +} + +void FQTermSession::leaveIdle() { + if (autoReplyTimer_->isActive()) { + autoReplyTimer_->stop(); + } + + if (idleTimer_->isActive()) { + idleTimer_->stop(); + } + + if (param_.isAntiIdle_) { + idleTimer_->start(param_.maxIdleSeconds_ * 1000); + } +} + +void FQTermSession::copyArticle() { + if (!acThread_->isRunning()) { + acThread_->start(); + } +} + +void FQTermSession::getLineColorInfo(const FQTermTextLine * line, + LineColorInfo * colorInfo) +{ + colorInfo->backgroundColorIndex.clear(); + colorInfo->foregroundColorIndex.clear(); + colorInfo->hasBackgroundColor = false; + colorInfo->uniBackgroundColor = true; + colorInfo->uniForegroundColor = true; + colorInfo->hasForegroundColor = false; + + if (!line || line->getWidth() == 0) { + return; + } + + const unsigned char *color = line->getColors(); + unsigned char background = GETBG(color[0]); + unsigned char foreground = GETFG(color[0]); + colorInfo->backgroundColorIndex.append(background); + colorInfo->foregroundColorIndex.append(foreground); + //for (int i = 0; i < color.length() / 2; i++) { + // TODO_UTF16: why use color.length()/2 formerly? + for (int i = 0; i < (int)line->getWidth(); i++) { + if (GETBG(color[i]) != background) { + colorInfo->uniBackgroundColor = false; + background = GETBG(color[i]); + colorInfo->backgroundColorIndex.append(background); + } + if (GETBG(color[i]) != GETBG(NO_COLOR)) { + colorInfo->hasBackgroundColor = true; + } + if (GETFG(color[i]) != foreground) { + colorInfo->uniForegroundColor = false; + foreground = GETFG(color[i]); + colorInfo->foregroundColorIndex.append(foreground); + } + if (GETFG(color[i]) != GETFG(NO_COLOR)) { + colorInfo->hasForegroundColor = true; + } + } +} + +bool FQTermSession::readyForInput() +{ + return telnet_->readyForInput(); +} + +void FQTermSession::onEnqReceived() { + if (FQTermPref::getInstance()->replyENQ_) + telnet_->write(ANSWERBACK_MESSAGE, sizeof(ANSWERBACK_MESSAGE)); +} + +void FQTermSession::onSSHAuthOK() { + isSSHLogining_ = false; + //setTermSize(80, 24); + //setTermSize(param_.numColumns_, param_.numRows_); + telnet_->windowSizeChanged(param_.numColumns_, param_.numRows_); +} + +void FQTermSession::updateSetting( const FQTermParam& p ) { + bool toggleAutoReconnect = (p.isAutoReconnect_ != param_.isAutoReconnect_); + param_ = p; + setTermSize(p.numColumns_, p.numRows_); + setAntiIdle(isAntiIdle()); + if (toggleAutoReconnect) + setAutoReconnect(param_.isAutoReconnect_); +} + +ArticleCopyThread::ArticleCopyThread( + FQTermSession &bbs, QWaitCondition &waitCondition, QReadWriteLock &bufferLock) + : session_(bbs), + waitCondition_(waitCondition), + lock_(bufferLock) { + FQ_VERIFY(connect(this, SIGNAL(writeSession(const QString&)), + &session_, SLOT(handleInput(const QString&)), + Qt::QueuedConnection)); +} + +ArticleCopyThread::~ArticleCopyThread() { +} + +static void removeTrailSpace(QString &line) { + for (int last_non_space = line.size() - 1; + last_non_space >= 0; --last_non_space) { + QChar a = line.at(last_non_space); + if (!a.isSpace()) { + line.resize(last_non_space + 1); + break; + } + + if (last_non_space == 0) { + line.resize(0); + } + } +} + + +/////////////////////////////////////// +//analyze last line when copy article +static std::pair<int, int> ParseLastLine(QString str) { + int startLine = -1; + int endLine = -1; + int infoStart = str.indexOf('(', str.indexOf('(') + 1); + int infoEnd = str.indexOf(')', infoStart); + int infoSplit = str.indexOf('-', infoStart); + if (infoStart == -1 || infoEnd == -1 || infoSplit == -1) { + return std::make_pair(-1, -1); + } + bool ok = true; + startLine = str.mid(infoStart + 1, infoSplit - infoStart - 1).toInt(&ok); + if (!ok) { + return std::make_pair(-1, -1); + } + endLine = str.mid(infoSplit + 1, infoEnd - infoSplit - 1).toInt(&ok); + if (!ok) { + return std::make_pair(-1, -1); + } + return std::make_pair(startLine, endLine); +} +/////////////////////////////////////// + + +void ArticleCopyThread::run() { + QReadLocker locker(&lock_); + + typedef std::vector<QString> Page; + const FQTermBuffer *buffer = session_.getBuffer();; + Page page; + + QString firstPageLastLine; + buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(firstPageLastLine); + bool MultiPage = (firstPageLastLine.indexOf("%") != -1); + if (!MultiPage) { + emit writeSession("q"); + emit writeSession("\n"); + if (!waitCondition_.wait(&lock_, 10000)) { + emit articleCopied(DAE_TIMEOUT, ""); + return; + } + buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(firstPageLastLine); + MultiPage = (firstPageLastLine.indexOf("%") != -1); + } + + if (MultiPage) { + //session_.write("e", 1); + emit writeSession("e"); + if (!waitCondition_.wait(&lock_, 10000)) { + emit articleCopied(DAE_TIMEOUT, ""); + return; + } + QString LastPageLastLine; + buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(LastPageLastLine); + std::pair<int, int> range = ParseLastLine(LastPageLastLine); + if (range.first == -1 || range.second == -1) { + return; + } + page.resize(range.second); + for (int i = range.first; i <= range.second; ++i) { + QString strTemp; + buffer->getTextLineInTerm(i - range.first)->getAllPlainText(strTemp); + page[i - 1] = strTemp; + } + //session_.write("s", 1); + emit writeSession("s"); + if (!waitCondition_.wait(&lock_, 10000)) { + emit articleCopied(DAE_TIMEOUT, ""); + return; + } + } + else { + for (int i = 0; i < buffer->getNumRows() - 1; ++i) { + QString strTemp; + buffer->getTextLineInTerm(i)->getAllPlainText(strTemp); + page.push_back(strTemp); + } + } + + while (MultiPage) { + // the end of article + int lineIndex = buffer->getNumRows() - 1; + //QByteArray lastLine = buffer->getLineInScreen(lineIndex)->getText(); + QString lastLine; + buffer->getTextLineInTerm(lineIndex)->getAllPlainText(lastLine); + + std::pair<int, int> range = ParseLastLine(lastLine); + if (range.first == -1 || range.second == -1) { + page.push_back(lastLine); + break; + } + + // copy a page of lines exclude the last line. + for (int i = range.first; i <= range.second; ++i) { + QString strTemp; + buffer->getTextLineInTerm(i - range.first)->getAllPlainText(strTemp); + page[i - 1] = strTemp; + } + + + // wait for next page. + //session_.write(" ", 1); + emit writeSession(" "); + if (!waitCondition_.wait(&lock_, 10000)) { + emit articleCopied(DAE_TIMEOUT, ""); + break; + } + } + + + + QString articleText; + + for (Page::iterator line = page.begin(); line != page.end(); ++line) { + removeTrailSpace(*line); + articleText += (*line); + articleText += (OS_NEW_LINE); + } + + //qApp->postEvent(pWin, new QCustomEvent(DAE_FINISH)); + emit articleCopied(DAE_FINISH, articleText); +} + +} // namespace FQTerm + +#include "fqterm_session.moc" diff --git a/src/terminal/fqterm_session.h b/src/terminal/fqterm_session.h new file mode 100644 index 0000000..78aaa31 --- /dev/null +++ b/src/terminal/fqterm_session.h @@ -0,0 +1,344 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_SESSION_H +#define FQTERM_SESSION_H + +#include <vector> + +#include <QPoint> +#include <QRect> +#include <QObject> +#include <QThread> +#include <QString> +#include <QWaitCondition> +#include <QList> +#include <QtScript/QScriptValue> +#include <QReadWriteLock> +#include "fqterm_param.h" +#include "fqterm_config.h" +#include "fqterm_convert.h" +#ifdef HAVE_PYTHON +#include <Python.h> +#endif //HAVE_PYTHON + +class QRect; +class QTimer; + +namespace FQTerm { + +class FQTermScriptEventListener { +public: + bool postScriptCallback(const QString& func, +#ifdef HAVE_PYTHON + PyObject* pArgs, +#endif //HAVE_PYTHON + const QScriptValueList & args = QScriptValueList()) + { + bool res = postQtScriptCallback(func, args); +#ifdef HAVE_PYTHON + res = postPythonCallback(func, pArgs) || res; +#endif //HAVE_PYTHON + return res; + } + virtual long windowID() = 0; + virtual ~FQTermScriptEventListener() {} +private: + virtual bool postQtScriptCallback(const QString& func, const QScriptValueList & args = QScriptValueList()) = 0; +#ifdef HAVE_PYTHON + virtual bool postPythonCallback(const QString& func, PyObject* pArgs) = 0; +#endif //HAVE_PYTHON +}; + +struct LineColorInfo { + bool hasBackgroundColor; + bool hasForegroundColor; + bool uniBackgroundColor; + bool uniForegroundColor; + QList<unsigned char> backgroundColorIndex; + QList<unsigned char> foregroundColorIndex; +}; + +class FQTermConfig; +class FQTermBuffer; +class FQTermTextLine; +class FQTermTelnet; +class FQTermDecode; +class FQTermZmodem; +class ArticleCopyThread; + +class FQTermSession: public QObject { + Q_OBJECT; + public: + enum CursorType { + kHome = 0, + kEnd = 1, + kPageUp = 2, + kPageDown = 3, + kUp = 4, + kDown = 5, + kLeft = 6, + kRight = 7, + kNormal = 8 + }; + + FQTermSession(FQTermConfig *, FQTermParam); + ~FQTermSession(); + + enum PageState { + Undefined = -1, + Menu = 0, + MailMenu = 1, + ArticleList = 2, + EliteArticleList = 3, + BoardList = 4, + FriendMailList = 5, + Message = 6, + Read = 7, + Edit = 8, + TOP10 = 9, + }; + + enum ProtocolIndex { + Http = 0, + Https = 1, + Mms = 2, + Rstp = 3, + Ftp = 4, + Mailto = 5, + Telnet = 6, + ProtocolSupported = 7 + }; + + static const QString endOfUrl[]; + + static const QString protocolPrefix[]; + PageState getPageState(); + + // Set current cursor postion to pt, + // return whether the selection rectangle is changed. + // the output parameter rc be a rectangle including both + // the new and the old selection region. + bool setCursorPos(const QPoint &pt, QRect &rc); + CursorType getCursorType(const QPoint &); + + // Get the menu char detected in getSelectRect(). + char getMenuChar(); + + // Get current selection rectangle. + // also detect the menu char if in kMenu page state. + QRect detectMenuRect(); + QRect getMenuRect() {return menuRect_;} + void setMenuRect(int row, int col, int len); + // detect whether the given line or point is contained by current selection rectangle. + bool isSelectedMenu(int line); + bool isSelectedMenu(const QPoint &); + //selection. + void setSelect(const QPoint &pt1, const QPoint &pt2); + void clearSelect(); + + bool isUrl(QRect &, QRect &); + bool isIP(QRect &, QRect &); + QString getUrl(); + QString getIP(); + + + + // Set current screen start line to help detect cursor type + // and select rectangle corresponding to current cursor postion. + void setScreenStart(int); + + + + // Set a line of buffer to have been changed from start to end. + void clearLineChanged(int index); + void setLineAllChanged(int index); + + bool isAntiIdle(); + void setAntiIdle(bool antiIdle); + void leaveIdle(); + + bool isAutoReply(); + void setAutoReply(bool autoReply); + QString getMessage(); + + void setAutoReconnect(bool autoReconnect); + + + bool readyForInput(); + void setTermSize(int col, int row); + + QWaitCondition& getWaitCondition() {return waitCondition_;}; + //this function will do + //1. convert unicode to bbs encoding + //2. if there are some chars express same meaning in simplify/traditional + //Chinese, auto covert them by considering ime/bbs encoding. + QByteArray unicode2bbs_smart(const QString &); + QString bbs2unicode(const QByteArray &text); + QByteArray unicode2bbs(const QString &text); + + // Write data raw data + int write(const char *data, int len); + int writeStr(const char *str); + + // type: 0-no proxy; 1-wingate; 2-sock4; 3-socks5 + // needAuth: if authentation needed + void setProxy(int type, bool needAuth, + const QString &hostname, quint16 portNumber, + const QString &username, const QString &password); + // User close the connection + void close(); + + bool isConnected() {return isConnected_;} + void setSendingMessage(bool sending = true) {isSendingMessage_ = sending;} + const QPoint& urlStartPoint() {return urlStartPoint_;} + const QPoint& urlEndPoint() {return urlEndPoint_;} + + FQTermParam& param() {return param_;} + void updateSetting(const FQTermParam& p); + QReadWriteLock& getBufferLock() {return bufferWriteLock_;} + + public: + + + public slots: + FQTermBuffer *getBuffer() const; + void connectHost(const QString &hostname, quint16 portnumber); + void reconnect(); + void disconnect(); + + void cancelZmodem(); + void changeTelnetState(int state); + + void handleInput(const QString &text); + + void copyArticle(); + + signals: + void messageAutoReplied(); + void articleCopied(int state, const QString content); + void sessionUpdated(); + void connectionClosed(); + void bellReceived(); + void onTitleSet(const QString&); + void startAlert(); + void stopAlert(); + + void requestUserPwd(QString *user, QString *pwd, bool *isOK); + + void telnetStateChanged(int state); + void zmodemStateChanged(int type, int value, const char *status); + + void errorMessage(QString); + +private slots: + void readReady(int size, int raw_size); + void onIdle(); + void onAutoReply(); + void onEnqReceived(); + void onSSHAuthOK(); + void setMouseMode(bool on); + +private: + void sendMouseState(int, Qt::KeyboardModifier, Qt::KeyboardModifier, const QPoint &); + void getLineColorInfo(const FQTermTextLine * line, LineColorInfo * colorInfo); + bool isIllChar(char); + void detectPageState(); + QString expandUrl(const QPoint& pt, QPair<int, int>& range); + bool checkUrl(QRect &, QRect &, bool); + QByteArray parseString(const QByteArray &cstr, int *len = 0); + void finalizeConnection(); + bool isPageComplete(); + void autoReplyMessage(); + void reconnectProcess(); + void doAutoLogin(); + +private: + //this read-write lock will be locked as a writer's lock if the buffer is being changed + //so for a reader who wants to ensure thread-safe, he should lock + //this lock as a reader's lock. + mutable QReadWriteLock bufferWriteLock_; + + FQTermParam param_; + FQTermConvert encodingConverter_; + bool isTelnetLogining_; + bool isSSHLogining_; + + bool isIdling_; + bool isMouseX11_; + bool isConnected_; + bool isSendingMessage_; + + QRect urlRect_; + QPoint urlStartPoint_; + QPoint urlEndPoint_; + QString url_; + QString ip_; + char menuChar_; + QRect menuRect_; + PageState pageState_; + QPoint cursorPoint_; + int screenStartLineNumber_; + + FQTermZmodem *zmodem_; + FQTermDecode *decoder_; + FQTermTelnet *telnet_; + FQTermBuffer *termBuffer_; + + QTimer *idleTimer_; + QTimer *autoReplyTimer_; + + QWaitCondition waitCondition_; + ArticleCopyThread *acThread_; + + std::vector<char> telnet_data_; + std::vector<char> raw_data_; + + int reconnectRetry_; +public: + void setScriptListener(FQTermScriptEventListener* pythonListener) { + scriptListener_ = pythonListener; + } +private: + FQTermScriptEventListener* scriptListener_; +}; + +class ArticleCopyThread: public QThread { + Q_OBJECT; + public: + ArticleCopyThread(FQTermSession &bbs, QWaitCondition &waitCondition, QReadWriteLock &bufferLock); + + ~ArticleCopyThread(); + + signals: + void articleCopied(int state, const QString content); + void writeSession(const QString&); + protected: + virtual void run(); + private: + FQTermSession &session_; + QWaitCondition &waitCondition_; + + QReadWriteLock &lock_; +}; + +} // namespace FQTerm + +#endif // FQTERM_SESSION_H diff --git a/src/terminal/fqterm_text_line.cpp b/src/terminal/fqterm_text_line.cpp new file mode 100644 index 0000000..ed6d164 --- /dev/null +++ b/src/terminal/fqterm_text_line.cpp @@ -0,0 +1,541 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <stdio.h> +#include <QRegExp> +#include <QString> + +#include "fqterm.h" +#include "fqterm_text_line.h" + +namespace FQTerm { + +#ifndef NDEBUG +//#define TEXT_LINE_CHECK +#else +//#define TEXT_LINE_CHECK +#endif + +#ifndef TEXT_LINE_CHECK +void FQTermTextLine::verifyTextLine() const {} +void FQTermTextLine::verifyCellRange(unsigned, unsigned) const {} +#else +void FQTermTextLine::verifyTextLine() const { + unsigned last_cell = 0; + unsigned cur_cell = last_cell; + + if (cells_.size() == 0 + || cell_colors_.size() == 0 + || cell_attrs_.size() == 0 + || chars_.size() == 0 + ) { + FQ_VERIFY(cells_.size() == 0); + FQ_VERIFY(cell_colors_.size() == 0); + FQ_VERIFY(cell_attrs_.size() == 0); + FQ_VERIFY(chars_.size() == 0); + return; + } + + FQ_VERIFY(max_cell_count_ < 65536); + FQ_VERIFY(chars_.size() < 65536); + FQ_VERIFY(cells_.size() <= max_cell_count_); + FQ_VERIFY(cells_.size() == cell_colors_.size()); + FQ_VERIFY(cells_.size() == cell_attrs_.size()); + + const UTF16 *start = &chars_[0]; + const UTF16 *end = start + chars_.size(); + + for (const UTF16 *p = start; p < end;) { + int w = mk_advace_at_least_one_cell(&p, end); + + FQ_VERIFY(w >= 0); // should be valid unicode string. + + cur_cell += w; + + for (unsigned i = last_cell; i < cur_cell; ++i) { + unsigned char_end = p - start; + FQ_VERIFY(cells_[i] == char_end); + } + + last_cell = cur_cell; + } + + FQ_VERIFY(cur_cell == getWidth()); + FQ_VERIFY(cells_.back() == chars_.size()); +} + +void FQTermTextLine::verifyCellRange(unsigned cell_begin, unsigned cell_end) const { + FQ_VERIFY(cell_begin <= cell_end); + FQ_VERIFY(cell_end <= getWidth()); + + unsigned real_cell_begin = getCellBegin(cell_begin); + FQ_VERIFY(real_cell_begin == cell_begin); + + unsigned real_cell_end = getCellBegin(cell_end); + FQ_VERIFY(real_cell_end == cell_end); + + unsigned char_begin = getCharBegin(cell_begin); + FQ_VERIFY(char_begin <= chars_.size()); + + unsigned char_end = getCharEnd(cell_end); + FQ_VERIFY(char_begin <= char_end); + FQ_VERIFY(char_end <= chars_.size()); + +} + +#endif + +FQTermTextLine::FQTermTextLine(unsigned max_cell_count) + : max_cell_count_(max_cell_count), + dirty_cell_begin_(0), + dirty_cell_end_(max_cell_count), + stored_color_(0), + stored_attr_(0){ + cells_.reserve(max_cell_count_); + cell_colors_.reserve(max_cell_count_); + cell_attrs_.reserve(max_cell_count_); + chars_.reserve(max_cell_count_); +} + +FQTermTextLine::~FQTermTextLine() { +} + +void FQTermTextLine::setMaxCellCount(unsigned max_cell_count) { + // FIXME: how about dirty flag? + max_cell_count_ = max_cell_count; + + if (cells_.size() > max_cell_count_) { + // TODO_UTF16: what if an incomplete utf-16 byte left. + cells_.resize(max_cell_count_); + cell_colors_.resize(max_cell_count_); + cell_attrs_.resize(max_cell_count_); + chars_.resize(cells_.back()); + } +} + +// Whether this line of text contains blink characters. +bool FQTermTextLine::hasBlink() const { + bool blink = false; + + char tempea; + for (unsigned i = 0; i < cells_.size(); i++) { + tempea = cell_attrs_[i]; + if (GETBLINK(tempea)) { + blink = true; + break; + } + } + + return blink; +} + +void FQTermTextLine::setDirtyFlag(unsigned cell_begin, unsigned cell_end) { +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cell_begin <= cell_end); + FQ_VERIFY(cell_end <= max_cell_count_); +#endif + if (dirty_cell_begin_ > cell_begin) { + dirty_cell_begin_ = cell_begin; + } + + if (dirty_cell_end_ < cell_end) { + dirty_cell_end_ = cell_end; + } +} + +void FQTermTextLine::safelySetDirtyFlag(unsigned cell_begin, unsigned cell_end) { + if (cell_end > max_cell_count_) cell_end = max_cell_count_; + + if (cell_begin >= cell_end) + return; + + setDirtyFlag(cell_begin, cell_end); +} + +void FQTermTextLine::clearDirtyFlag() { + dirty_cell_begin_ = 0; + dirty_cell_end_ = 0; +} + +bool FQTermTextLine::getDirtyCells(unsigned &cell_begin, unsigned &cell_end) const { + cell_begin = dirty_cell_begin_; + cell_end = dirty_cell_end_; + return dirty_cell_begin_ < dirty_cell_end_; +} + +void FQTermTextLine::getPlainText(unsigned cell_begin, unsigned cell_end, + QString &result) const { + verifyCellRange(cell_begin, cell_end); + + if (cells_.size() == 0 || cell_begin == cell_end) return; + + unsigned char_begin = getCharBegin(cell_begin); + unsigned char_end = getCharEnd(cell_end); + + result.insert(result.size(), + (QChar *) &chars_[char_begin], + char_end - char_begin); + + typedef char Verify_QChar_Is_Two_Bytes[ + (sizeof(QChar) == 2)? 1 : -1]; +} + +void FQTermTextLine::getAnsiText(unsigned cell_begin, unsigned cell_end, + QString &result, + const char *escape) const { + verifyCellRange(cell_begin, cell_end); + + if (cells_.size() == 0 || cell_begin == cell_end) return; + + unsigned cur_cell = cell_begin; + unsigned next_cell = cell_begin; + + while (cur_cell < cell_end) { + unsigned char color = cell_colors_[cur_cell]; + unsigned char attr = cell_attrs_[cur_cell]; + + for (next_cell = cur_cell; + next_cell < cell_end; + ++next_cell) { + if (!(cell_colors_[next_cell] == color + && cell_attrs_[next_cell] == attr)) { + if (next_cell > getCellBegin(next_cell)) { + continue; + } else { + break; + } + } + + } + + getAnsiCtrlSeq(color, attr, escape, result); + getPlainText(cur_cell, next_cell, result); + result += escape; + result += "0m"; + + cur_cell = next_cell; + } +} + + +void FQTermTextLine::appendWhiteSpace( + unsigned count, unsigned char color, unsigned char attr, UTF16 space) { + FQ_ASSERT(get_str_width(space) == 1); + + cells_.reserve(getWidth() + count); + max_cell_count_ = qMax(max_cell_count_, getWidth() + count); + + cell_colors_.reserve(getWidth() + count); + cell_attrs_.reserve(getWidth() + count); + + setDirtyFlag(getWidth(), getWidth() + count); + + chars_.insert(chars_.end(), count, space); + cell_colors_.insert(cell_colors_.end(), count, color); + cell_attrs_.insert(cell_attrs_.end(), count, attr); + + int c = getCharEnd(getWidth()); + for (unsigned i = 0; i < count; ++i) { + cells_.push_back(c + i + 1); + } + + verifyTextLine(); +} + +void FQTermTextLine::insertWhiteSpace(unsigned count, unsigned cell_begin, + unsigned char color, unsigned char attr, + UTF16 space) { + FQ_ASSERT(get_str_width(space) == 1); + + breakCell(cell_begin); + + verifyCellRange(cell_begin, getWidth()); + + unsigned char_begin = getCharBegin(cell_begin); + + max_cell_count_ = qMax(max_cell_count_, getWidth() + count); + + chars_.insert(chars_.begin() + char_begin, count, space); + cell_colors_.insert(cell_colors_.begin() + cell_begin, count, color); + cell_attrs_.insert(cell_attrs_.begin() + cell_begin, count, attr); + + cells_.insert(cells_.begin() + cell_begin, count, 0); + for (unsigned i = cell_begin; i < cell_begin + count; ++i) { + cells_[i] = char_begin + (i - cell_begin) + 1; + } + for (unsigned i = cell_begin + count; i < getWidth(); ++i) { + cells_[i] += count; + } + + setDirtyFlag(cell_begin, this->getWidth()); + + verifyTextLine(); +} + +void FQTermTextLine::replaceWithWhiteSpace( + unsigned count, unsigned cell_begin, unsigned cell_end, + unsigned char color, unsigned char attr, UTF16 space) { + if(cell_end>cells_.size() || cell_begin>=cell_end) + return; + deleteText(cell_begin, cell_end); + insertWhiteSpace(count, cell_begin, color, attr, space); +} + + + +void FQTermTextLine::insertText(const UTF16 *str, unsigned count, + unsigned cell_begin, + unsigned char color, unsigned char attr, int charstate) { + if (count == 0) return; + + // Calculate how many cells should be occupied for str. + // TODO: avoid compute the width of str twice. + int width = get_str_width(str, count); + + if (width == 0) { +#ifdef TEXT_LINE_CHECK + //FQ_VERIFY(false); + //TODO: enable utf8 decoding +#endif + return; + } + + if (charstate & FQTermTextLine::FIRSTPART) { + stored_color_ = color; + stored_attr_ = attr; + width--; + count--; + if (width == 0 || count == 0) { + return; + } + } + + breakCell(cell_begin); + + verifyCellRange(cell_begin, getWidth()); + + unsigned char_begin = getCharBegin(cell_begin); + + chars_.insert(chars_.begin() + char_begin, str, str + count); + + // Insert colors. + cell_colors_.insert(cell_colors_.begin() + cell_begin, width, color); + + // Insert attrs. + cell_attrs_.insert(cell_attrs_.begin() + cell_begin, width, attr); + + if (charstate & FQTermTextLine::SECONDPART){ + cell_colors_[cell_begin] = stored_color_; + cell_attrs_[cell_begin] = stored_attr_; + } + + + // Change the cells_ mapping to chars_. + cells_.insert(cells_.begin() + cell_begin, width, 0); + max_cell_count_ = qMax(max_cell_count_, getWidth()); + + unsigned last_cell = cell_begin; + unsigned cur_cell = last_cell; + for (const UTF16 *p = str; p < str +count;) { + int w = mk_advace_at_least_one_cell(&p, str + count); + +#ifdef TEXT_LINE_CHECK + // TODO_UTF16: FIXME: deal with decoding error. + FQ_VERIFY(w >= 0); +#endif + + cur_cell += w; + + for (unsigned i = last_cell; i < cur_cell; ++i) { + cells_[i] = char_begin + (p - str); + } + + last_cell = cur_cell; + } + +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cur_cell == cell_begin + width); +#endif + + for (unsigned i = cell_begin + width; i < cells_.size(); ++i) { + cells_[i] += count; + } + + + + setDirtyFlag(cell_begin, cells_.size()); + + verifyTextLine(); +} + +void FQTermTextLine::deleteText(unsigned cell_begin, unsigned cell_end) { + if (cells_.size() == 0 || cell_begin == cell_end) return; + + breakCell(cell_begin); + breakCell(cell_end); + + verifyCellRange(cell_begin, cell_end); + + setDirtyFlag(cell_begin, cells_.size()); + + unsigned char_begin = getCharBegin(cell_begin); + unsigned char_end = getCharEnd(cell_end); + + chars_.erase(chars_.begin() + char_begin, chars_.begin() + char_end); + + cells_.erase(cells_.begin() + cell_begin, cells_.begin() + cell_end); + cell_colors_.erase(cell_colors_.begin() + cell_begin, + cell_colors_.begin() + cell_end); + cell_attrs_.erase(cell_attrs_.begin() + cell_begin, + cell_attrs_.begin() + cell_end); + + for (unsigned i = cell_begin; i < cells_.size(); ++i) { + cells_[i] -= (char_end - char_begin); + } + + verifyTextLine(); +} + +void FQTermTextLine::deleteAllText() { + deleteText(0, getWidth()); +} + +void FQTermTextLine::replaceText(const UTF16 *str, unsigned count, + unsigned cell_begin, unsigned cell_end, + unsigned char color, unsigned char attr, int charstate) { + if (charstate & FQTermTextLine::FIRSTPART) { + stored_color_ = color; + stored_attr_ = attr; + count--; + if (count == 0) + { + return; + } + charstate &= ~FQTermTextLine::FIRSTPART; + } + + deleteText(cell_begin, cell_end); + insertText(str, count, cell_begin, color, attr, charstate); + + setDirtyFlag(cell_begin, cells_.size()); +} + + +unsigned FQTermTextLine::getCellBegin(unsigned cell_begin) const { +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cell_begin <= getWidth()); +#endif + + if (cell_begin == cells_.size()) return cell_begin; + + while(cell_begin > 0 && cells_[cell_begin - 1] == cells_[cell_begin]) { + --cell_begin; + } + + return cell_begin; +} + +unsigned FQTermTextLine::getCellEnd(unsigned cell_end) const { +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cell_end <= getWidth()); +#endif + + if (cell_end == 0) return cell_end; + + while (cell_end < cells_.size() && cells_[cell_end - 1] == cells_[cell_end]) { + ++cell_end; + } + + return cell_end; +} + +unsigned FQTermTextLine::getCharBegin(unsigned cell_begin) const { +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cell_begin == getCellBegin(cell_begin)); +#endif + + if (cell_begin == 0) return 0; + + return cells_[cell_begin - 1]; +} + +unsigned FQTermTextLine::getCharEnd(unsigned cell_end) const { +#ifdef TEXT_LINE_CHECK + FQ_VERIFY(cell_end == getCellEnd(cell_end)); +#endif + + if (cell_end == 0) return 0; + + return cells_[cell_end - 1]; +} + +bool FQTermTextLine::breakCell(unsigned cell_begin) { + unsigned real_cell_begin = getCellBegin(cell_begin); + if (real_cell_begin == cell_begin) return false; + + unsigned real_cell_end = getCellEnd(cell_begin); + + verifyCellRange(real_cell_begin, real_cell_end); + + unsigned cell_count = real_cell_end - real_cell_begin; + + replaceWithWhiteSpace(cell_count, real_cell_begin, real_cell_end, + cell_colors_[cell_begin], cell_attrs_[cell_begin], URC); + + return true; +} + +void FQTermTextLine::getAnsiCtrlSeq( + unsigned char color, unsigned char attr, + const char *escape, QString &result) const { + + int fg = GETFG(color) + 30; + int bg = GETBG(color) + 40; + + result += escape; + result += QString("%1;%2").arg(fg).arg(bg); + + if (GETBRIGHT(attr)) { + result += ";1"; + } + if (GETDIM(attr)) { + result += ";2"; + } + if (GETUNDERLINE(attr)) { + result += ";4"; + } + if (GETBLINK(attr)) { + result += ";5"; + } + if (GETRAPIDBLINK(attr)) { + result += ";6"; + } + if (GETREVERSE(attr)) { + result += ";7"; + } + if (GETINVISIBLE(attr)) { + result += ";8"; + } + + result += "m"; +} + + +} // namespace FQTerm diff --git a/src/terminal/fqterm_text_line.h b/src/terminal/fqterm_text_line.h new file mode 100644 index 0000000..72ff4c0 --- /dev/null +++ b/src/terminal/fqterm_text_line.h @@ -0,0 +1,159 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_TEXTLINE_H +#define FQTERM_TEXTLINE_H + +#include <vector> + +#include "fqwcwidth.h" +#include "fqterm.h" +class QString; + +namespace FQTerm { +/* + A text line consists of several characters in a sequence of cells. A + character may occupy some cells, e.g. each English letter occupies + one cell, while a CJK character needs two. + + This class is used to manipolate a text line, delete/replace + characters in a given cell range, insert characters before a cell, + and retrieve characters/colors/attributs of a gaven cell range. +*/ +class FQTermTextLine { + public: + FQTermTextLine(unsigned max_cell_count); + ~FQTermTextLine(); + + enum CHARSTATE{NORMAL = 0x00, FIRSTPART = 0x01, SECONDPART = 0x02}; + // Get the number of used cells. + unsigned getWidth() const {return cells_.size();} + + void setMaxCellCount(unsigned max_cell_count); + unsigned getMaxCellCount() const { return max_cell_count_; } + + // Whether this line of text contains blink characters. + bool hasBlink() const; + + // Set/get the dirty flag, that indicates which cells have been + // modified. + void setDirtyFlag(unsigned cell_begin, unsigned cell_end); + void safelySetDirtyFlag(unsigned cell_begin, unsigned cell_end); + bool getDirtyCells(unsigned &cell_begin, unsigned &cell_end) const; + void clearDirtyFlag(); + void setAllDirty() {setDirtyFlag(0, max_cell_count_);} + + const unsigned char *getColors() const { + return cells_.size() == 0 ? NULL : &cell_colors_[0]; + } + + const unsigned char *getAttributes() const { + return cells_.size() == 0 ? NULL : &cell_attrs_[0]; + } + + // Get plain text in [cell_begin, cell_end). + void getPlainText(unsigned cell_begin, unsigned cell_end, + QString &result) const; + + void getAllPlainText(QString &result) const { + getPlainText(0, getWidth(), result); + } + + // Get text in cells [cell_begin, cell_end), with color and + // attribute in ANSI format. + void getAnsiText(unsigned cell_begin, unsigned cell_end, + QString &result, + const char *escape = "\x1b\x1b") const; + + void getAllAnsiText(QString &result, const char* escape = "\x1b\x1b") const { + getAnsiText(0, getWidth(), result, escape); + } + + void appendWhiteSpace(unsigned count, + unsigned char color = NO_COLOR, + unsigned char attr = NO_ATTR, + UTF16 space = ' '); + + void insertWhiteSpace(unsigned count, unsigned cell_begin, + unsigned char color = NO_COLOR, + unsigned char attr = NO_ATTR, + UTF16 space = ' '); + + void replaceWithWhiteSpace(unsigned count, + unsigned cell_begin, unsigned cell_end, + unsigned char color = NO_COLOR, + unsigned char attr = NO_ATTR, + UTF16 space = ' '); + + // insert string at specified cell, + void insertText(const UTF16 *str, unsigned count, + unsigned cell_begin, + unsigned char color, unsigned char attr, int charstate = FQTermTextLine::NORMAL); + void replaceText(const UTF16 *str, unsigned count, + unsigned cell_begin, unsigned cell_end, + unsigned char color, unsigned char attr, int charstate = FQTermTextLine::NORMAL); + void deleteText(unsigned cell_begin, unsigned cell_end); + void deleteAllText(); + + // If cell_begin is a begining of a readable character, return + // cell_begin itself, otherwise return the previous cell which starts a + // readable character. + unsigned getCellBegin(unsigned cell_begin) const; + + // If cell_end is a ending of a readable character, return cell_end + // itself, otherwise return the next cell which finishes a + // readable character. + unsigned getCellEnd(unsigned cell_end) const; + + unsigned getCharBegin(unsigned cell_begin) const; + unsigned getCharEnd(unsigned cell_end) const; + + private: + unsigned max_cell_count_; // max number of cells allowed. + + std::vector<uint16_t> cells_; // store the index of character in this->chars_ for each cell. + std::vector<unsigned char> cell_colors_; // store the color for each cell. + std::vector<unsigned char> cell_attrs_; // store the attribute for each cell. + std::vector<UTF16> chars_; // store all the character. + + unsigned char stored_color_; + unsigned char stored_attr_; + + // range of changed text. + unsigned dirty_cell_begin_, dirty_cell_end_; + + // if the given cell isn't a beginning of characters, + // i.e is in the middle of a wide character, + // break the character into several unicode replacement characters (URC) + // to make the given cell be a beginning of character. + bool breakCell(unsigned cell_begin); + + // calculate ANSI escape sequence with given color, attribute and escape strings. + void getAnsiCtrlSeq(unsigned char color, unsigned char attr, + const char *escape, QString &result) const; + + // Two functions for debugging/testing. + void verifyTextLine() const; + void verifyCellRange(unsigned cell_begin, unsigned cell_end) const; +}; + +} // namespace FQTerm + +#endif // FQTERM_TEXTLINE_H diff --git a/src/terminal/internal/fqterm_decode.cpp b/src/terminal/internal/fqterm_decode.cpp new file mode 100644 index 0000000..cb7bc9a --- /dev/null +++ b/src/terminal/internal/fqterm_decode.cpp @@ -0,0 +1,1279 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <QByteArray> + +#include "fqterm.h" +#include "fqterm_telnet.h" +#include "fqterm_decode.h" +#include "fqterm_buffer.h" +#include "fqwcwidth.h" + +namespace FQTerm { + +#define MODE_MouseX11 0 + +/************************************************************************/ +// state for FSM +// please read ANSI decoding +StateOption FQTermDecode::VT100StateMachine::normal_state_[] = { + { + CHAR_CR, &FQTermDecode::cr, normal_state_ + }, { + CHAR_LF, &FQTermDecode::lf, normal_state_ + }, { + CHAR_VT, &FQTermDecode::lf, normal_state_ + }, { + CHAR_ESC, 0, esc_state_ + }, { + CHAR_FF, &FQTermDecode::ff, normal_state_ + }, { + CHAR_TAB, &FQTermDecode::tab, normal_state_ + }, { + CHAR_BS, &FQTermDecode::bs, normal_state_ + }, { + CHAR_BELL, &FQTermDecode::bell, normal_state_ + }, { + CHAR_SO, &FQTermDecode::g1, normal_state_ + }, { + CHAR_SI, &FQTermDecode::g0, normal_state_ + }, { + CHAR_CAN, 0, normal_state_ // should be ignored + }, { + CHAR_DEL, &FQTermDecode::del, normal_state_ + }, { + CHAR_NUL, 0, normal_state_ // should be ignored + }, { + CHAR_ENQ, &FQTermDecode::enq, normal_state_ + }, { + CHAR_NORMAL, &FQTermDecode::normalInput, normal_state_ + } +}; + +// state after a ESC_CHAR +StateOption FQTermDecode::VT100StateMachine::esc_state_[] = { + { + '7', &FQTermDecode::saveCursor, normal_state_ + }, { + '8', &FQTermDecode::restoreCursor, normal_state_ + }, { + '[', &FQTermDecode::clearParam, bracket_state_ + }, { + ']', &FQTermDecode::clearParam, title_state_ + }, { + '(', 0, select_g0_charset_state_ + }, { + ')', 0, select_g1_charset_state_ + }, { + 'D', &FQTermDecode::moveCursorDown, normal_state_ + }, { + 'E', &FQTermDecode::nextLine, normal_state_ + }, { + 'H', &FQTermDecode::addTabStop, normal_state_ + }, { + 'M', &FQTermDecode::moveCursorUp, normal_state_ + }, { + '>', &FQTermDecode::setNumericKeypad, normal_state_ + }, { + '=', &FQTermDecode::setAppKeypad, normal_state_ + }, { + '<', &FQTermDecode::test, normal_state_ + }, { + '#', 0, sharp_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC [ +StateOption FQTermDecode::VT100StateMachine::bracket_state_[] = { + { + '0', &FQTermDecode::paramDigit, bracket_state_ + }, { + '1', &FQTermDecode::paramDigit, bracket_state_ + }, { + '2', &FQTermDecode::paramDigit, bracket_state_ + }, { + '3', &FQTermDecode::paramDigit, bracket_state_ + }, { + '4', &FQTermDecode::paramDigit, bracket_state_ + }, { + '5', &FQTermDecode::paramDigit, bracket_state_ + }, { + '6', &FQTermDecode::paramDigit, bracket_state_ + }, { + '7', &FQTermDecode::paramDigit, bracket_state_ + }, { + '8', &FQTermDecode::paramDigit, bracket_state_ + }, { + '9', &FQTermDecode::paramDigit, bracket_state_ + }, { + ';', &FQTermDecode::nextParam, bracket_state_ + }, { + '?', &FQTermDecode::clearParam, private_state_ + }, { + 'A', &FQTermDecode::cursorUp, normal_state_ + }, { + 'B', &FQTermDecode::cursorDown, normal_state_ + }, { + 'C', &FQTermDecode::cursorRight, normal_state_ + }, { + 'D', &FQTermDecode::cursorLeft, normal_state_ + }, { + 'H', &FQTermDecode::cursorPosition, normal_state_ + }, { + 'J', &FQTermDecode::eraseScreen, normal_state_ + }, { + 'K', &FQTermDecode::eraseLine, normal_state_ + }, { + 'L', &FQTermDecode::insertLine, normal_state_ + }, { + 'M', &FQTermDecode::deleteLine, normal_state_ + }, { + 'P', &FQTermDecode::deleteStr, normal_state_ + }, { + 'X', &FQTermDecode::eraseStr, normal_state_ + }, { + 'f', &FQTermDecode::cursorPosition, normal_state_ + }, { + 'g', &FQTermDecode::clearTabStop, normal_state_ + }, { + 'h', &FQTermDecode::setMode, normal_state_ + }, { + 'l', &FQTermDecode::resetMode, normal_state_ + }, { + 'm', &FQTermDecode::setAttr, normal_state_ + }, { + 'q', 0, normal_state_ // ignore LED-related commands. + }, { + 'r', &FQTermDecode::setMargins, normal_state_ + }, { + 's', &FQTermDecode::saveCursor, normal_state_ + }, { + 'u', &FQTermDecode::restoreCursor, normal_state_ + }, { + '@', &FQTermDecode::insertStr, normal_state_ + }, { + CHAR_CR, &FQTermDecode::cr, bracket_state_ + }, { + CHAR_LF, &FQTermDecode::lf, bracket_state_ + }, { + CHAR_VT, &FQTermDecode::lf, bracket_state_ + }, { + CHAR_FF, &FQTermDecode::ff, bracket_state_ + }, { + CHAR_TAB, &FQTermDecode::tab, bracket_state_ + }, { + CHAR_BS, &FQTermDecode::bs, bracket_state_ + }, { + CHAR_BELL, &FQTermDecode::bell, bracket_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC ( +StateOption FQTermDecode::VT100StateMachine::select_g0_charset_state_[] = { + { + 'A', &FQTermDecode::selectG0A, normal_state_ + }, { + 'B', &FQTermDecode::selectG0B, normal_state_ + }, { + '0', &FQTermDecode::selectG00, normal_state_ + }, { + '1', &FQTermDecode::selectG01, normal_state_ + }, { + '2', &FQTermDecode::selectG02, normal_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC ) +StateOption FQTermDecode::VT100StateMachine::select_g1_charset_state_[] = { + { + 'A', &FQTermDecode::selectG1A, normal_state_ + }, { + 'B', &FQTermDecode::selectG1B, normal_state_ + }, { + '0', &FQTermDecode::selectG10, normal_state_ + }, { + '1', &FQTermDecode::selectG11, normal_state_ + }, { + '2', &FQTermDecode::selectG12, normal_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC [ ? +StateOption FQTermDecode::VT100StateMachine::private_state_[] = { + { + '0', &FQTermDecode::paramDigit, private_state_ + }, { + '1', &FQTermDecode::paramDigit, private_state_ + }, { + '2', &FQTermDecode::paramDigit, private_state_ + }, { + '3', &FQTermDecode::paramDigit, private_state_ + }, { + '4', &FQTermDecode::paramDigit, private_state_ + }, { + '5', &FQTermDecode::paramDigit, private_state_ + }, { + '6', &FQTermDecode::paramDigit, private_state_ + }, { + '7', &FQTermDecode::paramDigit, private_state_ + }, { + '8', &FQTermDecode::paramDigit, private_state_ + }, { + '9', &FQTermDecode::paramDigit, private_state_ + }, { + ';', &FQTermDecode::nextParam, private_state_ + }, { + 'h', &FQTermDecode::setDecPrivateMode, normal_state_ + }, { + 'l', &FQTermDecode::resetDecPrivateMode, normal_state_ + }, { + 's', &FQTermDecode::saveMode, normal_state_ + }, { + 'r', &FQTermDecode::restoreMode, normal_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC # +StateOption FQTermDecode::VT100StateMachine::sharp_state_[] = { + { + '8', &FQTermDecode::fillScreen, normal_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +// state after ESC ] +// to change the terminal title dynamically. +// Dynamic titles +// Many people find it useful to set the title of a terminal to reflect dynamic information, +// such as the name of the host the user is logged into, the current working directory, etc. +// This may be done by using XTerm escape sequences. The following sequences are useful in this respect: +// ESC]0;stringBEL Set icon name and window title to string +// ESC]1;stringBEL Set icon name to string +// ESC]2;stringBEL Set window title to string +// where ESC is the escape character (\033), and BEL is the bell character (\007). +// Currently we just ignore this. +StateOption FQTermDecode::VT100StateMachine::title_state_[] = { + { + '0', &FQTermDecode::paramDigit, title_state_ + }, { + '1', &FQTermDecode::paramDigit, title_state_ + }, { + '2', &FQTermDecode::paramDigit, title_state_ + }, { + '4', &FQTermDecode::paramDigit, title_state_ + }, { + '5', &FQTermDecode::paramDigit, title_state_ + }, { + '6', &FQTermDecode::paramDigit, title_state_ + }, { + ';', &FQTermDecode::clearText, title_text_state_ + }, { + CHAR_NORMAL, 0, normal_state_ + } +}; + +StateOption FQTermDecode::VT100StateMachine::title_text_state_[] = { + { + CHAR_ESC, 0, esc_state_ + }, { + CHAR_BELL, &FQTermDecode::setTitle, normal_state_ + }, { + CHAR_NORMAL, &FQTermDecode::collectText, title_state_ + } +}; + +const char *FQTermDecode::getStateName(const StateOption *state) { + if (state == VT100StateMachine::normal_state_) { + return "VT100StateMachine::normal_state_"; + } else if (state == VT100StateMachine::esc_state_) { + return "VT100StateMachine::esc_state_"; + } else if (state == VT100StateMachine::bracket_state_) { + return "VT100StateMachine::bracket_state_"; + } else if (state == VT100StateMachine::private_state_) { + return "VT100StateMachine::private_state_"; + } else if (state == VT100StateMachine::title_state_) { + return "VT100StateMachine::title_state_"; + } else { + return "Unknow"; + } +} + + +FQTermDecode::FQTermDecode(FQTermBuffer *buffer, int server_encoding) { + leftToDecode_.clear(); + termBuffer_ = buffer; + + interrupt_decode_ = false; + + current_state_ = VT100StateMachine::normal_state_; + + default_color_ = NO_COLOR; + default_attr_ = NO_ATTR; + + current_color_ = default_color_; + current_attr_ = default_attr_; + + isBell_ = false; + + termBuffer_->setCurrentAttr(current_color_, current_attr_); + + // TODO_UTF16: please create a encoding enum. + server_encoding_ = server_encoding; + + currentMode_[MODE_MouseX11] = savedMode_[MODE_MouseX11] = false; + FQ_VERIFY(connect(termBuffer_, SIGNAL(caretChangeRow()), this, SLOT(onCaretChangeRow()))); +} + +FQTermDecode::~FQTermDecode() { + +} + +// precess input string from telnet socket +//void FQTermDecode::ansiDecode( const QCString &cstr, int length ) +int FQTermDecode::decode(const char *cstr, int length) { + inputData_ = cstr; + inputLength_ = length; //inputData.length(); + + dataIndex_ = 0; + isBell_ = false; + + interrupt_decode_ = false; + + int i; + StateOption *lastState; + + termBuffer_->startDecode(); + + // here we use FSM to ANSI decoding + // use switch case is ok too + // but i think use function pointer array can make this clear + // you can see the defination at the beginning + while (dataIndex_ < inputLength_) { + // current state always be initialized to point to the deginning of three structures + // ( normalState, escState, bracketState ) + i = 0; + while (current_state_[i].byte != CHAR_NORMAL && current_state_[i].byte != + inputData_[dataIndex_]) { + i++; + } + + // action must be allowed to redirect state change + // get current state with input character i ( hehe, current now become last one ) + lastState = current_state_ + i; // good !! + + bool trace_state = true; + if (current_state_ == VT100StateMachine::normal_state_ + && lastState->nextState == VT100StateMachine::normal_state_) { + trace_state = false; + } + + if (trace_state) { + FQ_TRACE("ansi", 10) << "Current state: " << getStateName(current_state_); + FQ_TRACE("ansi", 10) << dumpHexString << std::string("") + inputData_[dataIndex_] + << dumpNormalString << " " << inputData_[dataIndex_]; + } + + if (lastState->action != 0) { + (this->*(lastState->action))(); + } else { + if (trace_state) FQ_TRACE("ansi", 10) << "No action"; + } + if (trace_state) FQ_TRACE("ansi", 10) << "Next state: " << getStateName(lastState->nextState); + + // reinit current state + current_state_ = lastState->nextState; + + dataIndex_++; + + if (interrupt_decode_) { + break; + } + } + termBuffer_->endDecode(); + + return dataIndex_; +} + + +QString FQTermDecode::bbs2unicode(const QByteArray &text) { + return encoding2unicode(text, server_encoding_); +} + +/////////////////////////////////// +//helper function to decode utf8// +////////////////////////////////// +static int utf8_expected_byte_count(char first_byte) +{ + char expected = 0; + if (!(first_byte & 0x80)) + return 0; //1 byte ascii + else + expected++; + if (!(first_byte & 0x40)) + { + return -1; //not a leading byte. + } + if (!(first_byte & 0x20)) + return expected; + else + expected++; + if (!(first_byte & 0x10)) + return expected; + else + expected++; + if (!(first_byte & 0x08)) + return expected; + return -1; +} + +/////////////////////////////////// +//helper function to decode utf8// +////////////////////////////////// +static int gdbnbig5_expected_byte_count(char first_byte) +{ + char expected = 0; + if (!(first_byte & 0x80)) + return 0; //1 byte ascii + else + expected++; + return expected; +} + +int FQTermDecode::processInput(QByteArray& result) +{ + int charstate = FQTermTextLine::NORMAL; + int expect_bytes = 0; + if (leftToDecode_.size()) + { + switch(server_encoding_) + { + case FQTERM_ENCODING_GBK: + case FQTERM_ENCODING_BIG5: + case FQTERM_ENCODING_HKSCS: + case FQTERM_ENCODING_UAO: + expect_bytes = gdbnbig5_expected_byte_count(leftToDecode_[0]); + break; + case FQTERM_ENCODING_UTF8: + expect_bytes = utf8_expected_byte_count(leftToDecode_[0]); + break; + } + } + + //TODO: error. + if (expect_bytes < 0) { + expect_bytes = 0; + } + + + int n = 0; + int last_char_index = n; + while (((dataIndex_ + n) < inputLength_) && (signed(inputData_[dataIndex_ + n]) >= 0x20 + || signed(inputData_[dataIndex_ + n]) < 0x00)) { + + if (expect_bytes != 0) { + expect_bytes--; + } else { + last_char_index = n; + switch(server_encoding_) + { + case FQTERM_ENCODING_GBK: + case FQTERM_ENCODING_BIG5: + case FQTERM_ENCODING_HKSCS: + case FQTERM_ENCODING_UAO: + expect_bytes = gdbnbig5_expected_byte_count(inputData_[dataIndex_ + n]); + break; + case FQTERM_ENCODING_UTF8: + expect_bytes = utf8_expected_byte_count(inputData_[dataIndex_ + n]); + break; + } + //TODO: error. + if (expect_bytes < 0) { + expect_bytes = 0; + } + + + } + + n++; + } + + if (expect_bytes) { + if(dataIndex_ + n == inputLength_) { + interrupt_decode_ = true; + } + } + + result.clear(); + result.reserve(n + 1); + if (leftToDecode_.size()) { + result += leftToDecode_; + switch(server_encoding_) + { + case FQTERM_ENCODING_GBK: + case FQTERM_ENCODING_BIG5: + case FQTERM_ENCODING_HKSCS: + case FQTERM_ENCODING_UAO: + charstate |= FQTermTextLine::SECONDPART; + break; + case FQTERM_ENCODING_UTF8: + break; + } + leftToDecode_.clear(); + } + int real_n = n; + if (expect_bytes) { + real_n = last_char_index; + leftToDecode_ = QByteArray(inputData_ + dataIndex_ + last_char_index, n - last_char_index); + } + + result.push_back(QByteArray(inputData_ + dataIndex_, real_n)); + if (expect_bytes) { + switch(server_encoding_) + { + case FQTERM_ENCODING_GBK: + case FQTERM_ENCODING_BIG5: + case FQTERM_ENCODING_HKSCS: + case FQTERM_ENCODING_UAO: + result.push_back('?'); //make sure the attr is recorded, + //since last -1 operation can make cstr to be empty + charstate |= FQTermTextLine::FIRSTPART; + break; + case FQTERM_ENCODING_UTF8: + break; + } + } + + n--; + dataIndex_ += n; + + return charstate; +} + + +// fill letters into char buffer +// TODO: this function may contain bug, need double-check. +// 1. input should be ascii-compitable encoding. +void FQTermDecode::normalInput() { + + FQ_FUNC_TRACE("ansi", 8); + + // TODO_UTF16: check ascii-incompitable encoding. + if (signed(inputData_[dataIndex_]) < 0x20 && signed(inputData_[dataIndex_]) >= 0x00) { + // not print char + return ; + } + + QByteArray cstr; + int charstate = processInput(cstr); + + + + QString str = bbs2unicode(cstr); + FQ_TRACE("normal_input", 9) << "hex: " << dumpHexString + << str.toLocal8Bit().constData() ; + FQ_TRACE("normal_input", 9) << "text: " << str; + termBuffer_->writeText(str, charstate); + +} + +// non-printing characters functions +void FQTermDecode::cr() { + FQ_FUNC_TRACE("ansi", 8); + // FIXME: dirty + termBuffer_->carriageReturn(); +} + +void FQTermDecode::lf() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->lineFeed(); +} + +void FQTermDecode::ff() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->eraseEntireTerm(); + + termBuffer_->moveCaretTo(0, 0); +} + +void FQTermDecode::tab() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->tab(); +} + +void FQTermDecode::bs() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->moveCaretOffset(-1, 0); +} + +void FQTermDecode::bell() { + FQ_FUNC_TRACE("ansi", 8); + isBell_ = true; +} + +void FQTermDecode::del() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->moveCaretOffset(-1, 0); + //termBuffer_->deleteText(1); +} + +void FQTermDecode::g0() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->invokeCharset(true); +} + +void FQTermDecode::g1() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->invokeCharset(false); +} + +void FQTermDecode::enq() { + emit enqReceived(); +} + +void FQTermDecode::addTabStop() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->addTabStop(); +} + +void FQTermDecode::clearTabStop() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + if (param_[0] == 0) { + termBuffer_->clearTabStop(false); + } else if (param_[0] == 3) { + termBuffer_->clearTabStop(true); + } +} + +void FQTermDecode::setMargins() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + if (isParamAvailable_) { + termBuffer_->setMargins(param_[0] - 1, param_[1] - 1); + } else { + termBuffer_->setMargins(0, termBuffer_->getNumRows() - 1); + } +} + +// parameters functions +void FQTermDecode::clearParam() { + FQ_FUNC_TRACE("ansi", 9); + paramIndex_ = 0; + memset(param_, 0, sizeof(param_)); + isParamAvailable_ = false; +} + +// for performance, this grabs all digits +void FQTermDecode::paramDigit() { + FQ_FUNC_TRACE("ansi", 10); + isParamAvailable_ = true; + + // make stream into number + // ( e.g. this input character is '1' and this param is 4 + // after the following sentence this param is changed to 41 + param_[paramIndex_] = param_[paramIndex_] *10+inputData_[dataIndex_] - '0'; +} + +void FQTermDecode::nextParam() { + FQ_FUNC_TRACE("ansi", 9); + paramIndex_++; +} + +void FQTermDecode::saveCursor() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->saveCaret(); +} + +void FQTermDecode::restoreCursor() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->restoreCaret(); +} + +void FQTermDecode::cursorPosition() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int x = param_[1]; + int y = param_[0]; + + if (x == 0) { + x = 1; + } + if (y == 0) { + y = 1; + } + + termBuffer_->changeCaretPosition(x - 1, y - 1); +} + +void FQTermDecode::cursorLeft() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->moveCaretOffset(-n, 0); +} + +void FQTermDecode::cursorRight() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->moveCaretOffset(n, 0); +} + +void FQTermDecode::cursorUp() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->moveCaretOffset(0, -n); +} + +void FQTermDecode::cursorDown() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->moveCaretOffset(0, n); +} + +void FQTermDecode::moveCursorUp() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->scrollTerm(-1);//moveCaretOffset(0, -1, true); +} + +void FQTermDecode::moveCursorDown() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->scrollTerm(1);//moveCaretOffset(0, 1, true); +} + +void FQTermDecode::nextLine() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->carriageReturn(); + termBuffer_->moveCaretOffset(0, 1, true); +} + +void FQTermDecode::selectG0A() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::UNITED_KINGDOM_SET, true); +} + +void FQTermDecode::selectG0B() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::ASCII_SET, true); +} + +void FQTermDecode::selectG00() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::SPECIAL_GRAPHICS, true); +} + +void FQTermDecode::selectG01() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet( + FQTermBuffer::ALTERNATE_CHARACTER_ROM_STANDARD_CHARACTER_SET, true); +} + +void FQTermDecode::selectG02() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet( + FQTermBuffer::ALTERNATE_CHARACTER_ROM_SPECIAL_GRAPHICS, true); +} + +void FQTermDecode::selectG1A() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::UNITED_KINGDOM_SET, false); +} + +void FQTermDecode::selectG1B() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::ASCII_SET, false); +} + +void FQTermDecode::selectG10() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet(FQTermBuffer::SPECIAL_GRAPHICS, false); +} + +void FQTermDecode::selectG11() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet( + FQTermBuffer::ALTERNATE_CHARACTER_ROM_STANDARD_CHARACTER_SET, false); +} + +void FQTermDecode::selectG12() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->SelectVtCharacterSet( + FQTermBuffer::ALTERNATE_CHARACTER_ROM_SPECIAL_GRAPHICS, false); +} + +// erase functions +void FQTermDecode::eraseStr() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->eraseText(n); +} + +// insert functions +void FQTermDecode::insertStr() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->insertSpaces(n); +} + +// delete functions +void FQTermDecode::deleteStr() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->deleteText(n); +} + +void FQTermDecode::eraseLine() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + switch (param_[0]) { + case 0: + termBuffer_->eraseToLineEnd(); + break; + case 1: + termBuffer_->eraseToLineBegin(); + break; + case 2: + termBuffer_->eraseEntireLine(); + break; + default: + break; + } +} + +void FQTermDecode::insertLine() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->insertLines(n); +} + +void FQTermDecode::deleteLine() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + int n = param_[0]; + + if (n < 1) { + n = 1; + } + + termBuffer_->deleteLines(n); +} + +void FQTermDecode::eraseScreen() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + switch (param_[0]) { + case 0: + termBuffer_->eraseToTermEnd(); + break; + case 1: + termBuffer_->eraseToTermBegin(); + break; + case 2: + termBuffer_->eraseEntireTerm(); + break; + case 3: + break; + } +} + +void FQTermDecode::setAttr() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + // get all attributes of character + if (!paramIndex_ && param_[0] == 0) { + current_color_ = default_color_; + current_attr_ = default_attr_; + + termBuffer_->setCurrentAttr(current_color_, current_attr_); + return ; + } + + unsigned char cp = current_color_; + unsigned char ea = current_attr_; + for (int n = 0; n <= paramIndex_; n++) { + if (param_[n] / 10 == 4) { + // background color + cp = cp &~BGMASK; + cp += SETBG(param_[n] % 10); + } else if (param_[n] / 10 == 3) { + // front color + cp = cp &~FGMASK; + cp += SETFG(param_[n] % 10); + } else { + switch (param_[n]) { + case 0: + // attr off + cp = default_color_; //NO_COLOR; + ea = default_attr_; //NO_ATTR; + break; + case 1: + // bright + ea = SETBRIGHT(ea); + break; + case 2: + // dim + ea = SETDIM(ea); + break; + case 4: + // underline + ea = SETUNDERLINE(ea); + break; + case 5: + // blink + ea = SETBLINK(ea); + break; + case 7: + // reverse + ea = SETREVERSE(ea); + break; + case 8: + // invisible + ea = SETINVISIBLE(ea); + break; + default: + break; + } + } + } + + current_color_ = cp; + current_attr_ = ea; + + termBuffer_->setCurrentAttr(current_color_, current_attr_); +} + +void FQTermDecode::setMode() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 4: + termBuffer_->setMode(FQTermBuffer::INSERT_MODE); + break; + case 20: + termBuffer_->setMode(FQTermBuffer::NEWLINE_MODE); + break; + default: + break; + } + } +} + +void FQTermDecode::resetMode() { + //TODO: more modes here. + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 4: + termBuffer_->resetMode(FQTermBuffer::INSERT_MODE); + break; + case 20: + termBuffer_->resetMode(FQTermBuffer::NEWLINE_MODE); + break; + default: + break; + } + } +} + +void FQTermDecode::setDecPrivateMode() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 1: + termBuffer_->setMode(FQTermBuffer::CURSOR_MODE); + break; + case 2: + termBuffer_->setMode(FQTermBuffer::ANSI_MODE); + break; + case 3: + termBuffer_->setTermSize(132, 24); + break; + case 4: + // smooth scrolling mode + // nothing to do. + break; + case 5: + termBuffer_->setMode(FQTermBuffer::LIGHTBG_MODE); + break; + case 6: + termBuffer_->setMode(FQTermBuffer::ORIGIN_MODE); + break; + case 7: + termBuffer_->setMode(FQTermBuffer::AUTOWRAP_MODE); + break; + case 8: + termBuffer_->setMode(FQTermBuffer::AUTOREPEAT_MODE); + break; + case 9: + // Interlace mode + // nothing to do. + break; + case 1000: + case 1001: + emit mouseMode(true); + currentMode_[MODE_MouseX11] = true; + break; + default: + break; + } + } +} + +void FQTermDecode::resetDecPrivateMode() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 1: + termBuffer_->resetMode(FQTermBuffer::CURSOR_MODE); + break; + case 2: + termBuffer_->resetMode(FQTermBuffer::ANSI_MODE); + break; + case 3: + termBuffer_->setTermSize(80, 24); + break; + case 4: + // jump scrolling mode + // nothing to do. + break; + case 5: + termBuffer_->resetMode(FQTermBuffer::LIGHTBG_MODE); + break; + case 6: + termBuffer_->resetMode(FQTermBuffer::ORIGIN_MODE); + break; + case 7: + termBuffer_->resetMode(FQTermBuffer::AUTOWRAP_MODE); + break; + case 8: + termBuffer_->resetMode(FQTermBuffer::AUTOREPEAT_MODE); + break; + case 9: + // Interlace mode + // nothing to do. + break; + case 1000: + case 1001: + currentMode_[MODE_MouseX11] = false; + emit mouseMode(false); + break; + default: + break; + } + } +} + +void FQTermDecode::setNumericKeypad() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->setMode(FQTermBuffer::NUMERIC_MODE); +} + +void FQTermDecode::setAppKeypad() { + FQ_FUNC_TRACE("ansi", 8); + termBuffer_->resetMode(FQTermBuffer::NUMERIC_MODE); +} + +void FQTermDecode::saveMode() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 1000: + case 1001: + savedMode_[MODE_MouseX11] = currentMode_[MODE_MouseX11]; + break; + default: + break; + } + } +} + +void FQTermDecode::restoreMode() { + FQ_FUNC_TRACE("ansi", 8); + logParam(); + + for (int i = 0; i <= paramIndex_; i++) { + int n = param_[i]; + switch (n) { + case 1000: + case 1001: + currentMode_[MODE_MouseX11] = savedMode_[MODE_MouseX11]; + emit mouseMode(currentMode_[MODE_MouseX11]); + break; + default: + break; + } + } +} + +void FQTermDecode::fillScreen() { + FQ_FUNC_TRACE("ansi", 8); + + termBuffer_->fillScreenWith('E'); +} + +void FQTermDecode::test() { + FQ_FUNC_TRACE("ansi", 8); +} + +void FQTermDecode::logParam() const { + if (isParamAvailable_) { + for (int i = 8; i <= paramIndex_; ++i) { + FQ_TRACE("ansi", 8) << "Parameter[" << i << "] = " << param_[i]; + } + } else { + FQ_TRACE("ansi", 8) << "No parameter."; + } +} + +void FQTermDecode::onCaretChangeRow() { + leftToDecode_.clear(); +} + +void FQTermDecode::setTitle() { + if (!isParamAvailable_) { + return; + } + switch(param_[0]) + { + case 0: + //icon and text + break; + case 1: + //icon + break; + case 2: + //text + emit onTitleSet(bbs2unicode(textParam_)); + break; + case 46: + //log file + break; + case 50: + //font + break; + default: + break; + } +} + +void FQTermDecode::collectText() { + FQ_FUNC_TRACE("ansi", 10); + if (textParam_.length() < 4096) + textParam_ += inputData_[dataIndex_]; +} + +void FQTermDecode::clearText() { + FQ_FUNC_TRACE("ansi", 10); + textParam_.clear(); +} + + + + +} // namespace FQTerm + +#include "fqterm_decode.moc" diff --git a/src/terminal/internal/fqterm_decode.h b/src/terminal/internal/fqterm_decode.h new file mode 100644 index 0000000..5f3f46d --- /dev/null +++ b/src/terminal/internal/fqterm_decode.h @@ -0,0 +1,194 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_DECODE_H +#define FQTERM_DECODE_H + +#include <QObject> +#include "fqterm_text_line.h" + +namespace FQTerm { + +class FQTermDecode; +class FQTermBuffer; +class FQTermTelnet; + +// this for FSM +typedef void(FQTermDecode:: *StateFunc)(); + +struct StateOption { + int byte; // char value to look for; -1==end/default + StateFunc action; + StateOption *nextState; +}; + +class FQTermDecode: public QObject { + Q_OBJECT; + public: + FQTermDecode(FQTermBuffer *, int server_encoding); + ~FQTermDecode(); + + + // translate data from telnet socket to our own buffer + // return how many bytes are processed. + int decode(const char *cstr, int length); + + bool bellReceive() { + return isBell_; + } + + void setServerEncoding(int encoding) { + server_encoding_ = encoding; + } + + + //signals: + // void decodeFinished(); + signals: + void mouseMode(bool); + void enqReceived(); + void onTitleSet(const QString&); + public slots: + void onCaretChangeRow(); + private: + // escape sequence actions + // you'd better see FSM structure array in FQTermDecode.cpp + + void setAttr(); + void setMargins(); + + // char screen functions + void deleteStr(); + void deleteLine(); + void insertStr(); + void insertLine(); + void eraseStr(); + void eraseLine(); + void eraseScreen(); + + // cursor functions + void saveCursor(); + void restoreCursor(); + void cursorPosition(); + + // Move cursor, stop at boundary. + void cursorLeft(); + void cursorDown(); + void cursorRight(); + void cursorUp(); + + // Move cursor, scroll at boundary. + void moveCursorUp(); + void moveCursorDown(); + void nextLine(); + + void selectG0A(); + void selectG0B(); + void selectG00(); + void selectG01(); + void selectG02(); + + void selectG1A(); + void selectG1B(); + void selectG10(); + void selectG11(); + void selectG12(); + + // other escape sequence actions + void normalInput(); + + //title settings + void setTitle(); + void collectText(); + void clearText(); + + // action parameters + void clearParam(); + void paramDigit(); + void nextParam(); + + // non-printing characters + void cr(), lf(), ff(), bell(), tab(), bs(), del(), g0(), g1(), enq(); + + void addTabStop(); + void clearTabStop(); + + void setMode(); + void resetMode(); + void setDecPrivateMode(); + void resetDecPrivateMode(); + void setNumericKeypad(); + void setAppKeypad(); + + void saveMode(); + void restoreMode(); + + // video alignment test - fill screen with E's. + void fillScreen(); + + void test(); + + private: + + bool isBell_; + // short currentAttr_, defaultAttr_; + unsigned char current_color_, default_color_; + unsigned char current_attr_, default_attr_; + + // ********** ansi decoder states **************** + StateOption *current_state_; + + struct VT100StateMachine { + static StateOption normal_state_[], esc_state_[], bracket_state_[]; + static StateOption private_state_[], sharp_state_[]; + static StateOption title_state_[], title_text_state_[]; + static StateOption select_g0_charset_state_[], select_g1_charset_state_[]; + }; + + static const char *getStateName(const StateOption *state); + + void logParam() const; + + + bool interrupt_decode_; + + // ********** decoder ***************** + const char *inputData_; + int inputLength_, dataIndex_; + + int paramIndex_, param_[30]; + bool isParamAvailable_; + + QByteArray textParam_; + + bool savedMode_[30]; + bool currentMode_[30]; + + FQTermBuffer *termBuffer_; + int server_encoding_; + + QString bbs2unicode(const QByteArray &text); + int processInput(QByteArray& result); + QByteArray leftToDecode_; +}; + +} // namespace FQTerm + +#endif // FQTERM_DECODE_H diff --git a/src/terminal/internal/fqterm_telnet.cpp b/src/terminal/internal/fqterm_telnet.cpp new file mode 100644 index 0000000..2446b8a --- /dev/null +++ b/src/terminal/internal/fqterm_telnet.cpp @@ -0,0 +1,1041 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <QAbstractSocket> + +#include "fqterm.h" +#include "fqterm_telnet.h" +#include "fqterm_socket.h" + +#ifndef _NO_SSH_COMPILED +#include "fqterm_ssh_socket.h" +#endif + +/* TELNET Command Codes: */ +/* Hints: These NVT control characters are sent from client to server, So + the client side will not receive these commands */ +#define TCSB (u_char)250 /* Start Subnegotiation */ +#define TCSE (u_char)240 /* End Of Subnegotiation */ +#define TCNOP (u_char)241 /* No Operation */ +#define TCDM (u_char)242 /* Data Mark (for Sync) */ +#define TCBRK (u_char)243 /* NVT Character BRK */ +#define TCIP (u_char)244 /* Interrupt Process */ +#define TCAO (u_char)245 /* Abort Output */ +#define TCAYT (u_char)246 /* "Are You There?" Function */ +#define TCEC (u_char)247 /* Erase Character */ +#define TCEL (u_char)248 /* Erase Line */ +#define TCGA (u_char)249 /* "Go Ahead" Function */ +#define TCWILL (u_char)251 /* Desire/Confirm Will Do Option*/ +#define TCWONT (u_char)252 /* Refusal To Do Option */ +#define TCDO (u_char)253 /* Request To Do Option */ +#define TCDONT (u_char)254 /* Request NOT To Do Option */ +#define TCIAC (u_char)255 /* Interpret As Command Escape */ + +/* Telnet Option Codes: */ +#define TOTXBINARY (u_char) 0 /* TRANSMIT-BINARY option */ +#define TOECHO (u_char) 1 /* ECHO Option */ +#define TONOGA (u_char) 3 /* Suppress Go-Ahead Option */ +#define TOTERMTYPE (u_char) 24 /* Terminal-Type Option */ +#define TONAWS (u_char) 31 /* Window Size */ + +/* Network Virtual Printer Special Characters: */ +/* In normal situations, these characters will to translated into local + control characters , then pass to upper layer term. But in our situation, + we can pass them to term directly */ + +#define VPLF '\n' /* Line Feed */ +#define VPCR '\r' /* Carriage Return */ +#define VPBEL '\a' /* Bell (attention signal) */ +#define VPBS '\b' /* Back Space */ +#define VPHT '\t' /* Horizontal Tab */ +#define VPVT '\v' /* Vertical Tab */ +#define VPFF '\f' /* Form Feed */ + +/* Keyboard Command Characters: */ + +/* Option Subnegotiation Constants: */ +#define TT_IS 0 /* TERMINAL_TYPE option "IS" command */ +#define TT_SEND 1 /* TERMINAL_TYPE option "SEND" command */ + +/* Telnet Socket-Input FSM States: */ +#define TSDATA 0 /* normal data processing */ +#define TSIAC 1 /* have seen IAC */ +#define TSWOPT 2 /* have seen IAC-{WILL/WONT} */ +#define TSDOPT 3 /* have seen IAC-{DO/DONT} */ +#define TSSUBNEG 4 /* have seen IAC-SB */ +#define TSSUBIAC 5 /* have seen IAC-SB-...-IAC */ +// if any state added here, please update NTSTATES (currently 6). + +// Telnet Option Subnegotiation FSM States: +#define SS_START 0 // initial state +#define SS_TERMTYPE 1 // TERMINAL_TYPE option subnegotiation +#define SS_END 2 // state after all legal input +// if any state added here, please update NSSTATES (currently 3). + +#define FSINVALID 0xff // an invalid state number +#define TCANY (NCHRS+1) // match any character + +#define TINVALID 0xff // an invalid transition index + +namespace FQTerm { + +struct fsm_trans FQTermTelnet::ttstab[] = { + /* State Input Next State Action */ + /* ------ ------ ----------- ------- */ + { + TSDATA, TCIAC, TSIAC, &FQTermTelnet::no_op + }, { + TSDATA, TCANY, TSDATA, &FQTermTelnet::ttputc + }, { + TSIAC, TCIAC, TSDATA, &FQTermTelnet::ttputc + }, { + TSIAC, TCSB, TSSUBNEG, &FQTermTelnet::no_op + }, {/* Telnet Commands */ + TSIAC, TCNOP, TSDATA, &FQTermTelnet::no_op + }, { + TSIAC, TCDM, TSDATA, &FQTermTelnet::tcdm + }, { /* Option Negotiation */ + TSIAC, TCWILL, TSWOPT, &FQTermTelnet::recopt + }, { + TSIAC, TCWONT, TSWOPT, &FQTermTelnet::recopt + }, { + TSIAC, TCDO, TSDOPT, &FQTermTelnet::recopt + }, { + TSIAC, TCDONT, TSDOPT, &FQTermTelnet::recopt + }, { + TSIAC, TCANY, TSDATA, &FQTermTelnet::no_op + }, { /* Option Subnegotion */ + TSSUBNEG, TCIAC, TSSUBIAC, &FQTermTelnet::no_op + }, { + TSSUBNEG, TCANY, TSSUBNEG, &FQTermTelnet::subopt + }, { + TSSUBIAC, TCSE, TSDATA, &FQTermTelnet::subend + }, { + TSSUBIAC, TCANY, TSSUBNEG, &FQTermTelnet::subopt + }, { + TSWOPT, TOECHO, TSDATA, &FQTermTelnet::do_echo + }, { + TSWOPT, TONOGA, TSDATA, &FQTermTelnet::do_noga + }, { + TSWOPT, TOTXBINARY, TSDATA, &FQTermTelnet::do_txbinary + }, { + TSWOPT, TCANY, TSDATA, &FQTermTelnet::do_notsup + },{ + TSDOPT, TONAWS, TSDATA, &FQTermTelnet::will_naws + }, { + TSDOPT, TOTERMTYPE, TSDATA, &FQTermTelnet::will_termtype + }, { + TSDOPT, TOTXBINARY, TSDATA, &FQTermTelnet::will_txbinary + }, { + TSDOPT, TCANY, TSDATA, &FQTermTelnet::will_notsup + }, { + FSINVALID, TCANY, FSINVALID, &FQTermTelnet::tnabort + }, +}; + + +struct fsm_trans FQTermTelnet::substab[] = { + /* State Input Next State Action */ + /* ---------------------------------------------------- */ + { + SS_START, TOTERMTYPE, SS_TERMTYPE, &FQTermTelnet::no_op + }, { + SS_START, TCANY, SS_END, &FQTermTelnet::no_op + }, { + SS_TERMTYPE, TT_SEND, SS_END, &FQTermTelnet::subtermtype + }, { + SS_TERMTYPE, TCANY, SS_END, &FQTermTelnet::no_op + }, { + SS_END, TCANY, SS_END, &FQTermTelnet::no_op + }, { + FSINVALID, TCANY, FSINVALID, &FQTermTelnet::tnabort + }, +}; + + + +/*------------------------------------------------------------------------ + * Constructor + *------------------------------------------------------------------------ + */ +FQTermTelnet::FQTermTelnet(const QString &strTermType, int rows, int columns, + int protocolType, int hostType, const char *sshuser, const char *sshpasswd) + : from_socket(), + to_ansi(), + from_ansi(), + to_socket(), + hostType_(hostType), + protocolType_(protocolType) { + term = new char[21]; + memset(term, 0, sizeof(term)); + // TODO: clean up, need??? +#ifdef WIN32 + _snprintf(term, sizeof(term), "%s", strTermType.toLatin1().constData()); +#else + strncpy(term,strTermType.toLatin1(),20); +#endif + + wx = columns; + wy = rows; + wsize = 0; + done_naws = 0; + synching = 0; + doecho = 0; + sndbinary = 0; + rcvbinary = 0; + noga = 0; + termtype = 0; + naws = 0; + server_sent_do_naws = 0; + raw_size = 0; + +#ifndef _NO_SSH_COMPILED + if (protocolType == 1 || protocolType == 2) { + socket = new FQTermSSHSocket(columns, rows, strTermType, sshuser, sshpasswd); + FQ_VERIFY(connect(socket, SIGNAL(sshAuthOK()), + this, SIGNAL(onSSHAuthOK()))); + } else if (protocolType == 3) { + socket = new FQTermLocalSocket(""); + } else { + socket = new FQTermTelnetSocket(); + } +#else + socket = new FQTermTelnetSocket(); +#endif + + // connect signal and slots + FQ_VERIFY(connect(socket, SIGNAL(connected()), this, SLOT(connected()))); + FQ_VERIFY(connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()))); + FQ_VERIFY(connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(showError(QAbstractSocket::SocketError)))); + FQ_VERIFY(connect(socket, SIGNAL(hostFound()), this, SLOT(hostFound()))); + FQ_VERIFY(connect(socket, SIGNAL(delayedCloseFinished()), + this, SLOT(delayCloseFinished()))); + FQ_VERIFY(connect(socket, SIGNAL(connectionClosed()), this, SLOT(closed()))); + FQ_VERIFY(connect(socket, SIGNAL(socketState(int)), this, SIGNAL(TelnetState(int)))); + + FQ_VERIFY(connect(socket, SIGNAL(requestUserPwd(QString *, QString *, bool *)), + this, SIGNAL(requestUserPwd(QString *, QString *, bool *)))); + + FQ_VERIFY(connect(socket, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString)))); + + // Init telnet, mainly the FSMs + init_telnet(); +} + +/*------------------------------------------------------------------------ + * destructor + *------------------------------------------------------------------------ + */ +FQTermTelnet::~FQTermTelnet() { + // delete objects + delete [] term; + delete socket; +} + + + +/*------------------------------------------------------------------------ + * init_telnet + *------------------------------------------------------------------------ + */ +void FQTermTelnet::init_telnet() { + fsmbuild(); /* setup FSMs */ +} + + +/*------------------------------------------------------------------------ + * fsmbuild - build the Finite State Machine data structures + *------------------------------------------------------------------------ + */ +void FQTermTelnet::fsmbuild() { + fsminit(ttfsm, ttstab, NTSTATES); + ttstate = TSDATA; + + fsminit(subfsm, substab, NSSTATES); + substate = SS_START; + +} + +/*------------------------------------------------------------------------ + * fsminit - Finite State Machine initializer + *------------------------------------------------------------------------ + */ +void FQTermTelnet::fsminit(u_char fsm[][NCHRS], struct fsm_trans ttab[], int + nstates) { + struct fsm_trans *pt; + int sn, ti, cn; + + for (cn = 0; cn < NCHRS; ++cn) { + for (ti = 0; ti < nstates; ++ti) { + fsm[ti][cn] = TINVALID; + } + } + + for (ti = 0; ttab[ti].ft_state != FSINVALID; ++ti) { + pt = &ttab[ti]; + sn = pt->ft_state; + if (pt->ft_char == TCANY) { + for (cn = 0; cn < NCHRS; ++cn) { + if (fsm[sn][cn] == TINVALID) { + fsm[sn][cn] = ti; + } + } + } else { + fsm[sn][pt->ft_char] = ti; + } + } + + /* set all uninitialized indices to an invalid transition */ + for (cn = 0; cn < NCHRS; ++cn) { + for (ti = 0; ti < nstates; ++ti) { + if (fsm[ti][cn] == TINVALID) { + fsm[ti][cn] = ti; + } + } + } + +} + +/*------------------------------------------------------------------------ + * connect to host + *------------------------------------------------------------------------ + */ +void FQTermTelnet::connectHost(const QString &hostname, quint16 portnumber) { + done_naws = 0; + synching = 0; + doecho = 0; + sndbinary = 0; + rcvbinary = 0; + noga = 0; + termtype = 0; + naws = 0; + + socket->connectToHost(hostname, portnumber); + // host name resolving + emit TelnetState(TSRESOLVING); +} + +void FQTermTelnet::windowSizeChanged(int x, int y) { + wx = x; + wy = y; + if (bConnected) { + if (hostType_ == 1 && (protocolType_ == 1 || protocolType_ == 2)) { + //This is a *nix host, with ssh connection. + socket->setTermSize(x, y); + return; + } + naws = 0; + + QByteArray cmd(10, 0); + cmd[0] = (char)TCIAC; + cmd[1] = (char)TCSB; + cmd[2] = (char)TONAWS; + cmd[3] = (char)(short(wx) >> 8); + cmd[4] = (char)(short(wx) & 0xff); + cmd[5] = (char)(short(wy) >> 8); + cmd[6] = (char)(short(wy) & 0xff); + cmd[7] = (char)TCIAC; + cmd[8] = (char)TCSE; + socket->writeBlock(cmd); + + } +} + +/*------------------------------------------------------------------------ + * set proxy + *----------------------------------------------------------------------- + */ +void FQTermTelnet::setProxy(int nProxyType, bool bAuth, const QString + &strProxyHost, quint16 uProxyPort, + const QString &strProxyUsr, + const QString &strProxyPwd) { + socket->setProxy(nProxyType, bAuth, strProxyHost, + uProxyPort, strProxyUsr, strProxyPwd); +} + +/*------------------------------------------------------------------------ + * close connection + *----------------------------------------------------------------------- + */ + +void FQTermTelnet::close() { + socket->close(); +} + +/*------------------------------------------------------------------------ + * SLOT connected + *------------------------------------------------------------------------ + */ +void FQTermTelnet::connected() { + bConnected = true; + emit TelnetState(TSHOSTCONNECTED); +} + +/*------------------------------------------------------------------------ + * SLOT closed + *------------------------------------------------------------------------ + */ + +void FQTermTelnet::closed() { + bConnected = false; + emit TelnetState(TSCLOSED); +} + +/*------------------------------------------------------------------------ + * SLOT delayCloseFinished + *------------------------------------------------------------------------ + */ + +void FQTermTelnet::delayCloseFinished() { + bConnected = false; + emit TelnetState(TSCLOSEFINISH); +} + +/*------------------------------------------------------------------------ + * SLOT hostFound + *------------------------------------------------------------------------ + */ +void FQTermTelnet::hostFound() { + emit TelnetState(TSHOSTFOUND); +} + +/*------------------------------------------------------------------------ + * SLOT error + *------------------------------------------------------------------------ + */ +void FQTermTelnet::showError(QAbstractSocket::SocketError index) { + + switch (index) { + case QAbstractSocket::ConnectionRefusedError: emit TelnetState(TSREFUSED); + break; + case QAbstractSocket::HostNotFoundError: emit TelnetState(TSHOSTNOTFOUND); + break; + //FIXME: am I right + case QAbstractSocket::SocketAccessError: emit TelnetState(TSREADERROR); + break; + case QAbstractSocket::RemoteHostClosedError: emit TelnetState(TSCLOSED); + break; + default: + emit TelnetState(TSERROR); + ; + break; + } + +} + +/*------------------------------------------------------------------------ + * SLOOT socketReadyRead - + * when socket has data to upload, it send out readyRead() signal, it + * invokes this SLOT to read data in, do telnet decode job, and send out + * readyRead() SIGNAL to upper layer + *------------------------------------------------------------------------ + */ +void FQTermTelnet::socketReadyRead() { + int nbytes, nread; + // TODO_UTF16: check this function. + + // get the data size + nbytes = socket->bytesAvailable(); + if (nbytes <= 0) { + return ; + } + + raw_size = nbytes; + + //resize input buffer + from_socket.resize(0); + + //read data from socket to from_socket + from_socket = socket->readBlock(nbytes); + nread = from_socket.size(); + //do some checks + if (nread <= 0) { + FQ_TRACE("telnet", 8) << "Failed to read socket: " << nread << " <= 0."; + return ; + } + if (nread > nbytes) { + FQ_TRACE("telnet", 0) << "Overflow when reading socket: " + << " nread = " << nread + << " > nbytes = " << nbytes; + return ; + } + + //resize output buffer + to_ansi.resize(2 *nread); + to_socket.resize(4 *nread); + + rsize = 0; + wsize = 0; + + // do telnet decode job... + struct fsm_trans *pt; + int i, ti; + + u_char c; + for (i = 0; i < nread; ++i) { + c = (u_char)(from_socket[i]); + ti = ttfsm[ttstate][c]; + pt = &ttstab[ti]; + (this->*(pt->ft_action))((int)c); + ttstate = pt->ft_next; + } + + // flush the to_socket buffer, it contain response to server + if (wsize > 0) { + socket->writeBlock(to_socket.left(wsize)); + socket->flush(); + } + + /* send SIGNAL readyRead() with the size of data available*/ + if (rsize > 0 || raw_size > 0) { + emit readyRead(rsize, raw_size); + } + +} + +int FQTermTelnet::raw_len() { + return raw_size; +} + +/*------------------------------------------------------------------------ + * actions + *------------------------------------------------------------------------ + */ +int FQTermTelnet::read_raw(char *data, uint maxlen) { + //do some checks + if (data == 0) { + FQ_TRACE("telnet", 0) << "NULL pointer."; + return -1; + } + if ((long)maxlen < raw_size) { + /* we need all data be read out in one read */ + FQ_TRACE("telnet", 0) << "The upper layer's buffer is too small."; + return -1; + } + + //do it, memcpy( destination, source, size) + memcpy(data, from_socket.data(), raw_size); + return raw_size; +} + + +/*------------------------------------------------------------------------ + * actions + *------------------------------------------------------------------------ + */ +int FQTermTelnet::read(char *data, uint maxlen) { + //do some checks + if (data == 0) { + FQ_TRACE("telent", 0) << "NULL pointer."; + return -1; + } + if (maxlen < rsize) { + /* we need all data be read out in one read */ + FQ_TRACE("telnet", 0) << "The upper layer's buffer is too small."; + return -1; + } + + //do it, memcpy( destination, source, size) + memcpy(data, to_ansi.data(), rsize); + + FQ_TRACE("telnet", 8) << "read " << rsize << " bytes:\n" + << dumpHexString << std::string(data, rsize); + + return rsize; +} + +/*------------------------------------------------------------------------ + * actions + *------------------------------------------------------------------------ + */ + + +/*------------------------------------------------------------------------ + * writeBlock + * write data from data-> to socket, the length of data is len + *------------------------------------------------------------------------ + */ +int FQTermTelnet::write(const char *data, uint len) { + // TODO: accept data, (This seems can be removed????) + from_ansi.resize(len); + memcpy(from_ansi.data(), data, len); + + // resize output buffer + to_socket.resize(2 *len); + wsize = 0; + + // process keyboard input + // because we use GUI, there is no need to support a "command mode" + // So the keyboard-input FSM isnt' necessary. + + uint i; + + u_char c; // TODO: for gcc's happy :) + for (i = 0; i < len; ++i) { + c = (u_char)(from_ansi[i]); + soputc((int)c); + } + + FQ_TRACE("telnet", 2) << "write " << len << " bytes:\n" + << dumpHexString << std::string(data, len); + + //flush socket + socket->writeBlock(to_socket.left(wsize)); + socket->flush(); + + emit TelnetState(TSWRITED); + return 0; +} + + +/*------------------------------------------------------------------------ + * Trans functions + * All trans functions use from_socket, to_ansi, to_socket buffers, and + * rsize, wsize . + *------------------------------------------------------------------------ + */ + +/*------------------------------------------------------------------------ + * tcdm - handle the telnet "DATA MARK" command (marks end of SYNCH) + *------------------------------------------------------------------------ + */ +int FQTermTelnet::tcdm(int) { + if (synching > 0) { + synching--; + } + return 0; +} + +/*------------------------------------------------------------------------ + * rcvurg - receive urgent data input (indicates a telnet SYNCH) + *------------------------------------------------------------------------ + */ +/* + int FQTermTelnet::rcvurg(int sig) + { + synching++; + } +*/ + +/*------------------------------------------------------------------------ + * recopt - record option type + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::recopt(int c) { + option_cmd = c; + return 0; +} + +/*------------------------------------------------------------------------ + * no_op - do nothing + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::no_op(int) { + return 0; +} + + +/*------------------------------------------------------------------------ + * do_echo - handle TELNET WILL/WON'T ECHO option + *------------------------------------------------------------------------ + */ +int FQTermTelnet::do_echo(int c) { + if (doecho) { + if (option_cmd == TCWILL) { + return 0; + } + /* already doing ECHO */ + } else if (option_cmd == TCWONT) { + return 0; + } + /* already NOT doing ECHO */ + + doecho = !doecho; + + putc_down(TCIAC); + if (doecho) { + putc_down(TCDO); + } else { + putc_down(TCDONT); + } + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * putc_down - put a character in to_socket buffer. + * wsize represent the number of bytes in to_socket buffer, and the buffer + * is addressed from 0, NOT 1. + *------------------------------------------------------------------------ + */ +void FQTermTelnet::putc_down(u_char c) { + // check overflow + if ((long)(wsize + 1) > to_socket.size()) { + FQ_TRACE("telnet", 0) << "Buffer to_socket overflow."; + return ; + } + // put it in the buffer + //to_socket->replace(wsize, 1, (const char *)&c); + to_socket[wsize] = c; + + wsize++; + return ; +} + +/*------------------------------------------------------------------------ + * do_notsup - handle an unsupported telnet "will/won't" option + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::do_notsup(int c) { + putc_down(TCIAC); + putc_down(TCDONT); + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * do_noga - don't do telnet Go-Ahead's + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::do_noga(int c) { + if (noga) { + if (option_cmd == TCWILL) { + return 0; + } + } else if (option_cmd == TCWONT) { + return 0; + } + + noga = !noga; + + putc_down(TCIAC); + if (noga) { + putc_down(TCDO); + } else { + putc_down(TCDONT); + } + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * do_txbinary - handle telnet "will/won't" TRANSMIT-BINARY option + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::do_txbinary(int c) { + if (rcvbinary) { + if (option_cmd == TCWILL) { + return 0; + } + } else if (option_cmd == TCWONT) { + return 0; + } + + rcvbinary = !rcvbinary; + + putc_down(TCIAC); + if (rcvbinary) { + putc_down(TCDO); + } else { + putc_down(TCDONT); + } + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * will_notsup - handle an unsupported telnet "do/don't" option + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::will_notsup(int c) { + putc_down(TCIAC); + putc_down(TCWONT); + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * will_txbinary - handle telnet "do/don't" TRANSMIT-BINARY option + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::will_txbinary(int c) { + if (sndbinary) { + if (option_cmd == TCDO) { + return 0; + } + } else if (option_cmd == TCDONT) { + return 0; + } + + sndbinary = !sndbinary; + + putc_down(TCIAC); + if (sndbinary) { + putc_down(TCWILL); + } else { + putc_down(TCWONT); + } + putc_down((char)c); + return 0; +} + +/*------------------------------------------------------------------------ + * will_termtype - handle telnet "do/don't" TERMINAL-TYPE option + *------------------------------------------------------------------------ + */ +int FQTermTelnet::will_termtype(int c) { + if (termtype) { + if (option_cmd == TCDO) { + return 0; + } + } else if (option_cmd == TCDONT) { + return 0; + } + + termtype = !termtype; + + putc_down(TCIAC); + + if (termtype) { + putc_down(TCWILL); + } else { + putc_down(TCWONT); + } + + putc_down((char)c); + + // TODO: Do NOT use this assume! some foolish BBS not response the request + /* The client expects that once the remote application receives + terminal type information it will send control sequences for + the terminal, which cannot be sent using the NVT encoding, So + change the transfer mode to binary in both directions */ + /* set up binary data path; send WILL, DO */ + /* if (termtype) { + option_cmd = TCWILL; + do_txbinary(TOTXBINARY); + option_cmd = TCDO; + will_txbinary(TOTXBINARY); + } + */ + return 0; +} + +int FQTermTelnet::will_naws(int c) { + if (naws) { + if (option_cmd == TCDO) + return 0; + } else if (option_cmd == TCDONT) + return 0; + + + naws = !naws; + + putc_down(TCIAC); + if (naws) + putc_down(TCWILL); + else + putc_down(TCWONT); + putc_down((char)c); + + putc_down(TCIAC); + putc_down(TCSB); + putc_down(TONAWS); + putc_down((char)(short(wx) >> 8)); + putc_down((char)(short(wx)&0xff)); + putc_down((char)(short(wy) >> 8)); + putc_down((char)(short(wy)&0xff)); + putc_down(((char)TCIAC)); + putc_down((char)TCSE); + + return 0; +} + +/*------------------------------------------------------------------------ + * subopt - do option subnegotiation FSM transitions + *------------------------------------------------------------------------ + */ +int FQTermTelnet::subopt(int c) { + struct fsm_trans *pt; + int ti; + + ti = subfsm[substate][c]; + pt = &substab[ti]; + (this->*(pt->ft_action))(c); + substate = pt->ft_next; + return 0; +} + +/*------------------------------------------------------------------------ + * subtermtype - do terminal type option subnegotation + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::subtermtype(int) { + char *i; + /* have received IAC.SB.TERMTYPE.SEND */ + + putc_down(TCIAC); + putc_down(TCSB); + putc_down(TOTERMTYPE); + putc_down(TT_IS); + + //write term type string + //fputs(term, sfp); + for (i = term; (*i) != '\000'; i++) { + putc_down(*i); + } + + putc_down(TCIAC); + putc_down(TCSE); + return 0; +} + +/*------------------------------------------------------------------------ + * subend - end of an option subnegotiation; reset FSM + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::subend(int) { + substate = SS_START; + return 0; +} + +/*------------------------------------------------------------------------ + * soputc - move a character from the keyboard to the socket + * convert an character into the NVT encoding and send it + * through the socket to the server. + *------------------------------------------------------------------------ + */ + +int FQTermTelnet::soputc(int c) { + if (sndbinary) { + if (c == TCIAC) { + putc_down(TCIAC); + } + /* byte-stuff IAC */ + putc_down(c); + return 0; + } + + //c &= 0x7f; /* 7-bit ASCII only ???*/ + // Convert local special characters to NVT characters + /* TODO: // BBS don't need control signals + if (c == t_intrc || c == t_quitc) { // Interrupt + putc_down(TCIAC); + putc_down(TCIP); + } else if (c == sg_erase) { // Erase Char + putc_down(TCIAC); + putc_down(TCEC); + } else if (c == sg_kill) { // Erase Line + putc_down(TCIAC); + putc_down(TCEL); + } else if (c == t_flushc) { // Abort Output + putc_down(TCIAC); + putc_down(TCAO); + } else */ + putc_down((char)c); + + return 0; +} + +/*------------------------------------------------------------------------ + * xputc - putc to upper layer with optional file scripting + *------------------------------------------------------------------------ + */ +int FQTermTelnet::xputc_up(char ch) { + if ((long)(rsize + 1) > to_ansi.size()) { + FQ_TRACE("telnet", 0) << "Buffer to_ansi overflow."; + return - 1; + } + //to_ansi->replace(wsize, 1, &ch); + to_ansi[rsize] = u_char(ch); + rsize++; + + return 0; +} + +/*------------------------------------------------------------------------ + * xfputs - fputs with optional file scripting + *------------------------------------------------------------------------ + */ +int FQTermTelnet::xputs_up(char *str) { + /*if (scrfp) + fputs(str, scrfp);*/ + + char *i; + for (i = str; (*i) != '\000'; i++) { + xputc_up(*i); + } + return 0; +} + +/*------------------------------------------------------------------------ + * ttputc - print a single character on a Network Virtual Terminal + *------------------------------------------------------------------------ + */ +int FQTermTelnet::ttputc(int c) { + if (rcvbinary) { + xputc_up((char)c); /* print uninterpretted */ + return 0; + } + /* no data, if in SYNCH */ + /* + if (synching) + return 0; + */ + /* TODO: FQTermTelnet doesnot interpret NVT code, provide datas to upper + layer directly. So, <cr><lf> will not be replaced with <lf> + */ + xputc_up((char)c); + + return 0; +} + +/*------------------------------------------------------------------------ + * invalid state reached, aborted + *------------------------------------------------------------------------ + */ +int FQTermTelnet::tnabort(int) { + FQ_VERIFY(false); // "invalid state reached, aborted"; + // exit(-1); + return -1; +} + +bool FQTermTelnet::readyForInput() +{ + return socket->readyForInput(); +} + + + +} // namespace FQTerm + +#include "fqterm_telnet.moc" diff --git a/src/terminal/internal/fqterm_telnet.h b/src/terminal/internal/fqterm_telnet.h new file mode 100644 index 0000000..33e8c6e --- /dev/null +++ b/src/terminal/internal/fqterm_telnet.h @@ -0,0 +1,195 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_TELNET_H +#define FQTERM_TELNET_H + +#include <QAbstractSocket> +#include <QByteArray> + +namespace FQTerm { + +#ifndef u_char +#define u_char uchar +#endif + +#define NTSTATES 6 // # of Telnet Socket-Input FSM States +#define NSSTATES 3 // # of Telnet Option Subnegotiation FSM States +#define NCHRS 256 // # of valid characters + +// decleration +class FQTermTelnet; +// actionFunc is a pointer, point to a FQTermTelnet's func +typedef int(FQTermTelnet:: *ptrActionFunc)(int c); + +//fsm struct +struct fsm_trans { + u_char ft_state; // current state + short ft_char; // input character + u_char ft_next; // next state + ptrActionFunc ft_action; // action to take +}; + + +/*------------------------------------------------------------------------------ + * FQTermTelnet class definition + *------------------------------------------------------------------------------- + */ +class FQTermSocket; + +// Telnet connection, a wrapper of socket. +// It will translate raw NVT data from low level socket to ansi data, +// and then upper level application can read it. +// It also can send ascii data (0~127). +class FQTermTelnet: public QObject { + Q_OBJECT; + public: + FQTermTelnet(const QString &termtype, int rows, int numColumns, int protocolType, int hostType, const + char *sshuser = NULL, const char *sshpasswd = NULL); + ~FQTermTelnet(); + + void setProxy(int nProxyType, //0-no proxy; 1-wingate; 2-sock4; 3-socks5 + bool bAuth, // if authentation needed + const QString &strProxyHost, quint16 uProxyPort, const QString &strProxyUsr, + const QString &strProxyPwd); + void connectHost(const QString &hostname, quint16 portnumber); + + // Read ansi data. + int read(char *data, uint maxlen); + + // Write data raw data + int write(const char *data, uint len); + + void close(); // User close the connection + + int raw_len(); + int read_raw(char *data, uint maxlen); + + bool readyForInput(); + + + signals: + void readyRead(int, int); // There are datas to be read out + void TelnetState(int); // The state telnet, defined as TSXXXX in fqterm.h + void requestUserPwd(QString *user, QString *pwd, bool *isOK); + void errorMessage(QString); + + public slots: + void windowSizeChanged(int, int); + + private slots: + // Retrieve data from socket, translate NVT data to ansi data, + // then notify data ready. + void socketReadyRead(); + + void connected(); + void showError(QAbstractSocket::SocketError); + void hostFound(); + void delayCloseFinished(); + void closed(); + protected: + //init structure fsm + void init_telnet(); + void fsmbuild(); + void fsminit(u_char fsm[][NCHRS], struct fsm_trans ttab[], int nstates); + + //actions + int tcdm(int); + int recopt(int); + int no_op(int); + int do_echo(int); + int do_notsup(int); + int do_noga(int); + int do_txbinary(int); + int will_notsup(int); + int will_txbinary(int); + int will_termtype(int); + int will_naws(int); + int subopt(int); + int subtermtype(int); + int subend(int); + int soputc(int); + int ttputc(int); + int tnabort(int); + + //utility functions + int xputc_up(char); + int xputs_up(char*); + void putc_down(u_char); + +signals: + + void onSSHAuthOK(); + + private: + // Boolean Flags + char synching, doecho, sndbinary, rcvbinary; + char noga; + char naws; + char server_sent_do_naws; + u_char option_cmd; // has value WILL, WONT, DO, or DONT + + char termtype; // non-zero if received "DO TERMTYPE" + char *term; // terminal name + + /* // TODO: BBS don't need control signals + // Special keys - Terminal control characters + // need work... + static const char t_flushc=FQTERM_CTRL('S'); // Abort Output i.e:(^S) + static const char t_intrc=FQTERM_CTRL('C'); // Interrupt i.e:(^C) + static const char t_quitc=FQTERM_CTRL('\\'); // Quit i.e:(^\) + static const char sg_erase=FQTERM_CTRL('?'); // Erase a character i.e:(^?) + static const char sg_kill=FQTERM_CTRL('U'); // Kill a line i.e:(^U) + */ + + // FSM stuffs + static struct fsm_trans ttstab[]; + int ttstate; + u_char ttfsm[NTSTATES][NCHRS]; + + static struct fsm_trans substab[]; + int substate; + u_char subfsm[NSSTATES][NCHRS]; + + // socket stuffs + FQTermSocket *socket; + + //Pointers to internal buffers + // + // |-->from_socket-->process-->to_ansi-->| + // socket<---> <---> ansi decode + // |<---to_socket<--process<--from_ansi--| + // + QByteArray from_socket, to_ansi, from_ansi, to_socket; + uint rsize; // size of to_ansi buffer + uint wsize; // size of to_socket buffer + + // for test + int wx, wy; + int done_naws; + bool bConnected; + int raw_size; + int hostType_; + int protocolType_; +}; + +} // namespace FQTerm + +#endif // FQTERM_TELNET_H diff --git a/src/terminal/internal/fqterm_zmodem.cpp b/src/terminal/internal/fqterm_zmodem.cpp new file mode 100644 index 0000000..afb99c9 --- /dev/null +++ b/src/terminal/internal/fqterm_zmodem.cpp @@ -0,0 +1,3056 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <QApplication> +#include <QString> +#include <QFileDialog> +#include <QFileInfo> + +#include "fqterm.h" +#include "fqterm_config.h" +#include "fqterm_param.h" +#include "fqterm_path.h" +#include "fqterm_telnet.h" +#include "fqterm_zmodem.h" +#include "fqterm_filedialog.h" + +#ifdef FQTERM_ZMODEM_DEBUG +#include <sys/time.h> +#endif + +namespace FQTerm { + +static const uchar zeros[4] = { + 0, 0, 0, 0 +}; + +static const char hexChars[] = "0123456789abcdef"; + +static const uchar AckStr[1] = { + ACK +}; +static const uchar NakStr[1] = { + NAK +}; +static const uchar CanStr[2] = { + CAN, CAN +}; +static const uchar eotstr[1] = { + EOT +}; + + +/* + * Crc calculation stuff + */ + +/* crctab calculated by Mark G. Mendel, Network Systems Corporation */ +unsigned short crctab[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, + 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, + 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, + 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, + 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, + 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, + 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, + 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, + 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, + 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, + 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, + 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, + 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, + 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, + 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, + 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, + 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, + 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, + 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, + 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, + 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, + 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, + 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, + 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, + 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, + 0x3eb2, 0x0ed1, 0x1ef0 +}; + +/* + * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. + * NOTE: First argument must be in range 0 to 255. + * Second argument is referenced twice. + * + * Programmers may incorporate any or all code into their programs, + * giving proper credit within the source. Publication of the + * source routines is permitted so long as proper credit is given + * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, + * Omen Technology. + */ + +#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp) + +/* + * Copyright (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* First, the polynomial itself and its table of feedback terms. The */ +/* polynomial is */ +/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ +/* Note that we take it "backwards" and put the highest-order term in */ +/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ +/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ +/* the MSB being 1. */ + +/* Note that the usual hardware shift register implementation, which */ +/* is what we're using (we're merely optimizing it by doing eight-bit */ +/* chunks at a time) shifts bits into the lowest-order term. In our */ +/* implementation, that means shifting towards the right. Why do we */ +/* do it this way? Because the calculated CRC must be transmitted in */ +/* order from highest-order term to lowest-order term. UARTs transmit */ +/* characters in order from LSB to MSB. By storing the CRC this way, */ +/* we hand it to the UART in the order low-byte to high-byte; the UART */ +/* sends each low-bit to hight-bit; and the result is transmission bit */ +/* by bit from highest- to lowest-order term without requiring any bit */ +/* shuffling on our part. Reception works similarly. */ + +/* The feedback terms table consists of 256, 32-bit entries. Notes: */ +/* */ +/* The table can be generated at runtime if desired; code to do so */ +/* is shown later. It might not be obvious, but the feedback */ +/* terms simply represent the results of eight shift/xor opera- */ +/* tions for all combinations of data and CRC register values. */ +/* */ +/* The values must be right-shifted by eight bits by the "updcrc" */ +/* logic; the shift must be unsigned (bring in zeroes). On some */ +/* hardware you could probably optimize the shift in assembler by */ +/* using byte-swap instructions. */ + +unsigned long cr3tab[] = { + /* CRC polynomial 0xedb88320 */ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + +#define UPDC32(b, c) (cr3tab[((int)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF)) + +#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp) + + + +#if 1 +StateTable FQTermZmodem::RStartOps[] = { + {ZSINIT, &FQTermZmodem::GotSinit, 0, 1, RSinitWait}, + /* SINIT, wait for attn str */ + {ZFILE, &FQTermZmodem::GotFile, 0, 0, RFileName}, + /* FILE, wait for filename */ + {ZRQINIT, &FQTermZmodem::SendRinit, 0, 1, RStart}, + /* sender confused, resend */ + {ZFIN, &FQTermZmodem::GotFin, 1, 0, RFinish}, + /* sender shutting down */ + {ZNAK, &FQTermZmodem::SendRinit, 1, 0, RStart}, + /* RINIT was bad, resend */ + {ZFREECNT, &FQTermZmodem::GotFreecnt, 0, 0, RStart}, + /* sender wants free space */ + {ZCOMMAND, &FQTermZmodem::GotCommand, 0, 0, CommandData}, + /* sender wants command */ + {ZSTDERR, &FQTermZmodem::GotStderr, 0, 0, StderrData}, + /* sender wants to send msg */ + {ZRINIT, &FQTermZmodem::ZmodemTInit, 1, 1, TStart}, + {99, &FQTermZmodem::ZPF, 0, 0, RStart} + /* anything else is an error */ +}; + +StateTable FQTermZmodem::RSinitWaitOps[] = { + /* waiting for data */ + { + 99, &FQTermZmodem::ZPF, 0, 0, RSinitWait + }, +}; + +StateTable FQTermZmodem::RFileNameOps[] = { + /* waiting for file name */ + { + 99, &FQTermZmodem::ZPF, 0, 0, RFileName + }, +}; + +StateTable FQTermZmodem::RCrcOps[] = { + /* waiting for CRC */ + { + ZCRC, &FQTermZmodem::GotFileCrc, 0, 0, RFile + }, { /* sender sent it */ + ZNAK, &FQTermZmodem::ResendCrcReq, 0, 0, RCrc + }, { /* ZCRC was bad, resend */ + ZRQINIT, &FQTermZmodem::SendRinit, 1, 1, RStart + }, { /* sender confused, restart */ + ZFIN, &FQTermZmodem::GotFin, 1, 1, RFinish + }, { /* sender signing off */ + 99, &FQTermZmodem::ZPF, 0, 0, RCrc + }, +}; + + +StateTable FQTermZmodem::RFileOps[] = { + /* waiting for ZDATA */ + { + ZDATA, &FQTermZmodem::GotData, 0, 0, RData + }, { /* got it */ + ZNAK, &FQTermZmodem::ResendRpos, 0, 0, RFile + }, { /* ZRPOS was bad, resend */ + ZEOF, &FQTermZmodem::GotEof, 0, 0, RStart + }, { /* end of file */ + ZRQINIT, &FQTermZmodem::SendRinit, 1, 1, RStart + }, { /* sender confused, restart */ + ZFILE, &FQTermZmodem::ResendRpos, 0, 0, RFile + }, { /* ZRPOS was bad, resend */ + ZFIN, &FQTermZmodem::GotFin, 1, 1, RFinish + }, { /* sender signing off */ + 99, &FQTermZmodem::ZPF, 0, 0, RFile + }, +}; + +/* waiting for data, but a packet could possibly arrive due + * to error recovery or something + */ +StateTable FQTermZmodem::RDataOps[] = { + { + ZRQINIT, &FQTermZmodem::SendRinit, 1, 1, RStart + }, { /* sender confused, restart */ + ZFILE, &FQTermZmodem::GotFile, 0, 1, RFileName + }, { /* start a new file (??) */ + ZNAK, &FQTermZmodem::ResendRpos, 1, 1, RFile + }, { /* ZRPOS was bad, resend */ + ZFIN, &FQTermZmodem::GotFin, 1, 1, RFinish + }, { /* sender signing off */ + ZDATA, &FQTermZmodem::GotData, 0, 1, RData + }, { /* file data follows */ + ZEOF, &FQTermZmodem::GotEof, 1, 1, RStart + }, { /* end of file */ + 99, &FQTermZmodem::ZPF, 0, 0, RData + }, +}; + +/* here if we've sent ZFERR or ZABORT. Waiting for ZFIN */ + +StateTable FQTermZmodem::RFinishOps[] = { + + { + ZRQINIT, &FQTermZmodem::SendRinit, 1, 1, RStart + } , { /* sender confused, restart */ + ZFILE, &FQTermZmodem::GotFile, 1, 1, RFileName + }, { /* start a new file */ + ZNAK, &FQTermZmodem::GotFin, 1, 1, RFinish + }, { /* resend ZFIN */ + ZFIN, &FQTermZmodem::GotFin, 1, 1, RFinish + }, { /* sender signing off */ + 99, &FQTermZmodem::ZPF, 0, 0, RFinish + }, +}; + + +/* sent ZRQINIT, waiting for response */ +StateTable FQTermZmodem::TStartOps[] = { + { + ZRINIT, &FQTermZmodem::GotRinit, 1, 1, TStart + }, { + ZCHALLENGE, &FQTermZmodem::AnswerChallenge, 1, 0, TStart + }, { + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZNAK, &FQTermZmodem::Ignore, 0, 0, TStart + }, { + ZCOMMAND, &FQTermZmodem::GotCommand, 0, 0, CommandData + }, { + ZSTDERR, &FQTermZmodem::GotStderr, 0, 0, StderrData + }, { + 99, &FQTermZmodem::ZPF, 0, 0, TStart + }, +}; + +/* sent ZSINIT, waiting for response */ +StateTable FQTermZmodem::TInitOps[] = { + { + ZACK, &FQTermZmodem::RetDone, 1, 0, TInit + }, { + ZNAK, &FQTermZmodem::SendZSInit, 1, 0, TInit + }, { + ZRINIT, &FQTermZmodem::GotRinit, 1, 1, TInit + }, { /* redundant, but who cares */ + ZCHALLENGE, &FQTermZmodem::AnswerChallenge, 1, 0, TInit + }, { + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZCOMMAND, &FQTermZmodem::GotCommand, 0, 0, CommandData + }, { + ZSTDERR, &FQTermZmodem::GotStderr, 0, 0, StderrData + }, { + 99, &FQTermZmodem::ZPF, 0, 0, TInit + }, +}; + +/* sent ZFILE, waiting for response */ +StateTable FQTermZmodem::FileWaitOps[] = { + { + ZRPOS, &FQTermZmodem::SendFileData, 1, 1, Sending + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 0, TStart + }, { + ZCRC, &FQTermZmodem::SendFileCrc, 1, 0, FileWait + }, { + ZNAK, &FQTermZmodem::sendFilename, 1, 0, FileWait + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + }, { /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZCHALLENGE, &FQTermZmodem::AnswerChallenge, 1, 0, FileWait + }, { + ZCOMMAND, &FQTermZmodem::GotCommand, 0, 0, CommandData + }, { + ZSTDERR, &FQTermZmodem::GotStderr, 0, 0, StderrData + }, { + ZACK, &FQTermZmodem::SendFileData, 1, 0, Sending + }, { // receiver always sends ZACK back + 99, &FQTermZmodem::ZPF, 0, 0, FileWait + }, +}; + +/* sent file CRC, waiting for response */ +StateTable FQTermZmodem::CrcWaitOps[] = { + { + ZRPOS, &FQTermZmodem::SendFileData, 1, 0, Sending + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 0, FileWait + }, { + ZNAK, &FQTermZmodem::SendFileCrc, 1, 0, CrcWait + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + }, { /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZCRC, &FQTermZmodem::SendFileCrc, 0, 0, CrcWait + }, { + ZCHALLENGE, &FQTermZmodem::AnswerChallenge, 0, 0, CrcWait + }, { + ZCOMMAND, &FQTermZmodem::GotCommand, 0, 0, CommandData + }, { + ZSTDERR, &FQTermZmodem::GotStderr, 0, 0, StderrData + }, { + 99, &FQTermZmodem::ZPF, 0, 0, CrcWait + }, +}; + +/* sending data, interruptable */ +StateTable FQTermZmodem::SendingOps[] = { + { + ZACK, &FQTermZmodem::GotSendAck, 0, 0, Sending + }, { + ZRPOS, &FQTermZmodem::GotSendPos, 1, 1, Sending + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 1, FileWait + }, { + ZNAK, &FQTermZmodem::GotSendNak, 1, 1, Sending + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + },{ /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + 99, &FQTermZmodem::ZPF, 0, 0, SendWait + }, +}; + +/* sent data, need to send EOF */ +StateTable FQTermZmodem::SendDoneOps[] = { + { + ZACK, &FQTermZmodem::GotSendDoneAck, 0, 0, SendWait + }, { + ZRPOS, &FQTermZmodem::GotSendPos, 1, 1, Sending + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 1, FileWait + }, { + ZNAK, &FQTermZmodem::GotSendNak, 1, 1, Sending + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + }, { /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + 99, &FQTermZmodem::ZPF, 0, 0, SendWait + }, +}; + +/* sending data, waiting for ACK */ +StateTable FQTermZmodem::SendWaitOps[] = { + { + ZACK, &FQTermZmodem::GotSendWaitAck, 0, 0, Sending + }, { + ZRPOS, &FQTermZmodem::GotSendPos, 0, 0, SendWait + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 1, FileWait + }, { + ZNAK, &FQTermZmodem::GotSendNak, 0, 0, Sending + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + }, { /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + 99, &FQTermZmodem::ZPF, 0, 0, SendWait + }, +}; + +/* sent ZEOF, waiting for new RINIT */ +StateTable FQTermZmodem::SendEofOps[] = { + { + ZRINIT, &FQTermZmodem::SkipFile, 1, 0, TFinish + }, { /* successful completion */ + ZACK, &FQTermZmodem::Ignore, 0, 0, SendEof + }, { /* probably ACK from last packet */ + ZRPOS, &FQTermZmodem::GotSendPos, 1, 1, SendWait + }, { + ZSKIP, &FQTermZmodem::SkipFile, 1, 1, TStart + }, { + ZNAK, &FQTermZmodem::ResendEof, 1, 0, SendEof + }, { + ZRINIT, &FQTermZmodem::sendFilename, 1, 1, FileWait + }, { /* rcvr confused, retry file */ + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + 99, &FQTermZmodem::ZPF, 0, 0, SendEof + }, +}; + +StateTable FQTermZmodem::TFinishOps[] = { + { + ZFIN, &FQTermZmodem::OverAndOut, 1, 1, Done + }, { + ZNAK, &FQTermZmodem::ZmodemTFinish, 1, 1, TFinish + }, { + ZRINIT, &FQTermZmodem::ZmodemTFinish, 1, 1, TFinish + }, { + ZABORT, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + ZFERR, &FQTermZmodem::GotAbort, 1, 1, TFinish + }, { + 99, &FQTermZmodem::ZPF, 0, 0, TFinish + }, +}; + +StateTable FQTermZmodem::CommandDataOps[] = { + { + 99, &FQTermZmodem::ZPF, 0, 0, CommandData + }, +}; + +StateTable FQTermZmodem::CommandWaitOps[] = { + { + 99, &FQTermZmodem::ZPF, 0, 0, CommandWait + }, +}; + +StateTable FQTermZmodem::StderrDataOps[] = { + { + 99, &FQTermZmodem::ZPF, 0, 0, StderrData + }, +}; + +StateTable FQTermZmodem::DoneOps[] = { + { + 99, &FQTermZmodem::ZPF, 0, 0, Done + }, +}; + +StateTable *FQTermZmodem::tables[] = { + FQTermZmodem::RStartOps, FQTermZmodem::RSinitWaitOps, FQTermZmodem::RFileNameOps, + FQTermZmodem::RCrcOps, FQTermZmodem::RFileOps, FQTermZmodem::RDataOps, + FQTermZmodem::RDataOps, /* RDataErr is the same as RData */ + FQTermZmodem::RFinishOps, + + FQTermZmodem::TStartOps, FQTermZmodem::TInitOps, FQTermZmodem::FileWaitOps, + FQTermZmodem::CrcWaitOps, FQTermZmodem::SendingOps, FQTermZmodem::SendWaitOps, + FQTermZmodem::SendDoneOps, FQTermZmodem::SendEofOps, FQTermZmodem::TFinishOps, + + FQTermZmodem::CommandDataOps, FQTermZmodem::CommandWaitOps, + FQTermZmodem::StderrDataOps, FQTermZmodem::DoneOps, +}; + +const char *FQTermZmodem::hdrnames[] = { + "ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", "ZFILE", "ZSKIP", "ZNAK", "ZABORT", + "ZFIN", "ZRPOS", "ZDATA", "ZEOF", "ZFERR", "ZCRC", "ZCHALLENGE", "ZCOMPL", + "ZCAN", "ZFREECNT", "ZCOMMAND", "ZSTDERR", +}; + +#endif + +FQTermZmodem::FQTermZmodem(FQTermConfig *config, FQTermTelnet *netinterface, int type, int serverEncoding) { + + //now set network interface Telnet + + connectionType = type; + + serverEncodingID = serverEncoding; + + switch (connectionType) { + case 0: + case 1: + case 2: + telnet_ = netinterface; + break; + default: + FQ_TRACE("zmodem", 0) << "connection type unknown! Expect segmentation fault!"; + telnet_ = netinterface; + break; + } + + config_ = config; + + sending = false; + + transferstate = notransfer; + + zmodemTimer = new QTimer(this); + FQ_VERIFY(connect(zmodemTimer, SIGNAL(timeout()), this, SLOT(ZmodemTimeout()))); + +#ifdef FQTERM_ZMODEM_DEBUG + zmodemlogfile = fopen("zmodem.log", "w+"); + fprintf(zmodemlogfile, "%s", "\n================================\n"); + fclose(zmodemlogfile); +#endif + + + //init struct INFO + info.zrinitflags = CANFDX | CANOVIO | CANBRK | CANFC32; + info.zsinitflags = 0; + info.attn = NULL; + info.bufsize = 0; /* full streaming */ + info.buffer = NULL; + + zerrno = 0; + lastPullByte = 0; + + ZmodemReset(&info); + // other init function not complete + +} + +FQTermZmodem::~FQTermZmodem(){} + +int FQTermZmodem::ZmodemTInit(ZModem *info) { + int err; + int i; + + info->state = TStart; + info->Protocol = ZMODEM; + info->crc32 = 0; + info->packetCount = 0; + info->errCount = 0; + info->escCtrl = info->escHibit = info->atSign = info->escape = 0; + info->InputState = Idle; + info->canCount = info->chrCount = 0; + info->windowCount = 0; + info->filename = NULL; + info->bufsize = 0; + info->interrupt = 0; + info->waitflag = 0; + + // if( info->packetsize <= 0 ) + info->packetsize = 8096; + info->windowsize = 0; + //we won't be receiving much data, pick a reasonable buffer + // size (largest packet will do) + // + + i = info->packetsize *2; + + if (info->buffer != NULL) { + free(info->buffer); + info->buffer = NULL; + } + //since in the constructor function buffer is malloc + + if (i < 1024) { + i = 1024; + } + info->buffer = (uchar*)malloc(i); + + ZIFlush(info); + +// FQTermConfig *config = new FQTermConfig(getPath(USER_CONFIG) + "fqterm.cfg"); +// QString strPrevSave = config->getItemValue("global", "previous"); + +// if (strPrevSave.isEmpty()) { +// strFileList = QFileDialog::getOpenFileNames(0, "Choose the files", +// getPath(USER_CONFIG), "All files(*)"); +// } else { +// strFileList = QFileDialog::getOpenFileNames(0, "Choose the files", +// strPrevSave, "All files(*)"); +// } + FQTermFileDialog fileDialog(config_); + strFileList = fileDialog.getOpenNames("Choose a file to upload", ""); + if (strFileList.count() != 0) { + QStringList::Iterator itFile = strFileList.begin(); + QFileInfo fi(*itFile); + } + + this->transferstate = transferstop; + + // optional: send "rz\r" to remote end + if (DoInitRZ) { + if ((err = ZXmitStr((uchar*)"rz\r", 3, info))) { + return err; + } + } + + if ((err = ZXmitHdr(ZRQINIT, ZHEX, zeros, info))) { + // nudge receiver + return err; + } + + info->timeout = 60; + + zmodemlog("ZmodemTInit[%s]: sent ZRQINIT\n", sname(info)); + + return 0; +} + + +int FQTermZmodem::ZmodemTFile(char *file, char *rfile, uint f0, uint f1, uint f2, + uint f3, int filesRem, int bytesRem, ZModem *info) { + if (file == NULL || (info->file = fopen(file, "rb")) == NULL) { + return ZmErrCantOpen; + } + + info->fileEof = 0; + info->filename = file; + if (rfile != NULL) { + info->rfilename = rfile; + } else { + info->rfilename = strdup("noname"); + } + info->filesRem = filesRem; + info->bytesRem = bytesRem; + info->fileFlags[3] = f0; + info->fileFlags[2] = f1; + info->fileFlags[1] = f2; + info->fileFlags[0] = f3; + info->offset = info->lastOffset = 0; + info->len = info->date = info->fileType = info->mode = 0; + if (info->filename != NULL) { + struct stat buf; + if (stat(info->filename, &buf) == 0) { + info->len = buf.st_size; + info->date = buf.st_mtime; + info->fileType = 0; + info->mode = (buf.st_mode &0777) | 0100000; + } + } + + if (info->Protocol == XMODEM) { + return YSendData(info); + } + + if (info->Protocol == YMODEM) { + return YSendFilename(info); + } + + info->state = FileWait; + ZStatus(FileBegin, info->bytesRem, info->rfilename); + zmodemlog("ZmodemTFile[%s]: send ZFILE(%s)\n", sname(info), info->rfilename); + return sendFilename(info); +} + + +int FQTermZmodem::ZmodemTFinish(ZModem *info) { + int i; + if (info->Protocol == XMODEM) { + return ZmDone; + } + + if (info->Protocol == YMODEM) { + return YSendFin(info); + } + + info->state = TFinish; + if (info->buffer != NULL) { + free(info->buffer); + info->buffer = NULL; + } + zmodemlog("ZmodemTFinish[%s]: send ZFIN\n", sname(info)); + i = ZXmitHdr(ZFIN, ZHEX, zeros, info); + + return i; +} + + + +int FQTermZmodem::ZmodemAbort(ZModem *info) { + uchar canistr[] = { + CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0 + }; + info->state = Done; + ZIFlush(info); + ZOFlush(info); + + transferstate = transferstop; // transfer complete + ZmodemReset(info); //Tranfer complete, zmodem return to receive state + + zmodemlog("ZmodemAbort[%s]: send CAN\n", sname(info)); + return ZXmitStr(canistr, sizeof(canistr), info); +} + + +int FQTermZmodem::ZmodemRInit(ZModem *info) { + info->packetCount = 0; + info->offset = 0; + info->errCount = 0; + info->escCtrl = info->escHibit = info->atSign = info->escape = 0; + info->InputState = Idle; + info->canCount = info->chrCount = 0; + info->filename = NULL; + info->interrupt = 0; + info->waitflag = 0; + info->attn = NULL; + info->file = NULL; + + if (info->buffer != NULL) { + free(info->buffer); + info->buffer = NULL; + } + + info->buffer = (uchar*)malloc(1024); + + info->state = RStart; + info->timeoutCount = 0; + + ZIFlush(info); + + /* Don't send ZRINIT right away, there might be a ZRQINIT in + * the input buffer. Instead, set timeout to zero and return. + * This will allow ZmodemRcv() to check the input stream first. + * If nothing found, a ZRINIT will be sent immediately. + */ + info->timeout = 0; + + zmodemlog("ZmodemRInit[%s]: flush input, new state = RStart\n", sname(info)); + + return 0; +} + +int FQTermZmodem::ZmodemRcv(uchar *str, int len, ZModem *info, + int &consumed_bytes) { + register uchar c; + int err; + + zmodemlog("zmodemRcv called"); + + info->rcvlen = len; + + while (--info->rcvlen >= 0) { + c = *str++; + + if (c == CAN) { + if (++info->canCount >= 5) { + ZStatus(RmtCancel, 0, NULL); + consumed_bytes = len - info->rcvlen - 1; + return ZmErrCancel; + } + } else { + info->canCount = 0; + } + + if (info->InputState == Ysend) { + if ((err = YsendChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + } else if (info->InputState == Yrcv) { + if ((err = YrcvChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + } else if (c != XON && c != XOFF) { + /* now look at what we have */ + switch (info->InputState) { + case Idle: + if ((err = IdleChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + break; + case Inhdr: + if ((err = HdrChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + break; + case Indata: + if ((err = DataChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + break; + case Finish: + if ((err = FinishChar(c, info))) { + consumed_bytes = len - info->rcvlen - 1; + return err; + } + break; + default: + break; + } + } + } + + consumed_bytes = len - info->rcvlen - 1; + return 0; +} + + + + +int FQTermZmodem::ZmodemTimeout( /*ZModem *info*/) { + /* timed out while waiting for input */ + + ++info.timeoutCount; + + zmodemlog("timeout %d [%s]\n", info.timeoutCount, sname(&info)); + + switch (info.state) { + /* receive */ + case RStart: + /* waiting for INIT frame from other end */ + if (info.timeoutCount > 4) { + return YmodemRInit(&info); + } + + case RSinitWait: + case RFileName: + if (info.timeout > 0) { + ZStatus(SndTimeout, info.timeoutCount, NULL); + } + if (info.timeoutCount > 4) { + return ZmErrRcvTo; + } + info.state = RStart; + return SendRinit(&info); + + case RCrc: + case RFile: + case RData: + ZStatus(SndTimeout, info.timeoutCount, NULL); + if (info.timeoutCount > 2) { + info.timeoutCount = 0; + info.state = RStart; + return SendRinit(&info); + } + return info.state == RCrc ? ResendCrcReq(&info): ResendRpos(&info); + + case RFinish: + ZStatus(SndTimeout, info.timeoutCount, NULL); + return ZmDone; + + case YRStart: + case YRDataWait: + case YRData: + case YREOF: + return YrcvTimeout(&info); + + /* transmit */ + case TStart: + /* waiting for INIT frame from other end */ + case TInit: + /* sent INIT, waiting for ZACK */ + case FileWait: + /* sent file header, waiting for ZRPOS */ + case CrcWait: + /* sent file crc, waiting for ZRPOS */ + case SendWait: + /* waiting for ZACK */ + case SendEof: + /* sent EOF, waiting for ZACK */ + case TFinish: + /* sent ZFIN, waiting for ZFIN */ + case YTStart: + case YTFile: + case YTDataWait: + case YTData: + case YTEOF: + case YTFin: + ZStatus(RcvTimeout, 0, NULL); + return ZmErrRcvTo; + + case Sending: + /* sending data subpackets, ready for int */ + return SendMoreFileData(&info); + + /* general */ + case CommandData: + /* waiting for command data */ + case StderrData: + /* waiting for stderr data */ + return ZmErrSndTo; + case CommandWait: + /* waiting for command to execute */ + return ZmErrCmdTo; + case Done: + return ZmDone; + default: + return 0; + } +} + + + +int FQTermZmodem::ZmodemAttention(ZModem *info) { + /* attention received from remote end */ + if (info->state == Sending) { + ZOFlush(info); + info->interrupt = 1; + } + return 0; +} + +int FQTermZmodem::YmodemTInit(ZModem *info) { + info->state = YTStart; + info->Protocol = YMODEM; + info->errCount = 0; + info->InputState = Ysend; + info->canCount = info->chrCount = 0; + info->windowCount = 0; + info->filename = NULL; + + if (info->packetsize != 1024) { + info->packetsize = 128; + } + + info->buffer = (uchar*)malloc(1024); + + ZIFlush(info); + ZFlowControl(0, info); + + info->timeout = 60; + + return 0; +} + + +int FQTermZmodem::XmodemTInit(ZModem *info) { + (void)YmodemTInit(info); + info->Protocol = XMODEM; + return 0; +} + + +int FQTermZmodem::YmodemRInit(ZModem *info) { + info->errCount = 0; + info->InputState = Yrcv; + info->canCount = info->chrCount = 0; + info->noiseCount = 0; + info->filename = NULL; + info->file = NULL; + + if (info->buffer == NULL) { + info->buffer = (uchar*)malloc(1024); + } + + info->state = YRStart; + info->packetCount = -1; + info->timeoutCount = 0; + info->timeout = 10; + info->offset = 0; + + ZIFlush(info); + + return ZXmitStr((uchar*)"C", 1, info); +} + + +int FQTermZmodem::XmodemRInit(ZModem *info) { +#if 0 + int err; + + state = Start; + protocol = prot; + ymodem = prot == Ymodem || prot == YmodemG; + + if (ymodem) { + strcpy(xmDefPath, file); + } else { + strcpy(xmFilename, file); + } + + eotCount = errorCount = errorCount2 = 0; + + if (err = XmodemRStart()) { + return err; + } + + state = Init; + packetId = ymodem ? 255 : 0; + packetCount = 0; + + pktHdrLen = protocol == Xmodem ? 3 : 4; +#endif + return 0; +} + + +ulong FQTermZmodem::FileCrc(char *name) { + ulong crc; + FILE *ifile = fopen(name, "rb"); + int i; + + if (ifile == NULL) + /* shouldn't happen, since we did access(2) */ { + return 0; + } + + crc = 0xffffffff; + + while ((i = fgetc(ifile)) != EOF) { + crc = UPDC32(i, crc); + } + + fclose(ifile); + return ~crc; +} + + + +const char *FQTermZmodem::sname(ZModem *info) { + return sname2(info->state); +} + +const char *FQTermZmodem::sname2(ZMState state) { + const char *names[] = { + "RStart", "RSinitWait", "RFileName", "RCrc", "RFile", "RData", "RDataErr", + "RFinish", "TStart", "TInit", "FileWait", "CrcWait", "Sending", "SendWait", + "SendDone", "SendEof", "TFinish", "CommandData", "CommandWait", "StderrData", + "Done", "YTStart", "YTFile", "YTDataWait", "YTData", "YTEOF", "YTFin", + "YRStart", "YRDataWait", "YRData", "YREOF" + }; + + return names[(int)state]; + +} + + +int FQTermZmodem::ZXmitChr(uchar c, ZModem *info) { + // to be completed + return 0; +} + +int FQTermZmodem::ZXmitStr(const uchar *str, int len, ZModem *info) { + //to be completed + telnet_->write((const char*)str, (uint)len); + return 0; +} + +void FQTermZmodem::ZIFlush(ZModem *info) { + //to be completed +} + +void FQTermZmodem::ZOFlush(ZModem *info) { + //to be completed +} + +int FQTermZmodem::ZAttn(ZModem *info) { + //to be completed + return 0; +} + +void FQTermZmodem::ZStatus(int type, int value, const char *status) { + emit ZmodemState(type, value, status); + switch (type) { + case RcvByteCount: + FQ_TRACE("zmodem", 5) << value << " bytes received."; + break; + case SndByteCount: + FQ_TRACE("zmodem", 5) << value << " bytes sent."; + break; + case RcvTimeout: + /* receiver did not respond, aborting */ + FQ_TRACE("zmodem", 0) << "Zmodem time out!"; + break; + case SndTimeout: + /* value is # of consecutive send timeouts */ + FQ_TRACE("zmodem", 0) << "Zmodem time out after trying " + << value << " times"; + break; + case RmtCancel: + /* remote end has cancelled */ + FQ_TRACE("zmodem", 1) << "Zmodem canceled by remote peer"; + break; + case ProtocolErr: + /* protocol error has occurred, val=hdr */ + FQ_TRACE("zmodem", 0) << "Unhandled header " << value + << " at state " << status; + break; + case RemoteMessage: + /* message from remote end */ + FQ_TRACE("zmodem", 0) << "Message from remote peer: " << status; + break; + case DataErr: + /* data error, val=error count */ + FQ_TRACE("zmodem", 0) << "Data errors " << value; + break; + case FileErr: + /* error writing file, val=errno */ + FQ_TRACE("zmodem", 0) << "Falied to write file."; + break; + case FileBegin: + /* file transfer begins, str=name */ + FQ_TRACE("zmodem", 0) << "Starting file: " << status; + break; + case FileEnd: + /* file transfer ends, str=name */ + FQ_TRACE("zmodem", 0) << "Finishing file: " << status; + break; + case FileSkip: + /* file being skipped, str=name */ + FQ_TRACE("zmodem", 0) << "Skipping file: " << status; + break; + } +} + +FILE *FQTermZmodem::ZOpenFile(char *name, ulong crc, ZModem *info) { + //to be complete + FILE *rval; + int apnd = 0; + QString strText; + strText = encoding2unicode(name, serverEncodingID); + QString str = FQTermPref::getInstance()->zmodemDir_ + strText; + + QString zpath = QFileInfo(FQTermPref::getInstance()->zmodemDir_).absoluteFilePath(); + QString path = QFileInfo(str).absoluteFilePath(); + + lastDownloadedFilename_ = path; + + if (path.startsWith(zpath)) { + // Ensure that file is only saved under zmodemDir_. + // TODO: lazy, should use bbs2unicode + rval = fopen(str.toLocal8Bit(), apnd ? "ab" : "wb"); + } else { + rval = NULL; + } + + if (rval == NULL) { + perror(name); + } + + return rval; +} + +int FQTermZmodem::ZXmitHdr(int type, int format, const uchar data[4], ZModem *info) { + if (format == ZBIN && info->crc32) { + format = ZBIN32; + } + + switch (format) { + case ZHEX: + return ZXmitHdrHex(type, data, info); + + case ZBIN: + return ZXmitHdrBin(type, data, info); + + case ZBIN32: + return ZXmitHdrBin32(type, data, info); + + default: + return 0; + } +} + + +int FQTermZmodem::ZXmitHdrHex(int type, const uchar data[4], ZModem *info) { + uchar buffer[128]; + uchar *ptr = buffer; + uint crc; + int i; + + zmodemlog("sending %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type], + data[0], data[1], data[2], data[3], ZDec4(data)); + + *ptr++ = ZPAD; + *ptr++ = ZPAD; + *ptr++ = ZDLE; + *ptr++ = ZHEX; + + ptr = putHex(ptr, type); + crc = updcrc(type, 0); + for (i = 4; --i >= 0; ++data) { + ptr = putHex(ptr, *data); + crc = updcrc(*data, crc); + } + crc = updcrc(0, crc); + crc = updcrc(0, crc); + ptr = putHex(ptr, (crc >> 8) &0xff); + ptr = putHex(ptr, crc &0xff); + *ptr++ = '\r'; + *ptr++ = '\n'; + if (type != ZACK && type != ZFIN) { + *ptr++ = XON; + } + + return ZXmitStr(buffer, ptr - buffer, info); +} + + +int FQTermZmodem::ZXmitHdrBin(int type, const uchar data[4], ZModem *info) { + uchar buffer[128]; + uchar *ptr = buffer; + uint crc; + int len; + + zmodemlog("sending %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type], + data[0], data[1], data[2], data[3], ZDec4(data)); + + *ptr++ = ZPAD; + *ptr++ = ZDLE; + *ptr++ = ZBIN; + + ptr = putZdle(ptr, type, info); + crc = updcrc(type, 0); + for (len = 4; --len >= 0; ++data) { + ptr = putZdle(ptr, *data, info); + crc = updcrc(*data, crc); + } + crc = updcrc(0, crc); + crc = updcrc(0, crc); + ptr = putZdle(ptr, (crc >> 8) &0xff, info); + ptr = putZdle(ptr, crc &0xff, info); + + len = ptr - buffer; + return ZXmitStr(buffer, len, info); +} + +int FQTermZmodem::ZXmitHdrBin32(int type, const uchar data[4], ZModem *info) { + uchar buffer[128]; + uchar *ptr = buffer; + ulong crc; + int len; + + zmodemlog("sending %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type], + data[0], data[1], data[2], data[3], ZDec4(data)); + + *ptr++ = ZPAD; + *ptr++ = ZDLE; + *ptr++ = ZBIN32; + ptr = putZdle(ptr, type, info); + crc = UPDC32(type, 0xffffffffL); + for (len = 4; --len >= 0; ++data) { + ptr = putZdle(ptr, *data, info); + crc = UPDC32(*data, crc); + } + crc = ~crc; + for (len = 4; --len >= 0; crc >>= 8) { + ptr = putZdle(ptr, crc &0xff, info); + } + + len = ptr - buffer; + return ZXmitStr(buffer, len, info); +} + + +uchar *FQTermZmodem::putZdle(uchar *ptr, uchar c, ZModem *info) { + uchar c2 = c &0177; + + if (c == ZDLE || c2 == 020 || c2 == 021 || c2 == 023 || c2 == 0177 || (c2 == + 015 && connectionType == 0 /*&& info->atSign*/) || +#ifdef COMMENT + c2 == 035 || (c2 == '~' && info->lastCR) || +#endif /* COMMENT */ + c2 == 035 || (c2 < 040 && info->escCtrl)) { + *ptr++ = ZDLE; + if (c == 0177) { + *ptr = ZRUB0; + } else if (c == 0377) { + *ptr = ZRUB1; + } else { + *ptr = c ^ 0100; + } + } else { + *ptr = c; + } + + info->atSign = c2 == '@'; + info->lastCR = c2 == '\r'; + + return ++ptr; +} + + +uchar *FQTermZmodem::ZEnc4(ulong n) { + static uchar buf[4]; + buf[0] = n &0xff; + n >>= 8; + buf[1] = n &0xff; + n >>= 8; + buf[2] = n &0xff; + n >>= 8; + buf[3] = n &0xff; + return buf; +} + +ulong FQTermZmodem::ZDec4(const uchar buf[4]) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); +} + + + + +int FQTermZmodem::YrcvChar(char c, register ZModem *info) { + int err; + + if (info->canCount >= 2) { + ZStatus(RmtCancel, 0, NULL); + return ZmErrCancel; + } + + switch (info->state) { + case YREOF: + if (c == EOT) { + ZCloseFile(info); + info->file = NULL; + ZStatus(FileEnd, 0, info->filename); + if (info->filename != NULL) { + free(info->filename); + } + if ((err = acceptPacket(info)) != 0) { + return err; + } + info->packetCount = -1; + info->offset = 0; + info->state = YRStart; + return ZXmitStr((uchar*)"C", 1, info); + } + /* else, drop through */ + + case YRStart: + case YRDataWait: + switch (c) { + case SOH: + case STX: + info->pktLen = c == SOH ? (128+4): (1024+4); + info->state = YRData; + info->chrCount = 0; + info->timeout = 1; + info->noiseCount = 0; + info->crc = 0; + break; + + case EOT: + /* ignore first EOT to protect against false eot */ + info->state = YREOF; + return rejectPacket(info); + + default: + if (++info->noiseCount > 135) { + return ZXmitStr(NakStr, 1, info); + } + break; + } + break; + + case YRData: + info->buffer[info->chrCount++] = c; + if (info->chrCount >= info->pktLen) { + return ProcessPacket(info); + } + break; + + default: + break; + } + + return 0; +} + +int FQTermZmodem::YrcvTimeout(ZModem *info) { + switch (info->state) { + case YRStart: + if (info->timeoutCount >= 10) { + (void)ZXmitStr(CanStr, 2, info); + return ZmErrInitTo; + } + return ZXmitStr((uchar*)"C", 1, info); + + case YRDataWait: + case YREOF: + case YRData: + if (info->timeoutCount >= 10) { + (void)ZXmitStr(CanStr, 2, info); + return ZmErrRcvTo; + } + return ZXmitStr(NakStr, 1, info); + default: + return 0; + } +} + + +void FQTermZmodem::ZIdleStr(uchar *buffer, int len, ZModem *info) { + //to be completed +} + + +int FQTermZmodem::FinishChar(char c, register ZModem *info) { + if (c == 'O') { + if (++info->chrCount >= 2) { + return ZmDone; + } + } else { + info->chrCount = 0; + } + return 0; +} + + + +int FQTermZmodem::DataChar(uchar c, register ZModem *info) { + if (c == ZDLE) { + info->escape = 1; + return 0; + } + + if (info->escape) { + info->escape = 0; + switch (c) { + case ZCRCE: + case ZCRCG: + case ZCRCQ: + case ZCRCW: + info->PacketType = c; + info->crcCount = (info->DataType == ZBIN32) ? 4 : 2; + if (info->DataType == ZBIN) { + info->crc = updcrc(c, info->crc); + } else { + info->crc = UPDC32(c, info->crc); + } + return 0; + case ZRUB0: + c = 0177; + break; + case ZRUB1: + c = 0377; + break; + default: + c ^= 0100; + break; + } + } + if (connectionType == 0) { + if (lastPullByte == 0x0d && c == 0x00) { + lastPullByte = 0; + return 0; + } else if (lastPullByte == 0xff && c == 0xff) { + lastPullByte = 0; + return 0; + } + } + lastPullByte = c; + + switch (info->DataType) { + /* TODO: are hex data packets ever used? */ + case ZBIN: + info->crc = updcrc(c, info->crc); + if (info->crcCount == 0) { + info->buffer[info->chrCount++] = c; + } else if (--info->crcCount == 0) { + return ZDataReceived(info, (info->crc &0xffff) == 0); + } + break; + + + case ZBIN32: + info->crc = UPDC32(c, info->crc); + if (info->crcCount == 0) { + info->buffer[info->chrCount++] = c; + } else if (--info->crcCount == 0) { + return ZDataReceived(info, info->crc == 0xdebb20e3); + } + break; + } + return 0; +} + + +int FQTermZmodem::HdrChar(uchar c, register ZModem *info) { + int i; + int crc = 0; + + if (c == ZDLE) { + info->escape = 1; + return 0; + } + + if (info->escape) { + info->escape = 0; + switch (c) { + case ZRUB0: + c = 0177; + break; + case ZRUB1: + c = 0377; + break; + default: + c ^= 0100; + break; + } + } + + if (info->chrCount == 0) { + /* waiting for format */ + switch (c) { + case ZHEX: + case ZBIN: + case ZBIN32: + info->DataType = c; + info->chrCount = 1; + info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL; + memset(info->hdrData, 0, sizeof(info->hdrData)); + break; + default: + info->InputState = Idle; + info->chrCount = 0; + return ZXmitHdrHex(ZNAK, zeros, info); + } + return 0; + } + + + switch (info->DataType) { + /* hex header is 14 hex digits, cr, lf. Optional xon is ignored */ + case ZHEX: + if (info->chrCount <= 14 && !isxdigit(c)) { + info->InputState = Idle; + info->chrCount = 0; + return ZXmitHdrHex(ZNAK, zeros, info); + } + + if (info->chrCount <= 14) { + i = (info->chrCount - 1) / 2; + info->hdrData[i] = rcvHex(info->hdrData[i], c); + } + + if (info->chrCount == 16) { + crc = 0; + for (i = 0; i < 7; ++i) { + crc = updcrc(info->hdrData[i], crc); + } + info->InputState = Idle; + info->chrCount = 0; + if ((crc &0xffff) != 0) { + return ZXmitHdrHex(ZNAK, zeros, info); + } else { + return ZProtocol(info); + } + } + else { + ++info->chrCount; + } + break; + + + case ZBIN: + /* binary header is type, 4 bytes data, 2 bytes CRC */ + info->hdrData[info->chrCount - 1] = c; + info->crc = updcrc(c, info->crc); + if (++info->chrCount > 7) { + info->InputState = Idle; + info->chrCount = 0; + if ((crc &0xffff) != 0) { + return ZXmitHdrHex(ZNAK, zeros, info); + } else { + return ZProtocol(info); + } + } + break; + + + case ZBIN32: + /* binary32 header is type, 4 bytes data, 4 bytes CRC */ + info->hdrData[info->chrCount - 1] = c; + info->crc = UPDC32(c, info->crc); + if (++info->chrCount > 9) { + info->InputState = Idle; + info->chrCount = 0; + if (info->crc != 0xdebb20e3) + /* see note below */ { + return ZXmitHdrHex(ZNAK, zeros, info); + } else { + return ZProtocol(info); + } + } + break; + } + return 0; +} + + + + +int FQTermZmodem::IdleChar(uchar c, register ZModem *info) { + if (info->chrCount == 0) { + if (c == ZPAD) { + ++info->chrCount; + } else if (info->state == Sending && ++info->noiseCount > MaxNoise) { + info->waitflag = 1; + } else if (info->state == TStart && (c == 'C' || c == 'G' || c == NAK)) { + /* switch to ymodem */ + info->state = YTStart; + info->InputState = Ysend; + info->Protocol = YMODEM; + return YsendChar(c, info); + } else { + ZIdleStr(&c, 1, info); + } + } + + else { + switch (c) { + case ZPAD: + ++info->chrCount; + break; + case ZDLE: + info->InputState = Inhdr; + info->chrCount = 0; + break; + default: + while (--info->chrCount >= 0) { + ZIdleStr((uchar*)"*", 1, info); + } + info->chrCount = 0; + break; + } + } + return 0; +} + + + +int FQTermZmodem::YsendChar(char c, ZModem *info) { + int err; + + if (info->canCount >= 2) { + ZStatus(RmtCancel, 0, NULL); + return ZmErrCancel; + } + + switch (info->state) { + case YTStart: + /* wait for 'G', 'C' or NAK */ + switch (c) { + case 'G': + /* streaming YModem */ + case 'C': + /* CRC YModem */ + case NAK: + /* checksum YModem */ + info->PacketType = c; + return ZmDone; + default: + return 0; + } + + case YTFile: + /* sent filename, waiting for ACK or NAK */ + switch (c) { + case NAK: + /* resend */ + case 'C': + case 'G': + ZStatus(DataErr, ++info->errCount, NULL); + return YSendFilename(info); + case ACK: + info->state = YTDataWait; + default: + return 0; + } + + case YTDataWait: + /* sent filename, waiting for G,C or NAK */ + switch (c) { + case NAK: + case 'C': + case 'G': + info->chrCount = 0; + if (info->PacketType == 'G') { + /* send it all at once */ + while (info->state == YTData) { + if ((err = YSendData(info))) { + return err; + } + } + return 0; + } else { + return YSendData(info); + } + break; + default: + return 0; + } + + case YTData: + /* sent data, waiting for ACK or NAK */ + switch (c) { + case 'C': + case 'G': + /* protocol failure, resend filename */ + if (info->Protocol == YMODEM) { + ZStatus(DataErr, ++info->errCount, NULL); + info->state = YTFile; + rewind(info->file); + return YSendFilename(info); + } + /* else XModem, treat it like a NAK */ + case NAK: + ZStatus(DataErr, ++info->errCount, NULL); + return YXmitData(info->buffer + info->bufp, info->ylen, info); + case ACK: + info->offset += info->ylen; + info->bufp += info->ylen; + info->chrCount -= info->ylen; + ZStatus(SndByteCount, info->offset, NULL); + return YSendData(info); + default: + return 0; + } + + case YTEOF: + /* sent EOF, waiting for ACK or NAK */ + switch (c) { + case NAK: + return ZXmitStr(eotstr, 1, info); + case ACK: + info->state = info->Protocol == YMODEM ? YTStart : Done; + return ZmDone; + default: + return 0; + } + + case YTFin: + /* sent Fin, waiting for ACK or NAK */ + switch (c) { + case NAK: + return YSendFin(info); + case ACK: + return ZmDone; + default: + return 0; + } + default: + return 0; + } +} + + + +int FQTermZmodem::ZProtocol(register ZModem *info) { + register StateTable *table; + + zmodemlog("received %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", + hdrnames[info->hdrData[0]], info->hdrData[1], info->hdrData[2], + info->hdrData[3], info->hdrData[4], ZDec4(info->hdrData + 1)); + + /* Flags are sent in F3 F2 F1 F0 order. Data is sent in P0 P1 P2 P3 */ + + info->timeoutCount = 0; + info->noiseCount = 0; + + // zmodemTimer->start(info->timeout*1000); + + table = tables[(int)info->state]; + while (table->type != 99 && table->type != info->hdrData[0]) { + ++table; + } + + zmodemlog(" state %s => %s, iflush=%d, oflush=%d, call %x\n", sname(info), + sname2(table->newstate), table->IFlush, table->OFlush, table->func); + + info->state = table->newstate; + + if (table->IFlush) { + info->rcvlen = 0; + ZIFlush(info); + } + if (table->OFlush) { + ZOFlush(info); + } + return (this->*(table->func))(info); +} + + +int FQTermZmodem::ZDataReceived(register ZModem *info, int crcGood) { + switch (info->state) { + case RSinitWait: + return GotSinitData(info, crcGood); + case RFileName: + return GotFileName(info, crcGood); + case RData: + return GotFileData(info, crcGood); + case CommandData: + return GotCommandData(info, crcGood); + case StderrData: + return GotStderrData(info, crcGood); + default: + return ZPF(info); + } +} + + +int FQTermZmodem::ZPF(ZModem *info) { + info->waitflag = 1; /* pause any in-progress transmission */ + ZStatus(ProtocolErr, info->hdrData[0], sname(info)); + return 0; +} + + +int FQTermZmodem::Ignore(ZModem *info) { + return 0; +} + + +int FQTermZmodem::AnswerChallenge(register ZModem *info) { + return ZXmitHdrHex(ZACK, info->hdrData + 1, info); +} + + +int FQTermZmodem::GotAbort(register ZModem *info) { + ZStatus(RmtCancel, 0, NULL); + return ZXmitHdrHex(ZFIN, zeros, info); +} + + +int FQTermZmodem::GotCancel(ZModem *info) { + return ZmErrCancel; +} + + +int FQTermZmodem::GotCommand(ZModem *info) { + uchar rbuf[4]; + /* TODO: add command capability */ + + + /// EPERM not defined???????? + + rbuf[0] = EPERM; + rbuf[1] = rbuf[2] = rbuf[3] = 0; + return ZXmitHdrHex(ZCOMPL, rbuf, info); +} + +int FQTermZmodem::GotStderr(register ZModem *info) { + info->InputState = Indata; + info->chrCount = 0; + return 0; +} + + +int FQTermZmodem::RetDone(ZModem *info) { + + + return ZmDone; +} + + +int FQTermZmodem::GotCommandData(register ZModem *info, int crcGood) { + /* TODO */ + return 0; +} + + +int FQTermZmodem::GotStderrData(register ZModem *info, int crcGood) { + info->buffer[info->chrCount] = '\0'; + ZStatus(RemoteMessage, info->chrCount, (char*)info->buffer); + return 0; +} + + +int FQTermZmodem::GotFileName(ZModem *info, int crcGood) { + info->InputState = Idle; + info->chrCount = 0; + + if (!crcGood) { + zmodemlog("GotFileName[%s]: bad crc, send ZNAK\n", sname(info)); + info->state = RStart; + return ZXmitHdrHex(ZNAK, zeros, info); + } + + parseFileName(info, (char*)info->buffer); + + if ((info->f1 &ZMMASK) == ZMCRC) { + info->state = RCrc; + return ZXmitHdrHex(ZCRC, zeros, info); + } + + zmodemlog("GotFileName[%s]: good crc, call requestFile\n", sname(info)); + info->state = RFile; + return requestFile(info, 0); +} + + +int FQTermZmodem::ResendCrcReq(ZModem *info) { + zmodemlog("ResendCrcReq[%s]: send ZCRC\n", sname(info)); + return ZXmitHdrHex(ZCRC, zeros, info); +} + + +int FQTermZmodem::GotSinitData(ZModem *info, int crcGood) { + info->InputState = Idle; + info->chrCount = 0; + info->state = RStart; + + zmodemlog("GotSinitData[%s]: crcGood=%d\n", sname(info), crcGood); + + if (!crcGood) { + return ZXmitHdrHex(ZNAK, zeros, info); + } + + if (info->attn != NULL) { + free(info->attn); + } + info->attn = NULL; + if (info->buffer[0] != '\0') { + info->attn = strdup((char*)info->buffer); + } + return ZXmitHdrHex(ZACK, ZEnc4(SerialNo), info); +} + + +int FQTermZmodem::ResendRpos(ZModem *info) { + zmodemlog("ResendRpos[%s]: send ZRPOS(%ld)\n", sname(info), info->offset); + return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info); +} + + +int FQTermZmodem::GotFileData(ZModem *info, int crcGood) { + /* OK, now what? Fushing the buffers and executing the + * attn sequence has likely chopped off the input stream + * mid-packet. Now we switch to idle mode and treat all + * incoming stuff like noise until we get a new valid + * packet. + */ + + if (!crcGood) { + /* oh bugger, an error. */ + zmodemlog("GotFileData[%s]: bad crc, send ZRPOS(%ld), new state = RFile\n", + sname(info), info->offset); + ZStatus(DataErr, ++info->errCount, NULL); + if (info->errCount > MaxErrs) { + ZmodemAbort(info); + return ZmDataErr; + } else { + info->state = RFile; + info->InputState = Idle; + info->chrCount = 0; + return fileError(info, ZRPOS, info->offset); + } + } + + if (ZWriteFile(info->buffer, info->chrCount, info->file, info)) { + /* RED ALERT! Could not write the file. */ + ZStatus(FileErr, zerrno, NULL); + info->state = RFinish; + info->InputState = Idle; + info->chrCount = 0; + return fileError(info, ZFERR, zerrno); + } + + zmodemlog("GotFileData[%s]: %ld.%d,", sname(info), info->offset, + info->chrCount); + info->offset += info->chrCount; + ZStatus(RcvByteCount, info->offset, NULL); + + /* if this was the last data subpacket, leave data mode */ + if (info->PacketType == ZCRCE || info->PacketType == ZCRCW) { + zmodemlog(" ZCRCE|ZCRCW, new state RFile"); + info->state = RFile; + info->InputState = Idle; + info->chrCount = 0; + } else { + zmodemlog(" call dataSetup"); + (void)dataSetup(info); + } + + if (info->PacketType == ZCRCQ || info->PacketType == ZCRCW) { + zmodemlog(", send ZACK\n"); + return ZXmitHdrHex(ZACK, ZEnc4(info->offset), info); + } else { + zmodemlog("\n"); + } + + return 0; +} + +int FQTermZmodem::SendRinit(ZModem *info) { + uchar dbuf[4]; + + + +#ifdef COMMENT + if (info->timeoutCount >= 5) + /* TODO: switch to Ymodem */ +#endif /* COMMENT */ + { + transferstate = transferstart; + } + //transfer would be active, it must be set to false when transfer complete or abort + zmodemlog("SendRinit[%s]: send ZRINIT\n", sname(info)); + + info->timeout = ResponseTime; + dbuf[0] = info->bufsize &0xff; + dbuf[1] = (info->bufsize >> 8) &0xff; + dbuf[2] = 0; + dbuf[3] = info->zrinitflags; + return ZXmitHdrHex(ZRINIT, dbuf, info); +} + + +int FQTermZmodem::SendMoreFileData(ZModem *info) { + int type; + int qfull = 0; + int err; + int len; /* max # chars to send this packet */ + long pending; /* # of characters sent but not acknowledged */ + + /* ZCRCE: CRC next, frame ends, header follows + * ZCRCG: CRC next, frame continues nonstop + * ZCRCQ: CRC next, send ZACK, frame continues nonstop + * ZCRCW: CRC next, send ZACK, frame ends, header follows + */ + + if (info->interrupt) { + /* Bugger, receiver sent an interrupt. Enter a wait state + * and see what they want. Next header *should* be ZRPOS. + */ + info->state = SendWait; + info->timeout = 60; + return 0; + } + + /* Find out how many bytes we can transfer in the next packet */ + + len = info->packetsize; + + pending = info->offset - info->lastOffset; + + if (info->windowsize != 0 && info->windowsize - pending <= len) { + len = info->windowsize - pending; + qfull = 1; + } + if (info->bufsize != 0 && info->bufsize - pending <= len) { + len = info->bufsize - pending; + qfull = 1; + } + + if (len == 0) { + /* window still full, keep waiting */ + info->state = SendWait; + info->timeout = 60; + return 0; + } + + + /* OK, we can safely transmit 'len' bytes of data. Start reading + * file until buffer is full. + */ + + len -= 10; /* Pre-deduct 10 bytes for trailing CRC */ + + + /* find out what kind of packet to send */ + if (info->waitflag) { + type = ZCRCW; + info->waitflag = 0; + } +#ifdef COMMENT + else if (info->fileEof) { + type = ZCRCE; + } +#endif /* COMMENT */ + else if (qfull) { + type = ZCRCW; + } else { + switch (info->Streaming) { + case Full: + case Segmented: + type = ZCRCG; + break; + + case StrWindow: + if ((info->windowCount += len) < info->windowsize / 4) { + type = ZCRCG; + } else { + type = ZCRCQ; + info->windowCount = 0; + } + break; + + default: + case SlidingWindow: + type = ZCRCQ; + break; + } + } + + { + int crc32 = info->crc32; + int c = 0, c2, atSign = 0; + ulong crc; + uchar *ptr = info->buffer; + + crc = crc32 ? 0xffffffff : 0; + + /* read characters from file and put into buffer until buffer is + * full or file is exhausted + */ + + + while (len > 0 && (c = getc(info->file)) != EOF) { + if (!crc32) { + crc = updcrc(c, crc); + } else { + crc = UPDC32(c, crc); + } + + /* zmodem protocol requires that CAN(ZDLE), DLE, XON, XOFF and + * a CR following '@' be escaped. In addition, I escape '^]' + * to protect telnet, "<CR>~." to protect rlogin, and ESC for good + * measure. + */ + c2 = c &0177; + if (c == ZDLE || c2 == 020 || c2 == 021 || c2 == 023 || c2 == 0177 || c2 == + '\r' || c2 == '\n' || c2 == 033 || c2 == 035 || (c2 < 040 && info->escCtrl)) { + *ptr++ = ZDLE; + if (c == 0177) { + *ptr = ZRUB0; + } else if (c == 0377) { + *ptr = ZRUB1; + } else { + *ptr = c ^ 0100; + } + len -= 2; + } else { + *ptr = c; + --len; + } + ++ptr; + + atSign = c2 == '@'; + ++info->offset; + } + + /* if we've reached file end, a ZEOF header will follow. If + * there's room in the outgoing buffer for it, end the packet + * with ZCRCE and append the ZEOF header. If there isn't room, + * we'll have to do a ZCRCW + */ + if ((info->fileEof = (c == EOF))) { + if (qfull || (info->bufsize != 0 && len < 24)) { + type = ZCRCW; + } else { + type = ZCRCE; + } + } + + *ptr++ = ZDLE; + if (!crc32) { + crc = updcrc(type, crc); + } else { + crc = UPDC32(type, crc); + } + *ptr++ = type; + + if (!crc32) { + crc = updcrc(0, crc); + crc = updcrc(0, crc); + ptr = putZdle(ptr, (crc >> 8) &0xff, info); + ptr = putZdle(ptr, crc &0xff, info); + } else { + crc = ~crc; + for (len = 4; --len >= 0; crc >>= 8) { + ptr = putZdle(ptr, crc &0xff, info); + } + } + + len = ptr - info->buffer; + } + + ZStatus(SndByteCount, info->offset, NULL); + + if ((err = ZXmitStr(info->buffer, len, info))) { + return err; + } + +#ifdef COMMENT + if ((err = ZXmitData(ZBIN, len, uchar(type), info->buffer, info))) { + return err; + } +#endif /* COMMENT */ + + /* finally, do we want to wait after this packet? */ + + switch (type) { + case ZCRCE: + info->state = SendEof; + info->timeout = 60; + return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info); + case ZCRCW: + info->state = info->fileEof ? SendDone : SendWait; + info->timeout = 60; + break; + default: + info->state = Sending; + info->timeout = 0; + break; + } + + +#ifdef COMMENT + if (info->fileEof) { + /* Yes, file is done, send EOF and wait */ + info->state = SendEof; + info->timeout = 60; + return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info); + } else if (type == ZCRCW) { + info->state = SendWait; + info->timeout = 60; + } else { + info->state = Sending; + info->timeout = 0; + } +#endif /* COMMENT */ + return 0; +} + + +uint FQTermZmodem::rcvHex(uint i, char c) { + if (c <= '9') { + c -= '0'; + } else if (c <= 'F') { + c -= 'A' - 10; + } else { + c -= 'a' - 10; + } + return (i << 4) + c; +} + + +int FQTermZmodem::dataSetup(register ZModem *info) { + info->InputState = Indata; + info->chrCount = 0; + info->crcCount = 0; + info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL; + return 0; +} + +int FQTermZmodem::ZWriteFile(uchar *buffer, int len, FILE *file, ZModem*) { + return (int)fwrite(buffer, 1, len, file) == len ? 0 : ZmErrSys; +} + +int FQTermZmodem::ZCloseFile(ZModem *info) { + //to be completed + fclose(info->file); + return 0; +} + +void FQTermZmodem::ZFlowControl(int onoff, ZModem *info) { + //to be completed +} + +int FQTermZmodem::GotSinit(ZModem *info) { + zmodemlog("GotSinit[%s]: call dataSetup\n", sname(info)); + + info->zsinitflags = info->hdrData[4]; + info->escCtrl = info->zsinitflags &TESCCTL; + info->escHibit = info->zsinitflags &TESC8; + ZFlowControl(1, info); + return dataSetup(info); +} + +int FQTermZmodem::GotFile(ZModem *info) { + zmodemlog("GotFile[%s]: call dataSetup\n", sname(info)); + + info->errCount = 0; + info->f0 = info->hdrData[4]; + info->f1 = info->hdrData[3]; + info->f2 = info->hdrData[2]; + info->f3 = info->hdrData[1]; + return dataSetup(info); +} + +int FQTermZmodem::GotFin(ZModem *info) { + int i; + zmodemlog("GotFin[%s]: send ZFIN\n", sname(info)); + info->InputState = Finish; + info->chrCount = 0; + if (info->filename != NULL) { + free(info->filename); + } + i = ZXmitHdrHex(ZFIN, zeros, info); + ZmodemReset(info); + transferstate = transferstop; // transfer complete + return i; +} + + +int FQTermZmodem::GotData(ZModem *info) { + int err; + + zmodemlog("GotData[%s]:\n", sname(info)); + + if (ZDec4(info->hdrData + 1) != info->offset) { + if (info->attn != NULL && (err = ZAttn(info)) != 0) { + return err; + } + zmodemlog(" bad, send ZRPOS(%ld)\n", info->offset); + return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info); + } + + /* Let's do it! */ + zmodemlog(" call dataSetup\n"); + return dataSetup(info); +} + +int FQTermZmodem::GotEof(ZModem *info) { + zmodemlog("GotEof[%s]: offset=%ld\n", sname(info), info->offset); + if (ZDec4(info->hdrData + 1) != info->offset) { + zmodemlog("zdec4(info->hdrdata+1)=%ld\n", ZDec4(info->hdrData + 1)); + zmodemlog(" bad length, state = RFile\n"); + info->state = RFile; + return 0; /* it was probably spurious */ + } + + /* TODO: if we can't close the file, send a ZFERR */ + + ZCloseFile(info); + info->file = NULL; + ZStatus(FileEnd, 0, info->filename); + if (info->filename != NULL) { + free(info->filename); + info->filename = NULL; + } + return SendRinit(info); +} + +int FQTermZmodem::GotFreecnt(ZModem *info) { + /* TODO: how do we find free space on system? */ + return ZXmitHdrHex(ZACK, ZEnc4(0xffffffff), info); +} + + +int FQTermZmodem::GotFileCrc(ZModem *info) { + zmodemlog("GotFileCrc[%s]: call requestFile\n", sname(info)); + return requestFile(info, ZDec4(info->hdrData + 1)); +} + +int FQTermZmodem::requestFile(ZModem *info, ulong crc) { + info->file = ZOpenFile((char*)info->buffer, crc, info); + + if (info->file == NULL) { + zmodemlog("requestFile[%s]: send ZSKIP\n", sname(info)); + + info->state = RStart; + ZStatus(FileSkip, 0, info->filename); + return ZXmitHdrHex(ZSKIP, zeros, info); + } else { + zmodemlog("requestFile[%s]: send ZRPOS(%ld)\n", sname(info), info->offset); + info->offset = info->f0 == ZCRESUM ? ftell(info->file): 0; + info->state = RFile; + ZStatus(FileBegin, info->len, info->filename); + return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info); + } +} + +void FQTermZmodem::parseFileName(ZModem *info, char *fileinfo) { + char *ptr; + int serial = 0; + + info->len = info->mode = info->filesRem = info->bytesRem = info->fileType = 0; + ptr = fileinfo + strlen(fileinfo) + 1; + if (info->filename != NULL) { + free(info->filename); + } + info->filename = strdup(fileinfo); + sscanf(ptr, "%d %lo %o %o %d %d %d", &info->len, &info->date, &info->mode, + &serial, &info->filesRem, &info->bytesRem, &info->fileType); +} + + + +int FQTermZmodem::fileError(ZModem *info, int type, int data) { + int err; + + info->InputState = Idle; + info->chrCount = 0; + + if (info->attn != NULL && (err = ZAttn(info)) != 0) { + return err; + } + return ZXmitHdrHex(type, ZEnc4(data), info); +} + + +int FQTermZmodem::ProcessPacket(ZModem *info) { + int idx = (uchar)info->buffer[0]; + int idxc = (uchar)info->buffer[1]; + int crc0, crc1; + int err; + + info->state = YRDataWait; + + if (idxc != 255-idx) { + ZStatus(DataErr, ++info->errCount, NULL); + return rejectPacket(info); + } + + if (idx == (info->packetCount % 256)) + /* quietly ignore dup */ { + return acceptPacket(info); + } + + if (idx != (info->packetCount + 1) % 256) { + /* out of sequence */ + (void)ZXmitStr(CanStr, 2, info); + return ZmErrSequence; + } + + crc0 = (uchar)info->buffer[info->pktLen - 2] << 8 + | (uchar)info->buffer[info->pktLen - 1]; + crc1 = calcCrc(info->buffer + 2, info->pktLen - 4); + if (crc0 != crc1) { + ZStatus(DataErr, ++info->errCount, NULL); + return rejectPacket(info); + } + + ++info->packetCount; + + if (info->packetCount == 0) { + /* packet 0 is filename */ + if (info->buffer[2] == '\0') { + /* null filename is FIN */ + (void)acceptPacket(info); + return ZmDone; + } + + parseFileName(info, (char*)info->buffer + 2); + info->file = ZOpenFile(info->filename, 0, info); + if (info->file == NULL) { + (void)ZXmitStr(CanStr, 2, info); + return ZmErrCantOpen; + } + if ((err = acceptPacket(info)) != 0) { + return err; + } + return ZXmitStr((uchar*)"C", 1, info); + } + + + if (ZWriteFile(info->buffer + 2, info->pktLen - 4, info->file, info)) { + ZStatus(FileErr, zerrno, NULL); + (void)ZXmitStr(CanStr, 2, info); + return ZmErrSys; + } + info->offset += info->pktLen - 4; + ZStatus(RcvByteCount, info->offset, NULL); + + (void)acceptPacket(info); + return 0; +} + +int FQTermZmodem::rejectPacket(ZModem *info) { + info->timeout = 10; + return ZXmitStr(NakStr, 1, info); +} + + +int FQTermZmodem::acceptPacket(ZModem *info) { + info->state = YRDataWait; + info->timeout = 10; + return ZXmitStr(AckStr, 1, info); +} + + +int FQTermZmodem::calcCrc(uchar *str, int len) { + int crc = 0; + while (--len >= 0) { + crc = updcrc(*str++, crc); + } + crc = updcrc(0, crc); + crc = updcrc(0, crc); + return crc &0xffff; +} + + +char *FQTermZmodem::strdup(const char *str) { + char *rval; + int len = strlen(str) + 1; + rval = (char*)malloc(len); + strcpy(rval, str); + return rval; +} + + +int FQTermZmodem::ZXmitData(int format, int len, uchar term, uchar *data, ZModem + *info) { + uchar *ptr = info->buffer; + uint crc; + + if (format == ZBIN && info->crc32) { + format = ZBIN32; + } + + zmodemlog("ZXmiteData: fmt=%c, len=%d, term=%c\n", format, len, term); + + crc = (format == ZBIN) ? 0 : 0xffffffff; + + while (--len >= 0) { + if (format == ZBIN) { + crc = updcrc(*data, crc); + } else { + crc = UPDC32(*data, crc); + } + ptr = putZdle(ptr, *data++, info); + } + + *ptr++ = ZDLE; + if (format == ZBIN) { + crc = updcrc(term, crc); + } else { + crc = UPDC32(term, crc); + } + *ptr++ = term; + if (format == ZBIN) { + crc = updcrc(0, crc); + crc = updcrc(0, crc); + ptr = putZdle(ptr, (crc >> 8) &0xff, info); + ptr = putZdle(ptr, crc &0xff, info); + } else { + crc = ~crc; + for (len = 4; --len >= 0; crc >>= 8) { + ptr = putZdle(ptr, crc &0xff, info); + } + } + + return ZXmitStr(info->buffer, ptr - info->buffer, info); +} + + + +int FQTermZmodem::YXmitData(uchar *buffer, int len, ZModem *info) { + uchar hdr[3]; + uchar trail[2]; + ulong crc = 0; + int i, err; + + hdr[0] = len == 1024 ? STX : SOH; + hdr[1] = info->packetCount; + hdr[2] = ~hdr[1]; + if ((err = ZXmitStr(hdr, 3, info)) || (err = ZXmitStr(buffer, len, info))) { + return err; + } + + if (info->PacketType == NAK) { + /* checksum */ + for (i = 0; i < len; ++i) { + crc += buffer[i]; + } + trail[0] = crc % 256; + return ZXmitStr(trail, 1, info); + } else { + for (i = 0; i < len; ++i) { + crc = updcrc(buffer[i], crc); + } + crc = updcrc(0, crc); + crc = updcrc(0, crc); + trail[0] = crc / 256; + trail[1] = crc % 256; + return ZXmitStr(trail, 2, info); + } +} + + +int FQTermZmodem::YSendFilename(ZModem *info) { + int i, len; + uchar obuf[1024]; + uchar *ptr = obuf; + + info->state = info->PacketType != 'G' ? YTFile : YTDataWait; + info->packetCount = 0; + info->offset = 0; + + i = strlen(info->rfilename); + memcpy(ptr, info->rfilename, i + 1); + ptr += i + 1; + sprintf((char*)ptr, "%d %lo %o 0", info->len, info->date, info->mode); + ptr += strlen((char*)ptr); + *ptr++ = '\0'; + /* pad out to 128 bytes or 1024 bytes */ + i = ptr - obuf; + len = i > 128 ? 1024 : 128; + for (; i < len; ++i) { + *ptr++ = '\0'; + } + + return YXmitData(obuf, len, info); +} + +int FQTermZmodem::YSendData(ZModem *info) { + int i; + + /* are there characters still in the read buffer? */ + + if (info->chrCount <= 0) { + info->bufp = 0; + info->chrCount = fread(info->buffer, 1, info->packetsize, info->file); + info->fileEof = feof(info->file); + } + + if (info->chrCount <= 0) { + fclose(info->file); + info->state = YTEOF; + return ZXmitStr(eotstr, 1, info); + } + + /* pad out to 128 bytes if needed */ + if (info->chrCount < 128) { + i = 128-info->chrCount; + memset(info->buffer + info->bufp + info->chrCount, 0x1a, i); + info->chrCount = 128; + } + + info->ylen = info->chrCount >= 1024 ? 1024 : 128; + ++info->packetCount; + + info->state = YTData; + + return YXmitData(info->buffer + info->bufp, info->ylen, info); +} + + +int FQTermZmodem::YSendFin(ZModem *info) { + uchar obuf[128]; + + info->state = YTFin; + info->packetCount = 0; + + memset(obuf, 0, 128); + + return YXmitData(obuf, 128, info); +} + +int FQTermZmodem::sendFilename(ZModem *info) { + int err; + int i; + uchar obuf[2048]; + uchar *ptr = obuf; + + info->state = FileWait; + + if ((err = ZXmitHdr(ZFILE, ZBIN, info->fileFlags, info))) { + return err; + } + + i = strlen(info->rfilename); + memcpy(ptr, info->rfilename, i + 1); + ptr += i + 1; + sprintf((char*)ptr, "%d %lo %o 0 %d %d 0", info->len, info->date, info->mode, + info->filesRem, info->bytesRem); + ptr += strlen((char*)ptr); + *ptr++ = '\0'; + + return ZXmitData(ZBIN, ptr - obuf, ZCRCW, obuf, info); +} + +int FQTermZmodem::GotRinit(ZModem *info) { + if (strFileList.count() == 0) { + return ZmodemTFinish(info); + } + + + info->bufsize = info->hdrData[1] + info->hdrData[2] *256; + info->zrinitflags = info->hdrData[4] + info->hdrData[3] *256; + info->crc32 = info->zrinitflags &CANFC32; + info->escCtrl = info->zrinitflags &ESCCTL; + info->escHibit = info->zrinitflags &ESC8; + + /* Full streaming: If receiver can overlap I/O, and if + * the sender can sample the reverse channel without hanging, + * and the receiver has not specified a buffer size, then we + * can simply blast away with ZCRCG packets. If the receiver + * detects an error, it sends an attn sequence and a new ZRPOS + * header to restart the file where the error occurred. + * + * [note that zmodem8.doc does not define how much noise is + * required to trigger a ZCRCW packet. We arbitrarily choose + * 64 bytes] + * + * If 'windowsize' is nonzero, and the receiver can do full + * duplex, ZCRCQ packets are sent instead of ZCRCG, to keep track + * of the number of characters in the queue. If the queue fills + * up, we pause and wait for a ZACK. + * + * + * Full Streaming with Reverse Interrupt: If sender cannot + * sample the input stream, then we define an Attn sequence + * that will be used to interrupt transmission. + * + * + * Full Streaming with Sliding Window: If sender cannot + * sample input stream or respond to Attn signal, we send + * several ZCRCQ packets until we're sure the receiver must + * have sent back at least one ZACK. Then we stop sending and + * read that ZACK. Then we send one more packet and so on. + * + * + * Segmented Streaming: If receiver cannot overlap I/O or can't do + * full duplex and has specified a maximum receive buffer size, + * whenever the buffer size is reached, we send a ZCRCW packet. + * + * TODO: what if receiver can't overlap, but hasn't set a buffer + * size? + * + * ZCRCE: CRC next, frame ends, header follows + * ZCRCG: CRC next, frame continues nonstop + * ZCRCQ: CRC next, send ZACK, frame continues nonstop + * ZCRCW: CRC next, send ZACK, frame ends, header follows + */ + + transferstate = transferstart; + + ZFlowControl(1, info); + + if ((info->zrinitflags &(CANFDX | CANOVIO)) == (CANFDX | CANOVIO) && + (SendSample || SendAttn) && info->bufsize == 0) { + if (info->windowsize == 0) { + info->Streaming = Full; + } else { + info->Streaming = StrWindow; + } + } + + else if ((info->zrinitflags &(CANFDX | CANOVIO)) == (CANFDX | CANOVIO) && + info->bufsize == 0) { + info->Streaming = SlidingWindow; + } + + else { + info->Streaming = Segmented; + } + // get filenames to transfer + zmodemlog("GotRinit[%s]\n", sname(info)); + + if (AlwaysSinit || info->zsinitflags != 0 || info->attn != NULL) { + SendZSInit(info); + } + + + itFile = strFileList.begin(); + QFileInfo fi(*itFile); + FQ_TRACE("zmodem", 0) << "Number of files to be transfered: " + << strFileList.count(); + char *filename = strdup(fi.absoluteFilePath().toLatin1()); + char *rfilename = strdup(fi.fileName().toLatin1()); + ZmodemTFile(filename, rfilename, 0, 0, 0, 0, strFileList.count(), fi.size(), + info); + strFileList.erase(itFile); + return ZmDone; +} + + +int FQTermZmodem::SendZSInit(ZModem *info) { + char tmp = '\0'; + int err; + //char *at = (info->attn != NULL) ? info->attn : "" ; + char *at; + if (info->attn != NULL) { + at = info->attn; + } else { + at = &tmp; + } + uchar fbuf[4]; + + /* TODO: zmodem8.doc states: "If the ZSINIT header specifies + * ESCCTL or ESC8, a HEX header is used, and the receiver + * activates the specified ESC modes before reading the following + * data subpacket." What does that mean? + */ + + zmodemlog("SendZSInit[%s]\n", sname(info)); + + info->state = TInit; + fbuf[0] = fbuf[1] = fbuf[2] = 0; + fbuf[3] = info->zsinitflags; + if ((err = ZXmitHdr(ZSINIT, ZBIN, fbuf, info)) || (err = ZXmitData(ZBIN, + strlen(at) + 1, ZCRCW, (uchar*)at, info))) { + return err; + } + return 0; +} + + +int FQTermZmodem::SendFileCrc(ZModem *info) { + ulong crc; + + crc = FileCrc(info->filename); + + zmodemlog("SendFileCrc[%s]: %lx\n", sname(info), crc); + + return ZXmitHdrHex(ZCRC, ZEnc4(crc), info); +} + +int FQTermZmodem::GotSendAck(ZModem *info) { + ulong offset; + + offset = ZDec4(info->hdrData + 1); + + if (offset > info->lastOffset) { + info->lastOffset = offset; + } + + zmodemlog("GotSendAck[%s]: %lx\n", sname(info), info->offset); + + return 0; /* DONT send more data, that will happen + * later anyway */ +} + +int FQTermZmodem::GotSendDoneAck(ZModem *info) { + ulong offset; + + offset = ZDec4(info->hdrData + 1); + + if (offset > info->lastOffset) { + info->lastOffset = offset; + } + + zmodemlog("GotSendDoneAck[%s]: %ld\n", sname(info), info->offset); + + info->state = SendEof; + info->timeout = 60; + return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info); +} + +int FQTermZmodem::GotSendNak(ZModem *info) { + info->offset = info->zrposOffset; + + fseek(info->file, info->offset, 0); + + /* TODO: what if fseek fails? Send EOF? */ + + zmodemlog("GotSendNak[%s]: %ld\n", sname(info), info->offset); + + return SendMoreFileData(info); +} + +int FQTermZmodem::GotSendWaitAck(ZModem *info) { + + ulong offset; + int err; + + offset = ZDec4(info->hdrData + 1); + + FQ_TRACE("zmodem", 10) << "last = " << info->lastOffset + << ", now = " << offset; + + if (offset > info->lastOffset) { + info->lastOffset = offset; + } + + //receiver return -1 without setting this flag, kingson 00:07 14-07-04 + info->waitflag = 1; + + zmodemlog("GotSendWaitAck[%s]\n", sname(info), offset); + + if ((err = ZXmitHdr(ZDATA, ZBIN, info->hdrData + 1, info))) { + return err; + } + return SendMoreFileData(info); +} + +int FQTermZmodem::SkipFile(ZModem *info) { + zmodemlog("SkipFile[%s]\n", sname(info)); + ZStatus(FileEnd, 0, info->rfilename); + fclose(info->file); + + // stupid SMTH doesnt send further command, kick + // lets send files in the list + info->state = TStart; + return GotRinit(info); +} + +int FQTermZmodem::GotSendPos(ZModem *info) { + ZStatus(DataErr, ++info->errCount, NULL); + info->waitflag = 1; /* next pkt should wait, to resync */ + FQ_TRACE("zmodem", 9) << "GotSendPos, offset = " << info->offset; + zmodemlog("GotSendPos[%s] %lx\n", sname(info), info->offset); + return startFileData(info); +} + +int FQTermZmodem::SendFileData(ZModem *info) { + info->waitflag = 0; + return startFileData(info); +} + +int FQTermZmodem::ResendEof(ZModem *info) { + return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info); +} + +int FQTermZmodem::OverAndOut(ZModem *info) { + zmodemlog("OverAndOut[%s]\n", sname(info)); + + ZXmitStr((uchar*)"OO", 2, info); + + transferstate = transferstop; // transfer complete + + ZmodemReset(info); //Tranfer complete, zmodem return to receive state + + return ZmDone; +} + +int FQTermZmodem::startFileData(ZModem *info) { + int err; + + info->offset = info->lastOffset = info->zrposOffset = ZDec4(info->hdrData + 1); + + fseek(info->file, info->offset, 0); + + /* TODO: what if fseek fails? Send EOF? */ + + zmodemlog("startFileData[%s]: %lx\n", sname(info), info->offset); + + if ((err = ZXmitHdr(ZDATA, ZBIN, ZEnc4(info->offset), info))) { + return err; + } + return SendMoreFileData(info); +} + +uchar *FQTermZmodem::putHex(uchar *ptr, uchar c) { + *ptr++ = hexChars[(c >> 4) &0xf]; + *ptr++ = hexChars[c &0xf]; + return ptr; +} + + +int FQTermZmodem::ZmodemReset(ZModem *info) { + zmodemlog("\nZmodemReset\n"); + + ZmodemRInit(info); + + return 0; +} + + +void FQTermZmodem::zmodemlog(const char *fmt, ...) { + // only for debug +#ifdef FQTERM_ZMODEM_DEBUG + va_list ap; + struct timeval tv; + struct tm *tm; + static int do_ts = 1; + + zmodemlogfile = fopen("zmodem.log", "a"); + + if (zmodemlogfile == NULL) { + return ; + } + + if (do_ts) { + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + fprintf(zmodemlogfile, "%2.2d:%2.2d:%2.2d.%2.2ld: ", tm->tm_hour, tm->tm_min, + tm->tm_sec, tv.tv_usec / 10000); + } + do_ts = strchr(fmt, '\n') != NULL; + + va_start(ap, fmt); + vfprintf(zmodemlogfile, fmt, ap); + va_end(ap); + + fclose(zmodemlogfile); +#endif + +} + +void FQTermZmodem::zmodemCancel() { + ZmodemAbort(&info); +} + +} // namespace FQTerm + +#include "fqterm_zmodem.moc" + diff --git a/src/terminal/internal/fqterm_zmodem.h b/src/terminal/internal/fqterm_zmodem.h new file mode 100644 index 0000000..bd1f4f4 --- /dev/null +++ b/src/terminal/internal/fqterm_zmodem.h @@ -0,0 +1,623 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef FQTERM_ZMODEM_H +#define FQTERM_ZMODEM_H + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <QByteArray> +#include <QObject> +#include <QTimer> +#include <QFile> +#include <QStringList> + +namespace FQTerm { + +/* PARAMETERS + * + * The following #defines control the behavior of the Zmodem + * package. Note that these may be replaced with variables + * if you like. For example, "#define DoInitRZ" may be replaced + * with "extern int DoInitRz" to use a global variable, or with + * "#define DoInitRZ (info->doInitRz)" to use a variable you + * add to the ZModem structure. + * + * It is assumed that the compiler is good enough to optimize + * "if( 0 )" and "if( 1 )" cases. If not, you may wish to modify + * the source code to use #ifdef instead. + */ + +#define DoInitRZ 1 /* send initial "rz\r" when transmitting */ +#define AllowCommand 0 /* allow remote end to execute commands */ +#define SendSample 1 /* sender can sample reverse channel */ +#define SendAttn 1 /* sender can be interrupted with Attn signal */ +#define ResponseTime 10 /* reasonable response time for sender to + * respond to requests from receiver */ +#define SerialNo 1 /* receiver serial # */ +#define MaxNoise 64 /* max "noise" characters before transmission + * pauses */ +#define MaxErrs 30 /* Max receive errors before cancel */ +#define AlwaysSinit 1 /* always send ZSINIT header, even if not + * needed, this makes protocol more robust */ + +#define SendOnly 0 /* compiles smaller version for send only */ +#define RcvOnly 0 /* compiles smaller version for receive only */ + +enum enum_InputState { + Idle, Padding, Inhdr, Indata, Finish, Ysend, Yrcv +}; +enum enum_Protocol { + XMODEM, YMODEM, ZMODEM +}; +enum enum_Streaming { + Full, StrWindow, SlidingWindow, Segmented +}; +enum enum_transferstate { + notransfer, transferstart, transferstop +}; + + +/* Internal State */ + +typedef enum zmstate { + /* receive */ + RStart, /* sent RINIT, waiting for ZFILE or SINIT */ + RSinitWait, /* got SINIT, waiting for data */ + RFileName, /* got ZFILE, waiting for filename & info */ + RCrc, /* got filename, want crc too */ + RFile, /* got filename, ready to read */ + RData, /* reading data */ + RDataErr, /* encountered error, ignoring input */ + RFinish, /* sent ZFIN, waiting for 'OO' */ + + /* transmit */ + TStart, /* waiting for INIT frame from other end */ + TInit, /* received INIT, sent INIT, waiting for ZACK */ + FileWait, /* sent file header, waiting for ZRPOS */ + CrcWait, /* sent file crc, waiting for ZRPOS */ + Sending, /* sending data subpackets, ready for int */ + SendWait, /* waiting for ZACK */ + SendDone, /* file finished, need to send EOF */ + SendEof, /* sent EOF, waiting for ZACK */ + TFinish, /* sent ZFIN, waiting for ZFIN */ + + /* general */ + CommandData, /* waiting for command data */ + CommandWait, /* waiting for command to execute */ + StderrData, /* waiting for stderr data */ + Done, + + /* x/ymodem transmit */ + YTStart, /* waiting for 'G', 'C' or NAK */ + YTFile, /* sent filename, waiting for ACK */ + YTDataWait, /* ready to send data, waiting for 'C' */ + YTData, /* sent data, waiting for ACK */ + YTEOF, /* sent eof, waiting for ACK */ + YTFin, /* sent null filename, waiting for ACK */ + + /* x/ymodem receive */ + YRStart, /* sent 'C', waiting for filename */ + YRDataWait, /* received filename, waiting for data */ + YRData, /* receiving filename or data */ + YREOF /* received first EOT, waiting for 2nd */ + +} ZMState; + + + + +typedef struct { + int ifd; /* input fd, for use by caller's routines */ + int ofd; /* output fd, for use by caller's routines */ + FILE *file; /* file being transfered */ + int zrinitflags; /* receiver capabilities, see below */ + int zsinitflags; /* sender capabilities, see below */ + char *attn; /* attention string, see below */ + int timeout; /* timeout value, in seconds */ + int bufsize; /* receive buffer size, bytes */ + int packetsize; /* preferred transmit packet size */ + int windowsize; /* max window size */ + + /* file attributes: read-only */ + + int filesRem, bytesRem; + uchar f0, f1, f2, f3; /* file flags */ + int len, mode, fileType; /* file flags */ + ulong date; /* file date */ + + /* From here down, internal to Zmodem package */ + + ZMState state; /* protocol internal state */ + char *filename; /* filename */ + char *rfilename; /* remote filename */ + int crc32; /* use 32-bit crc */ + int pktLen; /* length of this packet */ + int DataType; /* input data type */ + int PacketType; /* type of this packet */ + int rcvlen; + int chrCount; /* chars received in current header/buffer */ + int crcCount; /* crc characters remaining at end of buffer */ + int canCount; /* how many CAN chars received? */ + int noiseCount; /* how many noise chars received? */ + int errorFlush; /* ignore incoming data because of error */ + uchar *buffer; /* data buffer */ + ulong offset; /* file offset */ + ulong lastOffset; /* last acknowledged offset */ + ulong zrposOffset; /* last offset specified w/zrpos */ + int ylen, bufp; /* len,location of last Ymodem packet */ + int fileEof; /* file eof reached */ + int packetCount; /* # packets received */ + int errCount; /* how many data errors? */ + int timeoutCount; /* how many times timed out? */ + int windowCount; /* how much data sent in current window */ + int atSign; /* last char was '@' */ + int lastCR; /* last char was CR */ + int escCtrl; /* other end requests ctrl chars be escaped */ + int escHibit; /* other end requests hi bit be escaped */ + int escape; /* next character is escaped */ + int interrupt; /* received attention signal */ + int waitflag; /* next send should wait */ + /* parser state */ + // enum enum_InputState {Idle, Padding, Inhdr, Indata, Finish, Ysend, Yrcv} InputState ; + enum_InputState InputState; + // enum enum_Protocol {XMODEM, YMODEM, ZMODEM} Protocol ; + enum_Protocol Protocol; + uchar hdrData[9]; /* header type and data */ + uchar fileFlags[4]; /* file xfer flags */ + ulong crc; /* crc of incoming header/data */ + // enum enum_Streaming {Full, StrWindow, SlidingWindow, Segmented} Streaming ; + enum_Streaming Streaming; +} ZModem; + + + +/* ZRINIT flags. Describe receiver capabilities */ + +#define CANFDX 1 /* Rx is Full duplex */ +#define CANOVIO 2 /* Rx can overlap I/O */ +#define CANBRK 4 /* Rx can send a break */ +#define CANCRY 010 /* Rx can decrypt */ +#define CANLZW 020 /* Rx can uncompress */ +#define CANFC32 040 /* Rx can use 32-bit crc */ +#define ESCCTL 0100 /* Rx needs control chars escaped */ +#define ESC8 0200 /* Rx needs 8th bit escaped. */ + +/* ZSINIT flags. Describe sender capabilities */ + +#define TESCCTL 0100 /* Tx needs control chars escaped */ +#define TESC8 0200 /* Tx needs 8th bit escaped. */ + + +/* ZFILE transfer flags */ + +/* F0 */ +#define ZCBIN 1 /* binary transfer */ +#define ZCNL 2 /* convert NL to local eol convention */ +#define ZCRESUM 3 /* resume interrupted file xfer, or append to a + growing file. */ + +/* F1 */ +#define ZMNEWL 1 /* transfer if source newer or longer */ +#define ZMCRC 2 /* transfer if different CRC or length */ +#define ZMAPND 3 /* append to existing file, if any */ +#define ZMCLOB 4 /* replace existing file */ +#define ZMNEW 5 /* transfer if source is newer */ +#define ZMDIFF 6 /* transfer if dates or lengths different */ +#define ZMPROT 7 /* protect: transfer only if dest doesn't exist */ +#define ZMCHNG 8 /* change filename if destination exists */ +#define ZMMASK 037 /* mask for above. */ +#define ZMSKNOLOC 0200 /* skip if not present at Rx end */ + +/* F2 */ +#define ZTLZW 1 /* lzw compression */ +#define ZTRLE 3 /* run-length encoding */ + +/* F3 */ +#define ZCANVHDR 1 /* variable headers ok */ +#define ZRWOVR 4 /* byte position for receive window override/256 */ +#define ZXSPARS 64 /* encoding for sparse file ops. */ + + + +/* ATTN string special characters. All other characters sent verbose */ + +#define ATTNBRK '\335' /* send break signal */ +#define ATTNPSE '\336' /* pause for one second */ + +/* error code definitions [O] means link still open */ + +#define ZmDone -1 /* done */ +#define ZmErrInt -2 /* internal error */ +#define ZmErrSys -3 /* system error, see errno */ +#define ZmErrNotOpen -4 /* communication channel not open */ +#define ZmErrCantOpen -5 /* can't open file, see errno [O] */ +#define ZmFileTooLong -6 /* remote filename too long [O] */ +#define ZmFileCantWrite -7 /* could not write file, see errno */ +#define ZmDataErr -8 /* too many data errors */ +#define ZmErrInitTo -10 /* transmitter failed to respond to init req. */ +#define ZmErrSequence -11 /* packet received out of sequence */ +#define ZmErrCancel -12 /* cancelled by remote end */ +#define ZmErrRcvTo -13 /* remote receiver timed out during transfer */ +#define ZmErrSndTo -14 /* remote sender timed out during transfer */ +#define ZmErrCmdTo -15 /* remote command timed out */ + + + +/* ZModem character definitions */ + +#define ZDLE 030 /* zmodem escape is CAN */ +#define ZPAD '*' /* pad */ +#define ZBIN 'A' /* introduces 16-bit crc binary header */ +#define ZHEX 'B' /* introduces 16-bit crc hex header */ +#define ZBIN32 'C' /* introduces 32-bit crc binary header */ +#define ZBINR32 'D' /* introduces RLE packed binary frame w/32-bit crc */ +#define ZVBIN 'a' /* alternate ZBIN */ +#define ZVHEX 'b' /* alternate ZHEX */ +#define ZVBIN32 'c' /* alternate ZBIN32 */ +#define ZVBINR32 'd' /* alternate ZBINR32 */ +#define ZRESC 0177 /* RLE flag/escape character */ + + + +/* ZModem header type codes */ + +#define ZRQINIT 0 /* request receive init */ +#define ZRINIT 1 /* receive init */ +#define ZSINIT 2 /* send init sequence, define Attn */ +#define ZACK 3 /* ACK */ +#define ZFILE 4 /* file name, from sender */ +#define ZSKIP 5 /* skip file command, from receiver */ +#define ZNAK 6 /* last packet was garbled */ +#define ZABORT 7 /* abort */ +#define ZFIN 8 /* finish session */ +#define ZRPOS 9 /* resume file from this position, from receiver */ +#define ZDATA 10 /* data packets to follow, from sender */ +#define ZEOF 11 /* end of file, from sender */ +#define ZFERR 12 /* fatal i/o error, from receiver */ +#define ZCRC 13 /* request for file crc, from receiver */ +#define ZCHALLENGE 14 /* "send this number back to me", from receiver */ +#define ZCOMPL 15 /* request is complete */ +#define ZCAN 16 /* other end cancelled with CAN-CAN-CAN-CAN-CAN */ +#define ZFREECNT 17 /* request for free bytes on filesystem */ +#define ZCOMMAND 18 /* command, from sending program */ +#define ZSTDERR 19 /* output this message to stderr */ + + +/* ZDLE escape sequences */ + + +#define ZCRCE 'h' /* CRC next, frame ends, header follows */ +#define ZCRCG 'i' /* CRC next, frame continues nonstop */ +#define ZCRCQ 'j' /* CRC next, send ZACK, frame continues nonstop */ +#define ZCRCW 'k' /* CRC next, send ZACK, frame ends */ +#define ZRUB0 'l' /* translate to 0177 */ +#define ZRUB1 'm' /* translate to 0377 */ + + +/* ascii definitions */ + +#define SOH 1 /* ^A */ +#define STX 2 /* ^B */ +#define EOT 4 /* ^D */ +#define ACK 6 /* ^F */ +#define DLE 16 /* ^P */ +#define XON 17 /* ^Q */ +#define XOFF 19 /* ^S */ +#define NAK 21 /* ^U */ +#define SYN 22 /* ^V */ +#define CAN 24 /* ^X */ + + +/* state table entry. There is one row of the table per + * possible state. Each row is a row of all reasonable + * inputs for this state. The entries are sorted so that + * the most common inputs occur first, to reduce search time + * Unexpected input is reported and ignored, as it might be + * caused by echo or something. + * + * Extra ZRINIT headers are the receiver trying to resync. + */ +class FQTermConfig; + +class FQTermZmodem; + +class FQTermTelnet; + +class FQTermFileDialog; + +typedef int(FQTermZmodem:: *ActionFunc)(ZModem*); + +typedef struct { + int type; /* frame type */ + // int (*func)(ZModem *) ; /* transition function */ + ActionFunc func; + int IFlush; /* flag: flush input first */ + int OFlush; /* flag: flush output first */ + ZMState newstate; /* new state. May be overridden by func */ +} StateTable; + +//class FQTermTelnet; + +class FQTermZmodem: public QObject { + Q_OBJECT; + public: + FQTermZmodem(FQTermConfig *config, FQTermTelnet *netinterface, int type, int serverEncoding); + ~FQTermZmodem(); + + /* zmodem-supplied functions: */ + int ZmodemTInit(ZModem *info); + int ZmodemTFile(char *file, char *rmtname, uint f0, uint f1, uint f2, uint f3, + int filesRem, int bytesRem, ZModem *info); + int ZmodemTFinish(ZModem *info); + int ZmodemAbort(ZModem *info); + int ZmodemRInit(ZModem *info); + int ZmodemRcv(uchar *str, int len, ZModem *info, int &consumed_bytes); + int ZmodemAttention(ZModem *info); + + int ZmodemReset(ZModem *info); + + int YmodemTInit(ZModem *info); + int XmodemTInit(ZModem *info); + int YmodemRInit(ZModem *info); + int XmodemRInit(ZModem *info); + + ulong FileCrc(char *name); + const char *sname(ZModem*); + const char *sname2(ZMState); + + /* caller-supplied functions: */ + int ZXmitChr(uchar c, ZModem *info); + int ZXmitStr(const uchar *str, int len, ZModem *info); + void ZIFlush(ZModem *info); + void ZOFlush(ZModem *info); + int ZAttn(ZModem *info); + void ZStatus(int type, int value, const char *status); + FILE *ZOpenFile(char *name, ulong crc, ZModem *info); + + int ZWriteFile(uchar *buffer, int len, FILE *, ZModem*); + int ZCloseFile(ZModem *info); + void ZFlowControl(int onoff, ZModem *info); + + /* end caller-supplied functions */ + + int ZXmitHdr(int type, int format, const uchar data[4], ZModem *info); + int ZXmitHdrHex(int type, const uchar data[4], ZModem *info); + int ZXmitHdrBin(int type, const uchar data[4], ZModem *info); + int ZXmitHdrBin32(int type, const uchar data[4], ZModem *info); + uchar *putZdle(uchar *ptr, uchar c, ZModem *info); + uchar *putHex(uchar *ptr, uchar c); + + uchar *ZEnc4(ulong n); + ulong ZDec4(const uchar buf[4]); + + int YrcvChar(char c, register ZModem *info); + int YrcvTimeout(register ZModem *info); + void ZIdleStr(uchar *buffer, int len, ZModem *info); + + /* LEXICAL BOX: handle characters received from remote end. + * These may be header, data or noise. + * + * This section is a finite state machine for parsing headers + * and reading input data. The info->chrCount field is effectively + * the state variable. + */ + + int FinishChar(char c, register ZModem *info); + int DataChar(uchar c, register ZModem *info); + int HdrChar(uchar c, register ZModem *info); + int IdleChar(uchar c, register ZModem *info); + + int YsendChar(char c, ZModem *info); + + int ZPF(ZModem *info); + int Ignore(ZModem *info); + int AnswerChallenge(register ZModem *info); + int GotAbort(register ZModem *info); + int GotCancel(ZModem *info); + int GotCommand(ZModem *info); + int GotStderr(register ZModem *info); + int RetDone(ZModem *info); + int GotCommandData(register ZModem *info, int crcGood); + int GotStderrData(register ZModem *info, int crcGood); + + int GotFileName(ZModem *info, int crcGood); + int ResendCrcReq(ZModem *info); + int GotSinitData(ZModem *info, int crcGood); + int ResendRpos(ZModem *info); + int GotFileData(ZModem *info, int crcGood); + int SendRinit(ZModem *info); + + int GotSinit(ZModem *info); + int GotFile(ZModem *info); + int GotFin(ZModem *info); + int GotData(ZModem *info); + int GotEof(ZModem *info); + int GotFreecnt(ZModem *info); + int GotFileCrc(ZModem *info); + + int GotRinit(ZModem*); + int SendZSInit(ZModem*); + int SendFileCrc(ZModem*); + int GotSendAck(ZModem*); + int GotSendDoneAck(ZModem*); + int GotSendNak(ZModem*); + int GotSendWaitAck(ZModem*); + int SkipFile(ZModem*); + int GotSendPos(ZModem*); + + int requestFile(ZModem *info, ulong crc); + void parseFileName(ZModem *info, char *fileinfo); + int fileError(ZModem *info, int type, int data); + + int ProcessPacket(ZModem *info); + int rejectPacket(ZModem *info); + int acceptPacket(ZModem *info); + + int calcCrc(uchar *str, int len); + + char *strdup(const char *str); + + int SendMoreFileData(ZModem *info); + + uint rcvHex(uint i, char c); + + int ZDataReceived(register ZModem *info, int crcGood); + int dataSetup(register ZModem *info); + int ZProtocol(register ZModem *info); + + int ZXmitData(int, int, uchar, uchar *data, ZModem*); + int YXmitData(uchar *, int, ZModem*); + + int YSendFilename(ZModem*); + int YSendData(ZModem*); + int YSendFin(ZModem*); + + int sendFilename(ZModem*); + int SendFileData(ZModem*); + int ResendEof(ZModem*); + + int OverAndOut(ZModem*); + + int startFileData(ZModem *info); + + void zmodemlog(const char *fmt, ...); + + // interface function + // all not completed + void transferTimeOut(void*); + void upload(char*); + void uploadNext(char*); + void transferFinish(); + void xferCancel(); + + /* data received from remote, pass it to Zmodem funcs */ + void transferSendData(char *, int); + void TransferCancel(); + +#if 1 + static StateTable RStartOps[]; + static StateTable RSinitWaitOps[]; + static StateTable RFileNameOps[]; + static StateTable RCrcOps[]; + static StateTable RFileOps[]; + static StateTable RDataOps[]; + static StateTable RFinishOps[]; + + + static StateTable TStartOps[]; + static StateTable TInitOps[]; + static StateTable FileWaitOps[]; + static StateTable CrcWaitOps[]; + static StateTable SendingOps[]; + static StateTable SendDoneOps[]; + static StateTable SendWaitOps[]; + static StateTable SendEofOps[]; + static StateTable TFinishOps[]; + + static StateTable CommandDataOps[]; + static StateTable CommandWaitOps[]; + static StateTable StderrDataOps[]; + static StateTable DoneOps[]; + + static StateTable *tables[]; + static const char *hdrnames[]; +#endif + +#if 0 + StateTable RStartOps[]; + StateTable RSinitWaitOps[]; + StateTable RFileNameOps[]; + StateTable RCrcOps[]; + StateTable RFileOps[]; + StateTable RDataOps[]; + StateTable RFinishOps[]; + + + StateTable TStartOps[]; + StateTable TInitOps[]; + StateTable FileWaitOps[]; + StateTable CrcWaitOps[]; + StateTable SendingOps[]; + StateTable SendDoneOps[]; + StateTable SendWaitOps[]; + StateTable SendEofOps[]; + StateTable TFinishOps[]; + + StateTable CommandDataOps[]; + StateTable CommandWaitOps[]; + StateTable StderrDataOps[]; + StateTable DoneOps[]; + + StateTable *tables[]; + char *hdrnames[]; +#endif + FQTermTelnet *telnet_; + FQTermConfig *config_; + // Dialog + // QDialog *zmodemDialog, uploadListDialog; + + bool sending; + + enum_transferstate transferstate; + + ZModem info; + + // connection type, e.g. telnet or ssh , 0=telnet, 1=ssh + + int connectionType; + + // Timer + QTimer *zmodemTimer; + + //log file + FILE *zmodemlogfile; + + int zerrno; + uchar lastPullByte; + + QStringList strFileList; + QStringList::Iterator itFile; + + int serverEncodingID; + + QString lastDownloadedFilename_; + + // end Member + signals: + void ZmodemState(int, int, const char *); + + public slots: + void zmodemCancel(); + int ZmodemTimeout(); +}; + +} // namespace FQTerm + +#endif // FQTERM_ZMODEM_H |