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 |
2515 |
|
43 |
|
|
static void printUsage() { |
44 |
|
|
cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl; |
45 |
|
|
cout << endl; |
46 |
|
|
cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl; |
47 |
|
|
cout << endl; |
48 |
|
|
cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl; |
49 |
|
|
cout << endl; |
50 |
|
|
cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl; |
51 |
|
|
cout << endl; |
52 |
schoenebeck |
2516 |
cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl; |
53 |
|
|
cout << endl; |
54 |
schoenebeck |
2515 |
} |
55 |
|
|
|
56 |
schoenebeck |
2517 |
static void printWelcome() { |
57 |
|
|
cout << "Welcome to lscp " << VERSION << ", the LinuxSampler Control Protocol (LSCP) shell." << endl; |
58 |
|
|
cout << endl; |
59 |
|
|
} |
60 |
|
|
|
61 |
|
|
static void printPrompt() { |
62 |
|
|
cout << g_prompt << flush; |
63 |
|
|
} |
64 |
|
|
|
65 |
|
|
static int promptOffset() { |
66 |
|
|
return g_prompt.size(); |
67 |
|
|
} |
68 |
|
|
|
69 |
schoenebeck |
2515 |
// Called by the network reading thread, whenever new data arrived from the |
70 |
|
|
// network connection. |
71 |
|
|
static void onLSCPClientNewInputAvailable(LSCPClient* client) { |
72 |
|
|
g_todo.Set(true); |
73 |
|
|
} |
74 |
|
|
|
75 |
|
|
// Called by the keyboard reading thread, whenever a key stroke was received. |
76 |
|
|
static void onNewKeyboardInputAvailable(KeyboardReader* reader) { |
77 |
|
|
g_todo.Set(true); |
78 |
|
|
} |
79 |
|
|
|
80 |
schoenebeck |
2516 |
static void autoComplete() { |
81 |
|
|
if (g_suggestedPortion.empty()) return; |
82 |
|
|
String s; |
83 |
|
|
// let the server delete mistaken characters first |
84 |
|
|
for (int i = 0; i < g_badPortion.size(); ++i) s += '\b'; |
85 |
|
|
// now add the suggested, correct characters |
86 |
|
|
s += g_suggestedPortion; |
87 |
|
|
g_suggestedPortion.clear(); |
88 |
schoenebeck |
2517 |
g_client->send(s); |
89 |
schoenebeck |
2516 |
} |
90 |
|
|
|
91 |
schoenebeck |
2518 |
static void commandFromHistory(int offset) { |
92 |
|
|
if (g_commandHistoryIndex + offset < 0 || |
93 |
|
|
g_commandHistoryIndex + offset >= g_commandHistory.size()) return; |
94 |
|
|
g_commandHistoryIndex += offset; |
95 |
|
|
int len = g_goodPortion.size() + g_badPortion.size(); |
96 |
|
|
// erase current active line |
97 |
|
|
for (int i = 0; i < len; ++i) g_client->send('\b'); |
98 |
|
|
// transmit new/old line to LSCP server |
99 |
|
|
String command = g_commandHistory[g_commandHistory.size() - g_commandHistoryIndex - 1]; |
100 |
|
|
g_client->send(command); |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
static void previousCommand() { |
104 |
|
|
commandFromHistory(1); |
105 |
|
|
} |
106 |
|
|
|
107 |
|
|
static void nextCommand() { |
108 |
|
|
commandFromHistory(-1); |
109 |
|
|
} |
110 |
|
|
|
111 |
|
|
static void storeCommandInHistory(const String& sCommand) { |
112 |
|
|
g_commandHistoryIndex = -1; // reset history index |
113 |
|
|
// don't save the command if the previous one was the same |
114 |
|
|
if (g_commandHistory.empty() || g_commandHistory.back() != sCommand) |
115 |
|
|
g_commandHistory.push_back(sCommand); |
116 |
|
|
} |
117 |
|
|
|
118 |
schoenebeck |
2525 |
/** |
119 |
|
|
* This LSCP shell application is designed as thin client. That means the heavy |
120 |
|
|
* LSCP grammar evaluation tasks are peformed by the LSCP server and the shell |
121 |
|
|
* application's task are simply limited to forward individual characters typed |
122 |
|
|
* by the user to the LSCP server and showing the result of the LSCP server's |
123 |
|
|
* evaluation to the user on the screen. This has the big advantage that the |
124 |
|
|
* shell works perfectly with any machine running (some minimum recent version |
125 |
|
|
* of) LinuxSampler, no matter which precise LSCP version the server side |
126 |
|
|
* is using. Which reduces the maintenance efforts for the shell application |
127 |
|
|
* development tremendously. |
128 |
|
|
* |
129 |
|
|
* As soon as this application established a TCP connection to a LSCP server, it |
130 |
|
|
* sends this command to the LSCP server: |
131 |
|
|
* @code |
132 |
|
|
* SET SHELL INTERACT 1 |
133 |
|
|
* @endcode |
134 |
|
|
* which will inform the LSCP server that this LSCP client is actually a LSCP |
135 |
|
|
* shell application. The shell will then simply forward every single character |
136 |
|
|
* typed by the user immediately to the LSCP server, which in turn will evaluate |
137 |
|
|
* every single character typed by the user and will return immediately a |
138 |
|
|
* specially formatted string to the shell application like (assuming the user's |
139 |
|
|
* current command line was "CREATE AUasdf"): |
140 |
|
|
* @code |
141 |
|
|
* SHU:1:CREATE AU{{GF}}asdf{{CU}}{{SB}}DIO_OUTPUT_DEVICE |
142 |
|
|
* @endcode |
143 |
|
|
* which informs this shell application about the result of the LSCP grammar |
144 |
|
|
* evaluation and allows the shell to easily show that result of the evaluation |
145 |
|
|
* to the user on the screen. In the example reply above, the prefix "SHU:" just |
146 |
|
|
* indicates to the shell application that this response line is the result |
147 |
|
|
* of the latest grammar evaluation, the number followed (here 1) indicates the |
148 |
|
|
* semantic status of the current command line: |
149 |
|
|
* |
150 |
|
|
* - 0: Command line is complete, thus ENTER key may be hit by the user now. |
151 |
|
|
* - 1: Current command line contains syntax error(s). |
152 |
|
|
* - 2: Command line is incomplete, but contains no syntax errors so far. |
153 |
|
|
* |
154 |
|
|
* Then the actual current command line follows, with special markers: |
155 |
|
|
* |
156 |
|
|
* - Left of "{{GF}}" the command line is syntactically correct, right of that |
157 |
|
|
* marker the command line is syntactically wrong. |
158 |
|
|
* |
159 |
|
|
* - Marker "{{CU}}" indicates the current cursor position of the command line. |
160 |
|
|
* |
161 |
|
|
* - Right of "{{SB}}" follows the current auto completion suggestion, so that |
162 |
|
|
* string portion was not typed by the user yet, but is expected to be typed |
163 |
|
|
* by him next to retain syntax correctness. |
164 |
|
|
*/ |
165 |
schoenebeck |
2515 |
int main(int argc, char *argv[]) { |
166 |
|
|
String host = LSCP_DEFAULT_HOST; |
167 |
|
|
int port = LSCP_DEFAULT_PORT; |
168 |
schoenebeck |
2516 |
bool autoCorrect = true; |
169 |
schoenebeck |
2515 |
|
170 |
|
|
// parse command line arguments |
171 |
|
|
for (int i = 0; i < argc; ++i) { |
172 |
|
|
String s = argv[i]; |
173 |
|
|
if (s == "-h" || s == "--host") { |
174 |
|
|
if (++i >= argc) { |
175 |
|
|
printUsage(); |
176 |
|
|
return -1; |
177 |
|
|
} |
178 |
|
|
host = argv[i]; |
179 |
|
|
} else if (s == "-p" || s == "--port") { |
180 |
|
|
if (++i >= argc) { |
181 |
|
|
printUsage(); |
182 |
|
|
return -1; |
183 |
|
|
} |
184 |
|
|
port = atoi(argv[i]); |
185 |
|
|
if (port <= 0) { |
186 |
|
|
cerr << "Error: invalid port argument \"" << argv[i] << "\"\n"; |
187 |
|
|
return -1; |
188 |
|
|
} |
189 |
schoenebeck |
2516 |
} else if (s == "--no-auto-correct") { |
190 |
|
|
autoCorrect = false; |
191 |
schoenebeck |
2515 |
} else if (s[0] == '-') { // invalid / unknown command line argument ... |
192 |
|
|
printUsage(); |
193 |
|
|
return -1; |
194 |
|
|
} |
195 |
|
|
} |
196 |
|
|
|
197 |
|
|
// try to connect to the sampler's LSCP server and start a thread for |
198 |
|
|
// receiving incoming network data from the sampler's LSCP server |
199 |
schoenebeck |
2517 |
g_client = new LSCPClient; |
200 |
|
|
g_client->setCallback(onLSCPClientNewInputAvailable); |
201 |
|
|
if (!g_client->connect(host, port)) return -1; |
202 |
|
|
String sResponse = g_client->sendCommandSync( |
203 |
schoenebeck |
2516 |
(autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0" |
204 |
|
|
); |
205 |
schoenebeck |
2517 |
sResponse = g_client->sendCommandSync("SET SHELL INTERACT 1"); |
206 |
schoenebeck |
2515 |
if (sResponse.substr(0, 2) != "OK") { |
207 |
|
|
cerr << "Error: sampler too old, it does not support shell instructions\n"; |
208 |
|
|
return -1; |
209 |
|
|
} |
210 |
schoenebeck |
2517 |
|
211 |
|
|
printWelcome(); |
212 |
|
|
printPrompt(); |
213 |
schoenebeck |
2515 |
|
214 |
|
|
// start a thread for reading from the local text input keyboard |
215 |
|
|
// (keyboard echo will be disabled as well to have a clean control on what |
216 |
|
|
// is appearing on the screen) |
217 |
schoenebeck |
2517 |
g_keyboardReader = new KeyboardReader; |
218 |
|
|
g_keyboardReader->setCallback(onNewKeyboardInputAvailable); |
219 |
|
|
g_keyboardReader->startReading(); |
220 |
schoenebeck |
2518 |
|
221 |
|
|
int iKbdEscapeCharsExpected = 0; |
222 |
|
|
char kbdPrevEscapeChar; |
223 |
|
|
|
224 |
schoenebeck |
2515 |
// main thread's loop |
225 |
schoenebeck |
2525 |
// |
226 |
|
|
// This application runs 3 threads: |
227 |
|
|
// |
228 |
|
|
// - Keyboard thread: reads constantly on stdin for new characters (which |
229 |
|
|
// will block this keyboard thread until new character(s) were typed by |
230 |
|
|
// the user) and pushes the typed characters into a FIFO buffer. |
231 |
|
|
// |
232 |
|
|
// - Network thread: reads constantly on the TCP connection for new bytes |
233 |
|
|
// being sent by the LSCP server (which will block this network thread |
234 |
|
|
// until new bytes were received) and pushes the received bytes into a |
235 |
|
|
// FIFO buffer. |
236 |
|
|
// |
237 |
|
|
// - Main thread: this thread runs in the loop below. The main thread sleeps |
238 |
|
|
// (by using the "g_todo" semaphore) until either new keys on the keyboard |
239 |
|
|
// were stroke by the user or until new bytes were received from the LSCP |
240 |
|
|
// server. The main thread will then accordingly send the typed characters |
241 |
|
|
// to the LSCP server and/or show the result of the LSCP server's latest |
242 |
|
|
// evaluation to the user on the screen (by pulling those data from the |
243 |
|
|
// other two thread's FIFO buffers). |
244 |
schoenebeck |
2515 |
while (true) { |
245 |
|
|
// sleep until either new data from the network or from keyboard arrived |
246 |
|
|
g_todo.WaitIf(false); |
247 |
|
|
// immediately unset the condition variable and unlock it |
248 |
|
|
g_todo.Set(false); |
249 |
|
|
g_todo.Unlock(); |
250 |
|
|
|
251 |
|
|
// did network data arrive? |
252 |
schoenebeck |
2517 |
while (g_client->messageComplete()) { |
253 |
|
|
String line = *g_client->popLine(); |
254 |
schoenebeck |
2515 |
//printf("line '%s'\n", line.c_str()); |
255 |
|
|
if (line.substr(0,4) == "SHU:") { |
256 |
|
|
int code = 0, n = 0; |
257 |
|
|
int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n); |
258 |
|
|
if (res >= 1) { |
259 |
|
|
String s = line.substr(n); |
260 |
|
|
|
261 |
schoenebeck |
2516 |
// extract portion that is already syntactically correct |
262 |
|
|
size_t iGood = s.find(LSCP_SHK_GOOD_FRONT); |
263 |
|
|
String sGood = s.substr(0, iGood); |
264 |
|
|
if (sGood.find(LSCP_SHK_CURSOR) != string::npos) |
265 |
|
|
sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
266 |
schoenebeck |
2515 |
|
267 |
schoenebeck |
2516 |
// extract portion that was written syntactically incorrect |
268 |
|
|
String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT)); |
269 |
|
|
if (sBad.find(LSCP_SHK_CURSOR) != string::npos) |
270 |
|
|
sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
271 |
|
|
if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos) |
272 |
|
|
sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion |
273 |
schoenebeck |
2528 |
if (sBad.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) |
274 |
|
|
sBad.erase(sBad.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion |
275 |
schoenebeck |
2515 |
|
276 |
schoenebeck |
2516 |
// extract portion that is suggested for auto completion |
277 |
|
|
String sSuggest; |
278 |
|
|
if (s.find(LSCP_SHK_SUGGEST_BACK) != string::npos) { |
279 |
|
|
sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK)); |
280 |
|
|
if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos) |
281 |
|
|
sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
282 |
schoenebeck |
2528 |
if (sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) |
283 |
|
|
sSuggest.erase(sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion |
284 |
schoenebeck |
2516 |
} |
285 |
|
|
|
286 |
schoenebeck |
2528 |
// extract portion that provides all current possibilities |
287 |
|
|
// (that is all branches in the current grammar tree) |
288 |
|
|
String sPossibilities; |
289 |
|
|
if (s.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) { |
290 |
|
|
sPossibilities = s.substr(s.find(LSCP_SHK_POSSIBILITIES_BACK) + strlen(LSCP_SHK_POSSIBILITIES_BACK)); |
291 |
|
|
} |
292 |
|
|
|
293 |
schoenebeck |
2516 |
// extract current cursor position |
294 |
|
|
int cursorColumn = sGood.size(); |
295 |
|
|
String sCursor = s; |
296 |
|
|
if (sCursor.find(LSCP_SHK_GOOD_FRONT) != string::npos) |
297 |
|
|
sCursor.erase(sCursor.find(LSCP_SHK_GOOD_FRONT), strlen(LSCP_SHK_GOOD_FRONT)); // erase good/bad marker |
298 |
|
|
if (sCursor.find(LSCP_SHK_SUGGEST_BACK) != string::npos) |
299 |
|
|
sCursor.erase(sCursor.find(LSCP_SHK_SUGGEST_BACK), strlen(LSCP_SHK_SUGGEST_BACK)); // erase suggestion marker |
300 |
|
|
if (sCursor.find(LSCP_SHK_CURSOR) != string::npos) |
301 |
|
|
cursorColumn = sCursor.find(LSCP_SHK_CURSOR); |
302 |
|
|
|
303 |
|
|
// store those informations globally for the auto-completion |
304 |
|
|
// feature |
305 |
|
|
g_goodPortion = sGood; |
306 |
|
|
g_badPortion = sBad; |
307 |
|
|
g_suggestedPortion = sSuggest; |
308 |
|
|
|
309 |
|
|
//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); |
310 |
|
|
|
311 |
schoenebeck |
2528 |
// clear current command line on screen |
312 |
|
|
// (which may have been printed over several lines) |
313 |
schoenebeck |
2516 |
CCursor cursor = CCursor::now().toColumn(0).clearLine(); |
314 |
schoenebeck |
2528 |
for (int i = 0; i < g_linesActive; ++i) |
315 |
|
|
cursor = cursor.down().clearLine(); |
316 |
|
|
if (g_linesActive) cursor = cursor.up(g_linesActive).toColumn(0); |
317 |
schoenebeck |
2517 |
printPrompt(); |
318 |
schoenebeck |
2516 |
|
319 |
schoenebeck |
2528 |
// print out the gathered informations on the screen |
320 |
|
|
TerminalPrinter p; |
321 |
schoenebeck |
2515 |
CFmt cfmt; |
322 |
|
|
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
323 |
|
|
else cfmt.bold().white(); |
324 |
schoenebeck |
2528 |
p << sGood; |
325 |
schoenebeck |
2515 |
cfmt.reset().red(); |
326 |
schoenebeck |
2528 |
p << sBad; |
327 |
schoenebeck |
2516 |
cfmt.bold().yellow(); |
328 |
schoenebeck |
2528 |
p << sSuggest; |
329 |
|
|
if (!sPossibilities.empty()) |
330 |
|
|
p << " <- " << sPossibilities; |
331 |
schoenebeck |
2516 |
|
332 |
schoenebeck |
2528 |
// move cursor back to the appropriate input position in |
333 |
|
|
// the command line (which may be several lines above) |
334 |
|
|
g_linesActive = p.linesAdvanced(); |
335 |
|
|
if (p.linesAdvanced()) cursor.up(p.linesAdvanced()); |
336 |
schoenebeck |
2517 |
cursor.toColumn(cursorColumn + promptOffset()); |
337 |
schoenebeck |
2515 |
} |
338 |
|
|
} else if (line.substr(0,2) == "OK") { // single-line response expected ... |
339 |
|
|
cout << endl << flush; |
340 |
|
|
CFmt cfmt; |
341 |
|
|
cfmt.green(); |
342 |
|
|
cout << line.substr(0,2) << flush; |
343 |
|
|
cfmt.reset(); |
344 |
|
|
cout << line.substr(2) << endl << flush; |
345 |
schoenebeck |
2517 |
printPrompt(); |
346 |
schoenebeck |
2515 |
} else if (line.substr(0,3) == "WRN") { // single-line response expected ... |
347 |
|
|
cout << endl << flush; |
348 |
|
|
CFmt cfmt; |
349 |
|
|
cfmt.yellow(); |
350 |
|
|
cout << line.substr(0,3) << flush; |
351 |
|
|
cfmt.reset(); |
352 |
|
|
cout << line.substr(3) << endl << flush; |
353 |
schoenebeck |
2517 |
printPrompt(); |
354 |
schoenebeck |
2515 |
} else if (line.substr(0,3) == "ERR") { // single-line response expected ... |
355 |
|
|
cout << endl << flush; |
356 |
|
|
CFmt cfmt; |
357 |
|
|
cfmt.bold().red(); |
358 |
|
|
cout << line.substr(0,3) << flush; |
359 |
|
|
cfmt.reset(); |
360 |
|
|
cout << line.substr(3) << endl << flush; |
361 |
schoenebeck |
2517 |
printPrompt(); |
362 |
|
|
} else if (g_client->multiLine()) { // multi-line response expected ... |
363 |
schoenebeck |
2515 |
cout << endl << flush; |
364 |
|
|
while (true) { |
365 |
|
|
cout << line << endl << flush; |
366 |
|
|
if (line.substr(0, 1) == ".") break; |
367 |
schoenebeck |
2517 |
if (!g_client->lineAvailable()) break; |
368 |
|
|
line = *g_client->popLine(); |
369 |
schoenebeck |
2515 |
} |
370 |
schoenebeck |
2517 |
printPrompt(); |
371 |
schoenebeck |
2515 |
} else { |
372 |
|
|
cout << endl << line << endl << flush; |
373 |
schoenebeck |
2517 |
printPrompt(); |
374 |
schoenebeck |
2515 |
} |
375 |
|
|
} |
376 |
|
|
|
377 |
|
|
// did keyboard input arrive? |
378 |
schoenebeck |
2517 |
while (g_keyboardReader->charAvailable()) { |
379 |
|
|
char c = g_keyboardReader->popChar(); |
380 |
schoenebeck |
2515 |
|
381 |
|
|
//std::cout << c << "(" << int(c) << ")" << std::endl << std::flush; |
382 |
schoenebeck |
2518 |
if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ... |
383 |
|
|
iKbdEscapeCharsExpected--; |
384 |
|
|
if (iKbdEscapeCharsExpected) kbdPrevEscapeChar = c; |
385 |
|
|
else { // escape sequence is complete ... |
386 |
|
|
if (kbdPrevEscapeChar == 91 && c == 65) // up key |
387 |
|
|
previousCommand(); |
388 |
|
|
else if (kbdPrevEscapeChar == 91 && c == 66) // down key |
389 |
|
|
nextCommand(); |
390 |
schoenebeck |
2531 |
else if (kbdPrevEscapeChar == 91 && c == 68) // left key |
391 |
|
|
g_client->send(2); // custom usage of this ASCII code |
392 |
|
|
else if (kbdPrevEscapeChar == 91 && c == 67) // right key |
393 |
|
|
g_client->send(3); // custom usage of this ASCII code |
394 |
schoenebeck |
2518 |
} |
395 |
|
|
continue; // don't send this escape sequence character to LSCP server |
396 |
|
|
} else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ... |
397 |
|
|
iKbdEscapeCharsExpected = 2; |
398 |
|
|
continue; // don't send ESC character to LSCP server |
399 |
|
|
} else if (c == KBD_BACKSPACE) { |
400 |
schoenebeck |
2528 |
c = '\b'; |
401 |
schoenebeck |
2516 |
} else if (c == '\t') { // auto completion ... |
402 |
|
|
autoComplete(); |
403 |
|
|
continue; // don't send tab character to LSCP server |
404 |
schoenebeck |
2518 |
} else if (c == '\n') { |
405 |
|
|
storeCommandInHistory(g_goodPortion + g_badPortion); |
406 |
schoenebeck |
2515 |
} |
407 |
|
|
|
408 |
schoenebeck |
2517 |
g_client->send(c); |
409 |
schoenebeck |
2515 |
} |
410 |
|
|
} |
411 |
|
|
|
412 |
|
|
return 0; |
413 |
|
|
} |