spice/client/application.cpp
Yonit Halperin 8d5b738ba1 spice client: creating a general process loop.
The process loop is responsible for: 1) waiting for events 2) timers 3) events queue for
actions that should be performed in the context of the thread and are pushed from other threads.
The benefits:
1) remove duplicity: till now, there was one implementaion of events loop for the channels and
another one for the main thread.
2) timers can be executed on each thread and not only on the main thread.
3) events can be pushed to each thread and not only to the main thread.
In this commit, only the main thread was modified to use the new process loop.
2009-11-09 14:39:33 +02:00

1588 lines
42 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#ifndef WIN32
#include "config.h"
#endif
#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 "cairo_canvas.h"
#include "gl_canvas.h"
#include "quic.h"
#include "mutex.h"
#include "cmd_line_parser.h"
#include "tunnel_channel.h"
#include <log4cpp/BasicConfigurator.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/RollingFileAppender.hh>
#ifdef CAIRO_CANVAS_CACH_IS_SHARED
mutex_t cairo_surface_user_data_mutex;
#endif
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->show_splash(0);
#ifndef RED_DEBUG
app->do_quit(SPICEC_ERROR_CODE_SUCCESS);
#endif
}
void ConnectionErrorEvent::response(AbstractProcessLoop& events_loop)
{
Application* app = static_cast<Application*>(events_loop.get_owner());
app->show_splash(0);
#ifndef RED_DEBUG
app->do_quit(_error_code);
#endif
}
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);
}
}
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();
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;
Point _splash_pos;
Point _info_pos;
bool _splash_mode;
Mutex _update_lock;
};
GUILayer::GUILayer()
: ScreenLayer(SCREEN_LAYER_GUI, false)
, _splash_pixmap (SPLASH_IMAGE_RES_ID)
, _info_pixmap (INFO_IMAGE_RES_ID)
, _splash_mode (false)
{
}
void GUILayer::draw_splash(const QRegion& dest_region, RedDrawable& dest)
{
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];
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)
{
Lock lock(_update_lock);
if (_splash_mode) {
draw_splash(dest_region, dest_dc);
} else {
draw_info(dest_region, dest_dc);
}
}
void GUILayer::set_splash_mode()
{
Lock 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);
}
void GUILayer::set_info_mode()
{
Lock 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);
}
void GUILayer::on_size_changed()
{
set_info_mode();
}
static InputsHandler default_inputs_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
};
Application::Application()
: ProcessLoop (this)
, _client (*this)
, _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")
{
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
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
, _commands_map));
_hot_keys = parser->get();
}
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<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::set_inputs_handler(InputsHandler& handler)
{
unpress_all();
_inputs_handler = &handler;
if (_active) {
handler.on_focus_in();
}
}
void Application::remove_inputs_handler(InputsHandler& handler)
{
if (_inputs_handler != &handler) {
return;
}
_inputs_handler = &default_inputs_handler;
}
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()
{
_client.connect();
}
int Application::run()
{
_client.connect();
_exit_code = ProcessLoop::run();
return _exit_code;
}
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_captured()) {
return false;
}
_active_screen->relase_inputs();
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;
if (_client.is_auto_display_res() && (mon = find_monitor(id))) {
Point size = mon->get_size();
screen = _screens[id] = new RedScreen(*this, id, _title, size.x, size.y);
} else {
screen = _screens[id] = new RedScreen(*this, id, _title, SCREEN_INIT_WIDTH,
SCREEN_INIT_HEIGHT);
}
if (_full_screen) {
bool capture;
mon = get_monitor(id);
capture = release_capture();
screen->set_monitor(mon);
position_screens();
screen->show_full_screen();
prepare_monitors();
if (capture) {
_main_screen->activate();
_main_screen->capture_inputs();
}
} else if (id != 0) {
screen->show(false, _main_screen);
}
} else {
screen = screen->ref();
}
return screen;
}
void Application::on_screen_destroyed(int id, bool was_captured)
{
bool reposition = false;
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) {
bool capture = was_captured || release_capture();
prepare_monitors();
position_screens();
if (capture) {
_main_screen->activate();
_main_screen->capture_inputs();
}
}
}
void Application::on_mouse_motion(int dx, int dy, int buttons_state)
{
_inputs_handler->on_mouse_motion(dx, dy, buttons_state);
}
void Application::on_mouse_position(int x, int y, int buttons_state, int display_id)
{
_inputs_handler->on_mouse_position(x, y, buttons_state, display_id);
}
void Application::on_mouse_down(int button, int buttons_state)
{
_inputs_handler->on_mouse_down(button, buttons_state);
}
void Application::on_mouse_up(int button, int buttons_state)
{
_inputs_handler->on_mouse_up(button, buttons_state);
}
void Application::init_scan_code(int index)
{
ASSERT((index & 0x80) == 0);
_key_table[index]._make = index;
_key_table[index]._break = index | 0x80;
}
void Application::init_korean_scan_code(int index)
{
_key_table[index]._make = index;
_key_table[index]._break = index;
}
void Application::init_escape_scan_code(int index)
{
ASSERT(((index - REDKEY_ESCAPE_BASE) & 0x80) == 0);
_key_table[index]._make = 0xe0 | ((index - REDKEY_ESCAPE_BASE) << 8);
_key_table[index]._break = _key_table[index]._make | 0x8000;
}
void Application::init_pause_scan_code()
{
_key_table[REDKEY_PAUSE]._make = 0x451de1;
_key_table[REDKEY_PAUSE]._break = 0xc59de1;
}
void Application::init_key_table()
{
memset(_key_table, 0, sizeof(_key_table));
init_scan_code(REDKEY_ESCAPE);
init_scan_code(REDKEY_1);
init_scan_code(REDKEY_2);
init_scan_code(REDKEY_3);
init_scan_code(REDKEY_4);
init_scan_code(REDKEY_5);
init_scan_code(REDKEY_6);
init_scan_code(REDKEY_7);
init_scan_code(REDKEY_8);
init_scan_code(REDKEY_9);
init_scan_code(REDKEY_0);
init_scan_code(REDKEY_MINUS);
init_scan_code(REDKEY_EQUALS);
init_scan_code(REDKEY_BACKSPACE);
init_scan_code(REDKEY_TAB);
init_scan_code(REDKEY_Q);
init_scan_code(REDKEY_W);
init_scan_code(REDKEY_E);
init_scan_code(REDKEY_R);
init_scan_code(REDKEY_T);
init_scan_code(REDKEY_Y);
init_scan_code(REDKEY_U);
init_scan_code(REDKEY_I);
init_scan_code(REDKEY_O);
init_scan_code(REDKEY_P);
init_scan_code(REDKEY_L_BRACKET);
init_scan_code(REDKEY_R_BRACKET);
init_scan_code(REDKEY_ENTER);
init_scan_code(REDKEY_L_CTRL);
init_scan_code(REDKEY_A);
init_scan_code(REDKEY_S);
init_scan_code(REDKEY_D);
init_scan_code(REDKEY_F);
init_scan_code(REDKEY_G);
init_scan_code(REDKEY_H);
init_scan_code(REDKEY_J);
init_scan_code(REDKEY_K);
init_scan_code(REDKEY_L);
init_scan_code(REDKEY_SEMICOLON);
init_scan_code(REDKEY_QUOTE);
init_scan_code(REDKEY_BACK_QUOTE);
init_scan_code(REDKEY_L_SHIFT);
init_scan_code(REDKEY_BACK_SLASH);
init_scan_code(REDKEY_Z);
init_scan_code(REDKEY_X);
init_scan_code(REDKEY_C);
init_scan_code(REDKEY_V);
init_scan_code(REDKEY_B);
init_scan_code(REDKEY_N);
init_scan_code(REDKEY_M);
init_scan_code(REDKEY_COMMA);
init_scan_code(REDKEY_PERIOD);
init_scan_code(REDKEY_SLASH);
init_scan_code(REDKEY_R_SHIFT);
init_scan_code(REDKEY_PAD_MULTIPLY);
init_scan_code(REDKEY_L_ALT);
init_scan_code(REDKEY_SPACE);
init_scan_code(REDKEY_CAPS_LOCK);
init_scan_code(REDKEY_F1);
init_scan_code(REDKEY_F2);
init_scan_code(REDKEY_F3);
init_scan_code(REDKEY_F4);
init_scan_code(REDKEY_F5);
init_scan_code(REDKEY_F6);
init_scan_code(REDKEY_F7);
init_scan_code(REDKEY_F8);
init_scan_code(REDKEY_F9);
init_scan_code(REDKEY_F10);
init_scan_code(REDKEY_NUM_LOCK);
init_scan_code(REDKEY_SCROLL_LOCK);
init_scan_code(REDKEY_PAD_7);
init_scan_code(REDKEY_PAD_8);
init_scan_code(REDKEY_PAD_9);
init_scan_code(REDKEY_PAD_MINUS);
init_scan_code(REDKEY_PAD_4);
init_scan_code(REDKEY_PAD_5);
init_scan_code(REDKEY_PAD_6);
init_scan_code(REDKEY_PAD_PLUS);
init_scan_code(REDKEY_PAD_1);
init_scan_code(REDKEY_PAD_2);
init_scan_code(REDKEY_PAD_3);
init_scan_code(REDKEY_PAD_0);
init_scan_code(REDKEY_PAD_POINT);
init_scan_code(REDKEY_EUROPEAN);
init_scan_code(REDKEY_F11);
init_scan_code(REDKEY_F12);
init_scan_code(REDKEY_JAPANESE_HIRAGANA_KATAKANA);
init_scan_code(REDKEY_JAPANESE_BACKSLASH);
init_scan_code(REDKEY_JAPANESE_HENKAN);
init_scan_code(REDKEY_JAPANESE_MUHENKAN);
init_scan_code(REDKEY_JAPANESE_YEN);
init_korean_scan_code(REDKEY_KOREAN_HANGUL);
init_korean_scan_code(REDKEY_KOREAN_HANGUL_HANJA);
init_escape_scan_code(REDKEY_ESCAPE_BASE);
init_escape_scan_code(REDKEY_PAD_ENTER);
init_escape_scan_code(REDKEY_R_CTRL);
init_escape_scan_code(REDKEY_FAKE_L_SHIFT);
init_escape_scan_code(REDKEY_PAD_DIVIDE);
init_escape_scan_code(REDKEY_FAKE_R_SHIFT);
init_escape_scan_code(REDKEY_CTRL_PRINT_SCREEN);
init_escape_scan_code(REDKEY_R_ALT);
init_escape_scan_code(REDKEY_CTRL_BREAK);
init_escape_scan_code(REDKEY_HOME);
init_escape_scan_code(REDKEY_UP);
init_escape_scan_code(REDKEY_PAGEUP);
init_escape_scan_code(REDKEY_LEFT);
init_escape_scan_code(REDKEY_RIGHT);
init_escape_scan_code(REDKEY_END);
init_escape_scan_code(REDKEY_DOWN);
init_escape_scan_code(REDKEY_PAGEDOWN);
init_escape_scan_code(REDKEY_INSERT);
init_escape_scan_code(REDKEY_DELETE);
init_escape_scan_code(REDKEY_LEFT_CMD);
init_escape_scan_code(REDKEY_RIGHT_CMD);
init_escape_scan_code(REDKEY_MENU);
init_pause_scan_code();
}
inline uint32_t Application::get_make_scan_code(RedKey key)
{
return _key_table[key]._make;
}
inline uint32_t Application::get_break_scan_code(RedKey key)
{
return _key_table[key]._break;
}
void Application::unpress_all()
{
for (int i = 0; i < REDKEY_NUM_KEYS; i++) {
if (_key_table[i].press) {
uint32_t scan_code = get_break_scan_code((RedKey)i);
ASSERT(scan_code);
_inputs_handler->on_key_up(scan_code);
_key_table[i].press = false;
}
}
}
void Application::on_connected()
{
}
void Application::on_disconnecting()
{
release_capture();
}
Menu* Application::get_app_menu()
{
if (!*_app_menu) {
return NULL;
}
return (*_app_menu)->ref();
}
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 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
void Application::on_key_down(RedKey key)
{
if (key <= 0 || key >= REDKEY_NUM_KEYS) {
return;
}
uint32_t scan_code = get_make_scan_code(key);
if (!scan_code) {
LOG_WARN("no make code for %d", key);
return;
}
_key_table[key].press = true;
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 || _key_table[REDKEY_L_ALT].press)) {
_key_table[key].press = false;
return;
}
if ((_key_table[REDKEY_L_CTRL].press || _key_table[REDKEY_R_CTRL].press) &&
(_key_table[REDKEY_L_ALT].press || _key_table[REDKEY_R_ALT].press)) {
if (key == REDKEY_END || key == REDKEY_PAD_1) {
_key_table[key].press = false;
_inputs_handler->on_key_down(get_make_scan_code(REDKEY_DELETE));
_inputs_handler->on_key_up(get_break_scan_code(REDKEY_DELETE));
} else if (key == REDKEY_DELETE || key == REDKEY_PAD_POINT) {
_key_table[key].press = false;
return;
}
}
#endif
_inputs_handler->on_key_down(scan_code);
}
void Application::on_key_up(RedKey key)
{
if (key < 0 || key >= REDKEY_NUM_KEYS || !_key_table[key].press) {
return;
}
_key_table[key].press = false;
uint32_t scan_code = get_break_scan_code(key);
if (!scan_code) {
LOG_WARN("no break code for %d", key);
return;
}
_inputs_handler->on_key_up(scan_code);
}
void Application::on_deactivate_screen(RedScreen* screen)
{
if (_active_screen == screen) {
release_capture();
_active_screen = NULL;
}
}
void Application::on_activate_screen(RedScreen* screen)
{
_active_screen = screen;
}
void Application::on_app_activated()
{
_active = true;
_inputs_handler->on_focus_in();
}
void Application::on_app_deactivated()
{
_active = false;
_inputs_handler->on_focus_out();
#ifdef WIN32
if (!_changing_screens) {
exit_full_screen();
}
#endif
}
bool Application::rearrange_monitors(RedScreen& screen)
{
if (!_full_screen) {
return false;
}
bool capture = release_capture();
prepare_monitors();
position_screens();
if (capture && _main_screen != &screen) {
capture = false;
_main_screen->activate();
_main_screen->capture_inputs();
}
return capture;
}
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()
{
for (int i = 0; i < (int)_screens.size(); i++) {
Monitor* mon;
if (_screens[i] && (mon = _screens[i]->get_monitor())) {
Point size = _screens[i]->get_size();
mon->set_mode(size.x, size.y);
}
}
//todo: test match of monitors size/position against real world size/position
}
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();
}
}
}
void Application::enter_full_screen()
{
_changing_screens = true;
release_capture();
assign_monitors();
hide();
prepare_monitors();
position_screens();
show_full_screen();
_main_screen->activate();
_main_screen->capture_inputs();
_changing_screens = false;
_full_screen = true;
}
void Application::exit_full_screen()
{
if (!_full_screen) {
return;
}
release_capture();
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();
}
}
}
restore_monitors();
_full_screen = false;
show();
_main_screen->activate();
}
bool Application::toggle_full_screen()
{
if (_full_screen) {
exit_full_screen();
} else {
enter_full_screen();
}
return _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();
}
void Application::show_splash(int screen_id)
{
if (screen_id != 0) {
return;
}
release_capture();
(*_gui_layer).set_splash_mode();
}
void Application::hide_splash(int screen_id)
{
if (screen_id != 0) {
return;
}
(*_gui_layer).set_info_mode();
}
uint32_t Application::get_mouse_mode()
{
return _client.get_mouse_mode();
}
void Application::set_title(std::wstring& 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 (!(_key_table[iter->main].press || _key_table[iter->alter].press)) {
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;
}
bool Application::is_cad_pressed()
{
return ((_key_table[REDKEY_L_CTRL].press || _key_table[REDKEY_R_CTRL].press) &&
(_key_table[REDKEY_L_ALT].press || _key_table[REDKEY_R_ALT].press) &&
(_key_table[REDKEY_DELETE].press || _key_table[REDKEY_PAD_POINT].press));
}
void Application::send_key_down(RedKey key)
{
_inputs_handler->on_key_down(get_make_scan_code(key));
}
void Application::send_key_up(RedKey key)
{
_inputs_handler->on_key_up(get_break_scan_code(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);
}
}
static inline int str_to_port(const char *str)
{
long port;
char *endptr;
port = strtol(str, &endptr, 0);
if (endptr != str + strlen(str) || port < 0 || port > 0xffff) {
return -1;
}
return port;
}
bool Application::set_channels_security(CmdLineParser& parser, bool on, char *val)
{
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"] = RED_CHANNEL_MAIN;
channels_names["display"] = RED_CHANNEL_DISPLAY;
channels_names["inputs"] = RED_CHANNEL_INPUTS;
channels_names["cursor"] = RED_CHANNEL_CURSOR;
channels_names["playback"] = RED_CHANNEL_PLAYBACK;
channels_names["record"] = RED_CHANNEL_RECORD;
channels_names["tunnel"] = RED_CHANNEL_TUNNEL;
if (!strcmp(val, "all")) {
if ((val = parser.next_argument())) {
std::cout << "\"all\" is exclusive in secure-channels\n";
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()) {
std::cout << "bad channel name \"" << val << "\" in secure-channels\n";
return false;
}
_peer_con_opt[(*iter).second] = option;
} while ((val = parser.next_argument()));
return true;
}
bool Application::set_canvas_option(CmdLineParser& parser, char *val)
{
typedef std::map< std::string, CanvasOption> CanvasNamesMap;
CanvasNamesMap canvas_types;
canvas_types["cairo"] = CANVAS_OPTION_CAIRO;
#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()) {
std::cout << "bad canvas type \"" << val << "\"\n";
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)
{
typedef std::map< std::string, int> ChannelsNamesMap;
ChannelsNamesMap channels_names;
channels_names["display"] = RED_CHANNEL_DISPLAY;
channels_names["inputs"] = RED_CHANNEL_INPUTS;
channels_names["cursor"] = RED_CHANNEL_CURSOR;
channels_names["playback"] = RED_CHANNEL_PLAYBACK;
channels_names["record"] = RED_CHANNEL_RECORD;
channels_names["tunnel"] = RED_CHANNEL_TUNNEL;
if (!strcmp(val, "all")) {
if ((val = parser.next_argument())) {
std::cout << "\"all\" is exclusive\n";
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()) {
std::cout << "bad channel name \"" << val << "\"\n";
return false;
}
_enabled_channels[(*iter).second] = enable;
} while ((val = parser.next_argument()));
return true;
}
bool Application::process_cmd_line(int argc, char** argv)
{
std::string host;
int sport = -1;
int port = -1;
bool auto_display_res = false;
bool full_screen = false;
std::string password;
enum {
SPICE_OPT_HOST = CmdLineParser::OPTION_FIRST_AVILABLE,
SPICE_OPT_PORT,
SPICE_OPT_SPORT,
SPICE_OPT_PASSWORD,
SPICE_OPT_FULL_SCREEN,
SPICE_OPT_SECURE_CHANNELS,
SPICE_OPT_UNSECURE_CHANNELS,
SPICE_OPT_ENABLE_CHANNELS,
SPICE_OPT_DISABLE_CHANNELS,
SPICE_OPT_CANVAS_TYPE,
};
CmdLineParser parser("Spice client", false);
parser.add(SPICE_OPT_HOST, "host", "spice server address", "host", true, 'h');
parser.set_reqired(SPICE_OPT_HOST);
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_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, ',');
_peer_con_opt[RED_CHANNEL_MAIN] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_DISPLAY] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_INPUTS] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_CURSOR] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_PLAYBACK] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_RECORD] = RedPeer::ConnectionOptions::CON_OP_INVALID;
_peer_con_opt[RED_CHANNEL_TUNNEL] = 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_HOST:
host = val;
break;
case SPICE_OPT_PORT: {
if ((port = str_to_port(val)) == -1) {
std::cout << "invalid port " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
break;
}
case SPICE_OPT_SPORT: {
if ((port = str_to_port(val)) == -1) {
std::cout << "invalid secure port " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
sport = port;
break;
}
case SPICE_OPT_FULL_SCREEN:
if (val) {
if (strcmp(val, "auto-conf")) {
std::cout << "invalid full screen mode " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
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)) {
return false;
}
break;
case SPICE_OPT_UNSECURE_CHANNELS:
if (!set_channels_security(parser, false, val)) {
return false;
}
break;
case SPICE_OPT_ENABLE_CHANNELS:
if (!set_enable_channels(parser, true, val)) {
std::cout << "invalid channels " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
break;
case SPICE_OPT_DISABLE_CHANNELS:
if (!set_enable_channels(parser, false, val)) {
std::cout << "invalid channels " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
break;
case SPICE_OPT_CANVAS_TYPE:
if (!set_canvas_option(parser, val)) {
std::cout << "invalid canvas option " << val << "\n";
_exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
return false;
}
break;
case CmdLineParser::OPTION_HELP:
parser.show_help();
return false;
case CmdLineParser::OPTION_ERROR:
return false;
default:
throw Exception("cmd line error");
}
}
if (parser.is_set(SPICE_OPT_SECURE_CHANNELS) && !parser.is_set(SPICE_OPT_SPORT)) {
std::cout << "missing --secure-port\n";
return false;
}
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 (parser.is_set(SPICE_OPT_PORT) && parser.is_set(SPICE_OPT_SPORT)) {
(*iter).second = RedPeer::ConnectionOptions::CON_OP_BOTH;
continue;
}
if (parser.is_set(SPICE_OPT_PORT)) {
(*iter).second = RedPeer::ConnectionOptions::CON_OP_UNSECURE;
continue;
}
if (parser.is_set(SPICE_OPT_SPORT)) {
(*iter).second = RedPeer::ConnectionOptions::CON_OP_SECURE;
continue;
}
std::cout << "missing --port or --sport\n";
return false;
}
if (_enabled_channels[RED_CHANNEL_DISPLAY]) {
_client.register_channel_factory(DisplayChannel::Factory());
}
if (_enabled_channels[RED_CHANNEL_CURSOR]) {
_client.register_channel_factory(CursorChannel::Factory());
}
if (_enabled_channels[RED_CHANNEL_INPUTS]) {
_client.register_channel_factory(InputsChannel::Factory());
}
if (_enabled_channels[RED_CHANNEL_PLAYBACK]) {
_client.register_channel_factory(PlaybackChannel::Factory());
}
if (_enabled_channels[RED_CHANNEL_RECORD]) {
_client.register_channel_factory(RecordChannel::Factory());
}
if (_enabled_channels[RED_CHANNEL_TUNNEL]) {
_client.register_channel_factory(TunnelChannel::Factory());
}
_client.init(host.c_str(), port, sport, password.c_str(), auto_display_res);
if (auto_display_res) {
Monitor* mon = find_monitor(0);
ASSERT(mon);
Point size = mon->get_size();
_main_screen->set_mode(size.x, size.y, 32);
}
if (full_screen) {
enter_full_screen();
} else {
_main_screen->show(true, NULL);
}
return true;
}
void Application::init_logger()
{
std::string temp_dir_name;
Platform::get_temp_dir(temp_dir_name);
std::string log_file_name = temp_dir_name + "spicec.log";
int fd = ::open(log_file_name.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0644);
if (fd == -1) {
log4cpp::BasicConfigurator::configure();
return;
}
log4cpp::Category& root = log4cpp::Category::getRoot();
#ifdef RED_DEBUG
root.setPriority(log4cpp::Priority::DEBUG);
root.removeAllAppenders();
::close(fd);
root.addAppender(new log4cpp::RollingFileAppender("_", log_file_name));
#else
root.setPriority(log4cpp::Priority::INFO);
root.removeAllAppenders();
root.addAppender(new log4cpp::FileAppender("_", fd));
#endif
}
void Application::init_globals()
{
init_logger();
srand((unsigned)time(NULL));
SSL_library_init();
SSL_load_error_strings();
cairo_canvas_init();
#ifdef USE_OGL
gl_canvas_init();
#endif
quic_init();
#ifdef WIN32
gdi_canvas_init();
#endif
#ifdef CAIRO_CANVAS_CACH_IS_SHARED
MUTEX_INIT(cairo_surface_user_data_mutex);
#endif
Platform::init();
RedWindow::init();
}
int Application::main(int argc, char** argv, const char* version_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)) {
return app->_exit_code;
}
return app->run();
}