xref: /haiku/src/apps/serialconnect/SerialWindow.cpp (revision 3a6bc1cf655f552194bae298fadfdde896dad690)
1 /*
2  * Copyright 2012-2019, Adrien Destugues, pulkomandy@pulkomandy.tk
3  * Distributed under the terms of the MIT licence.
4  */
5 
6 
7 #include "SerialWindow.h"
8 
9 #include <stdio.h>
10 
11 #include <Catalog.h>
12 #include <FilePanel.h>
13 #include <GroupLayout.h>
14 #include <Menu.h>
15 #include <MenuBar.h>
16 #include <MenuItem.h>
17 #include <ScrollView.h>
18 #include <SerialPort.h>
19 #include <StatusBar.h>
20 
21 #include "SerialApp.h"
22 #include "TermView.h"
23 
24 
25 #define B_TRANSLATION_CONTEXT "SerialWindow"
26 
27 
28 const int SerialWindow::kBaudrates[] = { 50, 75, 110, 134, 150, 200, 300, 600,
29 	1200, 1800, 2400, 4800, 9600, 19200, 31250, 38400, 57600, 115200, 230400
30 };
31 
32 
33 // The values for these constants are not in the expected order, so we have to
34 // rely on this lookup table if we want to keep the menu items sorted.
35 const int SerialWindow::kBaudrateConstants[] = { B_50_BPS, B_75_BPS, B_110_BPS,
36 	B_134_BPS, B_150_BPS, B_200_BPS, B_300_BPS, B_600_BPS, B_1200_BPS,
37 	B_1800_BPS, B_2400_BPS, B_4800_BPS, B_9600_BPS, B_19200_BPS, B_31250_BPS,
38 	B_38400_BPS, B_57600_BPS, B_115200_BPS, B_230400_BPS
39 };
40 
41 
42 const char* SerialWindow::kWindowTitle =
43 	B_TRANSLATE_MARK_SYSTEM_NAME("SerialConnect");
44 
45 
SerialWindow()46 SerialWindow::SerialWindow()
47 	: BWindow(BRect(100, 100, 400, 400),
48 		B_TRANSLATE_NOCOLLECT_SYSTEM_NAME(SerialWindow::kWindowTitle),
49 		B_DOCUMENT_WINDOW, B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS)
50 	, fLogFilePanel(NULL)
51 	, fSendFilePanel(NULL)
52 {
53 	BMenuBar* menuBar = new BMenuBar(Bounds(), "menuBar");
54 	menuBar->ResizeToPreferred();
55 
56 	BRect r = Bounds();
57 	r.top = menuBar->Bounds().bottom + 1;
58 	r.right -= B_V_SCROLL_BAR_WIDTH;
59 	fTermView = new TermView(r);
60 	fTermView->ResizeToPreferred();
61 
62 	r = fTermView->Frame();
63 	r.left = r.right + 1;
64 	r.right = r.left + B_V_SCROLL_BAR_WIDTH;
65 	r.top -= 1;
66 	r.bottom -= B_H_SCROLL_BAR_HEIGHT - 1;
67 
68 	BScrollBar* scrollBar = new BScrollBar(r, "scrollbar", NULL, 0, 0,
69 		B_VERTICAL);
70 
71 	scrollBar->SetTarget(fTermView);
72 
73 	ResizeTo(r.right - 1, r.bottom + B_H_SCROLL_BAR_HEIGHT - 1);
74 
75 	r = fTermView->Frame();
76 	r.top = r.bottom - 37;
77 
78 	fStatusBar = new BStatusBar(r, B_TRANSLATE("file transfer progress"),
79 		NULL, NULL);
80 	fStatusBar->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
81 	fStatusBar->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
82 	fStatusBar->Hide();
83 
84 	AddChild(menuBar);
85 	AddChild(fTermView);
86 	AddChild(scrollBar);
87 	AddChild(fStatusBar);
88 
89 	fConnectionMenu = new BMenu(B_TRANSLATE("Connection"));
90 	fFileMenu = new BMenu(B_TRANSLATE("File"));
91 	BMenu* settingsMenu = new BMenu(B_TRANSLATE("Settings"));
92 	BMenu* editMenu = new BMenu(B_TRANSLATE("Edit"));
93 
94 	fConnectionMenu->SetRadioMode(true);
95 
96 	menuBar->AddItem(fConnectionMenu);
97 	menuBar->AddItem(editMenu);
98 	menuBar->AddItem(fFileMenu);
99 	menuBar->AddItem(settingsMenu);
100 
101 	BMenuItem* logFile = new BMenuItem(
102 		B_TRANSLATE("Log to file" B_UTF8_ELLIPSIS), new BMessage(kMsgLogfile));
103 	fFileMenu->AddItem(logFile);
104 
105 	// The "send" items are disabled initially. They are enabled only once we
106 	// are connected to a serial port.
107 	BMessage* sendMsg = new BMessage(kMsgSendFile);
108 	sendMsg->AddString("protocol", "xmodem");
109 	BMenuItem* xmodemSend = new BMenuItem(
110 		B_TRANSLATE("XModem send" B_UTF8_ELLIPSIS),
111 		sendMsg);
112 	fFileMenu->AddItem(xmodemSend);
113 	xmodemSend->SetEnabled(false);
114 
115 	BMenuItem* rawSend = new BMenuItem(B_TRANSLATE("Raw send" B_UTF8_ELLIPSIS),
116 		new BMessage(kMsgSendFile));
117 	fFileMenu->AddItem(rawSend);
118 	rawSend->SetEnabled(false);
119 
120 #if 0
121 	// TODO implement this
122 	BMenuItem* xmodemReceive = new BMenuItem(
123 		"X/Y/Zmodem receive" B_UTF8_ELLIPSIS, NULL);
124 	fFileMenu->AddItem(xmodemReceive);
125 	xmodemReceive->SetEnabled(false);
126 #endif
127 
128 	// Items for the edit menu
129 	BMenuItem* clearScreen = new BMenuItem(B_TRANSLATE("Clear history"),
130 		new BMessage(kMsgClear), 'L');
131 	editMenu->AddItem(clearScreen);
132 
133 	BMenuItem* paste = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
134 	editMenu->AddItem(paste);
135 
136 	// TODO copy (when we have selection), paste
137 
138 	// Configuring all this by menus may be a bit unhandy. Make a setting
139 	// window instead ?
140 	fBaudrateMenu = new BMenu(B_TRANSLATE("Baud rate"));
141 	fBaudrateMenu->SetRadioMode(true);
142 	settingsMenu->AddItem(fBaudrateMenu);
143 
144 	fParityMenu = new BMenu(B_TRANSLATE("Parity"));
145 	fParityMenu->SetRadioMode(true);
146 	settingsMenu->AddItem(fParityMenu);
147 
148 	fStopbitsMenu = new BMenu(B_TRANSLATE("Stop bits"));
149 	fStopbitsMenu->SetRadioMode(true);
150 	settingsMenu->AddItem(fStopbitsMenu);
151 
152 	fFlowcontrolMenu = new BMenu(B_TRANSLATE("Flow control"));
153 	fFlowcontrolMenu->SetRadioMode(true);
154 	settingsMenu->AddItem(fFlowcontrolMenu);
155 
156 	fDatabitsMenu = new BMenu(B_TRANSLATE("Data bits"));
157 	fDatabitsMenu->SetRadioMode(true);
158 	settingsMenu->AddItem(fDatabitsMenu);
159 
160 	fLineTerminatorMenu = new BMenu(B_TRANSLATE("Line terminator"));
161 	fLineTerminatorMenu->SetRadioMode(true);
162 	settingsMenu->AddItem(fLineTerminatorMenu);
163 
164 	BMessage* message = new BMessage(kMsgSettings);
165 	message->AddInt32("parity", B_NO_PARITY);
166 	BMenuItem* parityNone =
167 		new BMenuItem(B_TRANSLATE_COMMENT("None", "Parity"), message);
168 
169 	message = new BMessage(kMsgSettings);
170 	message->AddInt32("parity", B_ODD_PARITY);
171 	BMenuItem* parityOdd = new BMenuItem(B_TRANSLATE_COMMENT("Odd", "Parity"),
172 		message);
173 
174 	message = new BMessage(kMsgSettings);
175 	message->AddInt32("parity", B_EVEN_PARITY);
176 	BMenuItem* parityEven =
177 		new BMenuItem(B_TRANSLATE_COMMENT("Even", "Parity"), message);
178 
179 	fParityMenu->AddItem(parityNone);
180 	fParityMenu->AddItem(parityOdd);
181 	fParityMenu->AddItem(parityEven);
182 	fParityMenu->SetTargetForItems(be_app);
183 
184 	message = new BMessage(kMsgSettings);
185 	message->AddInt32("databits", B_DATA_BITS_7);
186 	BMenuItem* data7 = new BMenuItem("7", message);
187 
188 	message = new BMessage(kMsgSettings);
189 	message->AddInt32("databits", B_DATA_BITS_8);
190 	BMenuItem* data8 = new BMenuItem("8", message);
191 
192 	fDatabitsMenu->AddItem(data7);
193 	fDatabitsMenu->AddItem(data8);
194 	fDatabitsMenu->SetTargetForItems(be_app);
195 
196 	message = new BMessage(kMsgSettings);
197 	message->AddInt32("stopbits", B_STOP_BITS_1);
198 	BMenuItem* stop1 = new BMenuItem("1", message);
199 
200 	message = new BMessage(kMsgSettings);
201 	message->AddInt32("stopbits", B_STOP_BITS_2);
202 	BMenuItem* stop2 = new BMenuItem("2", message);
203 
204 	fStopbitsMenu->AddItem(stop1);
205 	fStopbitsMenu->AddItem(stop2);
206 	fStopbitsMenu->SetTargetForItems(be_app);
207 
208 	// Loop backwards to add fastest rates at top of menu
209 	for (int i = sizeof(kBaudrates) / sizeof(kBaudrates[0]); --i >= 0;)
210 	{
211 		message = new BMessage(kMsgSettings);
212 		message->AddInt32("baudrate", kBaudrateConstants[i]);
213 
214 		char buffer[7];
215 		sprintf(buffer, "%d", kBaudrates[i]);
216 		BMenuItem* item = new BMenuItem(buffer, message);
217 
218 		fBaudrateMenu->AddItem(item);
219 	}
220 
221 	message = new BMessage(kMsgCustomBaudrate);
222 	BMenuItem* custom =
223 		new BMenuItem(B_TRANSLATE_COMMENT("custom" B_UTF8_ELLIPSIS,
224 		"Baudrate"), message);
225 	fBaudrateMenu->AddItem(custom);
226 
227 	fBaudrateMenu->SetTargetForItems(be_app);
228 
229 	message = new BMessage(kMsgSettings);
230 	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL);
231 	BMenuItem* hardware =
232 		new BMenuItem(B_TRANSLATE_COMMENT("Hardware", "Flowcontrol"), message);
233 
234 	message = new BMessage(kMsgSettings);
235 	message->AddInt32("flowcontrol", B_SOFTWARE_CONTROL);
236 	BMenuItem* software =
237 		new BMenuItem(B_TRANSLATE_COMMENT("Software", "Flowcontrol"), message);
238 
239 	message = new BMessage(kMsgSettings);
240 	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL | B_SOFTWARE_CONTROL);
241 	BMenuItem* both =
242 		new BMenuItem(B_TRANSLATE_COMMENT("Both", "Flowcontrol"), message);
243 
244 	message = new BMessage(kMsgSettings);
245 	message->AddInt32("flowcontrol", 0);
246 	BMenuItem* noFlow =
247 		new BMenuItem(B_TRANSLATE_COMMENT("None", "Flowcontrol"), message);
248 
249 	fFlowcontrolMenu->AddItem(hardware);
250 	fFlowcontrolMenu->AddItem(software);
251 	fFlowcontrolMenu->AddItem(both);
252 	fFlowcontrolMenu->AddItem(noFlow);
253 	fFlowcontrolMenu->SetTargetForItems(be_app);
254 
255 	message = new BMessage(kMsgSettings);
256 	message->AddString("terminator", "\n");
257 	BMenuItem* lf = new BMenuItem("LF (\\n)", message);
258 
259 	message = new BMessage(kMsgSettings);
260 	message->AddString("terminator", "\r");
261 	BMenuItem* cr = new BMenuItem("CR (\\r)", message);
262 
263 	message = new BMessage(kMsgSettings);
264 	message->AddString("terminator", "\r\n");
265 	BMenuItem* crlf = new BMenuItem("CR/LF (\\r\\n)", message);
266 
267 	fLineTerminatorMenu->AddItem(lf);
268 	fLineTerminatorMenu->AddItem(cr);
269 	fLineTerminatorMenu->AddItem(crlf);
270 
271 	CenterOnScreen();
272 }
273 
274 
~SerialWindow()275 SerialWindow::~SerialWindow()
276 {
277 	delete fLogFilePanel;
278 	delete fSendFilePanel;
279 }
280 
281 
MenusBeginning()282 void SerialWindow::MenusBeginning()
283 {
284 	// remove all items from the menu
285 	fConnectionMenu->RemoveItems(0, fConnectionMenu->CountItems(), true);
286 
287 	// fill it with the (updated) serial port list
288 	BSerialPort serialPort;
289 	int deviceCount = serialPort.CountDevices();
290 	bool connected = false;
291 
292 	for (int i = 0; i < deviceCount; i++)
293 	{
294 		char buffer[256];
295 		serialPort.GetDeviceName(i, buffer, 256);
296 
297 		BMessage* message = new BMessage(kMsgOpenPort);
298 		message->AddString("port name", buffer);
299 		BMenuItem* portItem = new BMenuItem(buffer, message);
300 		portItem->SetTarget(be_app);
301 
302 		const BString& connectedPort = ((SerialApp*)be_app)->GetPort();
303 
304 		if (connectedPort == buffer) {
305 			connected = true;
306 			portItem->SetMarked(true);
307 		}
308 
309 		fConnectionMenu->AddItem(portItem);
310 	}
311 
312 	if (deviceCount > 0) {
313 		fConnectionMenu->AddSeparatorItem();
314 
315 		BMenuItem* disconnect = new BMenuItem(B_TRANSLATE("Disconnect"),
316 			new BMessage(kMsgOpenPort), 'Z', B_OPTION_KEY);
317 		if (!connected)
318 			disconnect->SetEnabled(false);
319 		disconnect->SetTarget(be_app);
320 		fConnectionMenu->AddItem(disconnect);
321 	} else {
322 		BMenuItem* noDevices =
323 			new BMenuItem(B_TRANSLATE("<no serial port available>"), NULL);
324 		noDevices->SetEnabled(false);
325 		fConnectionMenu->AddItem(noDevices);
326 	}
327 }
328 
329 
MessageReceived(BMessage * message)330 void SerialWindow::MessageReceived(BMessage* message)
331 {
332 	switch (message->what)
333 	{
334 		case kMsgOpenPort:
335 		{
336 			BString path;
337 			bool open = (message->FindString("port name", &path) == B_OK);
338 			int i = 1; // Skip "log to file", which woeks even when offline.
339 			BMenuItem* item;
340 			while((item = fFileMenu->ItemAt(i++)))
341 			{
342 				item->SetEnabled(open);
343 			}
344 			return;
345 		}
346 		case kMsgDataRead:
347 		{
348 			const char* bytes;
349 			ssize_t length;
350 			if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
351 					&length) == B_OK)
352 				fTermView->PushBytes(bytes, length);
353 			return;
354 		}
355 		case kMsgLogfile:
356 		{
357 			// Let's lazy init the file panel
358 			if (fLogFilePanel == NULL) {
359 				fLogFilePanel = new BFilePanel(B_SAVE_PANEL,
360 					&be_app_messenger, NULL, B_FILE_NODE, false);
361 				fLogFilePanel->SetMessage(message);
362 			}
363 			fLogFilePanel->Show();
364 			return;
365 		}
366 		case kMsgSendFile:
367 		{
368 			// Let's lazy init the file panel
369 			if (fSendFilePanel == NULL) {
370 				fSendFilePanel = new BFilePanel(B_OPEN_PANEL,
371 					&be_app_messenger, NULL, B_FILE_NODE, false);
372 			}
373 			fSendFilePanel->SetMessage(message);
374 			fSendFilePanel->Show();
375 			return;
376 		}
377 		case kMsgSettings:
378 		{
379 			int32 baudrate;
380 			stop_bits stopBits;
381 			data_bits dataBits;
382 			parity_mode parity;
383 			uint32 flowcontrol;
384 			BString terminator;
385 
386 			if (message->FindInt32("databits", (int32*)&dataBits) == B_OK) {
387 				for (int i = 0; i < fDatabitsMenu->CountItems(); i++) {
388 					BMenuItem* item = fDatabitsMenu->ItemAt(i);
389 					int32 code;
390 					item->Message()->FindInt32("databits", &code);
391 
392 					if (code == dataBits)
393 						item->SetMarked(true);
394 				}
395 			}
396 
397 			if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK) {
398 				for (int i = 0; i < fStopbitsMenu->CountItems(); i++) {
399 					BMenuItem* item = fStopbitsMenu->ItemAt(i);
400 					int32 code;
401 					item->Message()->FindInt32("stopbits", &code);
402 
403 					if (code == stopBits)
404 						item->SetMarked(true);
405 				}
406 			}
407 
408 			if (message->FindInt32("parity", (int32*)&parity) == B_OK)
409 			{
410 				for (int i = 0; i < fParityMenu->CountItems(); i++) {
411 					BMenuItem* item = fParityMenu->ItemAt(i);
412 					int32 code;
413 					item->Message()->FindInt32("parity", &code);
414 
415 					if (code == parity)
416 						item->SetMarked(true);
417 				}
418 			}
419 
420 			if (message->FindInt32("flowcontrol", (int32*)&flowcontrol)
421 					== B_OK) {
422 				for (int i = 0; i < fFlowcontrolMenu->CountItems(); i++) {
423 					BMenuItem* item = fFlowcontrolMenu->ItemAt(i);
424 					int32 code;
425 					item->Message()->FindInt32("flowcontrol", &code);
426 
427 					if (code == (int32)flowcontrol)
428 						item->SetMarked(true);
429 				}
430 			}
431 
432 			if (message->FindInt32("baudrate", &baudrate) == B_OK) {
433 				int i;
434 				BMenuItem* item = NULL;
435 				for (i = 0; i < fBaudrateMenu->CountItems(); i++) {
436 					item = fBaudrateMenu->ItemAt(i);
437 					int32 code = 0;
438 					item->Message()->FindInt32("baudrate", &code);
439 
440 					if (baudrate == code) {
441 						item->SetMarked(true);
442 						break;
443 					}
444 				}
445 
446 				if (i == fBaudrateMenu->CountItems() && item != NULL) {
447 					// Rate was not found, mark it as "custom".
448 					// Since that is the last item in the menu, we still point
449 					// to it.
450 					item->SetMarked(true);
451 					item->Message()->SetInt32("baudrate", baudrate);
452 				}
453 			}
454 
455 			if (message->FindString("terminator", &terminator) == B_OK) {
456 				fTermView->SetLineTerminator(terminator);
457 				for (int i = 0; i < fLineTerminatorMenu->CountItems(); i++) {
458 					BMenuItem* item = fLineTerminatorMenu->ItemAt(i);
459 					BString code;
460 					item->Message()->FindString("terminator", &code);
461 
462 					if (terminator == code)
463 						item->SetMarked(true);
464 				}
465 			}
466 
467 			return;
468 		}
469 		case kMsgClear:
470 		{
471 			fTermView->Clear();
472 			return;
473 		}
474 		case B_PASTE:
475 		{
476 			fTermView->PasteFromClipboard();
477 		}
478 		case kMsgProgress:
479 		{
480 			// File transfer progress
481 			int32 pos = message->FindInt32("pos");
482 			int32 size = message->FindInt32("size");
483 			BString label = message->FindString("info");
484 
485 			if (pos >= size) {
486 				if (!fStatusBar->IsHidden()) {
487 					fStatusBar->Hide();
488 					fTermView->ResizeBy(0, fStatusBar->Bounds().Height() - 1);
489 				}
490 			} else {
491 				BString text;
492 				text.SetToFormat("%" B_PRId32 "/%" B_PRId32, pos, size);
493 				fStatusBar->SetMaxValue(size);
494 				fStatusBar->SetTo(pos, label, text);
495 				if (fStatusBar->IsHidden()) {
496 					fStatusBar->Show();
497 					fTermView->ResizeBy(0, -(fStatusBar->Bounds().Height() - 1));
498 				}
499 			}
500 			return;
501 		}
502 		default:
503 			BWindow::MessageReceived(message);
504 	}
505 }
506