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/Condition.h" |
23 |
|
24 |
#define LSCP_DEFAULT_HOST "localhost" |
25 |
#define LSCP_DEFAULT_PORT 8888 |
26 |
|
27 |
using namespace std; |
28 |
using namespace LinuxSampler; |
29 |
|
30 |
static LSCPClient g_client; |
31 |
static KeyboardReader g_keyboardReader; |
32 |
static Condition g_todo; |
33 |
|
34 |
static void printUsage() { |
35 |
cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl; |
36 |
cout << endl; |
37 |
cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl; |
38 |
cout << endl; |
39 |
cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl; |
40 |
cout << endl; |
41 |
cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl; |
42 |
cout << endl; |
43 |
} |
44 |
|
45 |
// Called by the network reading thread, whenever new data arrived from the |
46 |
// network connection. |
47 |
static void onLSCPClientNewInputAvailable(LSCPClient* client) { |
48 |
g_todo.Set(true); |
49 |
} |
50 |
|
51 |
// Called by the keyboard reading thread, whenever a key stroke was received. |
52 |
static void onNewKeyboardInputAvailable(KeyboardReader* reader) { |
53 |
g_todo.Set(true); |
54 |
} |
55 |
|
56 |
int main(int argc, char *argv[]) { |
57 |
String host = LSCP_DEFAULT_HOST; |
58 |
int port = LSCP_DEFAULT_PORT; |
59 |
|
60 |
// parse command line arguments |
61 |
for (int i = 0; i < argc; ++i) { |
62 |
String s = argv[i]; |
63 |
if (s == "-h" || s == "--host") { |
64 |
if (++i >= argc) { |
65 |
printUsage(); |
66 |
return -1; |
67 |
} |
68 |
host = argv[i]; |
69 |
} else if (s == "-p" || s == "--port") { |
70 |
if (++i >= argc) { |
71 |
printUsage(); |
72 |
return -1; |
73 |
} |
74 |
port = atoi(argv[i]); |
75 |
if (port <= 0) { |
76 |
cerr << "Error: invalid port argument \"" << argv[i] << "\"\n"; |
77 |
return -1; |
78 |
} |
79 |
} else if (s[0] == '-') { // invalid / unknown command line argument ... |
80 |
printUsage(); |
81 |
return -1; |
82 |
} |
83 |
} |
84 |
|
85 |
// try to connect to the sampler's LSCP server and start a thread for |
86 |
// receiving incoming network data from the sampler's LSCP server |
87 |
g_client.setCallback(onLSCPClientNewInputAvailable); |
88 |
if (!g_client.connect(host, port)) return -1; |
89 |
String sResponse = g_client.sendCommandSync("SET SHELL INTERACT 1"); |
90 |
if (sResponse.substr(0, 2) != "OK") { |
91 |
cerr << "Error: sampler too old, it does not support shell instructions\n"; |
92 |
return -1; |
93 |
} |
94 |
|
95 |
// start a thread for reading from the local text input keyboard |
96 |
// (keyboard echo will be disabled as well to have a clean control on what |
97 |
// is appearing on the screen) |
98 |
g_keyboardReader.setCallback(onNewKeyboardInputAvailable); |
99 |
g_keyboardReader.startReading(); |
100 |
|
101 |
// main thread's loop |
102 |
while (true) { |
103 |
// sleep until either new data from the network or from keyboard arrived |
104 |
g_todo.WaitIf(false); |
105 |
// immediately unset the condition variable and unlock it |
106 |
g_todo.Set(false); |
107 |
g_todo.Unlock(); |
108 |
|
109 |
// did network data arrive? |
110 |
while (g_client.messageComplete()) { |
111 |
String line = *g_client.popLine(); |
112 |
//printf("line '%s'\n", line.c_str()); |
113 |
if (line.substr(0,4) == "SHU:") { |
114 |
int code = 0, n = 0; |
115 |
int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n); |
116 |
if (res >= 1) { |
117 |
String s = line.substr(n); |
118 |
|
119 |
String key = LSCP_SHK_GOOD_FRONT; |
120 |
size_t i = s.find(key); |
121 |
String sGood = s.substr(0, i); |
122 |
String sBad = s.substr(i + key.length()); |
123 |
//printf("line '%s' good='%s' bad='%s'\n", line.c_str(), sGood.c_str(), sBad.c_str()); |
124 |
|
125 |
CCursor cursor = CCursor::now(); |
126 |
cursor.toColumn(0); |
127 |
cursor.clearLine(); |
128 |
|
129 |
CFmt cfmt; |
130 |
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
131 |
else cfmt.bold().white(); |
132 |
cout << sGood << flush; |
133 |
cfmt.reset().red(); |
134 |
cout << sBad << flush; |
135 |
} |
136 |
} else if (line.substr(0,2) == "OK") { // single-line response expected ... |
137 |
cout << endl << flush; |
138 |
CFmt cfmt; |
139 |
cfmt.green(); |
140 |
cout << line.substr(0,2) << flush; |
141 |
cfmt.reset(); |
142 |
cout << line.substr(2) << endl << flush; |
143 |
} else if (line.substr(0,3) == "WRN") { // single-line response expected ... |
144 |
cout << endl << flush; |
145 |
CFmt cfmt; |
146 |
cfmt.yellow(); |
147 |
cout << line.substr(0,3) << flush; |
148 |
cfmt.reset(); |
149 |
cout << line.substr(3) << endl << flush; |
150 |
} else if (line.substr(0,3) == "ERR") { // single-line response expected ... |
151 |
cout << endl << flush; |
152 |
CFmt cfmt; |
153 |
cfmt.bold().red(); |
154 |
cout << line.substr(0,3) << flush; |
155 |
cfmt.reset(); |
156 |
cout << line.substr(3) << endl << flush; |
157 |
} else if (g_client.multiLine()) { // multi-line response expected ... |
158 |
cout << endl << flush; |
159 |
while (true) { |
160 |
cout << line << endl << flush; |
161 |
if (line.substr(0, 1) == ".") break; |
162 |
if (!g_client.lineAvailable()) break; |
163 |
line = *g_client.popLine(); |
164 |
} |
165 |
} else { |
166 |
cout << endl << line << endl << flush; |
167 |
} |
168 |
} |
169 |
|
170 |
// did keyboard input arrive? |
171 |
while (g_keyboardReader.charAvailable()) { |
172 |
char c = g_keyboardReader.popChar(); |
173 |
|
174 |
CFmt cfmt; |
175 |
cfmt.white(); |
176 |
//std::cout << c << "(" << int(c) << ")" << std::endl << std::flush; |
177 |
if (c == KBD_BACKSPACE) { |
178 |
cout << "\b \b" << flush; |
179 |
c = '\b'; |
180 |
} else if (c != '\n') { // don't apply RETURN stroke yet, since the typed command might still be corrected by the sampler |
181 |
cout << c << flush; |
182 |
} |
183 |
|
184 |
g_client.send(c); |
185 |
} |
186 |
} |
187 |
|
188 |
return 0; |
189 |
} |