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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2518 - (show annotations) (download)
Sat Feb 8 00:49:30 2014 UTC (10 years, 2 months ago) by schoenebeck
File size: 12924 byte(s)
* LSCP shell (WIP): added support for browsing the command
  history with up / down keys.
* LSCP server: fixed compilation error with Bison 3.x.
* Bumped version (1.0.0.svn31).

1 /*
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
21 #include "../common/global.h"
22 #include "../common/global_private.h"
23 #include "../common/Condition.h"
24
25 #define LSCP_DEFAULT_HOST "localhost"
26 #define LSCP_DEFAULT_PORT 8888
27
28 using namespace std;
29 using namespace LinuxSampler;
30
31 static LSCPClient* g_client = NULL;
32 static KeyboardReader* g_keyboardReader = NULL;
33 static Condition g_todo;
34 static String g_goodPortion;
35 static String g_badPortion;
36 static String g_suggestedPortion;
37 static const String g_prompt = "lscp=# ";
38 static std::vector<String> g_commandHistory;
39 static int g_commandHistoryIndex = -1;
40
41 static void printUsage() {
42 cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl;
43 cout << endl;
44 cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl;
45 cout << endl;
46 cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl;
47 cout << endl;
48 cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl;
49 cout << endl;
50 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
51 cout << endl;
52 }
53
54 static void printWelcome() {
55 cout << "Welcome to lscp " << VERSION << ", the LinuxSampler Control Protocol (LSCP) shell." << endl;
56 cout << endl;
57 }
58
59 static void printPrompt() {
60 cout << g_prompt << flush;
61 }
62
63 static int promptOffset() {
64 return g_prompt.size();
65 }
66
67 // Called by the network reading thread, whenever new data arrived from the
68 // network connection.
69 static void onLSCPClientNewInputAvailable(LSCPClient* client) {
70 g_todo.Set(true);
71 }
72
73 // Called by the keyboard reading thread, whenever a key stroke was received.
74 static void onNewKeyboardInputAvailable(KeyboardReader* reader) {
75 g_todo.Set(true);
76 }
77
78 static void autoComplete() {
79 if (g_suggestedPortion.empty()) return;
80 String s;
81 // let the server delete mistaken characters first
82 for (int i = 0; i < g_badPortion.size(); ++i) s += '\b';
83 // now add the suggested, correct characters
84 s += g_suggestedPortion;
85 g_suggestedPortion.clear();
86 g_client->send(s);
87 }
88
89 static void commandFromHistory(int offset) {
90 if (g_commandHistoryIndex + offset < 0 ||
91 g_commandHistoryIndex + offset >= g_commandHistory.size()) return;
92 g_commandHistoryIndex += offset;
93 int len = g_goodPortion.size() + g_badPortion.size();
94 // erase current active line
95 for (int i = 0; i < len; ++i) g_client->send('\b');
96 // transmit new/old line to LSCP server
97 String command = g_commandHistory[g_commandHistory.size() - g_commandHistoryIndex - 1];
98 g_client->send(command);
99 }
100
101 static void previousCommand() {
102 commandFromHistory(1);
103 }
104
105 static void nextCommand() {
106 commandFromHistory(-1);
107 }
108
109 static void storeCommandInHistory(const String& sCommand) {
110 g_commandHistoryIndex = -1; // reset history index
111 // don't save the command if the previous one was the same
112 if (g_commandHistory.empty() || g_commandHistory.back() != sCommand)
113 g_commandHistory.push_back(sCommand);
114 }
115
116 int main(int argc, char *argv[]) {
117 String host = LSCP_DEFAULT_HOST;
118 int port = LSCP_DEFAULT_PORT;
119 bool autoCorrect = true;
120
121 // parse command line arguments
122 for (int i = 0; i < argc; ++i) {
123 String s = argv[i];
124 if (s == "-h" || s == "--host") {
125 if (++i >= argc) {
126 printUsage();
127 return -1;
128 }
129 host = argv[i];
130 } else if (s == "-p" || s == "--port") {
131 if (++i >= argc) {
132 printUsage();
133 return -1;
134 }
135 port = atoi(argv[i]);
136 if (port <= 0) {
137 cerr << "Error: invalid port argument \"" << argv[i] << "\"\n";
138 return -1;
139 }
140 } else if (s == "--no-auto-correct") {
141 autoCorrect = false;
142 } else if (s[0] == '-') { // invalid / unknown command line argument ...
143 printUsage();
144 return -1;
145 }
146 }
147
148 // try to connect to the sampler's LSCP server and start a thread for
149 // receiving incoming network data from the sampler's LSCP server
150 g_client = new LSCPClient;
151 g_client->setCallback(onLSCPClientNewInputAvailable);
152 if (!g_client->connect(host, port)) return -1;
153 String sResponse = g_client->sendCommandSync(
154 (autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0"
155 );
156 sResponse = g_client->sendCommandSync("SET SHELL INTERACT 1");
157 if (sResponse.substr(0, 2) != "OK") {
158 cerr << "Error: sampler too old, it does not support shell instructions\n";
159 return -1;
160 }
161
162 printWelcome();
163 printPrompt();
164
165 // start a thread for reading from the local text input keyboard
166 // (keyboard echo will be disabled as well to have a clean control on what
167 // is appearing on the screen)
168 g_keyboardReader = new KeyboardReader;
169 g_keyboardReader->setCallback(onNewKeyboardInputAvailable);
170 g_keyboardReader->startReading();
171
172 int iKbdEscapeCharsExpected = 0;
173 char kbdPrevEscapeChar;
174
175 // main thread's loop
176 while (true) {
177 // sleep until either new data from the network or from keyboard arrived
178 g_todo.WaitIf(false);
179 // immediately unset the condition variable and unlock it
180 g_todo.Set(false);
181 g_todo.Unlock();
182
183 // did network data arrive?
184 while (g_client->messageComplete()) {
185 String line = *g_client->popLine();
186 //printf("line '%s'\n", line.c_str());
187 if (line.substr(0,4) == "SHU:") {
188 int code = 0, n = 0;
189 int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n);
190 if (res >= 1) {
191 String s = line.substr(n);
192
193 // extract portion that is already syntactically correct
194 size_t iGood = s.find(LSCP_SHK_GOOD_FRONT);
195 String sGood = s.substr(0, iGood);
196 if (sGood.find(LSCP_SHK_CURSOR) != string::npos)
197 sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
198
199 // extract portion that was written syntactically incorrect
200 String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT));
201 if (sBad.find(LSCP_SHK_CURSOR) != string::npos)
202 sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
203 if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
204 sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion
205
206 // extract portion that is suggested for auto completion
207 String sSuggest;
208 if (s.find(LSCP_SHK_SUGGEST_BACK) != string::npos) {
209 sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK));
210 if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos)
211 sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
212 }
213
214 // extract current cursor position
215 int cursorColumn = sGood.size();
216 String sCursor = s;
217 if (sCursor.find(LSCP_SHK_GOOD_FRONT) != string::npos)
218 sCursor.erase(sCursor.find(LSCP_SHK_GOOD_FRONT), strlen(LSCP_SHK_GOOD_FRONT)); // erase good/bad marker
219 if (sCursor.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
220 sCursor.erase(sCursor.find(LSCP_SHK_SUGGEST_BACK), strlen(LSCP_SHK_SUGGEST_BACK)); // erase suggestion marker
221 if (sCursor.find(LSCP_SHK_CURSOR) != string::npos)
222 cursorColumn = sCursor.find(LSCP_SHK_CURSOR);
223
224 // store those informations globally for the auto-completion
225 // feature
226 g_goodPortion = sGood;
227 g_badPortion = sBad;
228 g_suggestedPortion = sSuggest;
229
230 //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);
231
232 CCursor cursor = CCursor::now().toColumn(0).clearLine();
233 printPrompt();
234
235 CFmt cfmt;
236 if (code == LSCP_SHU_COMPLETE) cfmt.bold().green();
237 else cfmt.bold().white();
238 cout << sGood << flush;
239 cfmt.reset().red();
240 cout << sBad << flush;
241 cfmt.bold().yellow();
242 cout << sSuggest << flush;
243
244 cursor.toColumn(cursorColumn + promptOffset());
245 }
246 } else if (line.substr(0,2) == "OK") { // single-line response expected ...
247 cout << endl << flush;
248 CFmt cfmt;
249 cfmt.green();
250 cout << line.substr(0,2) << flush;
251 cfmt.reset();
252 cout << line.substr(2) << endl << flush;
253 printPrompt();
254 } else if (line.substr(0,3) == "WRN") { // single-line response expected ...
255 cout << endl << flush;
256 CFmt cfmt;
257 cfmt.yellow();
258 cout << line.substr(0,3) << flush;
259 cfmt.reset();
260 cout << line.substr(3) << endl << flush;
261 printPrompt();
262 } else if (line.substr(0,3) == "ERR") { // single-line response expected ...
263 cout << endl << flush;
264 CFmt cfmt;
265 cfmt.bold().red();
266 cout << line.substr(0,3) << flush;
267 cfmt.reset();
268 cout << line.substr(3) << endl << flush;
269 printPrompt();
270 } else if (g_client->multiLine()) { // multi-line response expected ...
271 cout << endl << flush;
272 while (true) {
273 cout << line << endl << flush;
274 if (line.substr(0, 1) == ".") break;
275 if (!g_client->lineAvailable()) break;
276 line = *g_client->popLine();
277 }
278 printPrompt();
279 } else {
280 cout << endl << line << endl << flush;
281 printPrompt();
282 }
283 }
284
285 // did keyboard input arrive?
286 while (g_keyboardReader->charAvailable()) {
287 char c = g_keyboardReader->popChar();
288
289 CFmt cfmt;
290 cfmt.white();
291 //std::cout << c << "(" << int(c) << ")" << std::endl << std::flush;
292 if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ...
293 iKbdEscapeCharsExpected--;
294 if (iKbdEscapeCharsExpected) kbdPrevEscapeChar = c;
295 else { // escape sequence is complete ...
296 if (kbdPrevEscapeChar == 91 && c == 65) // up key
297 previousCommand();
298 else if (kbdPrevEscapeChar == 91 && c == 66) // down key
299 nextCommand();
300 else if (kbdPrevEscapeChar == 91 && c == 68) { // left key
301 //TODO: move cursor left
302 } else if (kbdPrevEscapeChar == 91 && c == 67) { // right key
303 //TODO: move cursor right
304 }
305 }
306 continue; // don't send this escape sequence character to LSCP server
307 } else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ...
308 iKbdEscapeCharsExpected = 2;
309 continue; // don't send ESC character to LSCP server
310 } else if (c == KBD_BACKSPACE) {
311 if (promptOffset() < CCursor::now().column())
312 cout << "\b \b" << flush;
313 c = '\b';
314 } else if (c == '\t') { // auto completion ...
315 autoComplete();
316 continue; // don't send tab character to LSCP server
317 } else if (c == '\n') {
318 storeCommandInHistory(g_goodPortion + g_badPortion);
319 } else { // don't apply RETURN stroke yet, since the typed command might still be corrected by the sampler
320 cout << c << flush;
321 }
322
323 g_client->send(c);
324 }
325 }
326
327 return 0;
328 }

  ViewVC Help
Powered by ViewVC