/* This file is part of SLC. Copyright (C) 2008 Sebastien LUTTRINGER SLC 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; version 2 of the License. SLC 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 SLC; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "slc.hh" #include "screen.hh" #include "options.hh" #include "history.hh" #include "engine.hh" #include "sll/connection.hh" #include #include #include /* ********************************************************************************* *************************************** PUBLIC ********************************** ********************************************************************************* */ /*! ** Screen constructor */ Screen::Screen() { // msg init msg_win_buffer_ = 0; // cmd init *cmd_buffer_ = 0; cmd_buf_off_ = 0; cmd_win_off_ = 0; cmd_history_off_ = 0; } /*! ** Switch in ncurses mode */ void Screen::init() { // Init ncurses initscr(); // register end stop ncurses before exit atexit((void (*)(void)) endwin); // Set good parameters cbreak(); keypad(stdscr, TRUE); // refresh parameters (needed by ncurses) refresh(); } /*! ** Create windows clear the screen, destroy already existing windows, ** create windows with good size and draw it. */ void Screen::create_windows() { // Check screen size if (LINES < 6 || COLS < 3) { std::cerr << "Screen is too small." << std::endl; exit(ERR_SCREENSZ); } if ((size_t) LINES * 2 > O.history_size) O.history_size = 2 * LINES; noecho(); clear(); refresh(); // msg window if (msg_) delwin(msg_); msg_ = newwin(LINES - 3, COLS, 0, 0); msg_win_buffer_ = (char *) realloc(msg_win_buffer_, COLS -2 + 1); msg_draw(); // cmd window if (cmd_) delwin(cmd_); cmd_ = newwin(3, COLS, LINES - 3, 0); cmd_draw(); } /*! ** Swith into login window mode and ** manage the login/pass ask processus */ void Screen::login() { char *buf; int size; const char *smsg; do { clear(); box(stdscr, 0,0); move(1, 1); // ask login if (O.login.empty()) { smsg = "Login: "; size = COLS - 2 - strlen(smsg); buf = new char[size + 1]; echo(); printw(smsg); refresh(); getnstr(buf, size); buf[size] = 0; O.login = buf; delete [] buf; move(2,1); } // ask password smsg = "Password: "; size = COLS - 2 - strlen(smsg); buf = new char[size + 1]; printw(smsg); noecho(); refresh(); getnstr(buf, size); buf[size] = 0; O.pass = buf; delete [] buf; } while (O.login.empty() || O.pass.empty()); } /*! ** Switch in dialog window mode */ void Screen::dialog() { //nominal screen nom create_windows(); } /*! ** Wait keyboard events and act accordingly */ void Screen::eventsloop() { while (1) { int key = getch(); switch(key) { // --------------------------- KEY UP ------------------------------------ case KEY_UP: if (cmd_history_off_ + 1 > H.size()) break; if (cmd_history_off_ == 0) memcpy(cmd_history_buffer_, cmd_buffer_, MAX_LINE_SIZE); strncpy(cmd_buffer_, H.get(cmd_history_off_++).c_str(), MAX_LINE_SIZE); cmd_buffer_[MAX_LINE_SIZE - 1] = 0; cmd_buf_off_ = strlen(cmd_buffer_); cmd_win_off_ = 0; cmd_draw(); break; // --------------------------- KEY DOWN ---------------------------------- case KEY_DOWN: if (cmd_history_off_ == 0) break; --cmd_history_off_; if (cmd_history_off_ == 0) memcpy(cmd_buffer_, cmd_history_buffer_, MAX_LINE_SIZE); else { strncpy(cmd_buffer_, H.get(cmd_history_off_ - 1).c_str(), MAX_LINE_SIZE); cmd_buffer_[MAX_LINE_SIZE - 1] = 0; } cmd_buf_off_ = strlen(cmd_buffer_); cmd_win_off_ = 0; cmd_draw(); break; // --------------------------- KEY LEFT ---------------------------------- case KEY_LEFT: if (cmd_buf_off_ > 0) --cmd_buf_off_; cmd_draw(); break; // --------------------------- KEY RIGHT --------------------------------- case KEY_RIGHT: if ((size_t) cmd_buf_off_ < strlen(cmd_buffer_)) ++cmd_buf_off_; cmd_draw(); break; // --------------------------- KEY HOME ---------------------------------- case KEY_HOME: cmd_buf_off_ = 0; cmd_draw(); break; // --------------------------- KEY END ----------------------------------- case KEY_END: cmd_buf_off_ = strlen(cmd_buffer_); cmd_draw(); break; // --------------------------- KEY RETURN -------------------------------- case KEY_RETURN: // check non empty buffer if (*cmd_buffer_ == 0) break; // save in history if (strncmp(cmd_buffer_, "/pass", 4)) H.add(cmd_buffer_); // Send to execution cmd::exec(cmd_buffer_); // purge buffer *cmd_buffer_ = 0; cmd_buf_off_ = 0; cmd_win_off_ = 0; cmd_history_off_ = 0; // print result cmd_draw(); break; // --------------------------- KEY BACKSPACE------------------------------ case KEY_BACKSPACE: if (cmd_buf_off_ != 0) if (del_char(cmd_buffer_, cmd_buf_off_ - 1, MAX_LINE_SIZE)) --cmd_buf_off_; cmd_draw(); break; // --------------------------- KEY DELETE -------------------------------- case KEY_DC: del_char(cmd_buffer_, cmd_buf_off_, MAX_LINE_SIZE); cmd_draw(); break; // --------------------------- KEY RESIZE -------------------------------- case KEY_RESIZE: create_windows(); break; // --------------------------- KEY EOF ----------------------------------- case KEY_EOF: return; // -------------------------ALL OTHERS KEYS ------------------------------ default: if (!isprint(key)) break; if (add_char(key, cmd_buffer_, cmd_buf_off_, MAX_LINE_SIZE)) ++cmd_buf_off_; cmd_draw(); } } } /*! ** Print a message in msg window (on one line) ** ** @param s message to print */ void Screen::msg_println(const string &s) { msg_buffer_ << s; msg_print("\n"); } /*! ** Print a number in msg window (on one line) ** ** @param s message to print */ void Screen::msg_println(int i) { msg_buffer_ << i; msg_print("\n"); } /*! ** Print @param s into msg window (no refresh) */ void Screen::msg_print(const string &s) { size_t offset = 0; size_t last = 0; msg_buffer_ << s; const string &buf = msg_buffer_.str(); // add line by line while ((offset = buf.find('\n', last)) != string::npos) { msg_add(buf.substr(last, offset - last)); last = offset + 1; } // update screen if (last > 0) { msg_buffer_.str(""); msg_draw(); cmd_draw(); } } /*! ** Print @param i into msg window (no refresh) */ void Screen::msg_print(int i) { msg_buffer_ << i; } /* ********************************************************************************* ************************************** PRIVATE ********************************** ********************************************************************************* */ /*! ** Draw the content of the cmd window */ void Screen::cmd_draw() { char print_buf[MAX_LINE_SIZE + 1]; // clean and redraw the cmd box werase(cmd_); box(cmd_, 0, 0); wmove(cmd_, 1, 1); // check cur pos is in screen if (cmd_buf_off_ + 1 > (size_t) COLS - 2 + cmd_win_off_) cmd_win_off_ = cmd_buf_off_ + 1 - (COLS - 2) ; else if (cmd_buf_off_ < cmd_win_off_) cmd_win_off_ = cmd_buf_off_; // copy part of buffer in print buffer size_t minsize = (MAX_LINE_SIZE < COLS - 2) ? MAX_LINE_SIZE : COLS - 2; strncpy(print_buf, cmd_buffer_ + cmd_win_off_, minsize); print_buf[minsize] = 0; // print print buffer wattron(cmd_, A_BOLD); wprintw(cmd_, "%s", print_buf); wattroff(cmd_, A_BOLD); // move cursor to right pos wmove(cmd_, 1, 1 + cmd_buf_off_ - cmd_win_off_); // refresh screen wrefresh(cmd_); } /*! ** Draw the content of the msg window */ void Screen::msg_draw() { werase(msg_); box(msg_, 0, 0); t_lines::const_iterator line; size_t free_lines = LINES - 5; for (line = msg_table_.begin(); line != msg_table_.end() && free_lines > 0; ++line) { size_t len = strlen((*line).c_str()); // print one line if (len <= (size_t) COLS - 2) mvwprintw(msg_, free_lines--, 1, "%s", (*line).c_str()); // print multi line else { // compute line count const size_t nline = 1 + (size_t) ceil(((double) ( len - (COLS - 2)) / (double) (COLS - 4))); // iterate for each line starting with the last for (size_t i = nline; i > 0 && free_lines > 0; --i) { // compute string offset for this line const size_t print_offset = (i == 1) ? 0 : COLS - 2 + ((i - 2) * (COLS - 4)); // copy line into a buffer strncpy(msg_win_buffer_, (*line).c_str() + print_offset, COLS - ((i == 1) ? 2 : 4)); msg_win_buffer_[COLS - ((i == 1) ? 2 : 4)] = 0; // print line into screen mvwprintw(msg_, free_lines--, 1, (i == 1) ? "%s" : "| %s", msg_win_buffer_); } } } // refresh screen wrefresh(msg_); } /*! ** Add char @param c in a @param string buffer at offset @param offset. ** @param buf_len is the size of the buffer ** ** @return true if char was added */ bool Screen::add_char(char c, char *string, ssize_t offset, size_t buf_len) { assert(string); size_t len = strlen(string); // check if add one char is in range if (len + 1 > buf_len - 1) return false; // check if offset is good if (offset >= (ssize_t) buf_len - 1 || offset < 0) return false; // move one char every char for (ssize_t i = len; i >= offset; --i) string[i + 1] = string[i]; string[offset] = c; return true; } /*! ** Del a char from @param string at offset @param offset. ** @param buf_len is the buffer size ** ** @return */ bool Screen::del_char(char *string, ssize_t offset, size_t buf_len) { assert(string); size_t len = strlen(string); // check if del one char is possible if (len == 0) return false; // check if offset is good if (offset >= (ssize_t) buf_len - 1 || offset < 0) return false; // move one char every char for (; string[offset] != 0; ++offset) string[offset] = string[offset + 1]; return true; } /*! ** Add msg @param s to msg list */ void Screen::msg_add(const string &s) { assert(s.find('\n') == string::npos); if (msg_table_.size() >= O.history_size) msg_table_.resize(O.history_size); msg_table_.push_front(s); } /*! ** Retrieve @param i th msg from msg list ** @return string corresponding to @param i in msg list */ const string &Screen::msg_get(size_t i) const { assert(i < msg_table_.size()); t_lines::const_iterator it; size_t pos; static string empty; for (pos = 0, it = msg_table_.begin(); it != msg_table_.end(); ++pos, ++it) if (pos == i) return *it; return empty; } /*! ** Compute the size of msg list ** ** @return count of message in msg list */ size_t Screen::msg_size() const { return msg_table_.size(); } /*! ** Load msg list from a file @param filename */ void Screen::msg_load(const string &filename) { ifstream fs; t_lines::const_iterator it; char buf[MAX_LINE_SIZE]; fs.open(filename.c_str(), ifstream::in); if (!fs.is_open()) return; while (!fs.eof()) { if (fs.fail()) throw Error(ERR_FILE, "Unable to load history"); fs.getline(buf, MAX_LINE_SIZE); msg_add(buf); } fs.close(); } /*! ** Save msg list to a file @param filename */ void Screen::msg_save(const string &filename) const { ofstream fs; t_lines::const_reverse_iterator rit; fs.open(filename.c_str(), ostream::out | ostream::trunc); if (!fs.is_open()) throw Error(ERR_FILE, "Unable to open history file"); for (rit = msg_table_.rbegin(); rit != msg_table_.rend(); ++rit) { fs.write((*rit).c_str(), (*rit).length()); fs.put('\n'); if (fs.fail()) throw Error(ERR_FILE, "Unable to save history"); } fs.close(); } /*! ** This is a sugar to print msg on msg window ** ** @param scr A Screen ** @param s string to print ** ** @return @param scr */ Screen &operator<< (Screen &scr, const string &s) { scr.msg_print(s); return scr; } /*! ** This is a sugar to print msg on msg window ** ** @param scr A Screen ** @param i string to print ** ** @return @param scr */ Screen &operator<< (Screen &scr, int i) { scr.msg_print(i); return scr; }