root/src/kits/app/Application.cpp
/*
 * Copyright 2001-2015 Haiku, inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 *              Jerome Duval
 *              Erik Jaesler, erik@cgsoftware.com
 */


#include <Application.h>

#include <new>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <Alert.h>
#include <AppFileInfo.h>
#include <Cursor.h>
#include <Debug.h>
#include <Entry.h>
#include <File.h>
#include <Locker.h>
#include <MessageRunner.h>
#include <ObjectList.h>
#include <Path.h>
#include <PropertyInfo.h>
#include <RegistrarDefs.h>
#include <Resources.h>
#include <Roster.h>
#include <Window.h>

#include <AppMisc.h>
#include <AppServerLink.h>
#include <AutoLocker.h>
#include <BitmapPrivate.h>
#include <DraggerPrivate.h>
#include <LaunchDaemonDefs.h>
#include <LaunchRoster.h>
#include <LooperList.h>
#include <MenuWindow.h>
#include <PicturePrivate.h>
#include <PortLink.h>
#include <RosterPrivate.h>
#include <ServerMemoryAllocator.h>
#include <ServerProtocol.h>


using namespace BPrivate;


static const char* kDefaultLooperName = "AppLooperPort";

BApplication* be_app = NULL;
BMessenger be_app_messenger;

pthread_once_t sAppResourcesInitOnce = PTHREAD_ONCE_INIT;
BResources* BApplication::sAppResources = NULL;
BObjectList<BLooper> sOnQuitLooperList;


enum {
        kWindowByIndex,
        kWindowByName,
        kLooperByIndex,
        kLooperByID,
        kLooperByName,
        kApplication
};


static property_info sPropertyInfo[] = {
        {
                "Window",
                {},
                {B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
                NULL, kWindowByIndex,
                {},
                {},
                {}
        },
        {
                "Window",
                {},
                {B_NAME_SPECIFIER},
                NULL, kWindowByName,
                {},
                {},
                {}
        },
        {
                "Looper",
                {},
                {B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
                NULL, kLooperByIndex,
                {},
                {},
                {}
        },
        {
                "Looper",
                {},
                {B_ID_SPECIFIER},
                NULL, kLooperByID,
                {},
                {},
                {}
        },
        {
                "Looper",
                {},
                {B_NAME_SPECIFIER},
                NULL, kLooperByName,
                {},
                {},
                {}
        },
        {
                "Name",
                {B_GET_PROPERTY},
                {B_DIRECT_SPECIFIER},
                NULL, kApplication,
                {B_STRING_TYPE},
                {},
                {}
        },
        {
                "Window",
                {B_COUNT_PROPERTIES},
                {B_DIRECT_SPECIFIER},
                NULL, kApplication,
                {B_INT32_TYPE},
                {},
                {}
        },
        {
                "Loopers",
                {B_GET_PROPERTY},
                {B_DIRECT_SPECIFIER},
                NULL, kApplication,
                {B_MESSENGER_TYPE},
                {},
                {}
        },
        {
                "Windows",
                {B_GET_PROPERTY},
                {B_DIRECT_SPECIFIER},
                NULL, kApplication,
                {B_MESSENGER_TYPE},
                {},
                {}
        },
        {
                "Looper",
                {B_COUNT_PROPERTIES},
                {B_DIRECT_SPECIFIER},
                NULL, kApplication,
                {B_INT32_TYPE},
                {},
                {}
        },

        { 0 }
};


// argc/argv
extern const int __libc_argc;
extern const char* const *__libc_argv;


// debugging
//#define DBG(x) x
#define DBG(x)
#define OUT     printf


//      #pragma mark - static helper functions


/*!
        \brief Checks whether the supplied string is a valid application signature.

        An error message is printed, if the string is no valid app signature.

        \param signature The string to be checked.

        \return A status code.
        \retval B_OK \a signature is a valid app signature.
        \retval B_BAD_VALUE \a signature is \c NULL or no valid app signature.
*/
static status_t
check_app_signature(const char* signature)
{
        bool isValid = false;
        BMimeType type(signature);

        if (type.IsValid() && !type.IsSupertypeOnly()
                && BMimeType("application").Contains(&type)) {
                isValid = true;
        }

        if (!isValid) {
                printf("bad signature (%s), must begin with \"application/\" and "
                           "can't conflict with existing registered mime types inside "
                           "the \"application\" media type.\n", signature);
        }

        return (isValid ? B_OK : B_BAD_VALUE);
}


#ifndef RUN_WITHOUT_REGISTRAR
// Fills the passed BMessage with B_ARGV_RECEIVED infos.
static void
fill_argv_message(BMessage &message)
{
        message.what = B_ARGV_RECEIVED;

        int32 argc = __libc_argc;
        const char* const *argv = __libc_argv;

        // add argc
        message.AddInt32("argc", argc);

        // add argv
        for (int32 i = 0; i < argc; i++) {
                if (argv[i] != NULL)
                        message.AddString("argv", argv[i]);
        }

        // add current working directory
        char cwd[B_PATH_NAME_LENGTH];
        if (getcwd(cwd, B_PATH_NAME_LENGTH))
                message.AddString("cwd", cwd);
}
#endif


//      #pragma mark - BApplication


BApplication::BApplication(const char* signature)
        :
        BLooper(kDefaultLooperName)
{
        _InitData(signature, true, NULL);
}


BApplication::BApplication(const char* signature, status_t* _error)
        :
        BLooper(kDefaultLooperName)
{
        _InitData(signature, true, _error);
}


BApplication::BApplication(const char* signature, const char* looperName,
        port_id port, bool initGUI, status_t* _error)
        :
        BLooper(B_NORMAL_PRIORITY + 1, port < 0 ? _GetPort(signature) : port,
                looperName != NULL ? looperName : kDefaultLooperName)
{
        _InitData(signature, initGUI, _error);
        if (port < 0)
                fOwnsPort = false;
}


BApplication::BApplication(BMessage* data)
        // Note: BeOS calls the private BLooper(int32, port_id, const char*)
        // constructor here, test if it's needed
        :
        BLooper(kDefaultLooperName)
{
        const char* signature = NULL;
        data->FindString("mime_sig", &signature);

        _InitData(signature, true, NULL);

        bigtime_t pulseRate;
        if (data->FindInt64("_pulse", &pulseRate) == B_OK)
                SetPulseRate(pulseRate);
}


#ifdef __HAIKU_BEOS_COMPATIBLE
BApplication::BApplication(uint32 signature)
{
}


BApplication::BApplication(const BApplication &rhs)
{
}


BApplication&
BApplication::operator=(const BApplication &rhs)
{
        return *this;
}
#endif


BApplication::~BApplication()
{
        Lock();

        // tell all loopers(usually windows) to quit. Also, wait for them.
        _QuitAllWindows(true);

        // quit registered loopers
        for (int32 i = 0; i < sOnQuitLooperList.CountItems(); i++) {
                BLooper* looper = sOnQuitLooperList.ItemAt(i);
                if (looper->Lock())
                        looper->Quit();
        }

        // unregister from the roster
        BRoster::Private().RemoveApp(Team());

#ifndef RUN_WITHOUT_APP_SERVER
        // tell app_server we're quitting...
        if (be_app) {
                // be_app can be NULL here if the application fails to initialize
                // correctly. For example, if it's already running and it's set to
                // exclusive launch.
                BPrivate::AppServerLink link;
                link.StartMessage(B_QUIT_REQUESTED);
                link.Flush();
        }
        // the sender port belongs to the app_server
        delete_port(fServerLink->ReceiverPort());
        delete fServerLink;
#endif  // RUN_WITHOUT_APP_SERVER

        delete fServerAllocator;

        // uninitialize be_app, the be_app_messenger is invalidated automatically
        be_app = NULL;
}


void
BApplication::_InitData(const char* signature, bool initGUI, status_t* _error)
{
        DBG(OUT("BApplication::InitData(`%s', %p)\n", signature, _error));
        // check whether there exists already an application
        if (be_app != NULL)
                debugger("2 BApplication objects were created. Only one is allowed.");

        fServerLink = new BPrivate::PortLink(-1, -1);
        fServerAllocator = NULL;
        fServerReadOnlyMemory = NULL;
        fInitialWorkspace = 0;
        //fDraggedMessage = NULL;
        fReadyToRunCalled = false;

        // initially, there is no pulse
        fPulseRunner = NULL;
        fPulseRate = 0;

        // check signature
        fInitError = check_app_signature(signature);
        fAppName = signature;

#ifndef RUN_WITHOUT_REGISTRAR
        bool registerApp = signature == NULL
                || (strcasecmp(signature, B_REGISTRAR_SIGNATURE) != 0
                        && strcasecmp(signature, kLaunchDaemonSignature) != 0);
        // get team and thread
        team_id team = Team();
        thread_id thread = BPrivate::main_thread_for(team);
#endif

        // get app executable ref
        entry_ref ref;
        if (fInitError == B_OK) {
                fInitError = BPrivate::get_app_ref(&ref);
                if (fInitError != B_OK) {
                        DBG(OUT("BApplication::InitData(): Failed to get app ref: %s\n",
                                strerror(fInitError)));
                }
        }

        // get the BAppFileInfo and extract the information we need
        uint32 appFlags = B_REG_DEFAULT_APP_FLAGS;
        if (fInitError == B_OK) {
                BAppFileInfo fileInfo;
                BFile file(&ref, B_READ_ONLY);
                fInitError = fileInfo.SetTo(&file);
                if (fInitError == B_OK) {
                        fileInfo.GetAppFlags(&appFlags);
                        char appFileSignature[B_MIME_TYPE_LENGTH];
                        // compare the file signature and the supplied signature
                        if (fileInfo.GetSignature(appFileSignature) == B_OK
                                && strcasecmp(appFileSignature, signature) != 0) {
                                printf("Signature in rsrc doesn't match constructor arg. (%s, %s)\n",
                                        signature, appFileSignature);
                        }
                } else {
                        DBG(OUT("BApplication::InitData(): Failed to get info from: "
                                "BAppFileInfo: %s\n", strerror(fInitError)));
                }
        }

#ifndef RUN_WITHOUT_REGISTRAR
        // check whether be_roster is valid
        if (fInitError == B_OK && registerApp
                && !BRoster::Private().IsMessengerValid(false)) {
                printf("FATAL: be_roster is not valid. Is the registrar running?\n");
                fInitError = B_NO_INIT;
        }

        // check whether or not we are pre-registered
        bool preRegistered = false;
        app_info appInfo;
        if (fInitError == B_OK && registerApp) {
                if (BRoster::Private().IsAppRegistered(&ref, team, 0, &preRegistered,
                                &appInfo) != B_OK) {
                        preRegistered = false;
                }
        }
        if (preRegistered) {
                // we are pre-registered => the app info has been filled in
                // Check whether we need to replace the looper port with a port
                // created by the roster.
                if (appInfo.port >= 0 && appInfo.port != fMsgPort) {
                        delete_port(fMsgPort);
                        fMsgPort = appInfo.port;
                } else
                        appInfo.port = fMsgPort;
                // check the signature and correct it, if necessary, also the case
                if (strcmp(appInfo.signature, fAppName))
                        BRoster::Private().SetSignature(team, fAppName);
                // complete the registration
                fInitError = BRoster::Private().CompleteRegistration(team, thread,
                                                appInfo.port);
        } else if (fInitError == B_OK) {
                // not pre-registered -- try to register the application
                team_id otherTeam = -1;
                if (registerApp) {
                        fInitError = BRoster::Private().AddApplication(signature, &ref,
                                appFlags, team, thread, fMsgPort, true, NULL, &otherTeam);
                        if (fInitError != B_OK) {
                                DBG(OUT("BApplication::InitData(): Failed to add app: %s\n",
                                        strerror(fInitError)));
                        }
                }
                if (fInitError == B_ALREADY_RUNNING) {
                        // An instance is already running and we asked for
                        // single/exclusive launch. Send our argv to the running app.
                        // Do that only, if the app is NOT B_ARGV_ONLY.
                        if (otherTeam >= 0) {
                                BMessenger otherApp(NULL, otherTeam);
                                app_info otherAppInfo;
                                bool argvOnly = be_roster->GetRunningAppInfo(otherTeam,
                                                &otherAppInfo) == B_OK
                                        && (otherAppInfo.flags & B_ARGV_ONLY) != 0;

                                if (__libc_argc > 1 && !argvOnly) {
                                        // create an B_ARGV_RECEIVED message
                                        BMessage argvMessage(B_ARGV_RECEIVED);
                                        fill_argv_message(argvMessage);

                                        // replace the first argv string with the path of the
                                        // other application
                                        BPath path;
                                        if (path.SetTo(&otherAppInfo.ref) == B_OK)
                                                argvMessage.ReplaceString("argv", 0, path.Path());

                                        // send the message
                                        otherApp.SendMessage(&argvMessage);
                                } else if (!argvOnly)
                                        otherApp.SendMessage(B_SILENT_RELAUNCH);
                        }
                } else if (fInitError == B_OK) {
                        // the registrations was successful
                        // Create a B_ARGV_RECEIVED message and send it to ourselves.
                        // Do that even, if we are B_ARGV_ONLY.
                        // TODO: When BLooper::AddMessage() is done, use that instead of
                        // PostMessage().

                        DBG(OUT("info: BApplication successfully registered.\n"));

                        if (__libc_argc > 1) {
                                BMessage argvMessage(B_ARGV_RECEIVED);
                                fill_argv_message(argvMessage);
                                PostMessage(&argvMessage, this);
                        }
                        // send a B_READY_TO_RUN message as well
                        PostMessage(B_READY_TO_RUN, this);
                } else if (fInitError > B_ERRORS_END) {
                        // Registrar internal errors shouldn't fall into the user's hands.
                        fInitError = B_ERROR;
                }
        }
#else
        // We need to have ReadyToRun called even when we're not using the registrar
        PostMessage(B_READY_TO_RUN, this);
#endif  // ifndef RUN_WITHOUT_REGISTRAR

        if (fInitError == B_OK) {
                // TODO: Not completely sure about the order, but this should be close.

                // init be_app and be_app_messenger
                be_app = this;
                be_app_messenger = BMessenger(NULL, this);

                // set the BHandler's name
                SetName(ref.name);

                // create meta MIME
                BPath path;
                if (registerApp && path.SetTo(&ref) == B_OK)
                        create_app_meta_mime(path.Path(), false, true, false);

#ifndef RUN_WITHOUT_APP_SERVER
                // app server connection and IK initialization
                if (initGUI)
                        fInitError = _InitGUIContext();
#endif  // RUN_WITHOUT_APP_SERVER
        }

        // Return the error or exit, if there was an error and no error variable
        // has been supplied.
        if (_error != NULL) {
                *_error = fInitError;
        } else if (fInitError != B_OK) {
                DBG(OUT("BApplication::InitData() failed: %s\n", strerror(fInitError)));
                exit(0);
        }
DBG(OUT("BApplication::InitData() done\n"));
}


port_id
BApplication::_GetPort(const char* signature)
{
        return BLaunchRoster().GetPort(signature, NULL);
}


BArchivable*
BApplication::Instantiate(BMessage* data)
{
        if (validate_instantiation(data, "BApplication"))
                return new BApplication(data);

        return NULL;
}


status_t
BApplication::Archive(BMessage* data, bool deep) const
{
        status_t status = BLooper::Archive(data, deep);
        if (status < B_OK)
                return status;

        app_info info;
        status = GetAppInfo(&info);
        if (status < B_OK)
                return status;

        status = data->AddString("mime_sig", info.signature);
        if (status < B_OK)
                return status;

        return data->AddInt64("_pulse", fPulseRate);
}


status_t
BApplication::InitCheck() const
{
        return fInitError;
}


thread_id
BApplication::Run()
{
        if (fInitError != B_OK)
                return fInitError;

        Loop();

        delete fPulseRunner;
        return fThread;
}


void
BApplication::Quit()
{
        bool unlock = false;
        if (!IsLocked()) {
                const char* name = Name();
                if (name == NULL)
                        name = "no-name";

                printf("ERROR - you must Lock the application object before calling "
                           "Quit(), team=%" B_PRId32 ", looper=%s\n", Team(), name);
                unlock = true;
                if (!Lock())
                        return;
        }
        // Delete the object, if not running only.
        if (!fRunCalled) {
                delete this;
        } else if (find_thread(NULL) != fThread) {
// ToDo: why shouldn't we set fTerminating to true directly in this case?
                // We are not the looper thread.
                // We push a _QUIT_ into the queue.
                // TODO: When BLooper::AddMessage() is done, use that instead of
                // PostMessage()??? This would overtake messages that are still at
                // the port.
                // NOTE: We must not unlock here -- otherwise we had to re-lock, which
                // may not work. This is bad, since, if the port is full, it
                // won't get emptier, as the looper thread needs to lock the object
                // before dispatching messages.
                while (PostMessage(_QUIT_, this) == B_WOULD_BLOCK)
                        snooze(10000);
        } else {
                // We are the looper thread.
                // Just set fTerminating to true which makes us fall through the
                // message dispatching loop and return from Run().
                fTerminating = true;
        }

        // If we had to lock the object, unlock now.
        if (unlock)
                Unlock();
}


bool
BApplication::QuitRequested()
{
        return _QuitAllWindows(false);
}


void
BApplication::Pulse()
{
        // supposed to be implemented by subclasses
}


void
BApplication::ReadyToRun()
{
        // supposed to be implemented by subclasses
}


void
BApplication::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_COUNT_PROPERTIES:
                case B_GET_PROPERTY:
                case B_SET_PROPERTY:
                {
                        int32 index;
                        BMessage specifier;
                        int32 what;
                        const char* property = NULL;
                        if (message->GetCurrentSpecifier(&index, &specifier, &what,
                                        &property) < B_OK
                                || !ScriptReceived(message, index, &specifier, what,
                                        property)) {
                                BLooper::MessageReceived(message);
                        }
                        break;
                }

                case B_SILENT_RELAUNCH:
                        // Sent to a B_SINGLE_LAUNCH application when it's launched again
                        // (see _InitData())
                        be_roster->ActivateApp(Team());
                        break;

                case kMsgAppServerStarted:
                        _ReconnectToServer();
                        break;

                default:
                        BLooper::MessageReceived(message);
        }
}


void
BApplication::ArgvReceived(int32 argc, char** argv)
{
        // supposed to be implemented by subclasses
}


void
BApplication::AppActivated(bool active)
{
        // supposed to be implemented by subclasses
}


void
BApplication::RefsReceived(BMessage* message)
{
        // supposed to be implemented by subclasses
}


void
BApplication::AboutRequested()
{
        // supposed to be implemented by subclasses
}


BHandler*
BApplication::ResolveSpecifier(BMessage* message, int32 index,
        BMessage* specifier, int32 what, const char* property)
{
        BPropertyInfo propInfo(sPropertyInfo);
        status_t err = B_OK;
        uint32 data;

        if (propInfo.FindMatch(message, 0, specifier, what, property, &data) >= 0) {
                switch (data) {
                        case kWindowByIndex:
                        {
                                int32 index;
                                err = specifier->FindInt32("index", &index);
                                if (err != B_OK)
                                        break;

                                if (what == B_REVERSE_INDEX_SPECIFIER)
                                        index = CountWindows() - index;

                                BWindow* window = WindowAt(index);
                                if (window != NULL) {
                                        message->PopSpecifier();
                                        BMessenger(window).SendMessage(message);
                                } else
                                        err = B_BAD_INDEX;
                                break;
                        }

                        case kWindowByName:
                        {
                                const char* name;
                                err = specifier->FindString("name", &name);
                                if (err != B_OK)
                                        break;

                                for (int32 i = 0;; i++) {
                                        BWindow* window = WindowAt(i);
                                        if (window == NULL) {
                                                err = B_NAME_NOT_FOUND;
                                                break;
                                        }
                                        if (window->Title() != NULL && !strcmp(window->Title(),
                                                        name)) {
                                                message->PopSpecifier();
                                                BMessenger(window).SendMessage(message);
                                                break;
                                        }
                                }
                                break;
                        }

                        case kLooperByIndex:
                        {
                                int32 index;
                                err = specifier->FindInt32("index", &index);
                                if (err != B_OK)
                                        break;

                                if (what == B_REVERSE_INDEX_SPECIFIER)
                                        index = CountLoopers() - index;

                                BLooper* looper = LooperAt(index);
                                if (looper != NULL) {
                                        message->PopSpecifier();
                                        BMessenger(looper).SendMessage(message);
                                } else
                                        err = B_BAD_INDEX;

                                break;
                        }

                        case kLooperByID:
                                // TODO: implement getting looper by ID!
                                break;

                        case kLooperByName:
                        {
                                const char* name;
                                err = specifier->FindString("name", &name);
                                if (err != B_OK)
                                        break;

                                for (int32 i = 0;; i++) {
                                        BLooper* looper = LooperAt(i);
                                        if (looper == NULL) {
                                                err = B_NAME_NOT_FOUND;
                                                break;
                                        }
                                        if (looper->Name() != NULL
                                                && strcmp(looper->Name(), name) == 0) {
                                                message->PopSpecifier();
                                                BMessenger(looper).SendMessage(message);
                                                break;
                                        }
                                }
                                break;
                        }

                        case kApplication:
                                return this;
                }
        } else {
                return BLooper::ResolveSpecifier(message, index, specifier, what,
                        property);
        }

        if (err != B_OK) {
                BMessage reply(B_MESSAGE_NOT_UNDERSTOOD);
                reply.AddInt32("error", err);
                reply.AddString("message", strerror(err));
                message->SendReply(&reply);
        }

        return NULL;

}


void
BApplication::ShowCursor()
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_SHOW_CURSOR);
        link.Flush();
}


void
BApplication::HideCursor()
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_HIDE_CURSOR);
        link.Flush();
}


void
BApplication::ObscureCursor()
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_OBSCURE_CURSOR);
        link.Flush();
}


bool
BApplication::IsCursorHidden() const
{
        BPrivate::AppServerLink link;
        int32 status = B_ERROR;
        link.StartMessage(AS_QUERY_CURSOR_HIDDEN);
        link.FlushWithReply(status);

        return status == B_OK;
}


void
BApplication::SetCursor(const void* cursorData)
{
        BCursor cursor(cursorData);
        SetCursor(&cursor, true);
                // forces the cursor to be sync'ed
}


void
BApplication::SetCursor(const BCursor* cursor, bool sync)
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_SET_CURSOR);
        link.Attach<bool>(sync);
        link.Attach<int32>(cursor->fServerToken);

        if (sync) {
                int32 code;
                link.FlushWithReply(code);
        } else
                link.Flush();
}


int32
BApplication::CountWindows() const
{
        return _CountWindows(false);
                // we're ignoring menu windows
}


BWindow*
BApplication::WindowAt(int32 index) const
{
        return _WindowAt(index, false);
                // we're ignoring menu windows
}


int32
BApplication::CountLoopers() const
{
        AutoLocker<BLooperList> ListLock(gLooperList);
        if (ListLock.IsLocked())
                return gLooperList.CountLoopers();

        // Some bad, non-specific thing has happened
        return B_ERROR;
}


BLooper*
BApplication::LooperAt(int32 index) const
{
        BLooper* looper = NULL;
        AutoLocker<BLooperList> listLock(gLooperList);
        if (listLock.IsLocked())
                looper = gLooperList.LooperAt(index);

        return looper;
}


status_t
BApplication::RegisterLooper(BLooper* looper)
{
        BWindow* window = dynamic_cast<BWindow*>(looper);
        if (window != NULL)
                return B_BAD_VALUE;

        if (sOnQuitLooperList.HasItem(looper))
                return B_ERROR;

        if (sOnQuitLooperList.AddItem(looper) != true)
                return B_ERROR;

        return B_OK;
}


status_t
BApplication::UnregisterLooper(BLooper* looper)
{
        BWindow* window = dynamic_cast<BWindow*>(looper);
        if (window != NULL)
                return B_BAD_VALUE;

        if (!sOnQuitLooperList.HasItem(looper))
                return B_ERROR;

        if (sOnQuitLooperList.RemoveItem(looper) != true)
                return B_ERROR;

        return B_OK;
}


bool
BApplication::IsLaunching() const
{
        return !fReadyToRunCalled;
}


const char*
BApplication::Signature() const
{
        return fAppName;
}


status_t
BApplication::GetAppInfo(app_info* info) const
{
        if (be_app == NULL || be_roster == NULL)
                return B_NO_INIT;
        return be_roster->GetRunningAppInfo(be_app->Team(), info);
}


BResources*
BApplication::AppResources()
{
        if (sAppResources == NULL)
                pthread_once(&sAppResourcesInitOnce, &_InitAppResources);

        return sAppResources;
}


void
BApplication::DispatchMessage(BMessage* message, BHandler* handler)
{
        if (handler != this) {
                // it's not ours to dispatch
                BLooper::DispatchMessage(message, handler);
                return;
        }

        switch (message->what) {
                case B_ARGV_RECEIVED:
                        _ArgvReceived(message);
                        break;

                case B_REFS_RECEIVED:
                {
                        // this adds the refs that are part of this message to the recent
                        // lists, but only folders and documents are handled here
                        entry_ref ref;
                        int32 i = 0;
                        while (message->FindRef("refs", i++, &ref) == B_OK) {
                                BEntry entry(&ref, true);
                                if (entry.InitCheck() != B_OK)
                                        continue;

                                if (entry.IsDirectory())
                                        BRoster().AddToRecentFolders(&ref);
                                else {
                                        // filter out applications, we only want to have documents
                                        // in the recent files list
                                        BNode node(&entry);
                                        BNodeInfo info(&node);

                                        char mimeType[B_MIME_TYPE_LENGTH];
                                        if (info.GetType(mimeType) != B_OK
                                                || strcasecmp(mimeType, B_APP_MIME_TYPE))
                                                BRoster().AddToRecentDocuments(&ref);
                                }
                        }

                        RefsReceived(message);
                        break;
                }

                case B_READY_TO_RUN:
                        if (!fReadyToRunCalled) {
                                ReadyToRun();
                                fReadyToRunCalled = true;
                        }
                        break;

                case B_ABOUT_REQUESTED:
                        AboutRequested();
                        break;

                case B_PULSE:
                        Pulse();
                        break;

                case B_APP_ACTIVATED:
                {
                        bool active;
                        if (message->FindBool("active", &active) == B_OK)
                                AppActivated(active);
                        break;
                }

                case B_COLORS_UPDATED:
                {
                        AutoLocker<BLooperList> listLock(gLooperList);
                        if (!listLock.IsLocked())
                                break;

                        BWindow* window = NULL;
                        uint32 count = gLooperList.CountLoopers();
                        for (uint32 index = 0; index < count; ++index) {
                                window = dynamic_cast<BWindow*>(gLooperList.LooperAt(index));
                                if (window == NULL || (window != NULL && window->fOffscreen))
                                        continue;
                                window->PostMessage(message);
                        }
                        break;
                }

                case _SHOW_DRAG_HANDLES_:
                {
                        bool show;
                        if (message->FindBool("show", &show) != B_OK)
                                break;

                        BDragger::Private::UpdateShowAllDraggers(show);
                        break;
                }

                // TODO: Handle these as well
                case _DISPOSE_DRAG_:
                case _PING_:
                        puts("not yet handled message:");
                        DBG(message->PrintToStream());
                        break;

                default:
                        BLooper::DispatchMessage(message, handler);
                        break;
        }
}


void
BApplication::SetPulseRate(bigtime_t rate)
{
        if (rate < 0)
                rate = 0;

        // BeBook states that we have only 100,000 microseconds granularity
        rate -= rate % 100000;

        if (!Lock())
                return;

        if (rate != 0) {
                // reset existing pulse runner, or create new one
                if (fPulseRunner == NULL) {
                        BMessage pulse(B_PULSE);
                        fPulseRunner = new BMessageRunner(be_app_messenger, &pulse, rate);
                } else
                        fPulseRunner->SetInterval(rate);
        } else {
                // turn off pulse messages
                delete fPulseRunner;
                fPulseRunner = NULL;
        }

        fPulseRate = rate;
        Unlock();
}


status_t
BApplication::GetSupportedSuites(BMessage* data)
{
        if (data == NULL)
                return B_BAD_VALUE;

        status_t status = data->AddString("suites", "suite/vnd.Be-application");
        if (status == B_OK) {
                BPropertyInfo propertyInfo(sPropertyInfo);
                status = data->AddFlat("messages", &propertyInfo);
                if (status == B_OK)
                        status = BLooper::GetSupportedSuites(data);
        }

        return status;
}


status_t
BApplication::Perform(perform_code d, void* arg)
{
        return BLooper::Perform(d, arg);
}


void BApplication::_ReservedApplication1() {}
void BApplication::_ReservedApplication2() {}
void BApplication::_ReservedApplication3() {}
void BApplication::_ReservedApplication4() {}
void BApplication::_ReservedApplication5() {}
void BApplication::_ReservedApplication6() {}
void BApplication::_ReservedApplication7() {}
void BApplication::_ReservedApplication8() {}


bool
BApplication::ScriptReceived(BMessage* message, int32 index,
        BMessage* specifier, int32 what, const char* property)
{
        BMessage reply(B_REPLY);
        status_t err = B_BAD_SCRIPT_SYNTAX;

        switch (message->what) {
                case B_GET_PROPERTY:
                        if (strcmp("Loopers", property) == 0) {
                                int32 count = CountLoopers();
                                err = B_OK;
                                for (int32 i=0; err == B_OK && i<count; i++) {
                                        BMessenger messenger(LooperAt(i));
                                        err = reply.AddMessenger("result", messenger);
                                }
                        } else if (strcmp("Windows", property) == 0) {
                                int32 count = CountWindows();
                                err = B_OK;
                                for (int32 i=0; err == B_OK && i<count; i++) {
                                        BMessenger messenger(WindowAt(i));
                                        err = reply.AddMessenger("result", messenger);
                                }
                        } else if (strcmp("Window", property) == 0) {
                                switch (what) {
                                        case B_INDEX_SPECIFIER:
                                        case B_REVERSE_INDEX_SPECIFIER:
                                        {
                                                int32 index = -1;
                                                err = specifier->FindInt32("index", &index);
                                                if (err != B_OK)
                                                        break;

                                                if (what == B_REVERSE_INDEX_SPECIFIER)
                                                        index = CountWindows() - index;

                                                err = B_BAD_INDEX;
                                                BWindow* window = WindowAt(index);
                                                if (window == NULL)
                                                        break;

                                                BMessenger messenger(window);
                                                err = reply.AddMessenger("result", messenger);
                                                break;
                                        }

                                        case B_NAME_SPECIFIER:
                                        {
                                                const char* name;
                                                err = specifier->FindString("name", &name);
                                                if (err != B_OK)
                                                        break;
                                                err = B_NAME_NOT_FOUND;
                                                for (int32 i = 0; i < CountWindows(); i++) {
                                                        BWindow* window = WindowAt(i);
                                                        if (window && window->Name() != NULL
                                                                && !strcmp(window->Name(), name)) {
                                                                BMessenger messenger(window);
                                                                err = reply.AddMessenger("result", messenger);
                                                                break;
                                                        }
                                                }
                                                break;
                                        }
                                }
                        } else if (strcmp("Looper", property) == 0) {
                                switch (what) {
                                        case B_INDEX_SPECIFIER:
                                        case B_REVERSE_INDEX_SPECIFIER:
                                        {
                                                int32 index = -1;
                                                err = specifier->FindInt32("index", &index);
                                                if (err != B_OK)
                                                        break;

                                                if (what == B_REVERSE_INDEX_SPECIFIER)
                                                        index = CountLoopers() - index;

                                                err = B_BAD_INDEX;
                                                BLooper* looper = LooperAt(index);
                                                if (looper == NULL)
                                                        break;

                                                BMessenger messenger(looper);
                                                err = reply.AddMessenger("result", messenger);
                                                break;
                                        }

                                        case B_NAME_SPECIFIER:
                                        {
                                                const char* name;
                                                err = specifier->FindString("name", &name);
                                                if (err != B_OK)
                                                        break;
                                                err = B_NAME_NOT_FOUND;
                                                for (int32 i = 0; i < CountLoopers(); i++) {
                                                        BLooper* looper = LooperAt(i);
                                                        if (looper != NULL && looper->Name()
                                                                && strcmp(looper->Name(), name) == 0) {
                                                                BMessenger messenger(looper);
                                                                err = reply.AddMessenger("result", messenger);
                                                                break;
                                                        }
                                                }
                                                break;
                                        }

                                        case B_ID_SPECIFIER:
                                        {
                                                // TODO
                                                debug_printf("Looper's ID specifier used but not "
                                                        "implemented.\n");
                                                break;
                                        }
                                }
                        } else if (strcmp("Name", property) == 0)
                                err = reply.AddString("result", Name());

                        break;

                case B_COUNT_PROPERTIES:
                        if (strcmp("Looper", property) == 0)
                                err = reply.AddInt32("result", CountLoopers());
                        else if (strcmp("Window", property) == 0)
                                err = reply.AddInt32("result", CountWindows());

                        break;
        }
        if (err == B_BAD_SCRIPT_SYNTAX)
                return false;

        if (err < B_OK) {
                reply.what = B_MESSAGE_NOT_UNDERSTOOD;
                reply.AddString("message", strerror(err));
        }
        reply.AddInt32("error", err);
        message->SendReply(&reply);

        return true;
}


void
BApplication::BeginRectTracking(BRect rect, bool trackWhole)
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_BEGIN_RECT_TRACKING);
        link.Attach<BRect>(rect);
        link.Attach<int32>(trackWhole);
        link.Flush();
}


void
BApplication::EndRectTracking()
{
        BPrivate::AppServerLink link;
        link.StartMessage(AS_END_RECT_TRACKING);
        link.Flush();
}


status_t
BApplication::_SetupServerAllocator()
{
        fServerAllocator = new (std::nothrow) BPrivate::ServerMemoryAllocator();
        if (fServerAllocator == NULL)
                return B_NO_MEMORY;

        return fServerAllocator->InitCheck();
}


status_t
BApplication::_InitGUIContext()
{
        // An app_server connection is necessary for a lot of stuff, so get that first.
        status_t error = _ConnectToServer();
        if (error != B_OK)
                return error;

        // Initialize the IK after we have set be_app because of a construction
        // of a AppServerLink (which depends on be_app) nested inside the call
        // to get_menu_info.
        error = _init_interface_kit_();
        if (error != B_OK)
                return error;

        // create global system cursors
        B_CURSOR_SYSTEM_DEFAULT = new BCursor(B_HAND_CURSOR);
        B_CURSOR_I_BEAM = new BCursor(B_I_BEAM_CURSOR);

        // TODO: would be nice to get the workspace at launch time from the registrar
        fInitialWorkspace = current_workspace();

        return B_OK;
}


status_t
BApplication::_ConnectToServer()
{
        status_t status
                = create_desktop_connection(fServerLink, "a<app_server", 100);
        if (status != B_OK)
                return status;

        // AS_CREATE_APP:
        //
        // Attach data:
        // 1) port_id - receiver port of a regular app
        // 2) port_id - looper port for this BApplication
        // 3) team_id - team identification field
        // 4) int32 - handler ID token of the app
        // 5) char* - signature of the regular app

        fServerLink->StartMessage(AS_CREATE_APP);
        fServerLink->Attach<port_id>(fServerLink->ReceiverPort());
        fServerLink->Attach<port_id>(_get_looper_port_(this));
        fServerLink->Attach<team_id>(Team());
        fServerLink->Attach<int32>(_get_object_token_(this));
        fServerLink->AttachString(fAppName);

        area_id sharedReadOnlyArea;
        team_id serverTeam;
        port_id serverPort;

        int32 code;
        if (fServerLink->FlushWithReply(code) == B_OK
                && code == B_OK) {
                // We don't need to contact the main app_server anymore
                // directly; we now talk to our server alter ego only.
                fServerLink->Read<port_id>(&serverPort);
                fServerLink->Read<area_id>(&sharedReadOnlyArea);
                fServerLink->Read<team_id>(&serverTeam);
        } else {
                fServerLink->SetSenderPort(-1);
                debugger("BApplication: couldn't obtain new app_server comm port");
                return B_ERROR;
        }
        fServerLink->SetTargetTeam(serverTeam);
        fServerLink->SetSenderPort(serverPort);

        status = _SetupServerAllocator();
        if (status != B_OK)
                return status;

        area_id area;
        uint8* base;
        status = fServerAllocator->AddArea(sharedReadOnlyArea, area, base, true);
        if (status < B_OK)
                return status;

        fServerReadOnlyMemory = base;

        return B_OK;
}


void
BApplication::_ReconnectToServer()
{
        team_info dummy;
        if (get_team_info(fServerLink->TargetTeam(), &dummy) == B_OK) {
                // We're already connected to the correct server.
                return;
        }

        // the sender port belongs to the app_server
        delete_port(fServerLink->ReceiverPort());

        if (_ConnectToServer() != B_OK)
                debugger("Can't reconnect to app server!");

        AutoLocker<BLooperList> listLock(gLooperList);
        if (!listLock.IsLocked())
                return;

        uint32 count = gLooperList.CountLoopers();
        for (uint32 i = 0; i < count ; i++) {
                BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
                if (window == NULL)
                        continue;
                BMessenger windowMessenger(window);
                windowMessenger.SendMessage(kMsgAppServerStarted);
        }

        reconnect_bitmaps_to_app_server();
        reconnect_pictures_to_app_server();
}


#if 0
void
BApplication::send_drag(BMessage* message, int32 vs_token, BPoint offset,
        BRect dragRect, BHandler* replyTo)
{
        // TODO: implement
}


void
BApplication::send_drag(BMessage* message, int32 vs_token, BPoint offset,
        int32 bitmapToken, drawing_mode dragMode, BHandler* replyTo)
{
        // TODO: implement
}


void
BApplication::write_drag(_BSession_* session, BMessage* message)
{
        // TODO: implement
}
#endif


bool
BApplication::_WindowQuitLoop(bool quitFilePanels, bool force)
{
        int32 index = 0;
        while (true) {
                 BWindow* window = WindowAt(index);
                 if (window == NULL)
                        break;

                // NOTE: the window pointer might be stale, in case the looper
                // was already quit by quitting an earlier looper... but fortunately,
                // we can still call Lock() on the invalid pointer, and it
                // will return false...
                if (!window->Lock())
                        continue;

                // don't quit file panels if we haven't been asked for it
                if (!quitFilePanels && window->IsFilePanel()) {
                        window->Unlock();
                        index++;
                        continue;
                }

                if (!force && !window->QuitRequested()
                        && !(quitFilePanels && window->IsFilePanel())) {
                        // the window does not want to quit, so we don't either
                        window->Unlock();
                        return false;
                }

                // Re-lock, just to make sure that the user hasn't done nasty
                // things in QuitRequested(). Quit() unlocks fully, thus
                // double-locking is harmless.
                if (window->Lock())
                        window->Quit();

                index = 0;
                        // we need to continue at the start of the list again - it
                        // might have changed
        }

        return true;
}


bool
BApplication::_QuitAllWindows(bool force)
{
        AssertLocked();

        // We need to unlock here because BWindow::QuitRequested() must be
        // allowed to lock the application - which would cause a deadlock
        Unlock();

        bool quit = _WindowQuitLoop(false, force);
        if (quit)
                quit = _WindowQuitLoop(true, force);

        Lock();

        return quit;
}


void
BApplication::_ArgvReceived(BMessage* message)
{
        ASSERT(message != NULL);

        // build the argv vector
        status_t error = B_OK;
        int32 argc = 0;
        char** argv = NULL;
        if (message->FindInt32("argc", &argc) == B_OK && argc > 0) {
                // allocate a NULL terminated array
                argv = new(std::nothrow) char*[argc + 1];
                if (argv == NULL)
                        return;

                // copy the arguments
                for (int32 i = 0; error == B_OK && i < argc; i++) {
                        const char* arg = NULL;
                        error = message->FindString("argv", i, &arg);
                        if (error == B_OK && arg) {
                                argv[i] = strdup(arg);
                                if (argv[i] == NULL)
                                        error = B_NO_MEMORY;
                        } else
                                argc = i;
                }

                argv[argc] = NULL;
        }

        // call the hook
        if (error == B_OK && argc > 0)
                ArgvReceived(argc, argv);

        if (error != B_OK) {
                printf("Error parsing B_ARGV_RECEIVED message. Message:\n");
                message->PrintToStream();
        }

        // cleanup
        if (argv) {
                for (int32 i = 0; i < argc; i++)
                        free(argv[i]);
                delete[] argv;
        }
}


uint32
BApplication::InitialWorkspace()
{
        return fInitialWorkspace;
}


int32
BApplication::_CountWindows(bool includeMenus) const
{
        uint32 count = 0;
        for (int32 i = 0; i < gLooperList.CountLoopers(); i++) {
                BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
                if (window != NULL && !window->fOffscreen && (includeMenus
                                || dynamic_cast<BMenuWindow*>(window) == NULL)) {
                        count++;
                }
        }

        return count;
}


BWindow*
BApplication::_WindowAt(uint32 index, bool includeMenus) const
{
        AutoLocker<BLooperList> listLock(gLooperList);
        if (!listLock.IsLocked())
                return NULL;

        uint32 count = gLooperList.CountLoopers();
        for (uint32 i = 0; i < count && index < count; i++) {
                BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
                if (window == NULL || (window != NULL && window->fOffscreen)
                        || (!includeMenus && dynamic_cast<BMenuWindow*>(window) != NULL)) {
                        index++;
                        continue;
                }

                if (i == index)
                        return window;
        }

        return NULL;
}


/*static*/ void
BApplication::_InitAppResources()
{
        entry_ref ref;
        bool found = false;

        // App is already running. Get its entry ref with
        // GetAppInfo()
        app_info appInfo;
        if (be_app && be_app->GetAppInfo(&appInfo) == B_OK) {
                ref = appInfo.ref;
                found = true;
        } else {
                // Run() hasn't been called yet
                found = BPrivate::get_app_ref(&ref) == B_OK;
        }

        if (!found)
                return;

        BFile file(&ref, B_READ_ONLY);
        if (file.InitCheck() != B_OK)
                return;

        BResources* resources = new (std::nothrow) BResources(&file, false);
        if (resources == NULL || resources->InitCheck() != B_OK) {
                delete resources;
                return;
        }

        sAppResources = resources;
}