87efd87b3cbb5f99615334519fc270b3f5c69ce4
[senf.git] / Scheduler / Console / Readline.cc
1 // $Id$
2 //
3 // Copyright (C) 2008 
4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Readline non-inline non-template implementation */
25
26 #include "Readline.hh"
27 //#include "Readline.ih"
28
29 // Custom includes
30 #include <stdio.h>
31 #include <readline/readline.h>
32 #include <readline/history.h>
33 #include <boost/algorithm/string/trim.hpp>
34 #include <boost/algorithm/string/replace.hpp>
35 #include "../../Utils/membind.hh"
36
37 //#include "Readline.mpp"
38 #define prefix_
39 ///////////////////////////////cc.p////////////////////////////////////////
40
41 //
42 // Readline integration is a bit awkward. There are several things to it:
43 //
44 //  - Readline uses global variables for all state. Therefore, we can use readline only for one
45 //    console.
46 //  - We need to make readline to read from the socket handle instead of some input stream. We can
47 //    do this by setting a custom rl_getc_function.
48 //  - We need to make readline to write to the NonblockingSocketOStream. This is possible in glibc
49 //    by using a 'cookie stream'.
50 //  - We need to correctly handle the terminal mode settings. Currently we unconditionally
51 //    initialize the remote telnet by sending a fixed telnet option string and ignoring any otpions
52 //    sent back to us.
53 //  - We need to make sure, readline never uses stderr -> we must disable beeping
54 //  - There are places, where readline calls read_key unconditionally even when NOT prompted by the
55 //    callback that another key is available. One such place is completion. (The 'show all
56 //    completions (y/n)?' question). For now, we disable completion support.
57 //
58
59 ///////////////////////////////////////////////////////////////////////////
60 // senf::console::detail::ReadlineClientReader
61
62 extern "C" {
63     extern int readline_echoing_p;
64     extern int _rl_bell_preference;
65
66     void _rl_erase_entire_line();
67 }
68
69
70 namespace {
71
72     int readline_getc_function(FILE *)
73     {
74         if (senf::console::detail::ReadlineClientReader::active())
75             return senf::console::detail::ReadlineClientReader::instance().getc();
76         else
77             return -1;
78     }
79
80     void readline_callback(char * input)
81     {
82         if (senf::console::detail::ReadlineClientReader::active()) {
83             if (input)
84                 return senf::console::detail::ReadlineClientReader::instance().callback(
85                     std::string(input) );
86             else // input == 0 -> EOF (or Ctrl-D)
87                 senf::console::detail::ReadlineClientReader::instance().eof();
88         }
89     }
90
91     ssize_t readline_cookie_write_function(void * cookie, char const * buffer, size_t size)
92     {
93         if (senf::console::detail::ReadlineClientReader::active() && buffer)
94             senf::console::detail::ReadlineClientReader::instance().write(
95                 std::string(buffer, size));
96         return size;
97     }
98
99     void readline_prep_term(int meta)
100     {
101         readline_echoing_p = 1;
102     }
103
104     void readline_deprep_term()
105     {}
106
107     int restart_line(int count, int key)
108     {
109         rl_kill_full_line(count, key);
110         rl_crlf();
111         rl_forced_update_display();
112         return 0;
113     }
114
115 }
116
117 prefix_ senf::console::detail::ReadlineClientReader::ReadlineClientReader(Client & client)
118     : ClientReader(client), ch_ (-1), skipChars_ (0), 
119       readevent_ ( "senf::console::detail::ReadlineClientReader", senf::membind(&ReadlineClientReader::charEvent, this),
120                    client.handle(), scheduler::FdEvent::EV_READ, false ),
121       terminate_ (false)
122 {
123     if (instance_ != 0)
124         throw DuplicateReaderException();
125     instance_ = this;
126
127     cookie_io_functions_t cookie_io = { 0, &readline_cookie_write_function, 0, 0 };
128     rl_outstream = fopencookie(0, "a", cookie_io);
129     if (rl_outstream == 0)
130         SENF_THROW_SYSTEM_EXCEPTION("");
131     if (setvbuf(rl_outstream, 0, _IONBF, BUFSIZ) < 0)
132         SENF_THROW_SYSTEM_EXCEPTION("");
133     rl_instream = rl_outstream;
134     rl_terminal_name = "vt100";
135     strncpy(nameBuffer_, client.name().c_str(), 128);
136     nameBuffer_[127] = 0;
137     rl_readline_name = nameBuffer_;
138     rl_prep_term_function = &readline_prep_term;
139     rl_deprep_term_function = &readline_deprep_term;
140     rl_getc_function = &readline_getc_function;
141     rl_bind_key('\t', &rl_insert);
142     rl_bind_key('\x03', &restart_line);
143     using_history();
144     
145     // Don't ask me, where I found this ...
146     static char options[] = { 0xFF, 0xFB, 0x01, // IAC WILL ECHO
147                               0xFF, 0xFE, 0x22, // IAC DONT LINEMODE
148                               0xFF, 0xFB, 0x03, // IAC WILL SGA
149                               0x00 };
150     handle().write(options, options+sizeof(options));
151     handle().write(std::string("(readline support enabled)\r\n"));
152
153     strncpy(promptBuffer_, promptString().c_str(), 1024);
154     promptBuffer_[1023] = 0;
155     rl_callback_handler_install(promptBuffer_, &readline_callback);
156
157     _rl_bell_preference = 0; // Set this *after* the config file has been read
158
159     readevent_.enable();
160 }
161
162 prefix_ senf::console::detail::ReadlineClientReader::~ReadlineClientReader()
163 {
164     rl_callback_handler_remove();
165     fclose(rl_outstream);
166     rl_outstream = 0;
167     rl_instream = 0;
168     instance_ = 0;
169 }
170
171 prefix_ void senf::console::detail::ReadlineClientReader::callback(std::string line)
172 {
173     boost::trim(line);
174     if (!line.empty())
175         add_history(line.c_str());
176     handleInput(line);
177     stream() << std::flush;
178     strncpy(promptBuffer_, promptString().c_str(), 1024);
179     promptBuffer_[1023] = 0;
180     rl_set_prompt(promptBuffer_);
181 }
182
183 prefix_ void senf::console::detail::ReadlineClientReader::v_disablePrompt()
184 {
185     _rl_erase_entire_line();
186 }
187
188 prefix_ void senf::console::detail::ReadlineClientReader::v_enablePrompt()
189 {
190     rl_forced_update_display();
191 }
192
193 prefix_ void senf::console::detail::ReadlineClientReader::v_translate(std::string & data)
194 {
195     boost::replace_all(data, "\n", "\n\r");
196     boost::replace_all(data, "\xff", "\xff\xff");
197 }
198
199 prefix_ void senf::console::detail::ReadlineClientReader::charEvent(int event)
200 {
201     char ch;
202     if (event != scheduler::FdEvent::EV_READ || handle().read(&ch, &ch+1) <= &ch) {
203         stopClient();
204         return;
205     }
206     ch_ = static_cast<unsigned char>(ch);
207
208     if (skipChars_ > 0)
209         --skipChars_;
210     else if (ch_ == 0xff)
211         skipChars_ = 2;
212     else if (ch_ != 0)
213         rl_callback_read_char();
214
215     if (terminate_)
216         stopClient();
217 }
218
219 senf::console::detail::ReadlineClientReader * 
220     senf::console::detail::ReadlineClientReader::instance_ (0);
221
222 ///////////////////////////////////////////////////////////////////////////
223 // senf::console::detail::SafeReadlineClientReader
224
225 prefix_ void senf::console::detail::SafeReadlineClientReader::v_disablePrompt()
226 {
227     reader_->disablePrompt();
228 }
229
230 prefix_ void senf::console::detail::SafeReadlineClientReader::v_enablePrompt()
231 {
232     reader_->enablePrompt();
233 }
234
235 prefix_ void senf::console::detail::SafeReadlineClientReader::v_translate(std::string & data)
236 {
237     reader_->translate(data);
238 }
239
240 ///////////////////////////////cc.e////////////////////////////////////////
241 #undef prefix_
242 //#include "Readline.mpp"
243
244 \f
245 // Local Variables:
246 // mode: c++
247 // fill-column: 100
248 // comment-column: 40
249 // c-file-style: "senf"
250 // indent-tabs-mode: nil
251 // ispell-local-dictionary: "american"
252 // compile-command: "scons -u test"
253 // End: