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