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