diff --git a/src/apps/serialconnect/Jamfile b/src/apps/serialconnect/Jamfile index 1a63ae473a..c2c1fc45e9 100644 --- a/src/apps/serialconnect/Jamfile +++ b/src/apps/serialconnect/Jamfile @@ -10,6 +10,7 @@ Application SerialConnect : SerialApp.cpp SerialWindow.cpp TermView.cpp + XModem.cpp encoding.c input.c parser.c diff --git a/src/apps/serialconnect/SerialApp.cpp b/src/apps/serialconnect/SerialApp.cpp index 3ee92462ac..a234f8a13f 100644 --- a/src/apps/serialconnect/SerialApp.cpp +++ b/src/apps/serialconnect/SerialApp.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com * Distributed under the terms of the MIT licence. */ @@ -65,6 +65,7 @@ const BPropertyInfo SerialApp::kScriptingProperties(sProperties); SerialApp::SerialApp() : BApplication(SerialApp::kApplicationSignature) , fLogFile(NULL) + , fFileSender(NULL) { fWindow = new SerialWindow(); @@ -78,6 +79,7 @@ SerialApp::SerialApp() SerialApp::~SerialApp() { delete fLogFile; + delete fFileSender; } @@ -99,21 +101,32 @@ void SerialApp::MessageReceived(BMessage* message) } else { fSerialPort.Close(); } + + // Forward to the window so it can enable/disable menu items + fWindow->PostMessage(message); return; } case kMsgDataRead: { - // forward the message to the window, which will display the - // incoming data - fWindow->PostMessage(message); + const char* bytes; + ssize_t length; + message->FindData("data", B_RAW_TYPE, (const void**)&bytes, + &length); - if (fLogFile) { - const char* bytes; - ssize_t length; - message->FindData("data", B_RAW_TYPE, (const void**)&bytes, - &length); - if (fLogFile->Write(bytes, length) != length) { - // TODO error handling + if (fFileSender != NULL) { + if (fFileSender->BytesReceived(bytes, length)) { + delete fFileSender; + fFileSender = NULL; + } + } else { + // forward the message to the window, which will display the + // incoming data + fWindow->PostMessage(message); + + if (fLogFile) { + if (fLogFile->Write(bytes, length) != length) { + // TODO error handling + } } } @@ -121,6 +134,10 @@ void SerialApp::MessageReceived(BMessage* message) } case kMsgDataWrite: { + // Do not allow sending if a file transfer is in progress. + if (fFileSender != NULL) + return; + const char* bytes; ssize_t size; @@ -147,6 +164,25 @@ void SerialApp::MessageReceived(BMessage* message) debugger("Invalid BMessage received"); return; } + case kMsgSendXmodem: + { + entry_ref ref; + + if (message->FindRef("refs", &ref) == B_OK) { + BFile* file = new BFile(&ref, B_READ_ONLY); + status_t error = file->InitCheck(); + if (error != B_OK) + puts(strerror(error)); + else { + delete fFileSender; + fFileSender = new XModemSender(file, &fSerialPort, fWindow); + } + } else { + message->PrintToStream(); + debugger("Invalid BMessage received"); + } + return; + } case kMsgCustomBaudrate: { // open the custom baudrate selector window diff --git a/src/apps/serialconnect/SerialApp.h b/src/apps/serialconnect/SerialApp.h index 8aff941a06..8f9e47095b 100644 --- a/src/apps/serialconnect/SerialApp.h +++ b/src/apps/serialconnect/SerialApp.h @@ -1,5 +1,5 @@ /* - * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com * Distributed under the terms of the MIT licence. */ @@ -13,6 +13,8 @@ #include #include +#include "XModem.h" + class BFile; class SerialWindow; @@ -44,6 +46,8 @@ class SerialApp: public BApplication BFile* fLogFile; BString fPortPath; + XModemSender* fFileSender; + static status_t PollSerial(void*); static const BPropertyInfo kScriptingProperties; @@ -57,7 +61,9 @@ enum messageConstants { kMsgDataWrite = 'dawr', kMsgLogfile = 'logf', kMsgOpenPort = 'open', + kMsgProgress = 'prog', kMsgSettings = 'stty', + kMsgSendXmodem = 'xmtx', }; #endif diff --git a/src/apps/serialconnect/SerialWindow.cpp b/src/apps/serialconnect/SerialWindow.cpp index 9737603b9a..ac089223bf 100644 --- a/src/apps/serialconnect/SerialWindow.cpp +++ b/src/apps/serialconnect/SerialWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015, Adrien Destugues, pulkomandy@pulkomandy.tk + * Copyright 2012-2017, Adrien Destugues, pulkomandy@pulkomandy.tk * Distributed under the terms of the MIT licence. */ @@ -15,6 +15,7 @@ #include #include #include +#include #include "SerialApp.h" #include "TermView.h" @@ -63,18 +64,27 @@ SerialWindow::SerialWindow() ResizeTo(r.right - 1, r.bottom + B_H_SCROLL_BAR_HEIGHT - 1); + r = fTermView->Frame(); + r.top = r.bottom - 37; + + fStatusBar = new BStatusBar(r, "file transfer progress", NULL, NULL); + fStatusBar->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT); + fStatusBar->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); + fStatusBar->Hide(); + AddChild(menuBar); AddChild(fTermView); AddChild(scrollBar); + AddChild(fStatusBar); fConnectionMenu = new BMenu("Connection"); - BMenu* fileMenu = new BMenu("File"); + fFileMenu = new BMenu("File"); BMenu* settingsMenu = new BMenu("Settings"); fConnectionMenu->SetRadioMode(true); menuBar->AddItem(fConnectionMenu); - menuBar->AddItem(fileMenu); + menuBar->AddItem(fFileMenu); menuBar->AddItem(settingsMenu); // TODO edit menu - what's in it ? @@ -83,15 +93,17 @@ SerialWindow::SerialWindow() BMenuItem* logFile = new BMenuItem("Log to file" B_UTF8_ELLIPSIS, new BMessage(kMsgLogfile)); - fileMenu->AddItem(logFile); -#if 0 + fFileMenu->AddItem(logFile); // TODO implement these - BMenuItem* xmodemSend = new BMenuItem("X/Y/ZModem send" B_UTF8_ELLIPSIS, - NULL); - fileMenu->AddItem(xmodemSend); + BMenuItem* xmodemSend = new BMenuItem("XModem send" B_UTF8_ELLIPSIS, + new BMessage(kMsgSendXmodem)); + fFileMenu->AddItem(xmodemSend); + xmodemSend->SetEnabled(false); +#if 0 BMenuItem* xmodemReceive = new BMenuItem( "X/Y/Zmodem receive" B_UTF8_ELLIPSIS, NULL); - fileMenu->AddItem(xmodemReceive); + fFileMenu->AddItem(xmodemReceive); + xmodemReceive->SetEnabled(false); #endif // Configuring all this by menus may be a bit unhandy. Make a setting @@ -279,6 +291,18 @@ void SerialWindow::MessageReceived(BMessage* message) { switch (message->what) { + case kMsgOpenPort: + { + BString path; + bool open = (message->FindString("port name", &path) == B_OK); + int i = 1; // Skip "log to file", which woeks even when offline. + BMenuItem* item; + while((item = fFileMenu->ItemAt(i++))) + { + item->SetEnabled(open); + } + return; + } case kMsgDataRead: { const char* bytes; @@ -289,11 +313,13 @@ void SerialWindow::MessageReceived(BMessage* message) return; } case kMsgLogfile: + case kMsgSendXmodem: { // Let's lazy init the file panel if (fLogFilePanel == NULL) { - fLogFilePanel = new BFilePanel(B_SAVE_PANEL, &be_app_messenger, - NULL, B_FILE_NODE, false); + fLogFilePanel = new BFilePanel( + message->what == kMsgSendXmodem ? B_OPEN_PANEL : B_SAVE_PANEL, + &be_app_messenger, NULL, B_FILE_NODE, false); fLogFilePanel->SetMessage(message); } fLogFilePanel->Show(); @@ -391,6 +417,30 @@ void SerialWindow::MessageReceived(BMessage* message) return; } + case kMsgProgress: + { + // File transfer progress + int32 pos = message->FindInt32("pos"); + int32 size = message->FindInt32("size"); + BString label = message->FindString("info"); + + if (pos >= size) { + if (!fStatusBar->IsHidden()) { + fStatusBar->Hide(); + fTermView->ResizeBy(0, fStatusBar->Bounds().Height() - 1); + } + } else { + BString text; + text.SetToFormat("%" B_PRId32 "/%" B_PRId32, pos, size); + fStatusBar->SetMaxValue(size); + fStatusBar->SetTo(pos, label, text); + if (fStatusBar->IsHidden()) { + fStatusBar->Show(); + fTermView->ResizeBy(0, -(fStatusBar->Bounds().Height() - 1)); + } + } + return; + } } BWindow::MessageReceived(message); diff --git a/src/apps/serialconnect/SerialWindow.h b/src/apps/serialconnect/SerialWindow.h index 186ac69dd3..7853e77dd6 100644 --- a/src/apps/serialconnect/SerialWindow.h +++ b/src/apps/serialconnect/SerialWindow.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015, Adrien Destugues, pulkomandy@pulkomandy.tk + * Copyright 2012-2017, Adrien Destugues, pulkomandy@pulkomandy.tk * Distributed under the terms of the MIT licence. */ @@ -9,6 +9,7 @@ class BFilePanel; class BMenu; +class BStatusBar; class TermView; @@ -32,7 +33,9 @@ class SerialWindow: public BWindow BMenu* fFlowcontrolMenu; BMenu* fBaudrateMenu; BMenu* fLineTerminatorMenu; + BMenu* fFileMenu; BFilePanel* fLogFilePanel; + BStatusBar* fStatusBar; static const int kBaudrates[]; static const int kBaudrateConstants[]; diff --git a/src/apps/serialconnect/XModem.cpp b/src/apps/serialconnect/XModem.cpp new file mode 100644 index 0000000000..4a427a7939 --- /dev/null +++ b/src/apps/serialconnect/XModem.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk + * Distributed under terms of the MIT license. + */ + + +#include "XModem.h" + +#include "SerialApp.h" + +#include + +#include +#include + + +// ASCII control characters used in XMODEM protocol +static const char kSOH = 1; +static const char kEOT = 4; +static const char kACK = 6; +static const char kNAK = 21; +static const char kSUB = 26; + +static const int kBlockSize = 128; + + +XModemSender::XModemSender(BDataIO* source, BSerialPort* sink, BHandler* listener) + : fSource(source), + fSink(sink), + fListener(listener), + fBlockNumber(0), + fEotSent(false) +{ + fStatus = "Waiting for receiver" B_UTF8_ELLIPSIS; + + BPositionIO* pos = dynamic_cast(source); + if (pos) + pos->GetSize(&fSourceSize); + else + fSourceSize = 0; + + NextBlock(); +} + + +XModemSender::~XModemSender() +{ + delete fSource; +} + + +bool +XModemSender::BytesReceived(const char* data, size_t length) +{ + size_t i; + + for (i = 0; i < length; i++) + { + switch (data[i]) + { + case kNAK: + if (fEotSent) { + fSink->Write(&kEOT, 1); + } else { + fStatus = "Checksum error, re-send block"; + SendBlock(); + } + break; + + case kACK: + if (fEotSent) { + return true; + } + + if (NextBlock() == B_OK) { + fStatus = "Sending" B_UTF8_ELLIPSIS; + SendBlock(); + } else { + fStatus = "Everything sent, waiting for acknowledge"; + fSink->Write(&kEOT, 1); + fEotSent = true; + } + break; + + default: + break; + } + } + + return false; +} + + +void +XModemSender::SendBlock() +{ + uint8_t header[3]; + uint8_t checksum = 0; + int i; + + header[0] = kSOH; + header[1] = fBlockNumber; + header[2] = 255 - fBlockNumber; + + for (i = 0; i < kBlockSize; i++) + checksum += fBuffer[i]; + + fSink->Write(header, 3); + fSink->Write(fBuffer, kBlockSize); + fSink->Write(&checksum, 1); +} + + +status_t +XModemSender::NextBlock() +{ + memset(fBuffer, kSUB, kBlockSize); + + if (fSource->Read(fBuffer, kBlockSize) > 0) { + fBlockNumber++; + BMessage msg(kMsgProgress); + msg.AddInt32("pos", fBlockNumber); + msg.AddInt32("size", fSourceSize / kBlockSize); + msg.AddString("info", fStatus); + fListener.SendMessage(&msg); + return B_OK; + } + return B_ERROR; +} diff --git a/src/apps/serialconnect/XModem.h b/src/apps/serialconnect/XModem.h new file mode 100644 index 0000000000..4a5cfb2505 --- /dev/null +++ b/src/apps/serialconnect/XModem.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk + * Distributed under terms of the MIT license. + */ + + +#ifndef XMODEM_H +#define XMODEM_H + + +#include +#include + + +class BDataIO; +class BHandler; +class BSerialPort; + + +class XModemSender { + public: + XModemSender(BDataIO* source, BSerialPort* sink, + BHandler* listener); + ~XModemSender(); + + bool BytesReceived(const char* data, size_t length); + private: + + void SendBlock(); + status_t NextBlock(); + + private: + BDataIO* fSource; + BSerialPort* fSink; + BMessenger fListener; + off_t fBlockNumber; + off_t fSourceSize; + uint8_t fBuffer[128]; + bool fEotSent; + BString fStatus; +}; + + +#endif /* !XMODEM_H */