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), '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 275 SerialWindow::~SerialWindow() 276 { 277 delete fLogFilePanel; 278 delete fSendFilePanel; 279 } 280 281 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 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