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