/*
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 .
*/
#ifndef WIN32
#include "config.h"
#endif
#include "common.h"
#ifdef WIN32
#include
#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 "cairo_canvas.h"
#include "gl_canvas.h"
#include "quic.h"
#include "mutex.h"
#include "cmd_line_parser.h"
#include "tunnel_channel.h"
#include "rect.h"
#include "gui/gui.h"
#include
#include
#include
#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(events_loop.get_owner())->on_connected();
}
void DisconnectedEvent::response(AbstractProcessLoop& events_loop)
{
Application* app = static_cast(events_loop.get_owner());
app->on_disconnected(_error_code);
}
void VisibilityEvent::response(AbstractProcessLoop& events_loop)
{
Application* app = static_cast(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(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(events_loop.get_owner());
app->switch_host(_host, _port, _sport, _cert_subject);
}
//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 cursor(Platform::create_inactive_cursor());
screen()->set_cursor(*cursor);
return;
}
private:
int _id;
AutoRef _cursor;
};
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 _info_pixmap;
AlphaImageFromRes _sticky_pixmap;
SpicePoint _info_pos;
SpicePoint _sticky_pos;
SpiceRect _sticky_rect;
bool _sticky_on;
RecurciveMutex _update_lock;
};
InfoLayer::InfoLayer()
: ScreenLayer(SCREEN_LAYER_INFO, false)
, _info_pixmap (INFO_IMAGE_RES_ID)
, _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 or info 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);
} else {
dest.blend_pixels(_info_pixmap, r.left - _info_pos.x, r.top - _info_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()
{
RecurciveLock lock(_update_lock);
ASSERT(screen());
SpicePoint size = _info_pixmap.get_size();
SpicePoint screen_size = screen()->get_size();
SpiceRect r;
r.left = (screen_size.x - size.x) / 2;
r.right = r.left + size.x;
_info_pos.x = r.right - size.x;
_info_pos.y = r.top = 0;
r.bottom = r.top + size.y;
lock.unlock();
set_rect_area(r);
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);
}
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
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
APP_CMD_SHOW_GUI,
};
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)
, _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 (L"SPICEc:%d")
, _sys_key_intercept_mode (false)
, _gui_mode (GUI_MODE_FULL)
, _during_host_switch(false)
, _state (DISCONNECTED)
{
DBG(0, "");
Platform::set_process_loop(*this);
init_monitors();
memset(_keyboard_state, 0, sizeof(_keyboard_state));
init_menu();
_main_screen = get_screen(0);
Platform::set_event_listener(this);
Platform::set_display_mode_listner(this);
_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
_commands_map["show-gui"] = APP_CMD_SHOW_GUI;
_canvas_types.resize(1);
#ifdef WIN32
_canvas_types[0] = CANVAS_OPTION_GDI;
#else
_canvas_types[0] = CANVAS_OPTION_CAIRO;
#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 parser(new HotKeysParser("toggle-fullscreen=shift+f11"
",release-cursor=shift+f12"
#ifdef RED_DEBUG
",connect=shift+f5"
",disconnect=shift+f6"
#endif
",show-gui=shift+f7"
, _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());
_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
for (int i = SPICE_CHANNEL_MAIN; i < SPICE_END_CHANNEL; i++) {
_peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_BOTH;
}
}
Application::~Application()
{
deactivate_interval_timer(*_gui_timer);
#ifdef GUI_DEMO
deactivate_interval_timer(*_gui_test_timer);
#endif
destroyed_gui_barriers();
_gui->set_screen(NULL);
if (_info_layer->screen()) {
_main_screen->detach_layer(*_info_layer);
}
_main_screen->unref();
destroy_monitors();
}
void Application::init_menu()
{
//fixme: menu items name need to be dynamically updated by hot keys configuration.
AutoRef