/[svn]/linuxsampler/trunk/src/shell/lscp.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/shell/lscp.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2534 - (hide annotations) (download)
Sun Mar 9 21:34:03 2014 UTC (10 years, 2 months ago) by schoenebeck
File size: 23110 byte(s)
* LSCP shell (WIP): Added initial support for built-in LSCP reference
  documentation, which will automatically show the relevant LSCP reference
  section on screen as soon as one specific LSCP command was detected while
  typing on the command line.
* Bumped version (1.0.0.svn37).

1 schoenebeck 2515 /*
2     * LSCP Shell
3     *
4     * Copyright (c) 2014 Christian Schoenebeck
5     *
6     * This program is part of LinuxSampler and released under the same terms.
7     */
8    
9     #include <stdio.h>
10     #include <stdlib.h>
11     #include <iostream>
12     #include <sstream>
13     #include <string.h>
14    
15     #include "LSCPClient.h"
16     #include "KeyboardReader.h"
17     #include "TerminalCtrl.h"
18     #include "CFmt.h"
19     #include "CCursor.h"
20 schoenebeck 2528 #include "TerminalPrinter.h"
21 schoenebeck 2515
22     #include "../common/global.h"
23 schoenebeck 2517 #include "../common/global_private.h"
24 schoenebeck 2515 #include "../common/Condition.h"
25    
26     #define LSCP_DEFAULT_HOST "localhost"
27     #define LSCP_DEFAULT_PORT 8888
28    
29     using namespace std;
30     using namespace LinuxSampler;
31    
32 schoenebeck 2517 static LSCPClient* g_client = NULL;
33     static KeyboardReader* g_keyboardReader = NULL;
34 schoenebeck 2515 static Condition g_todo;
35 schoenebeck 2516 static String g_goodPortion;
36     static String g_badPortion;
37     static String g_suggestedPortion;
38 schoenebeck 2528 static int g_linesActive = 0;
39 schoenebeck 2517 static const String g_prompt = "lscp=# ";
40 schoenebeck 2518 static std::vector<String> g_commandHistory;
41     static int g_commandHistoryIndex = -1;
42 schoenebeck 2534 static String g_doc;
43 schoenebeck 2515
44     static void printUsage() {
45     cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl;
46     cout << endl;
47     cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl;
48     cout << endl;
49     cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl;
50     cout << endl;
51     cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl;
52     cout << endl;
53 schoenebeck 2516 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
54     cout << endl;
55 schoenebeck 2534 cout << " --no-doc Don't show LSCP reference documentation on screen." << endl;
56     cout << endl;
57 schoenebeck 2515 }
58    
59 schoenebeck 2517 static void printWelcome() {
60     cout << "Welcome to lscp " << VERSION << ", the LinuxSampler Control Protocol (LSCP) shell." << endl;
61     cout << endl;
62     }
63    
64     static void printPrompt() {
65     cout << g_prompt << flush;
66     }
67    
68     static int promptOffset() {
69     return g_prompt.size();
70     }
71    
72 schoenebeck 2532 static void quitApp(int code = 0) {
73     std::cout << std::endl << std::flush;
74     if (g_client) delete g_client;
75     if (g_keyboardReader) delete g_keyboardReader;
76     exit(code);
77     }
78    
79 schoenebeck 2515 // Called by the network reading thread, whenever new data arrived from the
80     // network connection.
81     static void onLSCPClientNewInputAvailable(LSCPClient* client) {
82     g_todo.Set(true);
83     }
84    
85     // Called by the keyboard reading thread, whenever a key stroke was received.
86     static void onNewKeyboardInputAvailable(KeyboardReader* reader) {
87     g_todo.Set(true);
88     }
89    
90 schoenebeck 2532 // Called on network error or when server side closed the TCP connection.
91     static void onLSCPClientErrorOccured(LSCPClient* client) {
92     quitApp();
93     }
94    
95     /// Will be called when the user hits tab key to trigger auto completion.
96 schoenebeck 2516 static void autoComplete() {
97     if (g_suggestedPortion.empty()) return;
98     String s;
99     // let the server delete mistaken characters first
100     for (int i = 0; i < g_badPortion.size(); ++i) s += '\b';
101     // now add the suggested, correct characters
102     s += g_suggestedPortion;
103     g_suggestedPortion.clear();
104 schoenebeck 2517 g_client->send(s);
105 schoenebeck 2516 }
106    
107 schoenebeck 2518 static void commandFromHistory(int offset) {
108     if (g_commandHistoryIndex + offset < 0 ||
109     g_commandHistoryIndex + offset >= g_commandHistory.size()) return;
110     g_commandHistoryIndex += offset;
111     int len = g_goodPortion.size() + g_badPortion.size();
112     // erase current active line
113     for (int i = 0; i < len; ++i) g_client->send('\b');
114     // transmit new/old line to LSCP server
115     String command = g_commandHistory[g_commandHistory.size() - g_commandHistoryIndex - 1];
116     g_client->send(command);
117     }
118    
119 schoenebeck 2532 /// Will be called when the user hits arrow up key, to iterate to an older command line.
120 schoenebeck 2518 static void previousCommand() {
121     commandFromHistory(1);
122     }
123    
124 schoenebeck 2532 /// Will be called when the user hits arrow down key, to iterate to a more recent command line.
125 schoenebeck 2518 static void nextCommand() {
126     commandFromHistory(-1);
127     }
128    
129 schoenebeck 2532 /// Will be called whenever the user hits ENTER, to store the line in the command history.
130 schoenebeck 2518 static void storeCommandInHistory(const String& sCommand) {
131     g_commandHistoryIndex = -1; // reset history index
132     // don't save the command if the previous one was the same
133     if (g_commandHistory.empty() || g_commandHistory.back() != sCommand)
134     g_commandHistory.push_back(sCommand);
135     }
136    
137 schoenebeck 2534 /// Splits the given string into individual lines for the given screen resolution.
138     static std::vector<String> splitForScreen(const String& s, int cols, int rows) {
139     std::vector<String> lines;
140     if (rows <= 0 || cols <= 0) return lines;
141     String line;
142     for (int i = 0; i < s.size(); ++i) {
143     char c = s[i];
144     if (c == '\r') continue;
145     if (c == '\n') {
146     lines.push_back(line);
147     if (lines.size() >= rows) return lines;
148     line.clear();
149     continue;
150     }
151     line += c;
152     if (line.size() >= cols) {
153     lines.push_back(line);
154     if (lines.size() >= rows) return lines;
155     line.clear();
156     }
157     }
158     return lines;
159     }
160    
161 schoenebeck 2525 /**
162 schoenebeck 2534 * Will be called whenever the LSCP documentation reference to be shown, has
163     * been changed. This call will accordingly update the screen with the new
164     * documentation text received from LSCP server.
165     */
166     static void updateDoc() {
167     const int vOffset = 2;
168    
169     CCursor originalCursor = CCursor::now();
170     CCursor cursor = originalCursor;
171     cursor.toColumn(0).down(vOffset);
172    
173     // wipe out current documentation off screen
174     cursor.clearVerticalToBottom();
175    
176     if (g_doc.empty()) {
177     // restore original cursor position
178     cursor.up(vOffset).toColumn(originalCursor.column());
179     return;
180     }
181    
182     // get screen size (in characters)
183     const int cols = TerminalCtrl::columns();
184     const int rows = TerminalCtrl::rows();
185    
186     // convert the string block into individual lines according to screen resolution
187     std::vector<String> lines = splitForScreen(g_doc, cols - 1, rows);
188    
189     // print lines onto screen
190     for (int row = 0; row < lines.size(); ++row)
191     std::cout << lines[row] << std::endl;
192    
193     // restore original cursor position
194     cursor.up(vOffset + lines.size()).toColumn(originalCursor.column());
195     }
196    
197     /**
198 schoenebeck 2525 * This LSCP shell application is designed as thin client. That means the heavy
199     * LSCP grammar evaluation tasks are peformed by the LSCP server and the shell
200     * application's task are simply limited to forward individual characters typed
201     * by the user to the LSCP server and showing the result of the LSCP server's
202     * evaluation to the user on the screen. This has the big advantage that the
203     * shell works perfectly with any machine running (some minimum recent version
204     * of) LinuxSampler, no matter which precise LSCP version the server side
205     * is using. Which reduces the maintenance efforts for the shell application
206     * development tremendously.
207     *
208     * As soon as this application established a TCP connection to a LSCP server, it
209     * sends this command to the LSCP server:
210     * @code
211     * SET SHELL INTERACT 1
212     * @endcode
213     * which will inform the LSCP server that this LSCP client is actually a LSCP
214     * shell application. The shell will then simply forward every single character
215 schoenebeck 2532 * typed by the user immediately to the LSCP server. The LSCP server in turn
216     * will evaluate every single character received and will return immediately a
217 schoenebeck 2525 * specially formatted string to the shell application like (assuming the user's
218     * current command line was "CREATE AUasdf"):
219     * @code
220     * SHU:1:CREATE AU{{GF}}asdf{{CU}}{{SB}}DIO_OUTPUT_DEVICE
221     * @endcode
222     * which informs this shell application about the result of the LSCP grammar
223     * evaluation and allows the shell to easily show that result of the evaluation
224     * to the user on the screen. In the example reply above, the prefix "SHU:" just
225     * indicates to the shell application that this response line is the result
226     * of the latest grammar evaluation, the number followed (here 1) indicates the
227     * semantic status of the current command line:
228     *
229     * - 0: Command line is complete, thus ENTER key may be hit by the user now.
230     * - 1: Current command line contains syntax error(s).
231     * - 2: Command line is incomplete, but contains no syntax errors so far.
232     *
233     * Then the actual current command line follows, with special markers:
234     *
235     * - Left of "{{GF}}" the command line is syntactically correct, right of that
236     * marker the command line is syntactically wrong.
237     *
238     * - Marker "{{CU}}" indicates the current cursor position of the command line.
239     *
240     * - Right of "{{SB}}" follows the current auto completion suggestion, so that
241     * string portion was not typed by the user yet, but is expected to be typed
242 schoenebeck 2532 * by him next, to retain syntax correctness. The auto completion portion is
243     * added by the LSCP server only if there is one unique way to add characters
244     * to the current command line. If there are multiple possibilities, than this
245     * portion is missing due to ambiguity.
246     *
247     * - Optionally there might also be a "{{PB}" marker on right hand side of the
248     * line. The text right to that marker reflects all possibilities at the
249     * user's current input position (which cannot be auto completed) due to
250     * ambiguity, including abstract (a.k.a. "non-terminal") symbols like:
251     * @code
252     * <digit>, <text>, <number>, etc.
253     * @endcode
254     * This portion is added by the LSCP server only if there is not a unique way
255     * to add characters to the current command line.
256 schoenebeck 2525 */
257 schoenebeck 2515 int main(int argc, char *argv[]) {
258     String host = LSCP_DEFAULT_HOST;
259     int port = LSCP_DEFAULT_PORT;
260 schoenebeck 2516 bool autoCorrect = true;
261 schoenebeck 2534 bool showDoc = true;
262 schoenebeck 2515
263     // parse command line arguments
264     for (int i = 0; i < argc; ++i) {
265     String s = argv[i];
266     if (s == "-h" || s == "--host") {
267     if (++i >= argc) {
268     printUsage();
269     return -1;
270     }
271     host = argv[i];
272     } else if (s == "-p" || s == "--port") {
273     if (++i >= argc) {
274     printUsage();
275     return -1;
276     }
277     port = atoi(argv[i]);
278     if (port <= 0) {
279     cerr << "Error: invalid port argument \"" << argv[i] << "\"\n";
280     return -1;
281     }
282 schoenebeck 2516 } else if (s == "--no-auto-correct") {
283     autoCorrect = false;
284 schoenebeck 2534 } else if (s == "--no-doc") {
285     showDoc = false;
286 schoenebeck 2515 } else if (s[0] == '-') { // invalid / unknown command line argument ...
287     printUsage();
288     return -1;
289     }
290     }
291    
292     // try to connect to the sampler's LSCP server and start a thread for
293     // receiving incoming network data from the sampler's LSCP server
294 schoenebeck 2517 g_client = new LSCPClient;
295 schoenebeck 2532 g_client->setErrorCallback(onLSCPClientErrorOccured);
296 schoenebeck 2517 if (!g_client->connect(host, port)) return -1;
297 schoenebeck 2534 g_client->sendCommandSync(
298     (showDoc) ? "SET SHELL DOC 1" : "SET SHELL DOC 0"
299     );
300 schoenebeck 2517 String sResponse = g_client->sendCommandSync(
301 schoenebeck 2516 (autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0"
302     );
303 schoenebeck 2517 sResponse = g_client->sendCommandSync("SET SHELL INTERACT 1");
304 schoenebeck 2515 if (sResponse.substr(0, 2) != "OK") {
305     cerr << "Error: sampler too old, it does not support shell instructions\n";
306     return -1;
307     }
308 schoenebeck 2534 g_client->setCallback(onLSCPClientNewInputAvailable);
309    
310 schoenebeck 2517 printWelcome();
311     printPrompt();
312 schoenebeck 2515
313     // start a thread for reading from the local text input keyboard
314     // (keyboard echo will be disabled as well to have a clean control on what
315     // is appearing on the screen)
316 schoenebeck 2517 g_keyboardReader = new KeyboardReader;
317     g_keyboardReader->setCallback(onNewKeyboardInputAvailable);
318     g_keyboardReader->startReading();
319 schoenebeck 2518
320     int iKbdEscapeCharsExpected = 0;
321     char kbdPrevEscapeChar;
322    
323 schoenebeck 2515 // main thread's loop
324 schoenebeck 2525 //
325     // This application runs 3 threads:
326     //
327     // - Keyboard thread: reads constantly on stdin for new characters (which
328     // will block this keyboard thread until new character(s) were typed by
329     // the user) and pushes the typed characters into a FIFO buffer.
330     //
331     // - Network thread: reads constantly on the TCP connection for new bytes
332     // being sent by the LSCP server (which will block this network thread
333     // until new bytes were received) and pushes the received bytes into a
334     // FIFO buffer.
335     //
336     // - Main thread: this thread runs in the loop below. The main thread sleeps
337 schoenebeck 2532 // (by using the "g_todo" condition variable) until either new keys on the
338     // keyboard were stroke by the user or until new bytes were received from
339     // the LSCP server. The main thread will then accordingly send the typed
340     // characters to the LSCP server and/or show the result of the LSCP
341     // server's latest evaluation to the user on the screen (by pulling those
342     // data from the other two thread's FIFO buffers).
343 schoenebeck 2515 while (true) {
344     // sleep until either new data from the network or from keyboard arrived
345     g_todo.WaitIf(false);
346     // immediately unset the condition variable and unlock it
347     g_todo.Set(false);
348     g_todo.Unlock();
349    
350     // did network data arrive?
351 schoenebeck 2517 while (g_client->messageComplete()) {
352     String line = *g_client->popLine();
353 schoenebeck 2515 //printf("line '%s'\n", line.c_str());
354     if (line.substr(0,4) == "SHU:") {
355     int code = 0, n = 0;
356     int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n);
357     if (res >= 1) {
358     String s = line.substr(n);
359    
360 schoenebeck 2516 // extract portion that is already syntactically correct
361     size_t iGood = s.find(LSCP_SHK_GOOD_FRONT);
362     String sGood = s.substr(0, iGood);
363     if (sGood.find(LSCP_SHK_CURSOR) != string::npos)
364     sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
365 schoenebeck 2515
366 schoenebeck 2516 // extract portion that was written syntactically incorrect
367     String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT));
368     if (sBad.find(LSCP_SHK_CURSOR) != string::npos)
369     sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
370     if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
371     sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion
372 schoenebeck 2528 if (sBad.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos)
373     sBad.erase(sBad.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion
374 schoenebeck 2515
375 schoenebeck 2516 // extract portion that is suggested for auto completion
376     String sSuggest;
377     if (s.find(LSCP_SHK_SUGGEST_BACK) != string::npos) {
378     sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK));
379     if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos)
380     sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
381 schoenebeck 2528 if (sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos)
382     sSuggest.erase(sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion
383 schoenebeck 2516 }
384    
385 schoenebeck 2528 // extract portion that provides all current possibilities
386     // (that is all branches in the current grammar tree)
387     String sPossibilities;
388     if (s.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) {
389     sPossibilities = s.substr(s.find(LSCP_SHK_POSSIBILITIES_BACK) + strlen(LSCP_SHK_POSSIBILITIES_BACK));
390     }
391    
392 schoenebeck 2516 // extract current cursor position
393     int cursorColumn = sGood.size();
394     String sCursor = s;
395     if (sCursor.find(LSCP_SHK_GOOD_FRONT) != string::npos)
396     sCursor.erase(sCursor.find(LSCP_SHK_GOOD_FRONT), strlen(LSCP_SHK_GOOD_FRONT)); // erase good/bad marker
397     if (sCursor.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
398     sCursor.erase(sCursor.find(LSCP_SHK_SUGGEST_BACK), strlen(LSCP_SHK_SUGGEST_BACK)); // erase suggestion marker
399     if (sCursor.find(LSCP_SHK_CURSOR) != string::npos)
400     cursorColumn = sCursor.find(LSCP_SHK_CURSOR);
401    
402     // store those informations globally for the auto-completion
403     // feature
404     g_goodPortion = sGood;
405     g_badPortion = sBad;
406     g_suggestedPortion = sSuggest;
407    
408     //printf("line '%s' good='%s' bad='%s' suggested='%s' cursor=%d\n", line.c_str(), sGood.c_str(), sBad.c_str(), sSuggest.c_str(), cursorColumn);
409    
410 schoenebeck 2528 // clear current command line on screen
411     // (which may have been printed over several lines)
412 schoenebeck 2516 CCursor cursor = CCursor::now().toColumn(0).clearLine();
413 schoenebeck 2528 for (int i = 0; i < g_linesActive; ++i)
414     cursor = cursor.down().clearLine();
415     if (g_linesActive) cursor = cursor.up(g_linesActive).toColumn(0);
416 schoenebeck 2517 printPrompt();
417 schoenebeck 2516
418 schoenebeck 2528 // print out the gathered informations on the screen
419     TerminalPrinter p;
420 schoenebeck 2515 CFmt cfmt;
421     if (code == LSCP_SHU_COMPLETE) cfmt.bold().green();
422     else cfmt.bold().white();
423 schoenebeck 2528 p << sGood;
424 schoenebeck 2515 cfmt.reset().red();
425 schoenebeck 2528 p << sBad;
426 schoenebeck 2516 cfmt.bold().yellow();
427 schoenebeck 2528 p << sSuggest;
428     if (!sPossibilities.empty())
429     p << " <- " << sPossibilities;
430 schoenebeck 2516
431 schoenebeck 2528 // move cursor back to the appropriate input position in
432     // the command line (which may be several lines above)
433     g_linesActive = p.linesAdvanced();
434     if (p.linesAdvanced()) cursor.up(p.linesAdvanced());
435 schoenebeck 2517 cursor.toColumn(cursorColumn + promptOffset());
436 schoenebeck 2515 }
437 schoenebeck 2534 } else if (line.substr(0,4) == "SHD:") { // new LSCP doc reference section received ...
438     int code = LSCP_SHD_NO_MATCH;
439     int res = sscanf(line.c_str(), "SHD:%d", &code);
440     g_doc.clear();
441     if (code == LSCP_SHD_MATCH) {
442     while (true) { // multi-line response expected (terminated by dot line) ...
443     if (line.substr(0, 1) == ".") break;
444     if (!g_client->lineAvailable()) break;
445     line = *g_client->popLine();
446     g_doc += line;
447     }
448     }
449     updateDoc();
450 schoenebeck 2515 } else if (line.substr(0,2) == "OK") { // single-line response expected ...
451     cout << endl << flush;
452 schoenebeck 2534
453     // wipe out potential current documentation off screen
454     CCursor cursor = CCursor::now();
455     cursor.clearVerticalToBottom();
456    
457 schoenebeck 2515 CFmt cfmt;
458     cfmt.green();
459     cout << line.substr(0,2) << flush;
460     cfmt.reset();
461     cout << line.substr(2) << endl << flush;
462 schoenebeck 2517 printPrompt();
463 schoenebeck 2515 } else if (line.substr(0,3) == "WRN") { // single-line response expected ...
464     cout << endl << flush;
465 schoenebeck 2534
466     // wipe out potential current documentation off screen
467     CCursor cursor = CCursor::now();
468     cursor.clearVerticalToBottom();
469    
470 schoenebeck 2515 CFmt cfmt;
471     cfmt.yellow();
472     cout << line.substr(0,3) << flush;
473     cfmt.reset();
474     cout << line.substr(3) << endl << flush;
475 schoenebeck 2517 printPrompt();
476 schoenebeck 2515 } else if (line.substr(0,3) == "ERR") { // single-line response expected ...
477     cout << endl << flush;
478 schoenebeck 2534
479     // wipe out potential current documentation off screen
480     CCursor cursor = CCursor::now();
481     cursor.clearVerticalToBottom();
482    
483 schoenebeck 2515 CFmt cfmt;
484     cfmt.bold().red();
485     cout << line.substr(0,3) << flush;
486     cfmt.reset();
487     cout << line.substr(3) << endl << flush;
488 schoenebeck 2517 printPrompt();
489     } else if (g_client->multiLine()) { // multi-line response expected ...
490 schoenebeck 2515 cout << endl << flush;
491 schoenebeck 2534
492     // wipe out potential current documentation off screen
493     CCursor cursor = CCursor::now();
494     cursor.clearVerticalToBottom();
495    
496 schoenebeck 2515 while (true) {
497     cout << line << endl << flush;
498     if (line.substr(0, 1) == ".") break;
499 schoenebeck 2517 if (!g_client->lineAvailable()) break;
500     line = *g_client->popLine();
501 schoenebeck 2515 }
502 schoenebeck 2517 printPrompt();
503 schoenebeck 2515 } else {
504 schoenebeck 2534 cout << endl << flush;
505    
506     // wipe out potential current documentation off screen
507     CCursor cursor = CCursor::now();
508     cursor.clearVerticalToBottom();
509    
510     cout << line << endl << flush;
511 schoenebeck 2517 printPrompt();
512 schoenebeck 2515 }
513     }
514    
515     // did keyboard input arrive?
516 schoenebeck 2517 while (g_keyboardReader->charAvailable()) {
517     char c = g_keyboardReader->popChar();
518 schoenebeck 2515
519     //std::cout << c << "(" << int(c) << ")" << std::endl << std::flush;
520 schoenebeck 2518 if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ...
521     iKbdEscapeCharsExpected--;
522     if (iKbdEscapeCharsExpected) kbdPrevEscapeChar = c;
523     else { // escape sequence is complete ...
524     if (kbdPrevEscapeChar == 91 && c == 65) // up key
525     previousCommand();
526     else if (kbdPrevEscapeChar == 91 && c == 66) // down key
527     nextCommand();
528 schoenebeck 2531 else if (kbdPrevEscapeChar == 91 && c == 68) // left key
529     g_client->send(2); // custom usage of this ASCII code
530     else if (kbdPrevEscapeChar == 91 && c == 67) // right key
531     g_client->send(3); // custom usage of this ASCII code
532 schoenebeck 2518 }
533     continue; // don't send this escape sequence character to LSCP server
534     } else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ...
535     iKbdEscapeCharsExpected = 2;
536     continue; // don't send ESC character to LSCP server
537     } else if (c == KBD_BACKSPACE) {
538 schoenebeck 2528 c = '\b';
539 schoenebeck 2516 } else if (c == '\t') { // auto completion ...
540     autoComplete();
541     continue; // don't send tab character to LSCP server
542 schoenebeck 2518 } else if (c == '\n') {
543     storeCommandInHistory(g_goodPortion + g_badPortion);
544 schoenebeck 2515 }
545    
546 schoenebeck 2517 g_client->send(c);
547 schoenebeck 2515 }
548     }
549    
550     return 0;
551     }

  ViewVC Help
Powered by ViewVC