/*
Copyright (C) 2009 Red Hat, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. 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 "rect.h"
#include
#include
#include
#define STICKY_KEY_PIXMAP ALT_IMAGE_RES_ID
#define STICKY_KEY_TIMEOUT 750
#define CA_FILE_NAME "spice_truststore.pem"
#define SWITCH_HOST_TIMEOUT 150
#ifdef CAIRO_CANVAS_CACH_IS_SHARED
mutex_t cairo_surface_user_data_mutex;
#endif
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());
if (app->_during_host_switch) {
app->_client.connect();
app->activate_interval_timer(*app->_switch_host_timer, SWITCH_HOST_TIMEOUT);
// todo: add indication for migration
app->show_splash(0);
} else {
#ifdef RED_DEBUG
app->show_splash(0);
#else
app->do_quit(SPICEC_ERROR_CODE_SUCCESS);
#endif
}
}
void ConnectionErrorEvent::response(AbstractProcessLoop& events_loop)
{
Application* app = static_cast(events_loop.get_owner());
app->_during_host_switch = false;
#ifdef RED_DEBUG
app->show_splash(0);
#else
app->do_quit(_error_code);
#endif
}
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 SwitchHostTimer::response(AbstractProcessLoop& events_loop) {
Application* app = (Application*)events_loop.get_owner();
if (app->_during_host_switch) {
app->do_connect();
} else {
app->deactivate_interval_timer(this);
}
}
void SwitchHostEvent::response(AbstractProcessLoop& events_loop)
{
Application* app = static_cast(events_loop.get_owner());
app->switch_host(_host, _port, _sport, _cert_subject);
}
class GUILayer: public ScreenLayer {
public:
GUILayer();
virtual void copy_pixels(const QRegion& dest_region, RedDrawable& dest_dc);
void set_splash_mode();
void set_info_mode();
void set_sticky(bool is_on);
virtual void on_size_changed();
private:
void draw_splash(const QRegion& dest_region, RedDrawable& dest);
void draw_info(const QRegion& dest_region, RedDrawable& dest);
private:
ImageFromRes _splash_pixmap;
AlphaImageFromRes _info_pixmap;
AlphaImageFromRes _sticky_pixmap;
Point _splash_pos;
Point _info_pos;
Point _sticky_pos;
Rect _sticky_rect;
bool _splash_mode;
bool _sticky_on;
RecurciveMutex _update_lock;
};
GUILayer::GUILayer()
: ScreenLayer(SCREEN_LAYER_GUI, false)
, _splash_pixmap (SPLASH_IMAGE_RES_ID)
, _info_pixmap (INFO_IMAGE_RES_ID)
, _sticky_pixmap (STICKY_KEY_PIXMAP)
, _splash_mode (false)
, _sticky_on (false)
{
}
void GUILayer::draw_splash(const QRegion& dest_region, RedDrawable& dest)
{
ASSERT(!_sticky_on);
for (int i = 0; i < (int)dest_region.num_rects; i++) {
Rect* r = &dest_region.rects[i];
dest.copy_pixels(_splash_pixmap, r->left - _splash_pos.x, r->top - _splash_pos.y, *r);
}
}
void GUILayer::draw_info(const QRegion& dest_region, RedDrawable& dest)
{
for (int i = 0; i < (int)dest_region.num_rects; i++) {
Rect* r = &dest_region.rects[i];
/* 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 GUILayer::copy_pixels(const QRegion& dest_region, RedDrawable& dest_dc)
{
RecurciveLock lock(_update_lock);
if (_splash_mode) {
draw_splash(dest_region, dest_dc);
} else {
draw_info(dest_region, dest_dc);
}
}
void GUILayer::set_splash_mode()
{
RecurciveLock lock(_update_lock);
Point size = _splash_pixmap.get_size();
Point screen_size = screen()->get_size();
Rect r;
_splash_pos.y = r.top = (screen_size.y - size.y) / 2;
_splash_pos.x = r.left = (screen_size.x - size.x) / 2;
r.bottom = r.top + size.y;
r.right = r.left + size.x;
_splash_mode = true;
lock.unlock();
set_rect_area(r);
ASSERT(!_sticky_on);
}
void GUILayer::set_info_mode()
{
RecurciveLock lock(_update_lock);
Point size = _info_pixmap.get_size();
Point screen_size = screen()->get_size();
Rect 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;
_splash_mode = false;
lock.unlock();
set_rect_area(r);
set_sticky(_sticky_on);
}
void GUILayer::set_sticky(bool is_on)
{
RecurciveLock lock(_update_lock);
if (!_sticky_on && !is_on) {
return;
}
Point size = _sticky_pixmap.get_size();
Point screen_size = screen()->get_size();
_sticky_on = is_on;
if (_sticky_on) {
_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;
add_rect_area(_sticky_rect);
invalidate();
} else {
remove_rect_area(_sticky_rect);
}
}
void GUILayer::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->_key_table[sticky_info->key ].press);
ASSERT(sticky_info->key_first_down);
ASSERT(sticky_info->key_down);
sticky_info->sticky_mode = true;
DBG(0, "ON sticky");
app->_gui_layer->set_sticky(true);
app->deactivate_interval_timer(this);
}
static InputsHandler default_inputs_handler;
enum AppCommands {
APP_CMD_INVALID,
APP_CMD_SEND_CTL_ALT_DEL,
APP_CMD_TOGGLE_FULL_SCREEN,
APP_CMD_SEND_TOGGLE_KEYS,
APP_CMD_SEND_RELEASE_KEYS,
APP_CMD_SEND_CTL_ALT_END,
APP_CMD_RELEASE_CAPTURE,
#ifdef RED_DEBUG
APP_CMD_CONNECT,
APP_CMD_DISCONNECT,
#endif
APP_CMD_EXTERNAL_BEGIN = 0x400,
APP_CMD_EXTERNAL_END = 0x800,
};
Application::Application()
: ProcessLoop (this)
, _client (*this)
, _con_ciphers ("DEFAULT")
, _enabled_channels (RED_CHANNEL_END, true)
, _main_screen (NULL)
, _active (false)
, _full_screen (false)
, _changing_screens (false)
, _exit_code (0)
, _active_screen (NULL)
, _gui_layer (new GUILayer())
, _inputs_handler (&default_inputs_handler)
, _monitors (NULL)
, _title (L"SPICEc:%d")
, _splash_mode (true)
, _sys_key_intercept_mode (false)
, _during_host_switch(false)
, _enable_controller (false)
{
DBG(0, "");
Platform::set_process_loop(*this);
init_monitors();
init_key_table();
init_menu();
_main_screen = get_screen(0);
_main_screen->attach_layer(*_gui_layer);
_gui_layer->set_splash_mode();
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
_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
, _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());
_switch_host_timer.reset(new SwitchHostTimer());
}
Application::~Application()
{
_main_screen->detach_layer(*_gui_layer);
_main_screen->unref();
destroy_monitors();
}
void Application::init_menu()
{
//fixme: menu items name need to be dynamically updated by hot keys configuration.
AutoRef