/[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 2517 - (show annotations) (download)
Fri Feb 7 19:53:09 2014 UTC (10 years, 2 months ago) by schoenebeck
File size: 10657 byte(s)
* LSCP shell (WIP): show a prompt & welcome message
* LSCP shell (WIP): fixed startup crash that happened
  on some systems
* Bumped version (1.0.0.svn30).

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

  ViewVC Help
Powered by ViewVC