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