diff --git a/README.md b/README.md index 81675be..8e7b293 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Getting Mosh * [GNU Autotools][] * the [Protocol Buffers][] library and compiler * [Boost][] + * `ncurses` * `libutempter` * `zlib` * the Perl module [IO::Pty][] diff --git a/configure.ac b/configure.ac index 86320cf..e14581d 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,7 @@ AS_IF([test x"$with_skalibs" != xno], AC_SUBST([STDDJB_LDFLAGS], ["$SKALIBS_LDFLAGS -lstddjb"])]) # Checks for header files. -AC_CHECK_HEADERS([arpa/inet.h fcntl.h langinfo.h limits.h locale.h netinet/in.h pty.h stddef.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h termios.h unistd.h util.h wchar.h wctype.h]) +AC_CHECK_HEADERS([arpa/inet.h curses.h fcntl.h langinfo.h limits.h locale.h netinet/in.h pty.h stddef.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h term.h termios.h unistd.h util.h wchar.h wctype.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -99,6 +99,8 @@ AC_CHECK_FUNCS([gettimeofday inet_ntoa iswprint memchr memset nl_langinfo setenv AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [Define if clock_gettime is available.])]) +AC_CHECK_LIB([ncurses], [setupterm]) + AC_CHECK_DECL([mach_absolute_time], [AC_DEFINE([HAVE_MACH_ABSOLUTE_TIME], [1], [Define if mach_absolute_time is available.])], diff --git a/debian/control b/debian/control index c0ec54f..b28911e 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: mosh Section: net Priority: optional Maintainer: Keith Winstein -Build-Depends: debhelper (>= 7.0.50), autotools-dev, protobuf-compiler, libprotobuf-dev, dh-autoreconf, pkg-config, libutempter-dev, libboost-dev, zlib1g-dev, skalibs-dev +Build-Depends: debhelper (>= 7.0.50), autotools-dev, protobuf-compiler, libprotobuf-dev, dh-autoreconf, pkg-config, libutempter-dev, libboost-dev, zlib1g-dev, skalibs-dev, libncurses5-dev Standards-Version: 3.9.3 Homepage: http://mosh.mit.edu Vcs-Git: git://github.com/keithw/mosh.git diff --git a/src/examples/termemu.cc b/src/examples/termemu.cc index 7439bbf..bcc8756 100644 --- a/src/examples/termemu.cc +++ b/src/examples/termemu.cc @@ -148,7 +148,8 @@ int main( void ) } /* Print a frame if the last frame was more than 1/50 seconds ago */ -bool tick( Terminal::Framebuffer &state, const Terminal::Framebuffer &new_frame ) +bool tick( Terminal::Framebuffer &state, const Terminal::Framebuffer &new_frame, + const Terminal::Display &display ) { static bool initialized = false; static struct timeval last_time; @@ -164,7 +165,7 @@ bool tick( Terminal::Framebuffer &state, const Terminal::Framebuffer &new_frame if ( (!initialized) || (diff >= 0.02) ) { - std::string update = Terminal::Display::new_frame( initialized, state, new_frame ); + std::string update = display.new_frame( initialized, state, new_frame ); swrite( STDOUT_FILENO, update.c_str() ); state = new_frame; @@ -222,6 +223,9 @@ void emulate_terminal( int fd ) Terminal::Complete complete( window_size.ws_col, window_size.ws_row ); Terminal::Framebuffer state( window_size.ws_col, window_size.ws_row ); + /* open display */ + Terminal::Display display( true ); /* use TERM to initialize */ + struct pollfd pollfds[ 3 ]; pollfds[ 0 ].fd = STDIN_FILENO; @@ -310,14 +314,14 @@ void emulate_terminal( int fd ) break; } - if ( tick( state, complete.get_fb()) ) { /* there was a frame */ + if ( tick( state, complete.get_fb(), display ) ) { /* there was a frame */ poll_timeout = -1; } else { poll_timeout = 20; } } - std::string update = Terminal::Display::new_frame( true, state, complete.get_fb() ); + std::string update = display.new_frame( true, state, complete.get_fb() ); swrite( STDOUT_FILENO, update.c_str() ); swrite( STDOUT_FILENO, Terminal::Emulator::close().c_str() ); diff --git a/src/frontend/mosh-client.cc b/src/frontend/mosh-client.cc index 1a1ff19..b61e648 100644 --- a/src/frontend/mosh-client.cc +++ b/src/frontend/mosh-client.cc @@ -89,6 +89,8 @@ int main( int argc, char *argv[] ) } catch ( Crypto::CryptoException e ) { fprintf( stderr, "Crypto exception: %s\r\n", e.text.c_str() ); + } catch ( std::string s ) { + fprintf( stderr, "Error: %s\r\n", s.c_str() ); } printf( "\n[mosh is exiting.]\n" ); diff --git a/src/frontend/stmclient.cc b/src/frontend/stmclient.cc index 66c5729..6b181e5 100644 --- a/src/frontend/stmclient.cc +++ b/src/frontend/stmclient.cc @@ -130,7 +130,7 @@ void STMClient::main_init( void ) local_framebuffer = new Terminal::Framebuffer( window_size.ws_col, window_size.ws_row ); /* initialize screen */ - string init = Terminal::Display::new_frame( false, *local_framebuffer, *local_framebuffer ); + string init = display.new_frame( false, *local_framebuffer, *local_framebuffer ); swrite( STDOUT_FILENO, init.data(), init.size() ); /* open network */ @@ -158,11 +158,11 @@ void STMClient::output_new_frame( void ) overlays.apply( new_state ); /* calculate minimal difference from where we are */ - const string diff( Terminal::Display::new_frame( !repaint_requested, - *local_framebuffer, - new_state ) ); + const string diff( display.new_frame( !repaint_requested, + *local_framebuffer, + new_state ) ); swrite( STDOUT_FILENO, diff.data(), diff.size() ); - *local_framebuffer = new_state; + *local_framebuffer = new_state; repaint_requested = false; } diff --git a/src/frontend/stmclient.h b/src/frontend/stmclient.h index 9e24265..173b288 100644 --- a/src/frontend/stmclient.h +++ b/src/frontend/stmclient.h @@ -42,6 +42,7 @@ private: Terminal::Framebuffer *local_framebuffer; Overlay::OverlayManager overlays; Network::Transport< Network::UserStream, Terminal::Complete > *network; + Terminal::Display display; bool repaint_requested, quit_sequence_started; @@ -61,6 +62,7 @@ public: local_framebuffer( NULL ), overlays(), network( NULL ), + display( true ), /* use TERM environment var to initialize display */ repaint_requested( false ), quit_sequence_started( false ) { diff --git a/src/statesync/completeterminal.cc b/src/statesync/completeterminal.cc index d232519..8f37145 100644 --- a/src/statesync/completeterminal.cc +++ b/src/statesync/completeterminal.cc @@ -75,7 +75,7 @@ string Complete::diff_from( const Complete &existing ) const new_res->MutableExtension( resize )->set_height( terminal.get_fb().ds.get_height() ); } Instruction *new_inst = output.add_instruction(); - new_inst->MutableExtension( hostbytes )->set_hoststring( Terminal::Display::new_frame( true, existing.get_fb(), terminal.get_fb() ) ); + new_inst->MutableExtension( hostbytes )->set_hoststring( display.new_frame( true, existing.get_fb(), terminal.get_fb() ) ); } return output.SerializeAsString(); diff --git a/src/statesync/completeterminal.h b/src/statesync/completeterminal.h index 99c4e5c..384adc1 100644 --- a/src/statesync/completeterminal.h +++ b/src/statesync/completeterminal.h @@ -32,6 +32,7 @@ namespace Terminal { private: Parser::UTF8Parser parser; Terminal::Emulator terminal; + Terminal::Display display; std::list< std::pair > input_history; uint64_t echo_ack; @@ -39,7 +40,7 @@ namespace Terminal { static const int ECHO_TIMEOUT = 50; /* for late ack */ public: - Complete( size_t width, size_t height ) : parser(), terminal( width, height ), + Complete( size_t width, size_t height ) : parser(), terminal( width, height ), display( false ), input_history(), echo_ack( 0 ) {} std::string act( const std::string &str ); diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 9cf22f2..bfdb09b 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -3,5 +3,4 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) -fno-default-inline -pipe noinst_LIBRARIES = libmoshterminal.a -libmoshterminal_a_SOURCES = parseraction.cc parseraction.h parser.cc parser.h parserstate.cc parserstatefamily.h parserstate.h parsertransition.h terminal.cc terminaldispatcher.cc terminaldispatcher.h terminaldisplay.cc terminaldisplay.h terminalframebuffer.cc terminalframebuffer.h terminalfunctions.cc terminal.h terminaluserinput.cc terminaluserinput.h - +libmoshterminal_a_SOURCES = parseraction.cc parseraction.h parser.cc parser.h parserstate.cc parserstatefamily.h parserstate.h parsertransition.h terminal.cc terminaldispatcher.cc terminaldispatcher.h terminaldisplay.cc terminaldisplayinit.cc terminaldisplay.h terminalframebuffer.cc terminalframebuffer.h terminalfunctions.cc terminal.h terminaluserinput.cc terminaluserinput.h diff --git a/src/terminal/terminaldisplay.cc b/src/terminal/terminaldisplay.cc index d30d479..57d3342 100644 --- a/src/terminal/terminaldisplay.cc +++ b/src/terminal/terminaldisplay.cc @@ -25,7 +25,7 @@ using namespace Terminal; /* Print a new "frame" to the terminal, using ANSI/ECMA-48 escape codes. */ -std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f ) +std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f ) const { FrameState frame( last ); @@ -201,7 +201,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer &last, const return frame.str; } -void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f ) +void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f ) const { char tmp[ 64 ]; @@ -238,13 +238,28 @@ void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer & } } - frame.x += clear_count; - if ( frame.x >= f.ds.get_width() ) { + assert( frame.x + clear_count <= f.ds.get_width() ); + + /* can we go to the end of the line? */ + if ( frame.x + clear_count == f.ds.get_width() ) { snprintf( tmp, 64, "\033[K" ); + frame.append( tmp ); + frame.x += clear_count; } else { - snprintf( tmp, 64, "\033[%dX", clear_count ); + if ( has_ech ) { + if ( clear_count == 1 ) { + frame.append( "\033[X" ); + } else { + snprintf( tmp, 64, "\033[%dX", clear_count ); + frame.append( tmp ); + } + frame.x += clear_count; + } else { /* no ECH, so just print a space */ + frame.append( " " ); + frame.cursor_x++; + frame.x++; + } } - frame.append( tmp ); return; } diff --git a/src/terminal/terminaldisplay.h b/src/terminal/terminaldisplay.h index 1d2069d..3482f57 100644 --- a/src/terminal/terminaldisplay.h +++ b/src/terminal/terminaldisplay.h @@ -49,10 +49,15 @@ namespace Terminal { class Display { private: - static void put_cell( bool initialized, FrameState &frame, const Framebuffer &f ); + bool has_ech; /* erase character is part of vt200 but not supported by tmux + (or by "screen" terminfo entry, which is what tmux advertises) */ + + void put_cell( bool initialized, FrameState &frame, const Framebuffer &f ) const; public: - static std::string new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f ); + std::string new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f ) const; + + Display( bool use_environment ); }; } diff --git a/src/terminal/terminaldisplayinit.cc b/src/terminal/terminaldisplayinit.cc new file mode 100644 index 0000000..07c9052 --- /dev/null +++ b/src/terminal/terminaldisplayinit.cc @@ -0,0 +1,60 @@ +/* + Mosh: the mobile shell + Copyright 2012 Keith Winstein + + 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 3 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, see . +*/ + +/* This is in its own file because otherwise the ncurses #defines + alias our own variable names. */ + +#include "terminaldisplay.h" + +#include + +#include +#include + +using namespace Terminal; + +Display::Display( bool use_environment ) + : has_ech( true ) +{ + if ( use_environment ) { + int errret = -2; + int ret = setupterm( (char *)0, 1, &errret ); + + if ( ret != OK ) { + switch ( errret ) { + case 1: + throw std::string( "Terminal is hardcopy and cannot be used by curses applications." ); + break; + case 0: + throw std::string( "Unknown terminal type." ); + break; + case -1: + throw std::string( "Terminfo database could not be found." ); + break; + default: + throw std::string( "Unknown terminfo error." ); + break; + } + } + + char *val = tigetstr( "ech" ); + if ( val <= 0 ) { + has_ech = false; + } + } +}