OXYGENHUB
◆ main openmsx / Contrib / openmsx-control-stdio.cc
9.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/**
2
 * Example implementation for bidirectional communication with openMSX.
3
 *
4
 *   requires: libxml2
5
 *   compile: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control-stdio.cc
6
 */
7
8
#include <array>
9
#include <cstring>
10
#include <string>
11
#include <list>
12
#include <iostream>
13
#include <unistd.h>
14
#include <sys/types.h>
15
#include <sys/select.h>
16
#include <libxml/parser.h>
17
18
19
class OpenMSXComm
20
{
21
public:
22
	// main loop
23
	void start();
24
25
	// send a command to openmsx
26
	void sendCommand(const std::string& command);
27
28
private:
29
	// these methods get called when openMSX has send the corresponding tag
30
	void openmsx_cmd_ok(const std::string& msg);
31
	void openmsx_cmd_nok(const std::string& msg);
32
	void openmsx_cmd_info(const std::string& msg) const;
33
	void openmsx_cmd_warning(const std::string& msg) const;
34
	void openmsx_cmd_update(const std::string& name, const std::string& value) const;
35
36
	// XML parsing call-back functions
37
	static void cb_start_element(OpenMSXComm* comm, const xmlChar* name,
38
	                             const xmlChar** attrs);
39
	static void cb_end_element(OpenMSXComm* comm, const xmlChar* name);
40
	static void cb_text(OpenMSXComm* comm, const xmlChar* chars, int len);
41
42
	void parseReply(const char** attrs);
43
	void parseLog(const char** attrs);
44
	void parseUpdate(const char** attrs);
45
46
	void doReply();
47
	void doLog() const;
48
	void doUpdate() const;
49
50
	void deprecated() const;
51
52
	// commands being executed
53
	std::list<std::string> commandStack;
54
55
	// XML parsing
56
	enum State {
57
		START,
58
		TAG_OPENMSX,
59
		TAG_REPLY,
60
		TAG_LOG,
61
		TAG_UPDATE,
62
		TAG_OK,
63
		TAG_NOK,
64
		TAG_INFO,
65
		TAG_WARNING,
66
	} state;
67
	unsigned unknownLevel;
68
	std::string content;
69
	xmlSAXHandler sax_handler;
70
	xmlParserCtxt* parser_context;
71
72
	enum ReplyStatus {
73
		REPLY_UNKNOWN,
74
		REPLY_OK,
75
		REPLY_NOK
76
	} replyStatus;
77
78
	enum LogLevel {
79
		LOG_UNKNOWN,
80
		LOG_INFO,
81
		LOG_WARNING
82
	} logLevel;
83
84
	enum UpdateType {
85
		UPDATE_UNKNOWN,
86
		UPDATE_LED
87
	} updateType;
88
	std::string updateName;
89
90
	// communication with openmsx process
91
	int fd_out;
92
};
93
94
95
void OpenMSXComm::openmsx_cmd_ok(const std::string& msg)
96
{
97
	const std::string& command = commandStack.front();
98
	std::cout << "CMD: '" << command << "' executed\n";
99
	if (!msg.empty()) {
100
		std::cout << msg << '\n';
101
	}
102
	std::cout << std::flush;
103
	commandStack.pop_front();
104
}
105
106
void OpenMSXComm::openmsx_cmd_nok(const std::string& msg)
107
{
108
	const std::string& command = commandStack.front();
109
	std::cout << "CMD: '" << command << "' failed\n";
110
	if (!msg.empty()) {
111
		std::cout << msg << '\n';
112
	}
113
	std::cout << std::flush;
114
	commandStack.pop_front();
115
}
116
117
void OpenMSXComm::openmsx_cmd_info(const std::string& msg) const
118
{
119
	std::cout << "INFO: " << msg << '\n' << std::flush;
120
}
121
122
void OpenMSXComm::openmsx_cmd_warning(const std::string& msg) const
123
{
124
	std::cout << "WARNING: " << msg << '\n' << std::flush;
125
}
126
127
void OpenMSXComm::openmsx_cmd_update(const std::string& name, const std::string& value) const
128
{
129
	std::cout << "UPDATE: " << name << ' ' << value << '\n' << std::flush;
130
}
131
132
void OpenMSXComm::deprecated() const
133
{
134
	static bool alreadyPrinted = false;
135
	if (!alreadyPrinted) {
136
		alreadyPrinted = true;
137
		std::cout << "The openMSX you're running still uses the old communication protocol.\n"
138
		             "Because of this some stuff will not work (LED status for example).\n"
139
		             "Please upgrade to openMSX 0.4.0 or higher.\n";
140
	}
141
}
142
143
void OpenMSXComm::cb_start_element(OpenMSXComm* comm, const xmlChar* name,
144
                                   const xmlChar** attrs)
145
{
146
	if (comm->unknownLevel) {
147
		++(comm->unknownLevel);
148
		return;
149
	}
150
	switch (comm->state) {
151
		case START:
152
			if (strcmp((const char*)name, "openmsx-output") == 0) {
153
				comm->state = TAG_OPENMSX;
154
			} else {
155
				++(comm->unknownLevel);
156
			}
157
			break;
158
		case TAG_OPENMSX:
159
			if (strcmp((const char*)name, "reply") == 0) {
160
				comm->state = TAG_REPLY;
161
				comm->parseReply((const char**)attrs);
162
			} else if (strcmp((const char*)name, "log") == 0) {
163
				comm->state = TAG_LOG;
164
				comm->parseLog((const char**)attrs);
165
			} else if (strcmp((const char*)name, "update") == 0) {
166
				comm->state = TAG_UPDATE;
167
				comm->parseUpdate((const char**)attrs);
168
			// backwards compatiblity stuff
169
			} else if (strcmp((const char*)name, "ok") == 0) {
170
				comm->state = TAG_OK;
171
				comm->replyStatus = REPLY_OK;
172
				comm->deprecated();
173
			} else if (strcmp((const char*)name, "nok") == 0) {
174
				comm->state = TAG_NOK;
175
				comm->replyStatus = REPLY_NOK;
176
				comm->deprecated();
177
			} else if (strcmp((const char*)name, "info") == 0) {
178
				comm->state = TAG_INFO;
179
				comm->logLevel = LOG_INFO;
180
				comm->deprecated();
181
			} else if (strcmp((const char*)name, "warning") == 0) {
182
				comm->state = TAG_WARNING;
183
				comm->logLevel = LOG_WARNING;
184
				comm->deprecated();
185
			} else {
186
				++(comm->unknownLevel);
187
			}
188
			break;
189
		default:
190
			++(comm->unknownLevel);
191
			break;
192
	}
193
	comm->content.clear();
194
}
195
196
void OpenMSXComm::parseReply(const char** attrs)
197
{
198
	replyStatus = REPLY_UNKNOWN;
199
	if (!attrs) return;
200
	for ( ; *attrs; attrs += 2) {
201
		if (strcmp(attrs[0], "result") == 0) {
202
			if (strcmp(attrs[1], "ok") == 0) {
203
				replyStatus = REPLY_OK;
204
			} else if (strcmp(attrs[1], "nok") == 0) {
205
				replyStatus = REPLY_NOK;
206
			}
207
		}
208
	}
209
}
210
211
void OpenMSXComm::parseLog(const char** attrs)
212
{
213
	logLevel = LOG_UNKNOWN;
214
	if (!attrs) return;
215
	for ( ; *attrs; attrs += 2) {
216
		if (strcmp(attrs[0], "level") == 0) {
217
			if (strcmp(attrs[1], "info") == 0) {
218
				logLevel = LOG_INFO;
219
			} else if (strcmp(attrs[1], "warning") == 0) {
220
				logLevel = LOG_WARNING;
221
			}
222
		}
223
	}
224
}
225
226
void OpenMSXComm::parseUpdate(const char** attrs)
227
{
228
	updateType = UPDATE_UNKNOWN;
229
	if (!attrs) return;
230
	for ( ; *attrs; attrs += 2) {
231
		if (strcmp(attrs[0], "type") == 0) {
232
			if (strcmp(attrs[1], "led") == 0) {
233
				updateType = UPDATE_LED;
234
			}
235
		} else if (strcmp(attrs[0], "name") == 0) {
236
			updateName = attrs[1];
237
		}
238
	}
239
}
240
241
void OpenMSXComm::cb_end_element(OpenMSXComm* comm, const xmlChar* /*name*/)
242
{
243
	if (comm->unknownLevel) {
244
		--(comm->unknownLevel);
245
		return;
246
	}
247
	switch (comm->state) {
248
		case TAG_OPENMSX:
249
			comm->state = START;
250
			break;
251
		case TAG_REPLY:
252
			comm->doReply();
253
			comm->state = TAG_OPENMSX;
254
			break;
255
		case TAG_LOG:
256
			comm->doLog();
257
			comm->state = TAG_OPENMSX;
258
			break;
259
		case TAG_UPDATE:
260
			comm->doUpdate();
261
			comm->state = TAG_OPENMSX;
262
			break;
263
		// backwards compatiblity stuff
264
		case TAG_OK:
265
			comm->openmsx_cmd_ok(comm->content);
266
			comm->state = TAG_OPENMSX;
267
			break;
268
		case TAG_NOK:
269
			comm->openmsx_cmd_nok(comm->content);
270
			comm->state = TAG_OPENMSX;
271
			break;
272
		case TAG_INFO:
273
			comm->openmsx_cmd_info(comm->content);
274
			comm->state = TAG_OPENMSX;
275
			break;
276
		case TAG_WARNING:
277
			comm->openmsx_cmd_warning(comm->content);
278
			comm->state = TAG_OPENMSX;
279
			break;
280
		default:
281
			break;
282
	}
283
}
284
285
void OpenMSXComm::doReply()
286
{
287
	switch (replyStatus) {
288
		case REPLY_OK:
289
			openmsx_cmd_ok(content);
290
			break;
291
		case REPLY_NOK:
292
			openmsx_cmd_nok(content);
293
			break;
294
		case REPLY_UNKNOWN:
295
			// ignore
296
			break;
297
	}
298
}
299
300
void OpenMSXComm::doLog() const
301
{
302
	switch (logLevel) {
303
		case LOG_INFO:
304
			openmsx_cmd_info(content);
305
			break;
306
		case LOG_WARNING:
307
			openmsx_cmd_warning(content);
308
			break;
309
		case LOG_UNKNOWN:
310
			// ignore
311
			break;
312
	}
313
}
314
315
void OpenMSXComm::doUpdate() const
316
{
317
	switch (updateType) {
318
		case UPDATE_LED:
319
			openmsx_cmd_update(updateName, content);
320
			break;
321
		case UPDATE_UNKNOWN:
322
			// ignore
323
			break;
324
	}
325
}
326
327
void OpenMSXComm::cb_text(OpenMSXComm* comm, const xmlChar* chars, int len)
328
{
329
	switch (comm->state) {
330
		case TAG_REPLY:
331
		case TAG_LOG:
332
		case TAG_UPDATE:
333
		case TAG_OK:
334
		case TAG_NOK:
335
		case TAG_INFO:
336
		case TAG_WARNING:
337
			comm->content.append((const char*)chars, len);
338
			break;
339
		default:
340
			break;
341
	}
342
}
343
344
345
void OpenMSXComm::sendCommand(const std::string& command)
346
{
347
	write(fd_out, "<command>", 9);
348
	write(fd_out, command.c_str(), command.length());
349
	write(fd_out, "</command>", 10);
350
	commandStack.push_back(command);
351
}
352
353
354
void OpenMSXComm::start()
355
{
356
	// init XML parser
357
	state = START;
358
	unknownLevel = 0;
359
	memset(&sax_handler, 0, sizeof(sax_handler));
360
	sax_handler.startElement = (startElementSAXFunc)cb_start_element;
361
	sax_handler.endElement   = (endElementSAXFunc)  cb_end_element;
362
	sax_handler.characters   = (charactersSAXFunc)  cb_text;
363
	parser_context = xmlCreatePushParserCtxt(&sax_handler, this, nullptr, 0, nullptr);
364
365
	// create pipes
366
	std::array<int, 2> pipe_to_child;
367
	std::array<int, 2> pipe_from_child;
368
	pipe(pipe_to_child.data());
369
	pipe(pipe_from_child.data());
370
	fd_out = pipe_to_child[1];
371
372
	// start openmsx sub-process
373
	if (pid_t pid = fork(); pid == 0) {
374
		dup2(pipe_to_child[0], STDIN_FILENO);
375
		dup2(pipe_from_child[1], STDOUT_FILENO);
376
		close(pipe_to_child[0]);
377
		close(pipe_to_child[1]);
378
		close(pipe_from_child[0]);
379
		close(pipe_from_child[1]);
380
		execlp("openmsx", "openmsx", "-control", "stdio:", nullptr);
381
		exit(0);
382
	}
383
	close(pipe_to_child[0]);
384
	close(pipe_from_child[1]);
385
386
	// send initial commands
387
	write(pipe_to_child[1], "<openmsx-control>", 17);
388
	sendCommand("set power on");
389
	sendCommand("restoredefault renderer");
390
391
	// event loop
392
	std::string command; // (partial) input from STDIN
393
	while (true) {
394
		std::array<char, 4096> buf;
395
		fd_set rdFs;
396
		FD_ZERO(&rdFs);
397
		FD_SET(pipe_from_child[0], &rdFs);
398
		FD_SET(STDIN_FILENO, &rdFs);
399
		select(pipe_from_child[0] + 1, &rdFs, nullptr, nullptr, nullptr);
400
		if (FD_ISSET(pipe_from_child[0], &rdFs)) {
401
			// data available from openMSX
402
			ssize_t size = read(pipe_from_child[0], buf.data(), buf.size());
403
			if (size == 0) {
404
				// openmsx process died
405
				break;
406
			}
407
			xmlParseChunk(parser_context, buf.data(), size, 0);
408
		}
409
		if (FD_ISSET(STDIN_FILENO, &rdFs)) {
410
			// data available from STDIN
411
			ssize_t size = read(STDIN_FILENO, buf.data(), buf.size());
412
			char* oldPos = buf.data();
413
			while (true) {
414
				if (auto* pos = static_cast<char*>(memchr(oldPos, '\n', size))) {
415
					auto num = pos - oldPos;
416
					command.append(oldPos, num);
417
					sendCommand(command);
418
					command.clear();
419
					oldPos = pos + 1;
420
					size -= num + 1;
421
				} else {
422
					command.append(oldPos, size);
423
					break;
424
				}
425
			}
426
		}
427
	}
428
429
	// cleanup
430
	xmlFreeParserCtxt(parser_context);
431
}
432
433
434
int main()
435
{
436
	OpenMSXComm comm;
437
	comm.start();
438
	return 0;
439
}