xref: /haiku/src/apps/serialconnect/SerialWindow.cpp (revision 99d1318ec02694fc520a0dc38ae38565db7e8c3c)
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 
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));
131 	editMenu->AddItem(clearScreen);
132 
133 	// TODO copy (when we have selection), paste
134 
135 	// Configuring all this by menus may be a bit unhandy. Make a setting
136 	// window instead ?
137 	fBaudrateMenu = new BMenu(B_TRANSLATE("Baud rate"));
138 	fBaudrateMenu->SetRadioMode(true);
139 	settingsMenu->AddItem(fBaudrateMenu);
140 
141 	fParityMenu = new BMenu(B_TRANSLATE("Parity"));
142 	fParityMenu->SetRadioMode(true);
143 	settingsMenu->AddItem(fParityMenu);
144 
145 	fStopbitsMenu = new BMenu(B_TRANSLATE("Stop bits"));
146 	fStopbitsMenu->SetRadioMode(true);
147 	settingsMenu->AddItem(fStopbitsMenu);
148 
149 	fFlowcontrolMenu = new BMenu(B_TRANSLATE("Flow control"));
150 	fFlowcontrolMenu->SetRadioMode(true);
151 	settingsMenu->AddItem(fFlowcontrolMenu);
152 
153 	fDatabitsMenu = new BMenu(B_TRANSLATE("Data bits"));
154 	fDatabitsMenu->SetRadioMode(true);
155 	settingsMenu->AddItem(fDatabitsMenu);
156 
157 	fLineTerminatorMenu = new BMenu(B_TRANSLATE("Line terminator"));
158 	fLineTerminatorMenu->SetRadioMode(true);
159 	settingsMenu->AddItem(fLineTerminatorMenu);
160 
161 	BMessage* message = new BMessage(kMsgSettings);
162 	message->AddInt32("parity", B_NO_PARITY);
163 	BMenuItem* parityNone =
164 		new BMenuItem(B_TRANSLATE_COMMENT("None", "Parity"), message);
165 
166 	message = new BMessage(kMsgSettings);
167 	message->AddInt32("parity", B_ODD_PARITY);
168 	BMenuItem* parityOdd = new BMenuItem(B_TRANSLATE_COMMENT("Odd", "Parity"),
169 		message);
170 
171 	message = new BMessage(kMsgSettings);
172 	message->AddInt32("parity", B_EVEN_PARITY);
173 	BMenuItem* parityEven =
174 		new BMenuItem(B_TRANSLATE_COMMENT("Even", "Parity"), message);
175 
176 	fParityMenu->AddItem(parityNone);
177 	fParityMenu->AddItem(parityOdd);
178 	fParityMenu->AddItem(parityEven);
179 	fParityMenu->SetTargetForItems(be_app);
180 
181 	message = new BMessage(kMsgSettings);
182 	message->AddInt32("databits", B_DATA_BITS_7);
183 	BMenuItem* data7 = new BMenuItem("7", message);
184 
185 	message = new BMessage(kMsgSettings);
186 	message->AddInt32("databits", B_DATA_BITS_8);
187 	BMenuItem* data8 = new BMenuItem("8", message);
188 
189 	fDatabitsMenu->AddItem(data7);
190 	fDatabitsMenu->AddItem(data8);
191 	fDatabitsMenu->SetTargetForItems(be_app);
192 
193 	message = new BMessage(kMsgSettings);
194 	message->AddInt32("stopbits", B_STOP_BITS_1);
195 	BMenuItem* stop1 = new BMenuItem("1", message);
196 
197 	message = new BMessage(kMsgSettings);
198 	message->AddInt32("stopbits", B_STOP_BITS_2);
199 	BMenuItem* stop2 = new BMenuItem("2", message);
200 
201 	fStopbitsMenu->AddItem(stop1);
202 	fStopbitsMenu->AddItem(stop2);
203 	fStopbitsMenu->SetTargetForItems(be_app);
204 
205 	// Loop backwards to add fastest rates at top of menu
206 	for (int i = sizeof(kBaudrates) / sizeof(kBaudrates[0]); --i >= 0;)
207 	{
208 		message = new BMessage(kMsgSettings);
209 		message->AddInt32("baudrate", kBaudrateConstants[i]);
210 
211 		char buffer[7];
212 		sprintf(buffer, "%d", kBaudrates[i]);
213 		BMenuItem* item = new BMenuItem(buffer, message);
214 
215 		fBaudrateMenu->AddItem(item);
216 	}
217 
218 	message = new BMessage(kMsgCustomBaudrate);
219 	BMenuItem* custom =
220 		new BMenuItem(B_TRANSLATE_COMMENT("custom" B_UTF8_ELLIPSIS,
221 		"Baudrate"), message);
222 	fBaudrateMenu->AddItem(custom);
223 
224 	fBaudrateMenu->SetTargetForItems(be_app);
225 
226 	message = new BMessage(kMsgSettings);
227 	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL);
228 	BMenuItem* hardware =
229 		new BMenuItem(B_TRANSLATE_COMMENT("Hardware", "Flowcontrol"), message);
230 
231 	message = new BMessage(kMsgSettings);
232 	message->AddInt32("flowcontrol", B_SOFTWARE_CONTROL);
233 	BMenuItem* software =
234 		new BMenuItem(B_TRANSLATE_COMMENT("Software", "Flowcontrol"), message);
235 
236 	message = new BMessage(kMsgSettings);
237 	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL | B_SOFTWARE_CONTROL);
238 	BMenuItem* both =
239 		new BMenuItem(B_TRANSLATE_COMMENT("Both", "Flowcontrol"), message);
240 
241 	message = new BMessage(kMsgSettings);
242 	message->AddInt32("flowcontrol", 0);
243 	BMenuItem* noFlow =
244 		new BMenuItem(B_TRANSLATE_COMMENT("None", "Flowcontrol"), message);
245 
246 	fFlowcontrolMenu->AddItem(hardware);
247 	fFlowcontrolMenu->AddItem(software);
248 	fFlowcontrolMenu->AddItem(both);
249 	fFlowcontrolMenu->AddItem(noFlow);
250 	fFlowcontrolMenu->SetTargetForItems(be_app);
251 
252 	message = new BMessage(kMsgSettings);
253 	message->AddString("terminator", "\n");
254 	BMenuItem* lf = new BMenuItem("LF (\\n)", message);
255 
256 	message = new BMessage(kMsgSettings);
257 	message->AddString("terminator", "\r");
258 	BMenuItem* cr = new BMenuItem("CR (\\r)", message);
259 
260 	message = new BMessage(kMsgSettings);
261 	message->AddString("terminator", "\r\n");
262 	BMenuItem* crlf = new BMenuItem("CR/LF (\\r\\n)", message);
263 
264 	fLineTerminatorMenu->AddItem(lf);
265 	fLineTerminatorMenu->AddItem(cr);
266 	fLineTerminatorMenu->AddItem(crlf);
267 
268 	CenterOnScreen();
269 }
270 
271 
272 SerialWindow::~SerialWindow()
273 {
274 	delete fLogFilePanel;
275 	delete fSendFilePanel;
276 }
277 
278 
279 void SerialWindow::MenusBeginning()
280 {
281 	// remove all items from the menu
282 	fConnectionMenu->RemoveItems(0, fConnectionMenu->CountItems(), true);
283 
284 	// fill it with the (updated) serial port list
285 	BSerialPort serialPort;
286 	int deviceCount = serialPort.CountDevices();
287 	bool connected = false;
288 
289 	for (int i = 0; i < deviceCount; i++)
290 	{
291 		char buffer[256];
292 		serialPort.GetDeviceName(i, buffer, 256);
293 
294 		BMessage* message = new BMessage(kMsgOpenPort);
295 		message->AddString("port name", buffer);
296 		BMenuItem* portItem = new BMenuItem(buffer, message);
297 		portItem->SetTarget(be_app);
298 
299 		const BString& connectedPort = ((SerialApp*)be_app)->GetPort();
300 
301 		if (connectedPort == buffer) {
302 			connected = true;
303 			portItem->SetMarked(true);
304 		}
305 
306 		fConnectionMenu->AddItem(portItem);
307 	}
308 
309 	if (deviceCount > 0) {
310 		fConnectionMenu->AddSeparatorItem();
311 
312 		BMenuItem* disconnect = new BMenuItem(B_TRANSLATE("Disconnect"),
313 			new BMessage(kMsgOpenPort), 'Z', B_OPTION_KEY);
314 		if (!connected)
315 			disconnect->SetEnabled(false);
316 		disconnect->SetTarget(be_app);
317 		fConnectionMenu->AddItem(disconnect);
318 	} else {
319 		BMenuItem* noDevices =
320 			new BMenuItem(B_TRANSLATE("<no serial port available>"), NULL);
321 		noDevices->SetEnabled(false);
322 		fConnectionMenu->AddItem(noDevices);
323 	}
324 }
325 
326 
327 void SerialWindow::MessageReceived(BMessage* message)
328 {
329 	switch (message->what)
330 	{
331 		case kMsgOpenPort:
332 		{
333 			BString path;
334 			bool open = (message->FindString("port name", &path) == B_OK);
335 			int i = 1; // Skip "log to file", which woeks even when offline.
336 			BMenuItem* item;
337 			while((item = fFileMenu->ItemAt(i++)))
338 			{
339 				item->SetEnabled(open);
340 			}
341 			return;
342 		}
343 		case kMsgDataRead:
344 		{
345 			const char* bytes;
346 			ssize_t length;
347 			if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
348 					&length) == B_OK)
349 				fTermView->PushBytes(bytes, length);
350 			return;
351 		}
352 		case kMsgLogfile:
353 		{
354 			// Let's lazy init the file panel
355 			if (fLogFilePanel == NULL) {
356 				fLogFilePanel = new BFilePanel(B_SAVE_PANEL,
357 					&be_app_messenger, NULL, B_FILE_NODE, false);
358 				fLogFilePanel->SetMessage(message);
359 			}
360 			fLogFilePanel->Show();
361 			return;
362 		}
363 		case kMsgSendFile:
364 		{
365 			// Let's lazy init the file panel
366 			if (fSendFilePanel == NULL) {
367 				fSendFilePanel = new BFilePanel(B_OPEN_PANEL,
368 					&be_app_messenger, NULL, B_FILE_NODE, false);
369 			}
370 			fSendFilePanel->SetMessage(message);
371 			fSendFilePanel->Show();
372 			return;
373 		}
374 		case kMsgSettings:
375 		{
376 			int32 baudrate;
377 			stop_bits stopBits;
378 			data_bits dataBits;
379 			parity_mode parity;
380 			uint32 flowcontrol;
381 			BString terminator;
382 
383 			if (message->FindInt32("databits", (int32*)&dataBits) == B_OK) {
384 				for (int i = 0; i < fDatabitsMenu->CountItems(); i++) {
385 					BMenuItem* item = fDatabitsMenu->ItemAt(i);
386 					int32 code;
387 					item->Message()->FindInt32("databits", &code);
388 
389 					if (code == dataBits)
390 						item->SetMarked(true);
391 				}
392 			}
393 
394 			if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK) {
395 				for (int i = 0; i < fStopbitsMenu->CountItems(); i++) {
396 					BMenuItem* item = fStopbitsMenu->ItemAt(i);
397 					int32 code;
398 					item->Message()->FindInt32("stopbits", &code);
399 
400 					if (code == stopBits)
401 						item->SetMarked(true);
402 				}
403 			}
404 
405 			if (message->FindInt32("parity", (int32*)&parity) == B_OK)
406 			{
407 				for (int i = 0; i < fParityMenu->CountItems(); i++) {
408 					BMenuItem* item = fParityMenu->ItemAt(i);
409 					int32 code;
410 					item->Message()->FindInt32("parity", &code);
411 
412 					if (code == parity)
413 						item->SetMarked(true);
414 				}
415 			}
416 
417 			if (message->FindInt32("flowcontrol", (int32*)&flowcontrol)
418 					== B_OK) {
419 				for (int i = 0; i < fFlowcontrolMenu->CountItems(); i++) {
420 					BMenuItem* item = fFlowcontrolMenu->ItemAt(i);
421 					int32 code;
422 					item->Message()->FindInt32("flowcontrol", &code);
423 
424 					if (code == (int32)flowcontrol)
425 						item->SetMarked(true);
426 				}
427 			}
428 
429 			if (message->FindInt32("baudrate", &baudrate) == B_OK) {
430 				int i;
431 				BMenuItem* item = NULL;
432 				for (i = 0; i < fBaudrateMenu->CountItems(); i++) {
433 					item = fBaudrateMenu->ItemAt(i);
434 					int32 code = 0;
435 					item->Message()->FindInt32("baudrate", &code);
436 
437 					if (baudrate == code) {
438 						item->SetMarked(true);
439 						break;
440 					}
441 				}
442 
443 				if (i == fBaudrateMenu->CountItems() && item != NULL) {
444 					// Rate was not found, mark it as "custom".
445 					// Since that is the last item in the menu, we still point
446 					// to it.
447 					item->SetMarked(true);
448 					item->Message()->SetInt32("baudrate", baudrate);
449 				}
450 			}
451 
452 			if (message->FindString("terminator", &terminator) == B_OK) {
453 				fTermView->SetLineTerminator(terminator);
454 				for (int i = 0; i < fLineTerminatorMenu->CountItems(); i++) {
455 					BMenuItem* item = fLineTerminatorMenu->ItemAt(i);
456 					BString code;
457 					item->Message()->FindString("terminator", &code);
458 
459 					if (terminator == code)
460 						item->SetMarked(true);
461 				}
462 			}
463 
464 			return;
465 		}
466 		case kMsgClear:
467 		{
468 			fTermView->Clear();
469 			return;
470 		}
471 		case kMsgProgress:
472 		{
473 			// File transfer progress
474 			int32 pos = message->FindInt32("pos");
475 			int32 size = message->FindInt32("size");
476 			BString label = message->FindString("info");
477 
478 			if (pos >= size) {
479 				if (!fStatusBar->IsHidden()) {
480 					fStatusBar->Hide();
481 					fTermView->ResizeBy(0, fStatusBar->Bounds().Height() - 1);
482 				}
483 			} else {
484 				BString text;
485 				text.SetToFormat("%" B_PRId32 "/%" B_PRId32, pos, size);
486 				fStatusBar->SetMaxValue(size);
487 				fStatusBar->SetTo(pos, label, text);
488 				if (fStatusBar->IsHidden()) {
489 					fStatusBar->Show();
490 					fTermView->ResizeBy(0, -(fStatusBar->Bounds().Height() - 1));
491 				}
492 			}
493 			return;
494 		}
495 		default:
496 			BWindow::MessageReceived(message);
497 	}
498 }
499