root/src/servers/app/decorator/DecorManager.cpp
/*
 * Copyright (c) 2001-2020 Haiku, Inc. All right reserved.
 * Distributed under the terms of the MIT license.
 *
 * Author:
 *              DarkWyrm <bpmagic@columbus.rr.com>
 *              Clemens Zeidler <haiku@clemens-zeidler.de>
 *              Joseph Groover <looncraz@satx.rr.com>
 *              John Scipione <jscipione@gmail.com>
 */

#include "DecorManager.h"

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

#include <syslog.h>

#include "AppServer.h"
#include "Desktop.h"
#include "DesktopSettings.h"
#include "ServerConfig.h"
#include "SATDecorator.h"
#include "Window.h"

typedef float get_version(void);
typedef DecorAddOn* create_decor_addon(image_id id, const char* name);

// Globals
DecorManager gDecorManager;


DecorAddOn::DecorAddOn(image_id id, const char* name)
        :
        fImageID(id),
        fName(name)
{
}


DecorAddOn::~DecorAddOn()
{
}


status_t
DecorAddOn::InitCheck() const
{
        return B_OK;
}


Decorator*
DecorAddOn::AllocateDecorator(Desktop* desktop, DrawingEngine* engine,
        BRect rect, const char* title, window_look look, uint32 flags)
{
        if (!desktop->LockSingleWindow())
                return NULL;

        DesktopSettings settings(desktop);
        Decorator* decorator;
        decorator = _AllocateDecorator(settings, rect, desktop);
        desktop->UnlockSingleWindow();
        if (!decorator)
                return NULL;

        decorator->UpdateColors(settings);

        if (decorator->AddTab(settings, title, look, flags) == NULL) {
                delete decorator;
                return NULL;
        }

        decorator->SetDrawingEngine(engine);
        return decorator;
}


WindowBehaviour*
DecorAddOn::AllocateWindowBehaviour(Window* window)
{
        return new (std::nothrow)SATWindowBehaviour(window,
                window->Desktop()->GetStackAndTile());
}


const DesktopListenerList&
DecorAddOn::GetDesktopListeners()
{
        return fDesktopListeners;
}


Decorator*
DecorAddOn::_AllocateDecorator(DesktopSettings& settings, BRect rect,
        Desktop* desktop)
{
        return new (std::nothrow)SATDecorator(settings, rect, desktop);
}


//      #pragma mark -


DecorManager::DecorManager()
        :
        fDefaultDecor(-1, "Default"),
        fCurrentDecor(&fDefaultDecor),
        fPreviewDecor(NULL),
        fPreviewWindow(NULL),
        fCurrentDecorPath("Default")
{
        _LoadSettingsFromDisk();
}


DecorManager::~DecorManager()
{
}


Decorator*
DecorManager::AllocateDecorator(Window* window)
{
        // Create a new instance of the current decorator.
        // Ownership is that of the caller

        if (!fCurrentDecor) {
                // We should *never* be here. If we do, it's a bug.
                debugger("DecorManager::AllocateDecorator has a NULL decorator");
                return NULL;
        }

        // Are we previewing a specific decorator?
        if (window == fPreviewWindow) {
                if (fPreviewDecor != NULL) {
                        return fPreviewDecor->AllocateDecorator(window->Desktop(),
                                window->GetDrawingEngine(), window->Frame(), window->Title(),
                                window->Look(), window->Flags());
                } else {
                        fPreviewWindow = NULL;
                }
        }

        return fCurrentDecor->AllocateDecorator(window->Desktop(),
                window->GetDrawingEngine(), window->Frame(), window->Title(),
                window->Look(), window->Flags());
}


WindowBehaviour*
DecorManager::AllocateWindowBehaviour(Window* window)
{
        if (!fCurrentDecor) {
                // We should *never* be here. If we do, it's a bug.
                debugger("DecorManager::AllocateDecorator has a NULL decorator");
                return NULL;
        }

        return fCurrentDecor->AllocateWindowBehaviour(window);
}


void
DecorManager::CleanupForWindow(Window* window)
{
        // Given window is being deleted, do any cleanup needed
        if (fPreviewWindow == window && window != NULL){
                fPreviewWindow = NULL;

                if (fPreviewDecor != NULL)
                        unload_add_on(fPreviewDecor->ImageID());

                fPreviewDecor = NULL;
        }
}


status_t
DecorManager::PreviewDecorator(BString path, Window* window)
{
        if (fPreviewWindow != NULL && fPreviewWindow != window){
                // Reset other window to current decorator - only one can preview
                Window* oldPreviewWindow = fPreviewWindow;
                fPreviewWindow = NULL;
                oldPreviewWindow->ReloadDecor();
        }

        if (window == NULL)
                return B_BAD_VALUE;

        // We have to jump some hoops because the window must be able to
        // delete its decorator before we unload the add-on
        status_t error = B_OK;
        DecorAddOn* decorPtr = _LoadDecor(path, error);
        if (decorPtr == NULL)
                return error == B_OK ? B_ERROR : error;

        BRegion border;
        window->GetBorderRegion(&border);

        DecorAddOn* oldDecor = fPreviewDecor;
        fPreviewDecor = decorPtr;
        fPreviewWindow = window;
        // After this call, the window has deleted its decorator.
        fPreviewWindow->ReloadDecor();

        BRegion newBorder;
        window->GetBorderRegion(&newBorder);

        border.Include(&newBorder);
        window->Desktop()->RebuildAndRedrawAfterWindowChange(window, border);

        if (oldDecor != NULL)
                unload_add_on(oldDecor->ImageID());

        return B_OK;
}


const DesktopListenerList&
DecorManager::GetDesktopListeners()
{
        return fCurrentDecor->GetDesktopListeners();
}


BString
DecorManager::GetCurrentDecorator() const
{
        return fCurrentDecorPath.String();
}


status_t
DecorManager::SetDecorator(BString path, Desktop* desktop)
{
        status_t error = B_OK;
        DecorAddOn* newDecor = _LoadDecor(path, error);
        if (newDecor == NULL)
                return error == B_OK ? B_ERROR : error;

        DecorAddOn* oldDecor = fCurrentDecor;

        BString oldPath = fCurrentDecorPath;
        image_id oldImage = fCurrentDecor->ImageID();

        fCurrentDecor = newDecor;
        fCurrentDecorPath = path.String();

        if (desktop->ReloadDecor(oldDecor)) {
                // now safe to unload all old decorator data
                // saves us from deleting oldDecor...
                unload_add_on(oldImage);
                _SaveSettingsToDisk();
                return B_OK;
        }

        // TODO: unloading the newDecor and its image
        // problem is we don't know how many windows failed... or why they failed...
        syslog(LOG_WARNING,
                "app_server:DecorManager:SetDecorator:\"%s\" *partly* failed\n",
                fCurrentDecorPath.String());

        fCurrentDecor = oldDecor;
        fCurrentDecorPath = oldPath;
        return B_ERROR;
}


DecorAddOn*
DecorManager::_LoadDecor(BString _path, status_t& error )
{
        if (_path == "Default") {
                error = B_OK;
                return &fDefaultDecor;
        }

        BEntry entry(_path.String(), true);
        if (!entry.Exists()) {
                error = B_ENTRY_NOT_FOUND;
                return NULL;
        }

        BPath path(&entry);
        image_id image = load_add_on(path.Path());
        if (image < 0) {
                error = B_BAD_IMAGE_ID;
                return NULL;
        }

        create_decor_addon*     createFunc;
        if (get_image_symbol(image, "instantiate_decor_addon", B_SYMBOL_TYPE_TEXT,
                        (void**)&createFunc) != B_OK) {
                unload_add_on(image);
                error = B_MISSING_SYMBOL;
                return NULL;
        }

        char name[B_FILE_NAME_LENGTH];
        entry.GetName(name);
        DecorAddOn* newDecor = createFunc(image, name);
        if (newDecor == NULL || newDecor->InitCheck() != B_OK) {
                unload_add_on(image);
                error = B_ERROR;
                return NULL;
        }

        return newDecor;
}


static const char* kSettingsDir = "system/app_server";
static const char* kSettingsFile = "decorator_settings";


bool
DecorManager::_LoadSettingsFromDisk()
{
        // get the user settings directory
        BPath path;
        status_t error = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
        if (error != B_OK)
                return false;

        path.Append(kSettingsDir);
        path.Append(kSettingsFile);
        BFile file(path.Path(), B_READ_ONLY);
        if (file.InitCheck() != B_OK)
                return false;

        BMessage settings;
        if (settings.Unflatten(&file) == B_OK) {
                BString itemPath;
                if (settings.FindString("decorator", &itemPath) == B_OK) {
                        status_t error = B_OK;
                        DecorAddOn* decor = _LoadDecor(itemPath, error);
                        if (decor != NULL) {
                                fCurrentDecor = decor;
                                fCurrentDecorPath = itemPath;
                                return true;
                        } else {
                                //TODO: do something with the reported error
                        }
                }
        }

        return false;
}


bool
DecorManager::_SaveSettingsToDisk()
{
        // get the user settings directory
        BPath path;
        status_t error = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
        if (error != B_OK)
                return false;

        path.Append(kSettingsDir);
        if (create_directory(path.Path(), 777) != B_OK)
                return false;

        path.Append(kSettingsFile);
        BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
        if (file.InitCheck() != B_OK)
                return false;

        BMessage settings;
        if (settings.AddString("decorator", fCurrentDecorPath.String()) != B_OK)
                return false;
        if (settings.Flatten(&file) != B_OK)
                return false;

        return true;
}