/[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 2517 - (hide 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 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    
21     #include "../common/global.h"
22 schoenebeck 2517 #include "../common/global_private.h"
23 schoenebeck 2515 #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 schoenebeck 2517 static LSCPClient* g_client = NULL;
32     static KeyboardReader* g_keyboardReader = NULL;
33 schoenebeck 2515 static Condition g_todo;
34 schoenebeck 2516 static String g_goodPortion;
35     static String g_badPortion;
36     static String g_suggestedPortion;
37 schoenebeck 2517 static const String g_prompt = "lscp=# ";
38 schoenebeck 2515
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 schoenebeck 2516 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
49     cout << endl;
50 schoenebeck 2515 }
51    
52 schoenebeck 2517 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 schoenebeck 2515 // 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 schoenebeck 2516 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 schoenebeck 2517 g_client->send(s);
85 schoenebeck 2516 }
86    
87 schoenebeck 2515 int main(int argc, char *argv[]) {
88     String host = LSCP_DEFAULT_HOST;
89     int port = LSCP_DEFAULT_PORT;
90 schoenebeck 2516 bool autoCorrect = true;
91 schoenebeck 2515
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 schoenebeck 2516 } else if (s == "--no-auto-correct") {
112     autoCorrect = false;
113 schoenebeck 2515 } 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 schoenebeck 2517 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 schoenebeck 2516 (autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0"
126     );
127 schoenebeck 2517 sResponse = g_client->sendCommandSync("SET SHELL INTERACT 1");
128 schoenebeck 2515 if (sResponse.substr(0, 2) != "OK") {
129     cerr << "Error: sampler too old, it does not support shell instructions\n";
130     return -1;
131     }
132 schoenebeck 2517
133     printWelcome();
134     printPrompt();
135 schoenebeck 2515
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 schoenebeck 2517 g_keyboardReader = new KeyboardReader;
140     g_keyboardReader->setCallback(onNewKeyboardInputAvailable);
141     g_keyboardReader->startReading();
142 schoenebeck 2515
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 schoenebeck 2517 while (g_client->messageComplete()) {
153     String line = *g_client->popLine();
154 schoenebeck 2515 //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 schoenebeck 2516 // 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 schoenebeck 2515
167 schoenebeck 2516 // 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 schoenebeck 2515
174 schoenebeck 2516 // 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 schoenebeck 2517 printPrompt();
202 schoenebeck 2516
203 schoenebeck 2515 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 schoenebeck 2516 cfmt.bold().yellow();
210     cout << sSuggest << flush;
211    
212 schoenebeck 2517 cursor.toColumn(cursorColumn + promptOffset());
213 schoenebeck 2515 }
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 schoenebeck 2517 printPrompt();
222 schoenebeck 2515 } 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 schoenebeck 2517 printPrompt();
230 schoenebeck 2515 } 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 schoenebeck 2517 printPrompt();
238     } else if (g_client->multiLine()) { // multi-line response expected ...
239 schoenebeck 2515 cout << endl << flush;
240     while (true) {
241     cout << line << endl << flush;
242     if (line.substr(0, 1) == ".") break;
243 schoenebeck 2517 if (!g_client->lineAvailable()) break;
244     line = *g_client->popLine();
245 schoenebeck 2515 }
246 schoenebeck 2517 printPrompt();
247 schoenebeck 2515 } else {
248     cout << endl << line << endl << flush;
249 schoenebeck 2517 printPrompt();
250 schoenebeck 2515 }
251     }
252    
253     // did keyboard input arrive?
254 schoenebeck 2517 while (g_keyboardReader->charAvailable()) {
255     char c = g_keyboardReader->popChar();
256 schoenebeck 2515
257     CFmt cfmt;
258     cfmt.white();
259     //std::cout << c << "(" << int(c) << ")" << std::endl << std::flush;
260     if (c == KBD_BACKSPACE) {
261 schoenebeck 2517 if (promptOffset() < CCursor::now().column())
262     cout << "\b \b" << flush;
263 schoenebeck 2515 c = '\b';
264 schoenebeck 2516 } else if (c == '\t') { // auto completion ...
265     autoComplete();
266     continue; // don't send tab character to LSCP server
267 schoenebeck 2515 } 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 schoenebeck 2517 g_client->send(c);
272 schoenebeck 2515 }
273     }
274    
275     return 0;
276     }

  ViewVC Help
Powered by ViewVC