xref: /haiku/src/apps/serialconnect/SerialApp.cpp (revision 2897df967633aab846ff4917b53e2af7d1e54eeb)
1 /*
2  * Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com
3  * Distributed under the terms of the MIT licence.
4  */
5 
6 
7 #include "SerialApp.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Directory.h>
13 #include <Entry.h>
14 #include <File.h>
15 #include <FindDirectory.h>
16 #include <Path.h>
17 
18 #include "CustomRateWindow.h"
19 #include "SerialWindow.h"
20 
21 
22 static property_info sProperties[] = {
23 	{ "baudrate",
24 		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
25 		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
26 		"get or set the baudrate",
27 		0, { B_INT32_TYPE }
28 	},
29 	{ "bits",
30 		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
31 		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
32 		"get or set the number of data bits (7 or 8)",
33 		0, { B_INT32_TYPE }
34 	},
35 	{ "stopbits",
36 		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
37 		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
38 		"get or set the number of stop bits (1 or 2)",
39 		0, { B_INT32_TYPE }
40 	},
41 	{ "parity",
42 		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
43 		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
44 		"get or set the parity (none, even or odd)",
45 		0, { B_STRING_TYPE }
46 	},
47 	{ "flowcontrol",
48 		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
49 		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
50 		"get or set the flow control (hardware, software, both, or none)",
51 		0, { B_STRING_TYPE }
52 	},
53 	{ "port",
54 		{ B_GET_PROPERTY, B_SET_PROPERTY, B_DELETE_PROPERTY, 0 },
55 		{ B_DIRECT_SPECIFIER, 0 },
56 		"get or set the port device",
57 		0, { B_STRING_TYPE }
58 	},
59 	{ 0 }
60 };
61 
62 const BPropertyInfo SerialApp::kScriptingProperties(sProperties);
63 
64 
65 SerialApp::SerialApp()
66 	: BApplication(SerialApp::kApplicationSignature)
67 	, fLogFile(NULL)
68 	, fFileSender(NULL)
69 {
70 	fWindow = new SerialWindow();
71 
72 	fSerialLock = create_sem(0, "Serial port lock");
73 	thread_id id = spawn_thread(PollSerial, "Serial port poller",
74 		B_LOW_PRIORITY, this);
75 	resume_thread(id);
76 }
77 
78 
79 SerialApp::~SerialApp()
80 {
81 	delete fLogFile;
82 	delete fFileSender;
83 }
84 
85 
86 void SerialApp::ReadyToRun()
87 {
88 	LoadSettings();
89 	fWindow->Show();
90 }
91 
92 
93 void SerialApp::MessageReceived(BMessage* message)
94 {
95 	switch (message->what) {
96 		case kMsgOpenPort:
97 		{
98 			if (message->FindString("port name", &fPortPath) == B_OK) {
99 				fSerialPort.Open(fPortPath);
100 				release_sem(fSerialLock);
101 			} else {
102 				fSerialPort.Close();
103 			}
104 
105 			// Forward to the window so it can enable/disable menu items
106 			fWindow->PostMessage(message);
107 			return;
108 		}
109 		case kMsgDataRead:
110 		{
111 			const uint8_t* bytes;
112 			ssize_t length;
113 			message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
114 				&length);
115 
116 			if (fFileSender != NULL) {
117 				if (fFileSender->BytesReceived(bytes, length)) {
118 					delete fFileSender;
119 					fFileSender = NULL;
120 				}
121 			} else {
122 				// forward the message to the window, which will display the
123 				// incoming data
124 				fWindow->PostMessage(message);
125 
126 				if (fLogFile) {
127 					if (fLogFile->Write(bytes, length) != length) {
128 						// TODO error handling
129 					}
130 				}
131 			}
132 
133 			return;
134 		}
135 		case kMsgDataWrite:
136 		{
137 			// Do not allow sending if a file transfer is in progress.
138 			if (fFileSender != NULL)
139 				return;
140 
141 			const char* bytes;
142 			ssize_t size;
143 
144 			if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
145 					&size) == B_OK)
146 				fSerialPort.Write(bytes, size);
147 			return;
148 		}
149 		case kMsgLogfile:
150 		{
151 			entry_ref parent;
152 			const char* filename;
153 
154 			if (message->FindRef("directory", &parent) == B_OK
155 				&& message->FindString("name", &filename) == B_OK) {
156 				delete fLogFile;
157 				BDirectory directory(&parent);
158 				fLogFile = new BFile(&directory, filename,
159 					B_WRITE_ONLY | B_CREATE_FILE | B_OPEN_AT_END);
160 				status_t error = fLogFile->InitCheck();
161 				if (error != B_OK)
162 					puts(strerror(error));
163 			} else
164 				debugger("Invalid BMessage received");
165 			return;
166 		}
167 		case kMsgSendFile:
168 		{
169 			entry_ref ref;
170 
171 			BString protocol = message->FindString("protocol");
172 
173 			if (message->FindRef("refs", &ref) == B_OK) {
174 				BFile* file = new BFile(&ref, B_READ_ONLY);
175 				status_t error = file->InitCheck();
176 				if (error != B_OK)
177 					puts(strerror(error));
178 				else {
179 					delete fFileSender;
180 					if (protocol == "xmodem")
181 						fFileSender = new XModemSender(file, &fSerialPort, fWindow);
182 					else
183 						fFileSender = new RawSender(file, &fSerialPort, fWindow);
184 				}
185 			} else {
186 				message->PrintToStream();
187 				debugger("Invalid BMessage received");
188 			}
189 			return;
190 		}
191 		case kMsgCustomBaudrate:
192 		{
193 			// open the custom baudrate selector window
194 			CustomRateWindow* window = new CustomRateWindow(fSerialPort.DataRate());
195 			window->Show();
196 			return;
197 		}
198 		case kMsgSettings:
199 		{
200 			int32 baudrate;
201 			stop_bits stopBits;
202 			data_bits dataBits;
203 			parity_mode parity;
204 			uint32 flowcontrol;
205 
206 			if (message->FindInt32("databits", (int32*)&dataBits) == B_OK)
207 				fSerialPort.SetDataBits(dataBits);
208 
209 			if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK)
210 				fSerialPort.SetStopBits(stopBits);
211 
212 			if (message->FindInt32("parity", (int32*)&parity) == B_OK)
213 				fSerialPort.SetParityMode(parity);
214 
215 			if (message->FindInt32("flowcontrol", (int32*)&flowcontrol) == B_OK)
216 				fSerialPort.SetFlowControl(flowcontrol);
217 
218 			if (message->FindInt32("baudrate", &baudrate) == B_OK) {
219 				data_rate rate = (data_rate)baudrate;
220 				fSerialPort.SetDataRate(rate);
221 			}
222 
223 			return;
224 		}
225 	}
226 
227 	// Handle scripting messages
228 	if (message->HasSpecifiers()) {
229 		BMessage specifier;
230 		int32 what;
231 		int32 index;
232 		const char* property;
233 
234 		BMessage reply(B_REPLY);
235 		BMessage settings(kMsgSettings);
236 		bool settingsChanged = false;
237 
238 		if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
239 			== B_OK) {
240 			switch (kScriptingProperties.FindMatch(message, index, &specifier,
241 				what, property)) {
242 				case 0: // baudrate
243 					if (message->what == B_GET_PROPERTY) {
244 						reply.AddInt32("result", fSerialPort.DataRate());
245 						message->SendReply(&reply);
246 						return;
247 					}
248 					if (message->what == B_SET_PROPERTY) {
249 						int32 rate = message->FindInt32("data");
250 						settingsChanged = true;
251 						settings.AddInt32("baudrate", rate);
252 					}
253 					break;
254 				case 1: // data bits
255 					if (message->what == B_GET_PROPERTY) {
256 						reply.AddInt32("result", fSerialPort.DataBits() + 7);
257 						message->SendReply(&reply);
258 						return;
259 					}
260 					if (message->what == B_SET_PROPERTY) {
261 						int32 bits = message->FindInt32("data");
262 						settingsChanged = true;
263 						settings.AddInt32("databits", bits - 7);
264 					}
265 					break;
266 				case 2: // stop bits
267 					if (message->what == B_GET_PROPERTY) {
268 						reply.AddInt32("result", fSerialPort.StopBits() + 1);
269 						message->SendReply(&reply);
270 						return;
271 					}
272 					if (message->what == B_SET_PROPERTY) {
273 						int32 bits = message->FindInt32("data");
274 						settingsChanged = true;
275 						settings.AddInt32("stopbits", bits - 1);
276 					}
277 					break;
278 				case 3: // parity
279 				{
280 					static const char* strings[] = {"none", "odd", "even"};
281 					if (message->what == B_GET_PROPERTY) {
282 						reply.AddString("result",
283 							strings[fSerialPort.ParityMode()]);
284 						message->SendReply(&reply);
285 						return;
286 					}
287 					if (message->what == B_SET_PROPERTY) {
288 						BString bits = message->FindString("data");
289 						int i;
290 						for (i = 0; i < 3; i++) {
291 							if (bits == strings[i])
292 								break;
293 						}
294 
295 						if (i < 3) {
296 							settingsChanged = true;
297 							settings.AddInt32("parity", i);
298 						}
299 					}
300 					break;
301 				}
302 				case 4: // flow control
303 				{
304 					static const char* strings[] = {"none", "hardware",
305 						"software", "both"};
306 					if (message->what == B_GET_PROPERTY) {
307 						reply.AddString("result",
308 							strings[fSerialPort.FlowControl()]);
309 						message->SendReply(&reply);
310 						return;
311 					}
312 					if (message->what == B_SET_PROPERTY) {
313 						BString bits = message->FindString("data");
314 						int i;
315 						for (i = 0; i < 4; i++) {
316 							if (bits == strings[i])
317 								break;
318 						}
319 
320 						if (i < 4) {
321 							settingsChanged = true;
322 							settings.AddInt32("flowcontrol", i);
323 						}
324 					}
325 					break;
326 				}
327 				case 5: // port
328 					if (message->what == B_GET_PROPERTY) {
329 						reply.AddString("port", GetPort());
330 						message->SendReply(&reply);
331 					} else if (message->what == B_DELETE_PROPERTY
332 						|| message->what == B_SET_PROPERTY) {
333 						BString path = message->FindString("data");
334 						BMessage openMessage(kMsgOpenPort);
335 						openMessage.AddString("port name", path);
336 						PostMessage(&openMessage);
337 						fWindow->PostMessage(&openMessage);
338 					}
339 					return;
340 			}
341 		}
342 
343 		if (settingsChanged) {
344 			PostMessage(&settings);
345 			fWindow->PostMessage(&settings);
346 			return;
347 		}
348 	}
349 
350 	BApplication::MessageReceived(message);
351 }
352 
353 
354 bool SerialApp::QuitRequested()
355 {
356 	if (BApplication::QuitRequested()) {
357 		SaveSettings();
358 		return true;
359 	}
360 	return false;
361 }
362 
363 
364 const BString& SerialApp::GetPort()
365 {
366 	return fPortPath;
367 }
368 
369 
370 void SerialApp::LoadSettings()
371 {
372 	BPath path;
373 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
374 	path.Append("SerialConnect");
375 
376 	BFile file(path.Path(), B_READ_ONLY);
377 	BMessage message(kMsgSettings);
378 	if (message.Unflatten(&file) != B_OK) {
379 		message.AddInt32("parity", fSerialPort.ParityMode());
380 		message.AddInt32("databits", fSerialPort.DataBits());
381 		message.AddInt32("stopbits", fSerialPort.StopBits());
382 		message.AddInt32("baudrate", fSerialPort.DataRate());
383 		message.AddInt32("flowcontrol", fSerialPort.FlowControl());
384 	}
385 
386 	be_app->PostMessage(&message);
387 	fWindow->PostMessage(&message);
388 }
389 
390 
391 void SerialApp::SaveSettings()
392 {
393 	BMessage message(kMsgSettings);
394 	message.AddInt32("parity", fSerialPort.ParityMode());
395 	message.AddInt32("databits", fSerialPort.DataBits());
396 	message.AddInt32("stopbits", fSerialPort.StopBits());
397 	message.AddInt32("baudrate", fSerialPort.DataRate());
398 	message.AddInt32("flowcontrol", fSerialPort.FlowControl());
399 
400 	BPath path;
401 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
402 	path.Append("SerialConnect");
403 
404 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
405 	message.Flatten(&file);
406 }
407 
408 
409 /* static */
410 status_t SerialApp::PollSerial(void*)
411 {
412 	SerialApp* application = (SerialApp*)be_app;
413 	char buffer[256];
414 
415 	for (;;) {
416 		ssize_t bytesRead;
417 
418 		bytesRead = application->fSerialPort.Read(buffer, sizeof(buffer));
419 		if (bytesRead == B_FILE_ERROR) {
420 			// Port is not open - wait for it and start over
421 			acquire_sem(application->fSerialLock);
422 		} else if (bytesRead > 0) {
423 			// We read something, forward it to the app for handling
424 			BMessage* serialData = new BMessage(kMsgDataRead);
425 			serialData->AddData("data", B_RAW_TYPE, buffer, bytesRead);
426 			be_app_messenger.SendMessage(serialData);
427 		}
428 	}
429 
430 	// Should not reach this line anyway...
431 	return B_OK;
432 }
433 
434 
435 const char* SerialApp::kApplicationSignature
436 	= "application/x-vnd.haiku.SerialConnect";
437 
438 
439 int main(int argc, char** argv)
440 {
441 	SerialApp app;
442 	app.Run();
443 }
444 
445 
446 status_t
447 SerialApp::GetSupportedSuites(BMessage* message)
448 {
449 	message->AddString("suites", "suite/vnd.Haiku-SerialPort");
450 	message->AddFlat("messages", &kScriptingProperties);
451 	return BApplication::GetSupportedSuites(message);
452 }
453 
454 
455 BHandler*
456 SerialApp::ResolveSpecifier(BMessage* message, int32 index,
457 	BMessage* specifier, int32 what, const char* property)
458 {
459 	if (kScriptingProperties.FindMatch(message, index, specifier, what,
460 		property) >= 0)
461 		return this;
462 
463 	return BApplication::ResolveSpecifier(message, index, specifier, what,
464 		property);
465 }
466