/*
   Copyright (C) 2009 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#ifdef WIN32
#include <io.h>
#endif

#include "application.h"
#include "screen.h"
#include "utils.h"
#include "debug.h"
#include "screen_layer.h"
#include "monitor.h"
#include "resource.h"
#ifdef WIN32
#include "red_gdi_canvas.h"
#endif
#include "platform.h"
#include "sw_canvas.h"
#include "gl_canvas.h"
#include "quic.h"
#include "mutex.h"
#include "cmd_line_parser.h"
#ifdef USE_TUNNEL
#include "tunnel_channel.h"
#endif
#include "rect.h"
#ifdef USE_GUI
#include "gui/gui.h"
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#ifdef USE_SMARTCARD
#include <smartcard_channel.h>
#endif

#define STICKY_KEY_PIXMAP ALT_IMAGE_RES_ID
#define STICKY_KEY_TIMEOUT 750

#define CA_FILE_NAME "spice_truststore.pem"

static const char* app_name = "spicec";

void ConnectedEvent::response(AbstractProcessLoop& events_loop)
{
    static_cast<Application*>(events_loop.get_owner())->on_connected();
}

void DisconnectedEvent::response(AbstractProcessLoop& events_loop)
{
    Application* app = static_cast<Application*>(events_loop.get_owner());
    app->on_disconnected(_error_code);
}

void VisibilityEvent::response(AbstractProcessLoop& events_loop)
{
    Application* app = static_cast<Application*>(events_loop.get_owner());
    app->on_visibility_start(_screen_id);
}

void MonitorsQuery::do_response(AbstractProcessLoop& events_loop)
{
    Monitor* mon;
    int i = 0;

    while ((mon = (static_cast<Application*>(events_loop.get_owner()))->find_monitor(i++))) {
        MonitorInfo info;
        info.size = mon->get_size();
        info.depth = 32;
        info.position = mon->get_position();
        _monitors.push_back(info);
    }
}

SwitchHostEvent::SwitchHostEvent(const char* host, int port, int sport, const char* cert_subject)
{
    _host = host;
    _port = port;
    _sport = sport;
    if (cert_subject) {
        _cert_subject = cert_subject;
    }
}

void SwitchHostEvent::response(AbstractProcessLoop& events_loop)
{
    Application* app = static_cast<Application*>(events_loop.get_owner());
    app->switch_host(_host, _port, _sport, _cert_subject);
}

#ifdef USE_GUI
//todo: add inactive visual appearance
class GUIBarrier: public ScreenLayer {
public:
    GUIBarrier(int id)
        : ScreenLayer(SCREEN_LAYER_GUI_BARIER, true)
        , _id (id)
        , _cursor (Platform::create_inactive_cursor())
    {
    }

    ~GUIBarrier()
    {
        detach();
    }

    int get_id() { return _id;}

    void attach(RedScreen& in_screen)
    {
        if (screen()) {
            ASSERT(&in_screen == screen())
            return;
        }
        in_screen.attach_layer(*this);
    }

    void detach()
    {
        if (!screen()) {
            return;
        }
        screen()->detach_layer(*this);
    }

    virtual bool pointer_test(int x, int y) { return true;}
    virtual void on_pointer_enter(int x, int y, unsigned int buttons_state)
    {
        AutoRef<LocalCursor> cursor(Platform::create_inactive_cursor());
        screen()->set_cursor(*cursor);
        return;
    }

private:
    int _id;
    AutoRef<LocalCursor> _cursor;
};

class GUITimer: public Timer {
public:
    GUITimer(GUI& gui)
        : _gui (gui)
    {
    }

    virtual void response(AbstractProcessLoop& events_loop)
    {
        _gui.idle();
    }

private:
    GUI& _gui;
};


#ifdef GUI_DEMO
class TestTimer: public Timer {
public:
    TestTimer(Application& app)
        : _app (app)
    {
    }

    virtual void response(AbstractProcessLoop& events_loop)
    {
        _app.message_box_test();
    }

private:
    Application& _app;
};
#endif

#endif // USE_GUI

class InfoLayer: public ScreenLayer {
public:
    InfoLayer();

    virtual void copy_pixels(const QRegion& dest_region, RedDrawable& dest_dc);

    void set_info_mode();
    void set_sticky(bool is_on);
    virtual void on_size_changed();

private:
    void draw_info(const QRegion& dest_region, RedDrawable& dest);
    void update_sticky_rect();

private:
    AlphaImageFromRes _sticky_pixmap;
    SpicePoint _sticky_pos;
    SpiceRect _sticky_rect;
    bool _sticky_on;
    RecurciveMutex _update_lock;
};

InfoLayer::InfoLayer()
    : ScreenLayer(SCREEN_LAYER_INFO, false)
    , _sticky_pixmap (STICKY_KEY_PIXMAP)
    , _sticky_on (false)
{
}

void InfoLayer::draw_info(const QRegion& dest_region, RedDrawable& dest)
{
    pixman_box32_t *rects;
    int num_rects;

    rects = pixman_region32_rectangles((pixman_region32_t *)&dest_region, &num_rects);
    for (int i = 0; i < num_rects; i++) {
        SpiceRect r;

        r.left = rects[i].x1;
        r.top = rects[i].y1;
        r.right = rects[i].x2;
        r.bottom = rects[i].y2;

        /* is rect inside sticky region ? */
        if (_sticky_on && rect_intersects(r, _sticky_rect)) {
            dest.blend_pixels(_sticky_pixmap, r.left - _sticky_pos.x, r.top - _sticky_pos.y, r);
        }
    }
}

void InfoLayer::copy_pixels(const QRegion& dest_region, RedDrawable& dest_dc)
{
    RecurciveLock lock(_update_lock);
    draw_info(dest_region, dest_dc);
}

void InfoLayer::set_info_mode()
{
    ASSERT(screen());

    clear_area();
    update_sticky_rect();
    set_sticky(_sticky_on);
}

void InfoLayer::update_sticky_rect()
{
    SpicePoint size = _sticky_pixmap.get_size();
    SpicePoint screen_size = screen()->get_size();

    _sticky_pos.x = (screen_size.x - size.x) / 2;
    _sticky_pos.y = screen_size.y * 2 / 3;
    _sticky_rect.left = _sticky_pos.x;
    _sticky_rect.top = _sticky_pos.y;
    _sticky_rect.right = _sticky_rect.left + size.x;
    _sticky_rect.bottom = _sticky_rect.top + size.y;
}

void InfoLayer::set_sticky(bool is_on)
{
    RecurciveLock lock(_update_lock);
    if (!_sticky_on && !is_on) {
        return;
    }

    _sticky_on = is_on;

    if (_sticky_on) {
        add_rect_area(_sticky_rect);
        invalidate(_sticky_rect);
    } else {
        remove_rect_area(_sticky_rect);
    }
}

void InfoLayer::on_size_changed()
{
    set_info_mode();
}

void StickyKeyTimer::response(AbstractProcessLoop& events_loop)
{
    Application* app = (Application*)events_loop.get_owner();
    StickyInfo* sticky_info = &app->_sticky_info;
    ASSERT(app->is_sticky_trace_key(sticky_info->key));
    ASSERT(app->_keyboard_state[sticky_info->key]);
    ASSERT(sticky_info->key_first_down);
    ASSERT(sticky_info->key_down);
    sticky_info->sticky_mode = true;
    DBG(0, "ON sticky");
    app->_info_layer->set_sticky(true);
    app->deactivate_interval_timer(this);
}

static MouseHandler default_mouse_handler;
static KeyHandler default_key_handler;

enum AppCommands {
    APP_CMD_INVALID,
    APP_CMD_SEND_CTL_ALT_DEL,
    APP_CMD_TOGGLE_FULL_SCREEN,
    APP_CMD_RELEASE_CAPTURE,
    APP_CMD_SEND_TOGGLE_KEYS,
    APP_CMD_SEND_RELEASE_KEYS,
    APP_CMD_SEND_CTL_ALT_END,
#ifdef RED_DEBUG
    APP_CMD_CONNECT,
    APP_CMD_DISCONNECT,
#endif
#ifdef USE_GUI
    APP_CMD_SHOW_GUI,
#endif // USE_GUI
#ifdef USE_SMARTCARD
    APP_CMD_SMARTCARD_INSERT,
    APP_CMD_SMARTCARD_REMOVE,
#endif
    APP_CMD_EXTERNAL_BEGIN = 0x400,
    APP_CMD_EXTERNAL_END = 0x800,
};

Application::Application()
    : ProcessLoop (this)
    , _client (*this)
    , _con_ciphers ("DEFAULT")
    , _enabled_channels (SPICE_END_CHANNEL, true)
    , _main_screen (NULL)
    , _active (false)
    , _full_screen (false)
    , _changing_screens (false)
    , _out_of_sync (false)
    , _exit_code (0)
    , _active_screen (NULL)
    , _num_keys_pressed (0)
    , _info_layer (new InfoLayer())
    , _key_handler (&default_key_handler)
    , _mouse_handler (&default_mouse_handler)
    , _monitors (NULL)
    , _title ("SPICEc:%d")
    , _sys_key_intercept_mode (false)
	, _enable_controller (false)
#ifdef USE_GUI
    , _gui_mode (GUI_MODE_FULL)
#endif // USE_GUI
    , _during_host_switch(false)
    , _state (DISCONNECTED)
#ifdef USE_SMARTCARD
    , _smartcard_options(new SmartcardOptions())
#endif
{
    DBG(0, "");

    _commands_map["toggle-fullscreen"] = APP_CMD_TOGGLE_FULL_SCREEN;
    _commands_map["release-cursor"] = APP_CMD_RELEASE_CAPTURE;
#ifdef RED_DEBUG
    _commands_map["connect"] = APP_CMD_CONNECT;
    _commands_map["disconnect"] = APP_CMD_DISCONNECT;
#endif
#ifdef USE_GUI
    _commands_map["show-gui"] = APP_CMD_SHOW_GUI;
#endif // USE_GUI
#ifdef USE_SMARTCARD
    _commands_map["smartcard-insert"] = APP_CMD_SMARTCARD_INSERT;
    _commands_map["smartcard-remove"] = APP_CMD_SMARTCARD_REMOVE;
#endif

    _canvas_types.resize(1);
#ifdef WIN32
    _canvas_types[0] = CANVAS_OPTION_GDI;
#else
    _canvas_types[0] = CANVAS_OPTION_SW;
#endif

    _host_auth_opt.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_NAME;

    Platform::get_app_data_dir(_host_auth_opt.CA_file, app_name);
    Platform::path_append(_host_auth_opt.CA_file, CA_FILE_NAME);

    std::auto_ptr<HotKeysParser> parser(new HotKeysParser("toggle-fullscreen=shift+f11"
                                                          ",release-cursor=shift+f12"
#ifdef RED_DEBUG
                                                          ",connect=shift+f5"
                                                          ",disconnect=shift+f6"
#endif
#ifdef USE_GUI
                                                          ",show-gui=shift+f7"
#endif // USE_GUI
#ifdef USE_SMARTCARD
                                                          ",smartcard-insert=shift+f8"
                                                          ",smartcard-remove=shift+f9"
#endif
                                                          , _commands_map));
    _hot_keys = parser->get();

    _sticky_info.trace_is_on = false;
    _sticky_info.sticky_mode = false;
    _sticky_info.key_first_down = false;
    _sticky_info.key_down = false;
    _sticky_info.key  = REDKEY_INVALID;
    _sticky_info.timer.reset(new StickyKeyTimer());

    for (int i = SPICE_CHANNEL_MAIN; i < SPICE_END_CHANNEL; i++) {
        _peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_BOTH;
    }
    memset(_keyboard_state, 0, sizeof(_keyboard_state));
}

Application::~Application()
{
#ifdef USE_GUI
    if (*_gui_timer != NULL) {
        deactivate_interval_timer(*_gui_timer);
    }
#ifdef GUI_DEMO
    if (*_gui_test_timer != NULL) {
        deactivate_interval_timer(*_gui_test_timer);
    }
#endif
    destroyed_gui_barriers();
    if (_gui.get() != NULL) {
        _gui->set_screen(NULL);
    }
#endif // USE_GUI

    if (_info_layer->screen()) {
        _main_screen->detach_layer(*_info_layer);
    }

    if (_main_screen != NULL) {
        _main_screen->unref();
        destroy_monitors();
    }
#ifdef USE_SMARTCARD
    delete _smartcard_options;
#endif
}

void Application::init_menu()
{
    //fixme: menu items name need to be dynamically updated by hot keys configuration.
    AutoRef<Menu> root_menu(new Menu(*this, ""));
    (*root_menu)->add_command("Send Ctrl+Alt+Del\tCtrl+Alt+End", APP_CMD_SEND_CTL_ALT_DEL);
    (*root_menu)->add_command("Toggle full screen\tShift+F11", APP_CMD_TOGGLE_FULL_SCREEN);
    AutoRef<Menu> key_menu(new Menu(*this, "Special keys"));
    (*key_menu)->add_command("Send Shift+F11", APP_CMD_SEND_TOGGLE_KEYS);
    (*key_menu)->add_command("Send Shift+F12", APP_CMD_SEND_RELEASE_KEYS);
    (*key_menu)->add_command("Send Ctrl+Alt+End", APP_CMD_SEND_CTL_ALT_END);
    (*root_menu)->add_sub(key_menu.release());
    _app_menu.reset(root_menu.release());
}

void Application::__remove_key_handler(KeyHandler& handler)
{
    KeyHandlersStack::iterator iter = _key_handlers.begin();

    for (; iter != _key_handlers.end(); iter++) {
        if (*iter == &handler) {
            _key_handlers.erase(iter);
            return;
        }
    }
}

void Application::set_key_handler(KeyHandler& handler)
{
    if (&handler == _key_handler) {
        return;
    }

    __remove_key_handler(handler);
    if (!_key_handler->permit_focus_loss()) {
        _key_handlers.push_front(&handler);
        return;
    }

    unpress_all();
    if (_active) {
        _key_handler->on_focus_out();
    }

    _key_handlers.push_front(_key_handler);
    _key_handler = &handler;
    if (_active) {
        _key_handler->on_focus_in();
    }
}

void Application::remove_key_handler(KeyHandler& handler)
{
    bool is_current = (&handler == _key_handler);

    if (!is_current) {
        __remove_key_handler(handler);
        return;
    }

    KeyHandler damy_handler;
    _key_handler = &damy_handler;
    set_key_handler(**_key_handlers.begin());
    __remove_key_handler(damy_handler);
}

void Application::set_mouse_handler(MouseHandler& handler)
{
    _mouse_handler = &handler;
}

void Application::remove_mouse_handler(MouseHandler& handler)
{
    if (_mouse_handler != &handler) {
        return;
    }
    _mouse_handler = &default_mouse_handler;
}

void Application::capture_mouse()
{
    if (!_active_screen
#ifdef USE_GUI
        || _gui->screen()
#endif // USE_GUI
        ) {
        return;
    }
    _active_screen->capture_mouse();
}

void Application::release_mouse_capture()
{
    if (!_active_screen || !_active_screen->is_mouse_captured()) {
        return;
    }
    _active_screen->relase_mouse();
}

void Application::abort()
{
    Platform::set_event_listener(NULL);
    Platform::set_display_mode_listner(NULL);
    unpress_all();
    while (!_client.abort()) {
        ProcessLoop::process_events_queue();
        Platform::msleep(100);
    }
}

class AutoAbort {
public:
    AutoAbort(Application& app) : _app(app) {}
    ~AutoAbort() { _app.abort();}

private:
    Application& _app;
};

void Application::connect()
{
    ASSERT(_state == DISCONNECTED);
    set_state(CONNECTING);
    _client.connect();
}

void Application::switch_host(const std::string& host, int port, int sport,
                              const std::string& cert_subject)
{
    LOG_INFO("host=%s port=%d sport=%d", host.c_str(), port, sport);
    _during_host_switch = true;
    // we will try to connect to the new host when DiconnectedEvent occurs
    do_disconnect();
    _client.set_target(host.c_str(), port, sport);

    if (!cert_subject.empty()) {
        set_host_cert_subject(cert_subject.c_str(), "spicec");
    }
}

int Application::run()
{
    _exit_code = ProcessLoop::run();
    return _exit_code;
}

void Application::on_start_running()
{
    _foreign_menu.reset(new ForeignMenu(this, _active));
    if (_enable_controller) {
        _controller.reset(new Controller(this));
        return;
    }
    //FIXME: _client.connect() or use the following instead?
#ifdef USE_GUI
    if (_gui_mode == GUI_MODE_FULL) {
        show_gui();
        return;
    }
#endif // HAVE_GUI
    connect();
}

RedScreen* Application::find_screen(int id)
{
    if ((int)_screens.size() < id + 1) {
        return NULL;
    }
    return _screens[id];
}

bool Application::release_capture()
{
    unpress_all();
    if (!_active_screen || !_active_screen->is_mouse_captured()) {
        return false;
    }
    _active_screen->relase_mouse();
    return true;
}

bool Application::do_connect()
{
    _client.connect();
    return true;
}

bool Application::do_disconnect()
{
    on_disconnecting();
    _client.disconnect();
    return true;
}

#define SCREEN_INIT_WIDTH 800
#define SCREEN_INIT_HEIGHT 600

RedScreen* Application::get_screen(int id)
{
    RedScreen *screen;

    if ((int)_screens.size() < id + 1) {
        _screens.resize(id + 1);
    }

    if (!(screen = _screens[id])) {
        Monitor* mon = find_monitor(id);
        SpicePoint size = {SCREEN_INIT_WIDTH, SCREEN_INIT_HEIGHT};

        if (_full_screen && mon) {
            size = mon->get_size();
        }
        screen = _screens[id] = new RedScreen(*this, id, _title, size.x, size.y);
#ifdef USE_GUI
        create_gui_barrier(*screen, id);
#endif // USE_GUI

        if (id != 0) {
            if (_full_screen) {
                mon = get_monitor(id);
                screen->set_monitor(mon);
                rearrange_monitors(false, true, screen);
            } else {
                screen->show(false, _main_screen);
            }
        }
    } else {
        screen = screen->ref();
    }

    return screen;
}

#ifdef USE_GUI
void Application::attach_gui_barriers()
{
    GUIBarriers::iterator iter = _gui_barriers.begin();

    for (; iter != _gui_barriers.end(); iter++) {
        GUIBarrier* barrier = *iter;
        ASSERT((int)_screens.size() > barrier->get_id() && _screens[barrier->get_id()]);
        barrier->attach(*_screens[barrier->get_id()]);
    }
}

void Application::detach_gui_barriers()
{
    GUIBarriers::iterator iter = _gui_barriers.begin();

    for (; iter != _gui_barriers.end(); iter++) {
        GUIBarrier* barrier = *iter;
        barrier->detach();
    }
}

void Application::create_gui_barrier(RedScreen& screen, int id)
{
     GUIBarrier* barrier = new GUIBarrier(id);
     _gui_barriers.push_front(barrier);
     if (_gui.get() && _gui->screen()) {
         barrier->attach(screen);
     }
}

void Application::destroyed_gui_barriers()
{
    while (_gui_barriers.begin() != _gui_barriers.end()) {
        GUIBarrier* barrier = *_gui_barriers.begin();
        _gui_barriers.erase(_gui_barriers.begin());
        delete barrier;
    }
}

void Application::destroyed_gui_barrier(int id)
{
    GUIBarriers::iterator iter = _gui_barriers.begin();

    for (; iter != _gui_barriers.end(); iter++) {
        GUIBarrier* barrier = *iter;
        if (barrier->get_id() == id) {
            _gui_barriers.erase(iter);
            delete barrier;
            return;
        }
    }
}
#endif // USE_GUI

void Application::on_screen_destroyed(int id, bool was_captured)
{
    bool reposition = false;

#ifdef USE_GUI
    destroyed_gui_barrier(id);
#endif // USE_GUI

    if ((int)_screens.size() < id + 1 || !_screens[id]) {
        THROW("no screen");
    }
    if (_active_screen == _screens[id]) {
        _active_screen = NULL;
    }

    if (_full_screen && _screens[id]->has_monitor()) {
        _screens[id]->get_monitor()->restore();
        reposition = true;
    }
    _screens[id] = NULL;
    if (reposition) {
        rearrange_monitors(was_captured, false);
    }
}

void Application::on_mouse_motion(int dx, int dy, int buttons_state)
{
    _mouse_handler->on_mouse_motion(dx, dy, buttons_state);
}

void Application::on_mouse_down(int button, int buttons_state)
{
    _mouse_handler->on_mouse_down(button, buttons_state);
}

void Application::on_mouse_up(int button, int buttons_state)
{
    _mouse_handler->on_mouse_up(button, buttons_state);
}

void Application::unpress_all()
{
    reset_sticky();
    for (int i = 0; i < REDKEY_NUM_KEYS; i++) {
        if (_keyboard_state[i]) {
            _key_handler->on_key_up((RedKey)i);
            unpress_key((RedKey)i);
        }
    }
}

void Application::set_state(State state)
{
    if (state == _state) {
        return;
    }
    _state = state;
#ifdef USE_GUI
    _gui->set_state(_state);
    if (_gui->screen() && !_gui->is_visible()) {
        hide_gui();
    }
#endif // USE_GUI
    reset_sticky();
}

void Application::on_connected()
{
    _during_host_switch = false;

    set_state(CONNECTED);
}

void Application::on_disconnected(int error_code)
{
    bool host_switch = _during_host_switch && (error_code == SPICEC_ERROR_CODE_SUCCESS);
    if (
#ifdef USE_GUI
    _gui_mode != GUI_MODE_FULL &&
#endif // USE_GUI
    !host_switch) {
        _during_host_switch = false;
        ProcessLoop::quit(error_code);
        return;
    }

    // todo: display special notification for host switch (during migration)
    set_state(DISCONNECTED);
#ifdef USE_GUI
    show_gui();
#endif
    if (host_switch) {
        _client.connect(true);
    }
}

void Application::on_visibility_start(int screen_id)
{
    if (screen_id) {
        return;
    }
    set_state(VISIBILITY);
#ifdef USE_GUI
    hide_gui();
#endif
    show_info_layer();
}

void Application::on_disconnecting()
{
    release_capture();
}

Menu* Application::get_app_menu()
{
    if (!*_app_menu) {
        return NULL;
    }
    return (*_app_menu)->ref();
}

void Application::show_info_layer()
{
    if (_info_layer->screen() || _state != VISIBILITY) {
        return;
    }

    _main_screen->attach_layer(*_info_layer);
    _info_layer->set_info_mode();
    reset_sticky();
}

void Application::hide_info_layer()
{
    if (!_info_layer->screen()) {
        return;
    }

    _main_screen->detach_layer(*_info_layer);
    reset_sticky();
}

#ifdef GUI_DEMO

class TestResponce: public GUI::BoxResponse {
public:
    virtual void response(int response)
    {
        DBG(0, "%d", response);
    }

    virtual void aborted()
    {
        DBG(0, "");
    }
};


TestResponce response_test;

void Application::message_box_test()
{
    GUI::ButtonsList list(3);
    list[0].id = 101;
    list[0].text = "Yes";
    list[1].id = 102;
    list[1].text = "No";
    list[2].id = 103;
    list[2].text = "Don't Know";


    if (!_gui->message_box(GUI::QUESTION, "My question", list, &response_test)) {
        DBG(0, "busy");
    } else {
        show_gui();
    }
}

#endif

#ifdef USE_GUI
void Application::show_gui()
{
    if (_gui->screen() || !_gui->prepare_dialog()) {
        return;
    }

    hide_info_layer();
    release_capture();
    _gui->set_screen(_main_screen);
    attach_gui_barriers();
}

void Application::hide_gui()
{
    if (!_gui->screen()) {
        return;
    }

    _gui->set_screen(NULL);
    detach_gui_barriers();
    show_info_layer();
}
#endif // USE_GUI

void Application::do_command(int command)
{
    switch (command) {
    case APP_CMD_SEND_CTL_ALT_DEL:
        send_alt_ctl_del();
        break;
    case APP_CMD_TOGGLE_FULL_SCREEN:
        toggle_full_screen();
        break;
    case APP_CMD_SEND_TOGGLE_KEYS:
        send_command_hotkey(APP_CMD_SEND_TOGGLE_KEYS);
        break;
    case APP_CMD_SEND_RELEASE_KEYS:
        send_command_hotkey(APP_CMD_SEND_RELEASE_KEYS);
        break;
    case APP_CMD_SEND_CTL_ALT_END:
        send_ctrl_alt_end();
        break;
    case APP_CMD_RELEASE_CAPTURE:
        release_capture();
        break;
#ifdef RED_DEBUG
    case APP_CMD_CONNECT:
        do_connect();
        break;
    case APP_CMD_DISCONNECT:
        do_disconnect();
        break;
#endif
#ifdef USE_GUI
    case APP_CMD_SHOW_GUI:
        show_gui();
        break;
#endif // USE_GUI
#ifdef USE_SMARTCARD
    case APP_CMD_SMARTCARD_INSERT:
        virtual_card_insert();
        break;
    case APP_CMD_SMARTCARD_REMOVE:
        virtual_card_remove();
        break;
#endif
    default:
        AppMenuItemMap::iterator iter = _app_menu_items.find(command);
        ASSERT(iter != _app_menu_items.end());
        AppMenuItem* item = &(*iter).second;
        if (item->type == APP_MENU_ITEM_TYPE_FOREIGN) {
            ASSERT(*_foreign_menu);
            (*_foreign_menu)->on_command(item->conn_ref, item->ext_id);
        } else if (item->type == APP_MENU_ITEM_TYPE_CONTROLLER) {
            ASSERT(*_controller);
            (*_controller)->on_command(item->conn_ref, item->ext_id);
        }
    }
}

#ifdef REDKEY_DEBUG

static void show_red_key(RedKey key)
{

#define KEYCASE(red_key)    \
    case red_key:           \
        DBG(0, #red_key);   \
        return;

    switch (key) {
        KEYCASE(REDKEY_INVALID);
        KEYCASE(REDKEY_ESCAPE);
        KEYCASE(REDKEY_1);
        KEYCASE(REDKEY_2);
        KEYCASE(REDKEY_3);
        KEYCASE(REDKEY_4);
        KEYCASE(REDKEY_5);
        KEYCASE(REDKEY_6);
        KEYCASE(REDKEY_7);
        KEYCASE(REDKEY_8);
        KEYCASE(REDKEY_9);
        KEYCASE(REDKEY_0);
        KEYCASE(REDKEY_MINUS);
        KEYCASE(REDKEY_EQUALS);
        KEYCASE(REDKEY_BACKSPACE);
        KEYCASE(REDKEY_TAB);
        KEYCASE(REDKEY_Q);
        KEYCASE(REDKEY_W);
        KEYCASE(REDKEY_E);
        KEYCASE(REDKEY_R);
        KEYCASE(REDKEY_T);
        KEYCASE(REDKEY_Y);
        KEYCASE(REDKEY_U);
        KEYCASE(REDKEY_I);
        KEYCASE(REDKEY_O);
        KEYCASE(REDKEY_P);
        KEYCASE(REDKEY_ENTER);
        KEYCASE(REDKEY_L_BRACKET);
        KEYCASE(REDKEY_R_BRACKET);
        KEYCASE(REDKEY_L_CTRL);
        KEYCASE(REDKEY_A);
        KEYCASE(REDKEY_S);
        KEYCASE(REDKEY_D);
        KEYCASE(REDKEY_F);
        KEYCASE(REDKEY_G);
        KEYCASE(REDKEY_H);
        KEYCASE(REDKEY_J);
        KEYCASE(REDKEY_K);
        KEYCASE(REDKEY_L);
        KEYCASE(REDKEY_SEMICOLON);
        KEYCASE(REDKEY_QUOTE);
        KEYCASE(REDKEY_BACK_QUOTE);
        KEYCASE(REDKEY_L_SHIFT);
        KEYCASE(REDKEY_BACK_SLASH);
        KEYCASE(REDKEY_Z);
        KEYCASE(REDKEY_X);
        KEYCASE(REDKEY_C);
        KEYCASE(REDKEY_V);
        KEYCASE(REDKEY_B);
        KEYCASE(REDKEY_N);
        KEYCASE(REDKEY_M);
        KEYCASE(REDKEY_COMMA);
        KEYCASE(REDKEY_PERIOD);
        KEYCASE(REDKEY_SLASH);
        KEYCASE(REDKEY_R_SHIFT);
        KEYCASE(REDKEY_PAD_MULTIPLY);
        KEYCASE(REDKEY_L_ALT);
        KEYCASE(REDKEY_SPACE);
        KEYCASE(REDKEY_CAPS_LOCK);
        KEYCASE(REDKEY_F1);
        KEYCASE(REDKEY_F2);
        KEYCASE(REDKEY_F3);
        KEYCASE(REDKEY_F4);
        KEYCASE(REDKEY_F5);
        KEYCASE(REDKEY_F6);
        KEYCASE(REDKEY_F7);
        KEYCASE(REDKEY_F8);
        KEYCASE(REDKEY_F9);
        KEYCASE(REDKEY_F10);
        KEYCASE(REDKEY_NUM_LOCK);
        KEYCASE(REDKEY_SCROLL_LOCK);
        KEYCASE(REDKEY_PAD_7);
        KEYCASE(REDKEY_PAD_8);
        KEYCASE(REDKEY_PAD_9);
        KEYCASE(REDKEY_PAD_MINUS);
        KEYCASE(REDKEY_PAD_4);
        KEYCASE(REDKEY_PAD_5);
        KEYCASE(REDKEY_PAD_6);
        KEYCASE(REDKEY_PAD_PLUS);
        KEYCASE(REDKEY_PAD_1);
        KEYCASE(REDKEY_PAD_2);
        KEYCASE(REDKEY_PAD_3);
        KEYCASE(REDKEY_PAD_0);
        KEYCASE(REDKEY_PAD_POINT);
        KEYCASE(REDKEY_F11);
        KEYCASE(REDKEY_F12);
        KEYCASE(REDKEY_PAD_ENTER);
        KEYCASE(REDKEY_R_CTRL);
        KEYCASE(REDKEY_FAKE_L_SHIFT);
        KEYCASE(REDKEY_PAD_DIVIDE);
        KEYCASE(REDKEY_FAKE_R_SHIFT);
        KEYCASE(REDKEY_CTRL_PRINT_SCREEN);
        KEYCASE(REDKEY_R_ALT);
        KEYCASE(REDKEY_CTRL_BREAK);
        KEYCASE(REDKEY_HOME);
        KEYCASE(REDKEY_UP);
        KEYCASE(REDKEY_PAGEUP);
        KEYCASE(REDKEY_LEFT);
        KEYCASE(REDKEY_RIGHT);
        KEYCASE(REDKEY_END);
        KEYCASE(REDKEY_DOWN);
        KEYCASE(REDKEY_PAGEDOWN);
        KEYCASE(REDKEY_INSERT);
        KEYCASE(REDKEY_DELETE);
        KEYCASE(REDKEY_LEFT_CMD);
        KEYCASE(REDKEY_RIGHT_CMD);
        KEYCASE(REDKEY_MENU);
    default:
        DBG(0, "???");
    }
}

#endif

bool Application::press_key(RedKey key)
{
    if (_keyboard_state[key]) {
        return true;
    } else {
        _keyboard_state[key] = true;
        _num_keys_pressed++;
        return false;
    }
}

bool Application::unpress_key(RedKey key)
{
    ASSERT(!_sticky_info.key_down || !is_sticky_trace_key(key));

    if (_keyboard_state[key]) {
        _keyboard_state[key] = false;
        _num_keys_pressed--;
        ASSERT(_num_keys_pressed >= 0);
        return true;
    } else {
        return false;
    }
}

inline bool Application::is_sticky_trace_key(RedKey key)
{
    return ((key == REDKEY_L_ALT) || (key == REDKEY_R_ALT));
}

void Application::reset_sticky()
{
    _sticky_info.trace_is_on = (_state == VISIBILITY) && _sys_key_intercept_mode;
    _sticky_info.key_first_down = false;
    deactivate_interval_timer(*_sticky_info.timer);
    if (_sticky_info.sticky_mode) {
        ASSERT(_keyboard_state[_sticky_info.key]);
        // if it is physically down, we shouldn't unpress it
        if (!_sticky_info.key_down) {
            do_on_key_up(_sticky_info.key);
        }
        _sticky_info.sticky_mode = false;
        DBG(0, "OFF sticky");
        _info_layer->set_sticky(false);
    }
    _sticky_info.key_down = false;

    _sticky_info.key = REDKEY_INVALID;
}

struct ModifierKey {
    int modifier;
    RedKey key;
};

ModifierKey modifier_keys[] = {
    {Platform::L_SHIFT_MODIFIER, REDKEY_L_SHIFT},
    {Platform::R_SHIFT_MODIFIER, REDKEY_R_SHIFT},
    {Platform::L_CTRL_MODIFIER, REDKEY_L_CTRL},
    {Platform::R_CTRL_MODIFIER, REDKEY_R_CTRL},
    {Platform::L_ALT_MODIFIER, REDKEY_L_ALT},
    {Platform::R_ALT_MODIFIER, REDKEY_R_ALT},
};

void Application::sync_keyboard_modifiers()
{
    uint32_t modifiers = Platform::get_keyboard_modifiers();
    for (int i = 0; i < sizeof(modifier_keys) / sizeof(modifier_keys[0]); i++) {
        if (modifiers & modifier_keys[i].modifier) {
            on_key_down(modifier_keys[i].key);
        } else {
            on_key_up(modifier_keys[i].key);
        }
    }
}

void Application::on_key_down(RedKey key)
{
    if (key <= 0 || key >= REDKEY_NUM_KEYS) {
        return;
    }

    bool was_pressed = press_key(key);
    if (_sticky_info.trace_is_on) {
        if (key == _sticky_info.key) {
            _sticky_info.key_down = true;
        }

        if (!_sticky_info.sticky_mode) {
            // during tracing (traced key was pressed and no keyboard event has occurred till now)
            if (_sticky_info.key_first_down) {
                ASSERT(_sticky_info.key != REDKEY_INVALID);
                if (key != _sticky_info.key) {
                    reset_sticky();
                }
            } else if (is_sticky_trace_key(key) && (_num_keys_pressed == 1) && !was_pressed) {
                ASSERT(_sticky_info.key == REDKEY_INVALID);
                // start tracing
                _sticky_info.key =  key;
                _sticky_info.key_first_down = true;
                _sticky_info.key_down = true;
                activate_interval_timer(*_sticky_info.timer, STICKY_KEY_TIMEOUT);
            }
        }
    }

    if (!_sticky_info.sticky_mode) {
        int command = get_hotkeys_commnad();
        if (command != APP_CMD_INVALID) {
            do_command(command);
            return;
        }
    }

#ifdef WIN32
    if (!_active_screen->intercepts_sys_key() &&
                                           (key == REDKEY_LEFT_CMD || key == REDKEY_RIGHT_CMD ||
                                            key == REDKEY_MENU || _keyboard_state[REDKEY_L_ALT])) {
        unpress_key(key);
        return;
    }
    if (!_sticky_info.sticky_mode &&
        ((_keyboard_state[REDKEY_L_CTRL] || _keyboard_state[REDKEY_R_CTRL]) &&
        (_keyboard_state[REDKEY_L_ALT] || _keyboard_state[REDKEY_R_ALT]))) {
        if (key == REDKEY_END || key == REDKEY_PAD_1) {
            unpress_key(key);
            _key_handler->on_key_down(REDKEY_DELETE);
            _key_handler->on_key_up(REDKEY_DELETE);
        } else if (key == REDKEY_DELETE || key == REDKEY_PAD_POINT) {
            unpress_key(key);
            return;
        }
    }
#endif

    _key_handler->on_key_down(key);
}

void Application::do_on_key_up(RedKey key)
{
    unpress_key(key);
    _key_handler->on_key_up(key);
}

void Application::on_key_up(RedKey key)
{
    if(key < 0 || key >= REDKEY_NUM_KEYS || !_keyboard_state[key]) {
        return;
    }

    if (_sticky_info.trace_is_on) {
        ASSERT(_sticky_info.sticky_mode || (key == _sticky_info.key) ||
               (_sticky_info.key == REDKEY_INVALID));
        if (key == _sticky_info.key) {
            _sticky_info.key_down = false;
            if (_sticky_info.key_first_down) {
                _sticky_info.key_first_down = false;
                if (!_sticky_info.sticky_mode) {
                    reset_sticky();
                } else {
                    return; // ignore the sticky-key first release
                }
            }
        }

        if (_sticky_info.sticky_mode) {
            RedKey old_sticky_key = _sticky_info.key;
            reset_sticky();
            if (key == old_sticky_key) {
                return; // no need to send key_up twice
            }
        }
    }

    do_on_key_up(key);
}

void Application::on_char(uint32_t ch)
{
    _key_handler->on_char(ch);
}

void Application::on_deactivate_screen(RedScreen* screen)
{
    if (_active_screen == screen) {
        _sys_key_intercept_mode = false;
        release_capture();
        _active_screen = NULL;
    }
}

void Application::on_activate_screen(RedScreen* screen)
{
    ASSERT(!_active_screen || (_active_screen == screen));
    _active_screen = screen;
    sync_keyboard_modifiers();
}

void Application::on_start_screen_key_interception(RedScreen* screen)
{
    ASSERT(screen == _active_screen);

    _sys_key_intercept_mode = true;
    reset_sticky();
}

void Application::on_stop_screen_key_interception(RedScreen* screen)
{
    ASSERT(screen == _active_screen);

    _sys_key_intercept_mode = false;
    reset_sticky();
}

void Application::on_app_activated()
{
    _active = true;
    _key_handler->on_focus_in();
    if (*_foreign_menu) {
        (*_foreign_menu)->on_activate();
    }
}

void Application::on_app_deactivated()
{
    _active = false;
    _key_handler->on_focus_out();
    if (*_foreign_menu) {
        (*_foreign_menu)->on_deactivate();
    }
#ifdef WIN32
    if (!_changing_screens) {
        exit_full_screen();
    }
#endif
}

void Application::on_screen_unlocked(RedScreen& screen)
{
    if (_full_screen) {
        return;
    }

    screen.resize(SCREEN_INIT_WIDTH, SCREEN_INIT_HEIGHT);
}

void Application::rearrange_monitors(bool force_capture,
                                     bool enter_full_screen,
                                     RedScreen* screen)
{
    bool capture;
    bool toggle_full_screen;

    if (!_full_screen && !enter_full_screen) {
        return;
    }

    toggle_full_screen = enter_full_screen && !screen;
    capture = release_capture();
#ifndef WIN32
    if (toggle_full_screen) {
        /* performing hide during resolution changes resulted in
           missing WM_KEYUP events */
        hide();
    }
#endif
    prepare_monitors();
    position_screens();
    if (enter_full_screen) {
        // toggling to full screen
        if (toggle_full_screen) {
            show_full_screen();
            _main_screen->activate();

        } else { // already in full screen mode and a new screen is displayed
            screen->show_full_screen();
            if (screen->is_out_of_sync()) {
                _out_of_sync = true;
                /* If the client monitor cannot handle the guest resolution
                    drop back to windowed mode */
                exit_full_screen();
            }
        }
    } 

    if (force_capture || capture) {
        if (!toggle_full_screen) {
            _main_screen->activate();
        }
        _main_screen->capture_mouse();
    }
}

Monitor* Application::find_monitor(int id)
{
    ASSERT(_monitors);
    std::list<Monitor*>::const_iterator iter = _monitors->begin();
    for (; iter != _monitors->end(); iter++) {
        Monitor *mon = *iter;
        if (mon->get_id() == id) {
            return mon;
        }
    }
    return NULL;
}

Monitor* Application::get_monitor(int id)
{
    Monitor *mon = find_monitor(id);
    if ((mon = find_monitor(id))) {
        mon->set_used();
    }
    return mon;
}

void Application::assign_monitors()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            ASSERT(!_screens[i]->has_monitor());
            _screens[i]->set_monitor(get_monitor(i));
        }
    }
}

void Application::prepare_monitors()
{
    //todo: test match of monitors size/position against real world size/position
    for (int i = 0; i < (int)_screens.size(); i++) {
        Monitor* mon;
        if (_screens[i] && (mon = _screens[i]->get_monitor())) {

            if (_screens[i]->is_size_locked()) {
                SpicePoint size = _screens[i]->get_size();
                mon->set_mode(size.x, size.y);
            } else {
                SpicePoint size = mon->get_size();
                _screens[i]->resize(size.x, size.y);
            }
        }
    }
}

void Application::restore_monitors()
{
    //todo: renew monitors (destroy + init)
    std::list<Monitor*>::const_iterator iter = _monitors->begin();
    for (; iter != _monitors->end(); iter++) {
        (*iter)->restore();
    }
}

void Application::position_screens()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        Monitor* mon;
        if (_screens[i] && (mon = _screens[i]->get_monitor())) {
            _screens[i]->position_full_screen(mon->get_position());
        }
    }
}

void Application::hide()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            _screens[i]->hide();
        }
    }
}

void Application::show()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            _screens[i]->show();
        }
    }
}

void Application::external_show()
{
    DBG(0, "Entry, _screens.size()=%lu", _screens.size());
    for (size_t i = 0; i < _screens.size(); ++i) {
        DBG(0, "%lu", i);
        if (_screens[i]) {
            _screens[i]->external_show();
        }
    }
}

void Application::show_full_screen()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            _screens[i]->show_full_screen();
            if (_screens[i]->is_out_of_sync()) {
                _out_of_sync = true;
            }
        }
    }
}

void Application::enter_full_screen()
{
    LOG_INFO("");
    _changing_screens = true;
    assign_monitors();
    rearrange_monitors(false, true);
    _changing_screens = false;
    _full_screen = true;
    /* If the client monitor cannot handle the guest resolution drop back
       to windowed mode */
    if (_out_of_sync) {
        exit_full_screen();
    }
}

void Application::restore_screens_size()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (!_screens[i] || _screens[i]->is_size_locked()) {
            continue;
        }
        _screens[i]->resize(SCREEN_INIT_WIDTH, SCREEN_INIT_HEIGHT);
    }
}

void Application::exit_full_screen()
{
    if (!_full_screen) {
        return;
    }
    if (_out_of_sync) {
        LOG_WARN("Falling back to windowed mode (guest resolution too large for client?)");
    }
    LOG_INFO("");
    _changing_screens = true;
    release_capture();
    restore_monitors();
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            Monitor* mon;
            _screens[i]->exit_full_screen();
            if ((mon = _screens[i]->get_monitor())) {
                _screens[i]->set_monitor(NULL);
                mon->set_free();
            }
        }
    }
    _full_screen = false;
    _out_of_sync = false;
    restore_screens_size();
    show();
    _main_screen->activate();
    _changing_screens = false;
}

bool Application::toggle_full_screen()
{
    if (_full_screen) {
        exit_full_screen();
    } else {
        enter_full_screen();
    }
    return _full_screen;
}

void Application::resize_screen(RedScreen *screen, int width, int height)
{
    Monitor* mon;
    if (_full_screen) {
        if ((mon = screen->get_monitor())) {
            mon->set_mode(width, height);
        }
    }
    screen->resize(width, height);
    rearrange_monitors(false, false);
    if (screen->is_out_of_sync()) {
        _out_of_sync = true;
        /* If the client monitor cannot handle the guest resolution
           drop back to windowed mode */
        exit_full_screen();
    }
}

void Application::minimize()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            _screens[i]->minimize();
        }
    }
}

void Application::destroy_monitors()
{
    for (int i = 0; i < (int)_screens.size(); i++) {
        if (_screens[i]) {
            _screens[i]->set_monitor(NULL);
        }
    }
    Platform::destroy_monitors();
    _monitors = NULL;
}

void Application::init_monitors()
{
    exit_full_screen();
    destroy_monitors();
    _monitors = &Platform::init_monitors();
}

void Application::on_monitors_change()
{
    if (Monitor::is_self_change()) {
        return;
    }
    exit_full_screen();
    init_monitors();
}

void Application::on_display_mode_change()
{
    _client.on_display_mode_change();
}

uint32_t Application::get_mouse_mode()
{
    return _client.get_mouse_mode();
}

void Application::set_title(const std::string& title)
{
    _title = title;

    for (size_t i = 0; i < _screens.size(); ++i) {
        if (_screens[i]) {
            _screens[i]->set_name(_title);
        }
    }
}

bool Application::is_key_set_pressed(const HotkeySet& key_set)
{
    HotkeySet::const_iterator iter = key_set.begin();

    while (iter != key_set.end()) {
        if (!(_keyboard_state[iter->main] || _keyboard_state[iter->alter])) {
            break;
        }
        ++iter;
    }

    return iter == key_set.end();
}

int Application::get_hotkeys_commnad()
{
    HotKeys::const_iterator iter = _hot_keys.begin();

    while (iter != _hot_keys.end()) {
        if (is_key_set_pressed(iter->second)) {
            break;
        }
        ++iter;
    }

    return (iter != _hot_keys.end()) ? iter->first : APP_CMD_INVALID;
}

void Application::send_key_down(RedKey key)
{
    _key_handler->on_key_down(key);
}

void Application::send_key_up(RedKey key)
{
    _key_handler->on_key_up(key);
}

void Application::send_alt_ctl_del()
{
    send_key_down(REDKEY_L_CTRL);
    send_key_down(REDKEY_L_ALT);
    send_key_down(REDKEY_DELETE);

    send_key_up(REDKEY_DELETE);
    send_key_up(REDKEY_L_ALT);
    send_key_up(REDKEY_L_CTRL);
}

void Application::send_ctrl_alt_end()
{
    send_key_down(REDKEY_L_CTRL);
    send_key_down(REDKEY_L_ALT);
    send_key_down(REDKEY_END);

    send_key_up(REDKEY_L_CTRL);
    send_key_up(REDKEY_L_ALT);
    send_key_up(REDKEY_END);
}

void Application::send_command_hotkey(int action)
{
    HotKeys::const_iterator iter = _hot_keys.find(action);
    if (iter != _hot_keys.end()) {
        send_hotkey_key_set(iter->second);
    }
}

void Application::send_hotkey_key_set(const HotkeySet& key_set)
{
    HotkeySet::const_iterator iter;

    for (iter = key_set.begin(); iter != key_set.end(); ++iter) {
        send_key_down(iter->main);
    }

    for (iter = key_set.begin(); iter != key_set.end(); ++iter) {
        send_key_up(iter->main);
    }
}

int Application::get_menu_item_id(AppMenuItemType type, int32_t conn_ref, uint32_t ext_id)
{
    int free_id = APP_CMD_EXTERNAL_BEGIN;
    AppMenuItem item = {type, conn_ref, ext_id};
    AppMenuItemMap::iterator iter = _app_menu_items.begin();
    for (; iter != _app_menu_items.end(); iter++) {
        if (!memcmp(&(*iter).second, &item, sizeof(item))) {
            return (*iter).first;
        } else if (free_id == (*iter).first && ++free_id > APP_CMD_EXTERNAL_END) {
            return APP_CMD_INVALID;
        }
    }
    _app_menu_items[free_id] = item;
    return free_id;
}

void Application::clear_menu_items(int32_t opaque_conn_ref)
{
    AppMenuItemMap::iterator iter = _app_menu_items.begin();
    AppMenuItemMap::iterator curr;

    while (iter != _app_menu_items.end()) {
        curr = iter++;
        if (((*curr).second).conn_ref == opaque_conn_ref) {
            _app_menu_items.erase(curr);
        }
    }
}

void Application::remove_menu_item(int item_id)
{
    _app_menu_items.erase(item_id);
}

int Application::get_foreign_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id)
{
    return get_menu_item_id(APP_MENU_ITEM_TYPE_FOREIGN, opaque_conn_ref, msg_id);
}

void Application::update_menu()
{
    for (size_t i = 0; i < _screens.size(); ++i) {
        if (_screens[i]) {
            _screens[i]->update_menu();
        }
    }
}

//controller interface begin

void Application::set_auto_display_res(bool auto_display_res)
{
   _client.set_auto_display_res(auto_display_res);
}

bool Application::connect(const std::string& host, int port, int sport, const std::string& password)
{
    if (_state != DISCONNECTED) {
        return false;
    }
    _client.set_target(host, port, sport);
    _client.set_password(password);
    if (!set_channels_security(port, sport)) {
        return false;
    }
    register_channels();
    connect();
    return true;
}

void Application::disconnect()
{
    do_disconnect();
}

void Application::quit()
{
    ProcessLoop::quit(SPICEC_ERROR_CODE_SUCCESS);
}

void Application::show_me(bool full_screen)
{
    if (full_screen) {
        enter_full_screen();
    } else {
        _main_screen->show(true, NULL);
    }
}

void Application::hide_me()
{
    if (_full_screen) {
        exit_full_screen();
    }
    hide();
}

void Application::set_hotkeys(const std::string& hotkeys)
{
    std::auto_ptr<HotKeysParser> parser(new HotKeysParser(hotkeys, _commands_map));
    _hot_keys = parser->get();
}

int Application::get_controller_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id)
{
    return get_menu_item_id(APP_MENU_ITEM_TYPE_CONTROLLER, opaque_conn_ref, msg_id);
}

void Application::set_menu(Menu* menu)
{
    if (menu) {
        _app_menu.reset(menu->ref());
    } else {
        init_menu();
    }
    if (*_foreign_menu) {
        (*_foreign_menu)->add_sub_menus();
    }
    update_menu();
}

void Application::delete_menu()
{
    set_menu(NULL);
}

#ifdef USE_GUI
bool Application::is_disconnect_allowed()
{
    return _gui_mode == GUI_MODE_FULL;
}
#endif // USE_GUI

const std::string& Application::get_host()
{
    return _client.get_host();
}

int Application::get_port()
{
    return _client.get_port();
}

int Application::get_sport()
{
    return _client.get_sport();
}

const std::string& Application::get_password()
{
    return _client.get_password();
}

//controller interface end

bool Application::set_channels_security(CmdLineParser& parser, bool on, char *val,
                                        const char* arg0)
{
    RedPeer::ConnectionOptions::Type option;
    option = (on) ? RedPeer::ConnectionOptions::CON_OP_SECURE :
                    RedPeer::ConnectionOptions::CON_OP_UNSECURE;

    typedef std::map< std::string, int> ChannelsNamesMap;
    ChannelsNamesMap channels_names;
    channels_names["main"] = SPICE_CHANNEL_MAIN;
    channels_names["display"] = SPICE_CHANNEL_DISPLAY;
    channels_names["inputs"] = SPICE_CHANNEL_INPUTS;
    channels_names["cursor"] = SPICE_CHANNEL_CURSOR;
    channels_names["playback"] = SPICE_CHANNEL_PLAYBACK;
    channels_names["record"] = SPICE_CHANNEL_RECORD;
#ifdef USE_TUNNEL
    channels_names["tunnel"] = SPICE_CHANNEL_TUNNEL;
#endif
#ifdef USE_SMARTCARD
    channels_names["smartcard"] = SPICE_CHANNEL_SMARTCARD;
#endif

    if (!strcmp(val, "all")) {
        if ((val = parser.next_argument())) {
            Platform::term_printf("%s: \"all\" is exclusive in secure-channels\n", arg0);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        PeerConnectionOptMap::iterator iter = _peer_con_opt.begin();
        for (; iter != _peer_con_opt.end(); iter++) {
            (*iter).second = option;
        }
        return true;
    }

    do {
        ChannelsNamesMap::iterator iter = channels_names.find(val);
        if (iter == channels_names.end()) {
            Platform::term_printf("%s: bad channel name \"%s\" in secure-channels\n", arg0, val);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        _peer_con_opt[(*iter).second] = option;
    } while ((val = parser.next_argument()));
    return true;
}

bool Application::set_channels_security(int port, int sport)
{
    PeerConnectionOptMap::iterator iter = _peer_con_opt.begin();

    for (; iter != _peer_con_opt.end(); iter++) {
        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_SECURE) {
            continue;
        }

        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_UNSECURE) {
            continue;
        }

        if (port != -1 && sport != -1) {
            (*iter).second = RedPeer::ConnectionOptions::CON_OP_BOTH;
            continue;
        }

        if (port != -1) {
            (*iter).second = RedPeer::ConnectionOptions::CON_OP_UNSECURE;
            continue;
        }

        if (sport != -1) {
            (*iter).second = RedPeer::ConnectionOptions::CON_OP_SECURE;
            continue;
        }

        _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
        return false;
    }
    return true;
}

bool Application::set_connection_ciphers(const char* ciphers, const char* arg0)
{
    _con_ciphers = ciphers;
    return true;
}

bool Application::set_ca_file(const char* ca_file, const char* arg0)
{
    _host_auth_opt.CA_file = ca_file;
    return true;
}

bool Application::set_host_cert_subject(const char* subject, const char* arg0)
{
     if (!_host_auth_opt.set_cert_subject(subject)) {
        Platform::term_printf("%s: bad cert subject %s", arg0, subject);
        _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
        return false;
     }

     return true;
}

bool Application::set_canvas_option(CmdLineParser& parser, char *val, const char* arg0)
{
    typedef std::map< std::string, CanvasOption> CanvasNamesMap;
    CanvasNamesMap canvas_types;

    canvas_types["sw"] = CANVAS_OPTION_SW;
#ifdef WIN32
    canvas_types["gdi"] = CANVAS_OPTION_GDI;
#endif
#ifdef USE_OGL
    canvas_types["gl_fbo"] = CANVAS_OPTION_OGL_FBO;
    canvas_types["gl_pbuff"] = CANVAS_OPTION_OGL_PBUFF;
#endif
    _canvas_types.clear();

    do {
        CanvasNamesMap::iterator iter = canvas_types.find(val);
        if (iter == canvas_types.end()) {
            Platform::term_printf("%s: bad canvas type \"%s\"\n", arg0, val);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        _canvas_types.resize(_canvas_types.size() + 1);
        _canvas_types[_canvas_types.size() - 1] = (*iter).second;
    } while ((val = parser.next_argument()));

    return true;
}

bool Application::set_enable_channels(CmdLineParser& parser, bool enable, char *val,
                                      const char* arg0)
{
    typedef std::map< std::string, int> ChannelsNamesMap;
    ChannelsNamesMap channels_names;
    channels_names["display"] = SPICE_CHANNEL_DISPLAY;
    channels_names["inputs"] = SPICE_CHANNEL_INPUTS;
    channels_names["cursor"] = SPICE_CHANNEL_CURSOR;
    channels_names["playback"] = SPICE_CHANNEL_PLAYBACK;
    channels_names["record"] = SPICE_CHANNEL_RECORD;
#ifdef USE_TUNNEL
    channels_names["tunnel"] = SPICE_CHANNEL_TUNNEL;
#endif
#ifdef USE_SMARTCARD
    channels_names["smartcard"] = SPICE_CHANNEL_SMARTCARD;
#endif

    if (!strcmp(val, "all")) {
        if ((val = parser.next_argument())) {
            Platform::term_printf("%s: \"all\" is exclusive\n", arg0);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        for (unsigned int i = 0; i < _enabled_channels.size(); i++) {
            _enabled_channels[i] = enable;
        }
        return true;
    }

    do {
        ChannelsNamesMap::iterator iter = channels_names.find(val);
        if (iter == channels_names.end()) {
            Platform::term_printf("%s: bad channel name \"%s\"\n", arg0, val);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        _enabled_channels[(*iter).second] = enable;
    } while ((val = parser.next_argument()));
    return true;
}

bool Application::set_disabled_display_effects(CmdLineParser& parser, char *val, const char* arg0,
                                               DisplaySetting& disp_setting)
{
    if (!strcmp(val, "all")) {
        if ((val = parser.next_argument())) {
            Platform::term_printf("%s: \"all\" is exclusive\n", arg0);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
        disp_setting._disable_wallpaper = true;
        disp_setting._disable_font_smooth = true;
        disp_setting._disable_animation = true;
        return true;
    }

    do {
        if (!strcmp(val, "wallpaper")) {
            disp_setting._disable_wallpaper = true;
        } else if (!strcmp(val, "font-smooth")) {
            disp_setting._disable_font_smooth = true;
        } else if (!strcmp(val, "animation")) {
            disp_setting._disable_animation = true;
        } else {
            Platform::term_printf("%s: bad display effect type \"%s\"\n", arg0, val);
            _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
            return false;
        }
    } while ((val = parser.next_argument()));

    return true;
}

void Application::on_cmd_line_invalid_arg(const char* arg0, const char* what, const char* val)
{
    Platform::term_printf("%s: invalid %s value %s\n", arg0, what, val);
    _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
}

void Application::register_channels()
{
    if (_enabled_channels[SPICE_CHANNEL_DISPLAY]) {
        _client.register_channel_factory(DisplayChannel::Factory());
    }

    if (_enabled_channels[SPICE_CHANNEL_CURSOR]) {
        _client.register_channel_factory(CursorChannel::Factory());
    }

    if (_enabled_channels[SPICE_CHANNEL_INPUTS]) {
        _client.register_channel_factory(InputsChannel::Factory());
    }

    if (_enabled_channels[SPICE_CHANNEL_PLAYBACK]) {
        _client.register_channel_factory(PlaybackChannel::Factory());
    }

    if (_enabled_channels[SPICE_CHANNEL_RECORD]) {
        _client.register_channel_factory(RecordChannel::Factory());
    }

#ifdef USE_TUNNEL
    if (_enabled_channels[SPICE_CHANNEL_TUNNEL]) {
        _client.register_channel_factory(TunnelChannel::Factory());
    }
#endif
#ifdef USE_SMARTCARD
    if (_enabled_channels[SPICE_CHANNEL_SMARTCARD] && _smartcard_options->enable) {
        smartcard_init(_smartcard_options); // throws Exception
        _client.register_channel_factory(SmartCardChannel::Factory());
    }
#endif
}

bool Application::process_cmd_line(int argc, char** argv, bool &full_screen)
{
    std::string host = "";
    int sport = -1;
    int port = -1;
    bool auto_display_res = false;
    std::string password;
    DisplaySetting display_setting;

    enum {
        SPICE_OPT_VERSION = CmdLineParser::OPTION_FIRST_AVAILABLE,
        SPICE_OPT_HOST,
        SPICE_OPT_PORT,
        SPICE_OPT_SPORT,
        SPICE_OPT_PASSWORD,
        SPICE_OPT_FULL_SCREEN,
        SPICE_OPT_SECURE_CHANNELS,
        SPICE_OPT_UNSECURE_CHANNELS,
        SPICE_OPT_TLS_CIPHERS,
        SPICE_OPT_CA_FILE,
        SPICE_OPT_HOST_SUBJECT,
        SPICE_OPT_ENABLE_CHANNELS,
        SPICE_OPT_DISABLE_CHANNELS,
        SPICE_OPT_CANVAS_TYPE,
        SPICE_OPT_DISPLAY_COLOR_DEPTH,
        SPICE_OPT_DISABLE_DISPLAY_EFFECTS,
        SPICE_OPT_CONTROLLER,
        SPICE_OPT_TITLE,
#ifdef USE_SMARTCARD
        SPICE_OPT_SMARTCARD,
        SPICE_OPT_NOSMARTCARD,
        SPICE_OPT_SMARTCARD_CERT,
        SPICE_OPT_SMARTCARD_DB,
#endif
    };

    full_screen = false;

#ifdef USE_GUI
    if (argc == 1) {
        _gui_mode = GUI_MODE_FULL;
        register_channels();
        return true;
    }
#endif // USE_GUI

#ifdef USE_GUI
    _gui_mode = GUI_MODE_ACTIVE_SESSION;
#endif // USE_GUI

    CmdLineParser parser("Spice client", false);

    parser.add(SPICE_OPT_VERSION, "version", "spice client version", false);
    parser.add(SPICE_OPT_HOST, "host", "spice server address", "host", true, 'h');
    parser.add(SPICE_OPT_PORT, "port", "spice server port", "port", true, 'p');
    parser.add(SPICE_OPT_SPORT, "secure-port", "spice server secure port", "port", true, 's');
    parser.add(SPICE_OPT_SECURE_CHANNELS, "secure-channels",
               "force secure connection on the specified channels", "channel",
               true);
    parser.set_multi(SPICE_OPT_SECURE_CHANNELS, ',');
    parser.add(SPICE_OPT_UNSECURE_CHANNELS, "unsecure-channels",
               "force unsecure connection on the specified channels", "channel",
               true);
    parser.set_multi(SPICE_OPT_UNSECURE_CHANNELS, ',');
    parser.add(SPICE_OPT_TLS_CIPHERS, "tls-ciphers", "ciphers for secure connections",
               "ciphers", true);
    parser.add(SPICE_OPT_CA_FILE, "ca-file", "truststore file for secure connections",
               "ca-file", true);
    parser.add(SPICE_OPT_HOST_SUBJECT, "host-subject",
               "subject of the host certificate. Format: field=value pairs separated"
               " by commas. Commas and backslashes within values must be preceded by"
               " a backslash", "host-subject", true);
    parser.add(SPICE_OPT_PASSWORD, "password", "server password", "password", true, 'w');
    parser.add(SPICE_OPT_FULL_SCREEN, "full-screen", "open in full screen mode", "auto-conf",
               false, 'f');

    parser.add(SPICE_OPT_ENABLE_CHANNELS, "enable-channels", "enable channels", "channel", true);
    parser.set_multi(SPICE_OPT_ENABLE_CHANNELS, ',');

    parser.add(SPICE_OPT_DISABLE_CHANNELS, "disable-channels", "disable channels", "channel", true);
    parser.set_multi(SPICE_OPT_DISABLE_CHANNELS, ',');

    parser.add(SPICE_OPT_CANVAS_TYPE, "canvas-type", "set rendering canvas", "canvas_type", true);
    parser.set_multi(SPICE_OPT_CANVAS_TYPE, ',');

    parser.add(SPICE_OPT_DISPLAY_COLOR_DEPTH, "color-depth",
               "guest display color depth (if supported by the guest vdagent)",
               "16/32", true);

    parser.add(SPICE_OPT_DISABLE_DISPLAY_EFFECTS, "disable-effects",
               "disable guest display effects " \
               "(if supported by the guest vdagent)",
               "wallpaper/font-smooth/animation/all", true);
    parser.set_multi(SPICE_OPT_DISABLE_DISPLAY_EFFECTS, ',');

    parser.add(SPICE_OPT_CONTROLLER, "controller", "enable external controller");

    parser.add(SPICE_OPT_TITLE, "title", "set window title", "title", true, 't');

#ifdef USE_SMARTCARD
    parser.add(SPICE_OPT_SMARTCARD, "smartcard", "enable smartcard channel");
    parser.add(SPICE_OPT_NOSMARTCARD, "nosmartcard", "disable smartcard channel");
    parser.add(SPICE_OPT_SMARTCARD_CERT, "smartcard-cert", "Use virtual reader+card with given cert(s)",
        "smartcard-cert", true);
    parser.set_multi(SPICE_OPT_SMARTCARD_CERT, ',');
    parser.add(SPICE_OPT_SMARTCARD_DB, "smartcard-db", "Use given db for smartcard certs", "smartcard-db", true);
#endif

    for (int i = SPICE_CHANNEL_MAIN; i < SPICE_END_CHANNEL; i++) {
        _peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_INVALID;
    }

    parser.begin(argc, argv);

    char* val;
    int op;
    while ((op = parser.get_option(&val)) != CmdLineParser::OPTION_DONE) {
        switch (op) {
        case SPICE_OPT_VERSION: {
            std::ostringstream os;
            os << argv[0] << " "<< PACKAGE_VERSION << std::endl;
            Platform::term_printf(os.str().c_str());
            return false;
        }
        case SPICE_OPT_HOST:
            host = val;
            break;
        case SPICE_OPT_PORT: {
            if ((port = str_to_port(val)) == -1) {
                on_cmd_line_invalid_arg(argv[0], "port", val);
                return false;
            }
            break;
        }
        case SPICE_OPT_SPORT: {
            if ((sport = str_to_port(val)) == -1) {
                on_cmd_line_invalid_arg(argv[0], "secure port", val);
                return false;
            }
            break;
        }
        case SPICE_OPT_FULL_SCREEN:
            if (val) {
                if (strcmp(val, "auto-conf")) {
                    on_cmd_line_invalid_arg(argv[0], "full screen mode", val);
                    return false;
                }
                auto_display_res = true;
            }
            full_screen = true;
            break;
        case SPICE_OPT_PASSWORD:
            password = val;
            break;
        case SPICE_OPT_SECURE_CHANNELS:
            if (!set_channels_security(parser, true, val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_UNSECURE_CHANNELS:
            if (!set_channels_security(parser, false, val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_TLS_CIPHERS:
            if (!set_connection_ciphers(val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_CA_FILE:
            if (!set_ca_file(val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_HOST_SUBJECT:
            if (!set_host_cert_subject(val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_ENABLE_CHANNELS:
            if (!set_enable_channels(parser, true, val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_DISABLE_CHANNELS:
            if (!set_enable_channels(parser, false, val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_CANVAS_TYPE:
            if (!set_canvas_option(parser, val, argv[0])) {
                return false;
            }
            break;
        case SPICE_OPT_DISPLAY_COLOR_DEPTH:
            display_setting._set_color_depth = true;
            if (!strcmp(val, "16")) {
                display_setting._color_depth = 16;
            } else if (!strcmp(val, "32")) {
                display_setting._color_depth = 32;
            } else {
                 on_cmd_line_invalid_arg(argv[0], "color depth", val);
                 return false;
            }
            break;
        case SPICE_OPT_DISABLE_DISPLAY_EFFECTS:
            if (!set_disabled_display_effects(parser, val, argv[0], display_setting)) {
                return false;
            }
            break;
        case SPICE_OPT_CONTROLLER:
            if (argc > 2) {
                Platform::term_printf("%s: controller cannot be combined with other options\n",
                                      argv[0]);
                _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
                return false;
            }
            _enable_controller = true;
            return true;
        case SPICE_OPT_TITLE:
            set_title(val);
            break;
#ifdef USE_SMARTCARD
        case SPICE_OPT_SMARTCARD:
            _smartcard_options->enable= true;
            break;
        case SPICE_OPT_NOSMARTCARD:
            _smartcard_options->enable= false; // default
            break;
        case SPICE_OPT_SMARTCARD_CERT:
            do {
                _smartcard_options->certs.insert(
                    _smartcard_options->certs.end(), std::string(val));
            } while ((val=parser.next_argument()));
            break;
        case SPICE_OPT_SMARTCARD_DB:
            _smartcard_options->dbname = val;
            break;
#endif
        case CmdLineParser::OPTION_HELP:
            parser.show_help();
            return false;
        case CmdLineParser::OPTION_ERROR:
            _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
            return false;
        default:
            throw Exception("cmd line error", SPICEC_ERROR_CODE_CMD_LINE_ERROR);
        }
    }

    if (host.empty()) {
        Platform::term_printf("%s: missing --host\n", argv[0]);
        _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
        return false;
    }

    if (parser.is_set(SPICE_OPT_SECURE_CHANNELS) && !parser.is_set(SPICE_OPT_SPORT)) {
        Platform::term_printf("%s: missing --secure-port\n", argv[0]);
        _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
        return false;
    }

    if (!set_channels_security(port, sport)) {
        Platform::term_printf("%s: missing --port or --sport\n", argv[0]);
        return false;
    }
    register_channels();

    _client.set_target(host, port, sport);
    _client.set_password(password);
    _client.set_auto_display_res(auto_display_res);
    _client.set_display_setting(display_setting);

    return true;
}

#ifdef RED_DEBUG
static unsigned int log_level = LOG_DEBUG;
#else
static unsigned int log_level = LOG_INFO;
#endif

static FILE *log_file = NULL;

void spice_log_cleanup(void)
{
    if (log_file) {
        fclose(log_file);
        log_file = NULL;
    }
}

static inline std::string function_to_func_name(const std::string& f_name)
{
#ifdef __GNUC__
    std::string name(f_name);
    std::string::size_type end_pos = f_name.find('(');
    if (end_pos == std::string::npos) {
        return f_name;
    }
    std::string::size_type start = f_name.rfind(' ', end_pos);
    if (start == std::string::npos) {
        return name.substr(0, end_pos);
    }
    end_pos -= ++start;
    return name.substr(start, end_pos);
#else
    return f_name;
#endif
}

void spice_log(unsigned int type, const char *function, const char *format, ...)
{
    std::string formated_message;
    va_list ap;
    const char *type_as_char[] = { "DEBUG", "INFO", "WARN", "ERROR", "FATAL" };
    const char *type_as_nice_char[] = { "Debug", "Info", "Warning", "Error", "Fatal error" };

    if (type < log_level) {
      return;
    }

    ASSERT(type <= LOG_FATAL);

    va_start(ap, format);
    string_vprintf(formated_message, format, ap);
    va_end(ap);

    if (type >= log_level && log_file != NULL) {
        fprintf(log_file, "%ld %s [%llu:%llu] %s: %s\n", (long)time(NULL), type_as_char[type],
                (long long int)Platform::get_process_id(), (long long int)Platform::get_thread_id(),
                function_to_func_name(function).c_str(),
                formated_message.c_str());
		fflush(log_file);
    }

    if (type >= LOG_WARN) {
        fprintf(stderr, "%s: %s\n", type_as_nice_char[type], formated_message.c_str());
    }
}

void Application::init_logger()
{
    std::string log_file_name;

    Platform::get_app_data_dir(log_file_name, app_name);
    Platform::path_append(log_file_name, "spicec.log");

    log_file = ::fopen(log_file_name.c_str(), "a");

    if (log_file == NULL) {
        fprintf(stderr, "Failed to open log file %s\n", log_file_name.c_str());
        return;
    }
}

void Application::init_globals()
{
    init_logger();
    srand((unsigned)time(NULL));

    SSL_library_init();
    SSL_load_error_strings();

    sw_canvas_init();
#ifdef USE_OGL
    gl_canvas_init();
#endif
    quic_init();
#ifdef WIN32
    gdi_canvas_init();
#endif
}

/* seperated from init_globals to allow --help to work
 * faster and not require X on linux. */
void Application::init_platform_globals()
{
    Platform::init();
    RedWindow::init();
}

void Application::init_remainder()
{
    Platform::set_process_loop(*this);
    init_monitors();
    init_menu();
    _main_screen = get_screen(0);

    Platform::set_event_listener(this);
    Platform::set_display_mode_listner(this);

#ifdef USE_GUI
    _gui.reset(new GUI(*this, DISCONNECTED));
    _gui_timer.reset(new GUITimer(*_gui.get()));
    activate_interval_timer(*_gui_timer, 1000 / 30);
#ifdef GUI_DEMO
    _gui_test_timer.reset(new TestTimer(*this));
    activate_interval_timer(*_gui_test_timer, 1000 * 30);
#endif
#endif // USE_GUI
}

void Application::cleanup_platform_globals()
{
    RedWindow::cleanup();
}

void Application::cleanup_globals()
{
}

int Application::main(int argc, char** argv, const char* version_str)
{
    int ret;
    bool full_screen;
    char *log_level_str;

    log_level_str = getenv("SPICEC_LOG_LEVEL");
    if (log_level_str) {
        log_level = atoi(log_level_str);
    }

    init_globals();
    LOG_INFO("starting %s", version_str);
    std::auto_ptr<Application> app(new Application());
    AutoAbort auto_abort(*app.get());
    if (app->process_cmd_line(argc, argv, full_screen)) {
        init_platform_globals();
        app->init_remainder();
        if (full_screen) {
            app->enter_full_screen();
        } else {
            app->_main_screen->show(true, NULL);
        }
        ret = app->run();
        cleanup_platform_globals();
    } else {
        ret = app->_exit_code;
    }
    cleanup_globals();
    return ret;
}

