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