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