root/src/apps/serialconnect/SerialApp.cpp
/*
 * Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com
 * Distributed under the terms of the MIT licence.
 */


#include "SerialApp.h"

#include <stdio.h>
#include <string.h>

#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Path.h>

#include "CustomRateWindow.h"
#include "SerialWindow.h"


static property_info sProperties[] = {
        { "baudrate",
                { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
                "get or set the baudrate",
                0, { B_INT32_TYPE }
        },
        { "bits",
                { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
                "get or set the number of data bits (7 or 8)",
                0, { B_INT32_TYPE }
        },
        { "stopbits",
                { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
                "get or set the number of stop bits (1 or 2)",
                0, { B_INT32_TYPE }
        },
        { "parity",
                { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
                "get or set the parity (none, even or odd)",
                0, { B_STRING_TYPE }
        },
        { "flowcontrol",
                { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
                "get or set the flow control (hardware, software, both, or none)",
                0, { B_STRING_TYPE }
        },
        { "port",
                { B_GET_PROPERTY, B_SET_PROPERTY, B_DELETE_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "get or set the port device",
                0, { B_STRING_TYPE }
        },
        { 0 }
};

const BPropertyInfo SerialApp::kScriptingProperties(sProperties);


SerialApp::SerialApp()
        : BApplication(SerialApp::kApplicationSignature)
        , fLogFile(NULL)
        , fFileSender(NULL)
{
        fWindow = new SerialWindow();

        fSerialLock = create_sem(0, "Serial port lock");
        thread_id id = spawn_thread(PollSerial, "Serial port poller",
                B_LOW_PRIORITY, this);
        resume_thread(id);
}


SerialApp::~SerialApp()
{
        delete fLogFile;
        delete fFileSender;
}


void SerialApp::ReadyToRun()
{
        LoadSettings();
        fWindow->Show();
}


void SerialApp::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgOpenPort:
                {
                        if (message->FindString("port name", &fPortPath) == B_OK) {
                                fSerialPort.Open(fPortPath);
                                release_sem(fSerialLock);
                        } else {
                                fSerialPort.Close();
                        }

                        // Forward to the window so it can enable/disable menu items
                        fWindow->PostMessage(message);
                        return;
                }
                case kMsgDataRead:
                {
                        const uint8_t* bytes;
                        ssize_t length;
                        message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
                                &length);

                        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
                                        }
                                }
                        }

                        return;
                }
                case kMsgDataWrite:
                {
                        // Do not allow sending if a file transfer is in progress.
                        if (fFileSender != NULL)
                                return;

                        const char* bytes;
                        ssize_t size;

                        if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
                                        &size) == B_OK)
                                fSerialPort.Write(bytes, size);
                        return;
                }
                case kMsgLogfile:
                {
                        entry_ref parent;
                        const char* filename;

                        if (message->FindRef("directory", &parent) == B_OK
                                && message->FindString("name", &filename) == B_OK) {
                                delete fLogFile;
                                BDirectory directory(&parent);
                                fLogFile = new BFile(&directory, filename,
                                        B_WRITE_ONLY | B_CREATE_FILE | B_OPEN_AT_END);
                                status_t error = fLogFile->InitCheck();
                                if (error != B_OK)
                                        puts(strerror(error));
                        } else
                                debugger("Invalid BMessage received");
                        return;
                }
                case kMsgSendFile:
                {
                        entry_ref ref;

                        BString protocol = message->FindString("protocol");

                        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;
                                        if (protocol == "xmodem")
                                                fFileSender = new XModemSender(file, &fSerialPort, fWindow);
                                        else
                                                fFileSender = new RawSender(file, &fSerialPort, fWindow);
                                }
                        } else {
                                message->PrintToStream();
                                debugger("Invalid BMessage received");
                        }
                        return;
                }
                case kMsgCustomBaudrate:
                {
                        // open the custom baudrate selector window
                        CustomRateWindow* window = new CustomRateWindow(fSerialPort.DataRate());
                        window->Show();
                        return;
                }
                case kMsgSettings:
                {
                        int32 baudrate;
                        stop_bits stopBits;
                        data_bits dataBits;
                        parity_mode parity;
                        uint32 flowcontrol;

                        if (message->FindInt32("databits", (int32*)&dataBits) == B_OK)
                                fSerialPort.SetDataBits(dataBits);

                        if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK)
                                fSerialPort.SetStopBits(stopBits);

                        if (message->FindInt32("parity", (int32*)&parity) == B_OK)
                                fSerialPort.SetParityMode(parity);

                        if (message->FindInt32("flowcontrol", (int32*)&flowcontrol) == B_OK)
                                fSerialPort.SetFlowControl(flowcontrol);

                        if (message->FindInt32("baudrate", &baudrate) == B_OK) {
                                data_rate rate = (data_rate)baudrate;
                                fSerialPort.SetDataRate(rate);
                        }

                        return;
                }
        }

        // Handle scripting messages
        if (message->HasSpecifiers()) {
                BMessage specifier;
                int32 what;
                int32 index;
                const char* property;

                BMessage reply(B_REPLY);
                BMessage settings(kMsgSettings);
                bool settingsChanged = false;

                if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
                        == B_OK) {
                        switch (kScriptingProperties.FindMatch(message, index, &specifier,
                                what, property)) {
                                case 0: // baudrate
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddInt32("result", fSerialPort.DataRate());
                                                message->SendReply(&reply);
                                                return;
                                        }
                                        if (message->what == B_SET_PROPERTY) {
                                                int32 rate = message->FindInt32("data");
                                                settingsChanged = true;
                                                settings.AddInt32("baudrate", rate);
                                        }
                                        break;
                                case 1: // data bits
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddInt32("result", fSerialPort.DataBits() + 7);
                                                message->SendReply(&reply);
                                                return;
                                        }
                                        if (message->what == B_SET_PROPERTY) {
                                                int32 bits = message->FindInt32("data");
                                                settingsChanged = true;
                                                settings.AddInt32("databits", bits - 7);
                                        }
                                        break;
                                case 2: // stop bits
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddInt32("result", fSerialPort.StopBits() + 1);
                                                message->SendReply(&reply);
                                                return;
                                        }
                                        if (message->what == B_SET_PROPERTY) {
                                                int32 bits = message->FindInt32("data");
                                                settingsChanged = true;
                                                settings.AddInt32("stopbits", bits - 1);
                                        }
                                        break;
                                case 3: // parity
                                {
                                        static const char* strings[] = {"none", "odd", "even"};
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddString("result",
                                                        strings[fSerialPort.ParityMode()]);
                                                message->SendReply(&reply);
                                                return;
                                        }
                                        if (message->what == B_SET_PROPERTY) {
                                                BString bits = message->FindString("data");
                                                int i;
                                                for (i = 0; i < 3; i++) {
                                                        if (bits == strings[i])
                                                                break;
                                                }

                                                if (i < 3) {
                                                        settingsChanged = true;
                                                        settings.AddInt32("parity", i);
                                                }
                                        }
                                        break;
                                }
                                case 4: // flow control
                                {
                                        static const char* strings[] = {"none", "hardware",
                                                "software", "both"};
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddString("result",
                                                        strings[fSerialPort.FlowControl()]);
                                                message->SendReply(&reply);
                                                return;
                                        }
                                        if (message->what == B_SET_PROPERTY) {
                                                BString bits = message->FindString("data");
                                                int i;
                                                for (i = 0; i < 4; i++) {
                                                        if (bits == strings[i])
                                                                break;
                                                }

                                                if (i < 4) {
                                                        settingsChanged = true;
                                                        settings.AddInt32("flowcontrol", i);
                                                }
                                        }
                                        break;
                                }
                                case 5: // port
                                        if (message->what == B_GET_PROPERTY) {
                                                reply.AddString("port", GetPort());
                                                message->SendReply(&reply);
                                        } else if (message->what == B_DELETE_PROPERTY
                                                || message->what == B_SET_PROPERTY) {
                                                BString path = message->FindString("data");
                                                BMessage openMessage(kMsgOpenPort);
                                                openMessage.AddString("port name", path);
                                                PostMessage(&openMessage);
                                                fWindow->PostMessage(&openMessage);
                                        }
                                        return;
                        }
                }

                if (settingsChanged) {
                        PostMessage(&settings);
                        fWindow->PostMessage(&settings);
                        return;
                }
        }

        BApplication::MessageReceived(message);
}


bool SerialApp::QuitRequested()
{
        if (BApplication::QuitRequested()) {
                SaveSettings();
                return true;
        }
        return false;
}


const BString& SerialApp::GetPort()
{
        return fPortPath;
}


void SerialApp::LoadSettings()
{
        BPath path;
        find_directory(B_USER_SETTINGS_DIRECTORY, &path);
        path.Append("SerialConnect");

        BFile file(path.Path(), B_READ_ONLY);
        BMessage message(kMsgSettings);
        if (message.Unflatten(&file) != B_OK) {
                message.AddInt32("parity", fSerialPort.ParityMode());
                message.AddInt32("databits", fSerialPort.DataBits());
                message.AddInt32("stopbits", fSerialPort.StopBits());
                message.AddInt32("baudrate", fSerialPort.DataRate());
                message.AddInt32("flowcontrol", fSerialPort.FlowControl());
        }

        be_app->PostMessage(&message);
        fWindow->PostMessage(&message);
}


void SerialApp::SaveSettings()
{
        BMessage message(kMsgSettings);
        message.AddInt32("parity", fSerialPort.ParityMode());
        message.AddInt32("databits", fSerialPort.DataBits());
        message.AddInt32("stopbits", fSerialPort.StopBits());
        message.AddInt32("baudrate", fSerialPort.DataRate());
        message.AddInt32("flowcontrol", fSerialPort.FlowControl());

        BPath path;
        find_directory(B_USER_SETTINGS_DIRECTORY, &path);
        path.Append("SerialConnect");

        BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
        message.Flatten(&file);
}


/* static */
status_t SerialApp::PollSerial(void*)
{
        SerialApp* application = (SerialApp*)be_app;
        char buffer[256];

        for (;;) {
                ssize_t bytesRead;

                bytesRead = application->fSerialPort.Read(buffer, sizeof(buffer));
                if (bytesRead == B_FILE_ERROR) {
                        // Port is not open - wait for it and start over
                        acquire_sem(application->fSerialLock);
                } else if (bytesRead > 0) {
                        // We read something, forward it to the app for handling
                        BMessage* serialData = new BMessage(kMsgDataRead);
                        serialData->AddData("data", B_RAW_TYPE, buffer, bytesRead);
                        be_app_messenger.SendMessage(serialData);
                }
        }

        // Should not reach this line anyway...
        return B_OK;
}


const char* SerialApp::kApplicationSignature
        = "application/x-vnd.haiku.SerialConnect";


int main(int argc, char** argv)
{
        SerialApp app;
        app.Run();
}


status_t
SerialApp::GetSupportedSuites(BMessage* message)
{
        message->AddString("suites", "suite/vnd.Haiku-SerialPort");
        message->AddFlat("messages", &kScriptingProperties);
        return BApplication::GetSupportedSuites(message);
}


BHandler*
SerialApp::ResolveSpecifier(BMessage* message, int32 index,
        BMessage* specifier, int32 what, const char* property)
{
        if (kScriptingProperties.FindMatch(message, index, specifier, what,
                property) >= 0)
                return this;

        return BApplication::ResolveSpecifier(message, index, specifier, what,
                property);
}