mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
1184 lines
33 KiB
C++
1184 lines
33 KiB
C++
/*
|
|
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"
|
|
|
|
#include <shlobj.h>
|
|
#include <io.h>
|
|
#include <conio.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "platform.h"
|
|
#include "win_platform.h"
|
|
#include "utils.h"
|
|
#include "threads.h"
|
|
#include "debug.h"
|
|
#include "monitor.h"
|
|
#include "record.h"
|
|
#include "playback.h"
|
|
#include "cursor.h"
|
|
#include "named_pipe.h"
|
|
|
|
#ifndef DISABLE_CXIMAGE
|
|
#define USE_CXIMAGE
|
|
#endif
|
|
|
|
#ifdef USE_CXIMAGE
|
|
#include "ximage.h"
|
|
#endif
|
|
#include <spice/vd_agent.h>
|
|
|
|
int gdi_handlers = 0;
|
|
extern HINSTANCE instance;
|
|
|
|
class DefaultEventListener: public Platform::EventListener {
|
|
public:
|
|
virtual void on_app_activated() {}
|
|
virtual void on_app_deactivated() {}
|
|
virtual void on_monitors_change() {}
|
|
};
|
|
|
|
static DefaultEventListener default_event_listener;
|
|
static Platform::EventListener* event_listener = &default_event_listener;
|
|
static HWND platform_win = NULL;
|
|
static ProcessLoop* main_loop = NULL;
|
|
|
|
class DefaultClipboardListener: public Platform::ClipboardListener {
|
|
public:
|
|
virtual void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
|
|
virtual void on_clipboard_request(uint32_t type) {}
|
|
virtual void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
|
|
virtual void on_clipboard_release() {}
|
|
};
|
|
|
|
static DefaultClipboardListener default_clipboard_listener;
|
|
static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener;
|
|
|
|
// The next window in the clipboard viewer chain, which is refered in all clipboard events.
|
|
static HWND next_clipboard_viewer_win = NULL;
|
|
static HANDLE clipboard_event = NULL;
|
|
|
|
static const int CLIPBOARD_TIMEOUT_MS = 10000;
|
|
|
|
static const int CLIPBOARD_FORMAT_MAX_TYPES = 16;
|
|
|
|
typedef struct ClipboardFormat {
|
|
uint32_t format;
|
|
uint32_t types[CLIPBOARD_FORMAT_MAX_TYPES];
|
|
} ClipboardFormat;
|
|
|
|
static ClipboardFormat clipboard_formats[] = {
|
|
{CF_UNICODETEXT, {VD_AGENT_CLIPBOARD_UTF8_TEXT, 0}},
|
|
//FIXME: support more image types
|
|
{CF_DIB, {VD_AGENT_CLIPBOARD_IMAGE_PNG, VD_AGENT_CLIPBOARD_IMAGE_BMP, 0}},
|
|
};
|
|
|
|
#define clipboard_formats_count (sizeof(clipboard_formats) / sizeof(clipboard_formats[0]))
|
|
|
|
#ifdef USE_CXIMAGE
|
|
typedef struct ImageType {
|
|
uint32_t type;
|
|
DWORD cximage_format;
|
|
} ImageType;
|
|
|
|
static ImageType image_types[] = {
|
|
{VD_AGENT_CLIPBOARD_IMAGE_PNG, CXIMAGE_FORMAT_PNG},
|
|
{VD_AGENT_CLIPBOARD_IMAGE_BMP, CXIMAGE_FORMAT_BMP},
|
|
};
|
|
#endif
|
|
|
|
static std::set<uint32_t> grab_types;
|
|
|
|
static const unsigned long MODAL_LOOP_TIMER_ID = 1;
|
|
static const int MODAL_LOOP_DEFAULT_TIMEOUT = 100;
|
|
static bool modal_loop_active = false;
|
|
static bool set_modal_loop_timer();
|
|
|
|
void Platform::send_quit_request()
|
|
{
|
|
ASSERT(main_loop);
|
|
main_loop->quit(0);
|
|
}
|
|
|
|
static uint32_t get_clipboard_type(uint32_t format) {
|
|
uint32_t* types = NULL;
|
|
|
|
for (size_t i = 0; i < clipboard_formats_count && !types; i++) {
|
|
if (clipboard_formats[i].format == format) {
|
|
types = clipboard_formats[i].types;
|
|
}
|
|
}
|
|
if (!types) {
|
|
return VD_AGENT_CLIPBOARD_NONE;
|
|
}
|
|
for (uint32_t* ptype = types; *ptype; ptype++) {
|
|
if (grab_types.find(*ptype) != grab_types.end()) {
|
|
return *ptype;
|
|
}
|
|
}
|
|
return VD_AGENT_CLIPBOARD_NONE;
|
|
}
|
|
|
|
static uint32_t get_clipboard_format(uint32_t type) {
|
|
for (size_t i = 0; i < clipboard_formats_count; i++) {
|
|
for (uint32_t* ptype = clipboard_formats[i].types; *ptype; ptype++) {
|
|
if (*ptype == type) {
|
|
return clipboard_formats[i].format;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_available_clipboard_types(uint32_t** types)
|
|
{
|
|
int count = 0;
|
|
|
|
*types = new uint32_t[clipboard_formats_count * CLIPBOARD_FORMAT_MAX_TYPES];
|
|
for (size_t i = 0; i < clipboard_formats_count; i++) {
|
|
if (IsClipboardFormatAvailable(clipboard_formats[i].format)) {
|
|
for (uint32_t* ptype = clipboard_formats[i].types; *ptype; ptype++) {
|
|
(*types)[count++] = *ptype;
|
|
}
|
|
}
|
|
}
|
|
if (!count) {
|
|
delete[] *types;
|
|
*types = NULL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
#ifdef USE_CXIMAGE
|
|
static DWORD get_cximage_format(uint32_t type)
|
|
{
|
|
for (size_t i = 0; i < sizeof(image_types) / sizeof(image_types[0]); i++) {
|
|
if (image_types[i].type == type) {
|
|
return image_types[i].cximage_format;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static LRESULT CALLBACK PlatformWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message) {
|
|
case WM_TIMER:
|
|
if (modal_loop_active) {
|
|
main_loop->timers_action();
|
|
if (!set_modal_loop_timer()) {
|
|
LOG_WARN("failed to set modal loop timer");
|
|
}
|
|
} else {
|
|
LOG_WARN("received WM_TIMER not inside a modal loop");
|
|
}
|
|
break;
|
|
case WM_ACTIVATEAPP:
|
|
if (wParam) {
|
|
event_listener->on_app_activated();
|
|
} else {
|
|
event_listener->on_app_deactivated();
|
|
}
|
|
break;
|
|
case WM_DISPLAYCHANGE:
|
|
event_listener->on_monitors_change();
|
|
break;
|
|
case WM_CHANGECBCHAIN:
|
|
if (next_clipboard_viewer_win == (HWND)wParam) {
|
|
next_clipboard_viewer_win = (HWND)lParam;
|
|
} else if (next_clipboard_viewer_win) {
|
|
SendMessage(next_clipboard_viewer_win, message, wParam, lParam);
|
|
}
|
|
break;
|
|
case WM_DRAWCLIPBOARD:
|
|
if (platform_win != GetClipboardOwner()) {
|
|
int type_count;
|
|
uint32_t* types;
|
|
Platform::set_clipboard_owner(Platform::owner_none);
|
|
if ((type_count = get_available_clipboard_types(&types))) {
|
|
clipboard_listener->on_clipboard_grab(types, type_count);
|
|
delete[] types;
|
|
} else {
|
|
LOG_INFO("Unsupported clipboard format");
|
|
}
|
|
}
|
|
if (next_clipboard_viewer_win) {
|
|
SendMessage(next_clipboard_viewer_win, message, wParam, lParam);
|
|
}
|
|
break;
|
|
case WM_RENDERFORMAT: {
|
|
// In delayed rendering, Windows requires us to SetClipboardData before we return from
|
|
// handling WM_RENDERFORMAT. Therefore, we try our best by sending CLIPBOARD_REQUEST to the
|
|
// agent, while waiting alertably for a while (hoping for good) for receiving CLIPBOARD data
|
|
// or CLIPBOARD_RELEASE from the agent, which both will signal clipboard_event.
|
|
uint32_t type = get_clipboard_type(wParam);
|
|
if (!type) {
|
|
LOG_INFO("Unsupported clipboard format %u", wParam);
|
|
break;
|
|
}
|
|
clipboard_listener->on_clipboard_request(type);
|
|
DWORD start_tick = GetTickCount();
|
|
while (WaitForSingleObjectEx(clipboard_event, 1000, TRUE) != WAIT_OBJECT_0 &&
|
|
GetTickCount() < start_tick + CLIPBOARD_TIMEOUT_MS);
|
|
break;
|
|
}
|
|
default:
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void create_message_wind()
|
|
{
|
|
WNDCLASSEX wclass;
|
|
ATOM class_atom;
|
|
|
|
const LPCWSTR class_name = L"spicec_platform_wclass";
|
|
|
|
wclass.cbSize = sizeof(WNDCLASSEX);
|
|
wclass.style = 0;
|
|
wclass.lpfnWndProc = PlatformWinProc;
|
|
wclass.cbClsExtra = 0;
|
|
wclass.cbWndExtra = 0;
|
|
wclass.hInstance = instance;
|
|
wclass.hIcon = NULL;
|
|
wclass.hCursor = NULL;
|
|
wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wclass.lpszMenuName = NULL;
|
|
wclass.lpszClassName = class_name;
|
|
wclass.hIconSm = NULL;
|
|
|
|
if ((class_atom = RegisterClassEx(&wclass)) == 0) {
|
|
THROW("register class failed");
|
|
}
|
|
|
|
if (!(platform_win = CreateWindow(class_name, L"", 0, 0, 0, 0, 0, NULL, NULL, instance, NULL))) {
|
|
THROW("create message window failed");
|
|
}
|
|
|
|
if (!(next_clipboard_viewer_win = SetClipboardViewer(platform_win)) && GetLastError()) {
|
|
THROW("set clipboard viewer failed");
|
|
}
|
|
if (!(clipboard_event = CreateEvent(NULL, FALSE, FALSE, NULL))) {
|
|
THROW("create clipboard event failed");
|
|
}
|
|
}
|
|
|
|
NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface)
|
|
{
|
|
ASSERT(main_loop && main_loop->is_same_thread(pthread_self()));
|
|
return (ListenerRef)(new WinListener(name, listener_interface, *main_loop));
|
|
}
|
|
|
|
void NamedPipe::destroy(ListenerRef listener_ref)
|
|
{
|
|
ASSERT(main_loop && main_loop->is_same_thread(pthread_self()));
|
|
delete (WinListener *)listener_ref;
|
|
}
|
|
|
|
void NamedPipe::destroy_connection(ConnectionRef conn_ref)
|
|
{
|
|
ASSERT(main_loop && main_loop->is_same_thread(pthread_self()));
|
|
delete (WinConnection *)conn_ref;
|
|
}
|
|
|
|
int32_t NamedPipe::read(ConnectionRef conn_ref, uint8_t *buf, int32_t size)
|
|
{
|
|
return ((WinConnection *)conn_ref)->read(buf, size);
|
|
}
|
|
|
|
int32_t NamedPipe::write(ConnectionRef conn_ref, const uint8_t *buf, int32_t size)
|
|
{
|
|
return ((WinConnection *)conn_ref)->write(buf, size);
|
|
}
|
|
|
|
void Platform::msleep(unsigned int msec)
|
|
{
|
|
Sleep(msec);
|
|
}
|
|
|
|
void Platform::yield()
|
|
{
|
|
Sleep(0);
|
|
}
|
|
|
|
void Platform::set_thread_priority(void* thread, Platform::ThreadPriority in_priority)
|
|
{
|
|
ASSERT(thread == NULL);
|
|
int priority;
|
|
|
|
switch (in_priority) {
|
|
case PRIORITY_TIME_CRITICAL:
|
|
priority = THREAD_PRIORITY_TIME_CRITICAL;
|
|
break;
|
|
case PRIORITY_HIGH:
|
|
priority = THREAD_PRIORITY_HIGHEST;
|
|
break;
|
|
case PRIORITY_ABOVE_NORMAL:
|
|
priority = THREAD_PRIORITY_ABOVE_NORMAL;
|
|
break;
|
|
case PRIORITY_NORMAL:
|
|
priority = THREAD_PRIORITY_NORMAL;
|
|
break;
|
|
case PRIORITY_BELOW_NORMAL:
|
|
priority = THREAD_PRIORITY_BELOW_NORMAL;
|
|
break;
|
|
case PRIORITY_LOW:
|
|
priority = THREAD_PRIORITY_LOWEST;
|
|
break;
|
|
case PRIORITY_IDLE:
|
|
priority = THREAD_PRIORITY_IDLE;
|
|
break;
|
|
default:
|
|
THROW("invalid priority %d", in_priority);
|
|
}
|
|
SetThreadPriority(GetCurrentThread(), priority);
|
|
}
|
|
|
|
void Platform::set_event_listener(EventListener* listener)
|
|
{
|
|
event_listener = listener ? listener : &default_event_listener;
|
|
}
|
|
|
|
uint64_t Platform::get_monolithic_time()
|
|
{
|
|
return uint64_t(GetTickCount()) * 1000 * 1000;
|
|
}
|
|
|
|
void Platform::get_temp_dir(std::string& path)
|
|
{
|
|
DWORD len = GetTempPathA(0, NULL);
|
|
if (len <= 0) {
|
|
throw Exception("get temp patch failed");
|
|
}
|
|
char* tmp_path = new char[len + 1];
|
|
GetTempPathA(len, tmp_path);
|
|
path = tmp_path;
|
|
delete[] tmp_path;
|
|
}
|
|
|
|
uint64_t Platform::get_process_id()
|
|
{
|
|
static uint64_t pid = GetCurrentProcessId();
|
|
return pid;
|
|
}
|
|
|
|
uint64_t Platform::get_thread_id()
|
|
{
|
|
return GetCurrentThreadId();
|
|
}
|
|
|
|
void Platform::error_beep()
|
|
{
|
|
MessageBeep(MB_ICONERROR);
|
|
}
|
|
|
|
class WinMonitor: public Monitor {
|
|
public:
|
|
WinMonitor(int id, const wchar_t* name, const wchar_t* string);
|
|
|
|
virtual int get_depth() { return _depth;}
|
|
virtual SpicePoint get_position();
|
|
virtual SpicePoint get_size() const { SpicePoint size = {_width, _height}; return size;}
|
|
virtual bool is_out_of_sync() { return _out_of_sync;}
|
|
virtual int get_screen_id() { return 0;}
|
|
|
|
protected:
|
|
virtual ~WinMonitor();
|
|
virtual void do_set_mode(int width, int height);
|
|
virtual void do_restore();
|
|
|
|
private:
|
|
void update_position();
|
|
bool change_display_settings(int width, int height, int depth);
|
|
bool best_display_setting(uint32_t width, uint32_t height, uint32_t depth);
|
|
|
|
private:
|
|
std::wstring _dev_name;
|
|
std::wstring _dev_string;
|
|
bool _active;
|
|
SpicePoint _position;
|
|
int _width;
|
|
int _height;
|
|
int _depth;
|
|
bool _out_of_sync;
|
|
};
|
|
|
|
WinMonitor::WinMonitor(int id, const wchar_t* name, const wchar_t* string)
|
|
: Monitor(id)
|
|
, _dev_name (name)
|
|
, _dev_string (string)
|
|
, _active (false)
|
|
, _out_of_sync (false)
|
|
{
|
|
update_position();
|
|
}
|
|
|
|
WinMonitor::~WinMonitor()
|
|
{
|
|
do_restore();
|
|
}
|
|
|
|
void WinMonitor::update_position()
|
|
{
|
|
DEVMODE mode;
|
|
mode.dmSize = sizeof(DEVMODE);
|
|
mode.dmDriverExtra = 0;
|
|
EnumDisplaySettings(_dev_name.c_str(), ENUM_CURRENT_SETTINGS, &mode);
|
|
_position.x = mode.dmPosition.x;
|
|
_position.y = mode.dmPosition.y;
|
|
_width = mode.dmPelsWidth;
|
|
_height = mode.dmPelsHeight;
|
|
_depth = mode.dmBitsPerPel;
|
|
}
|
|
|
|
SpicePoint WinMonitor::get_position()
|
|
{
|
|
update_position();
|
|
return _position;
|
|
}
|
|
|
|
bool WinMonitor::change_display_settings(int width, int height, int depth)
|
|
{
|
|
DEVMODE mode;
|
|
mode.dmSize = sizeof(DEVMODE);
|
|
mode.dmDriverExtra = 0;
|
|
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
|
|
mode.dmPelsWidth = width;
|
|
mode.dmPelsHeight = height;
|
|
mode.dmBitsPerPel = depth;
|
|
|
|
return ChangeDisplaySettingsEx(_dev_name.c_str(), &mode, NULL, CDS_FULLSCREEN, NULL)
|
|
== DISP_CHANGE_SUCCESSFUL;
|
|
}
|
|
|
|
bool WinMonitor::best_display_setting(uint32_t width, uint32_t height, uint32_t depth)
|
|
{
|
|
DEVMODE mode;
|
|
DWORD mode_id = 0;
|
|
uint32_t mod_waste = ~0;
|
|
DWORD mod_width = 0;
|
|
DWORD mod_height = 0;
|
|
DWORD mod_depth = 0;
|
|
DWORD mod_frequency = 0;
|
|
|
|
mode.dmSize = sizeof(DEVMODE);
|
|
mode.dmDriverExtra = 0;
|
|
while (EnumDisplaySettings(_dev_name.c_str(), mode_id++, &mode)) {
|
|
// Workaround for
|
|
// Lenovo T61p, Nvidia Quadro FX 570M and
|
|
// Lenovo T61, Nvidia Quadro NVS 140M
|
|
//
|
|
// with dual monitors configuration
|
|
//
|
|
// we get strange values from EnumDisplaySettings 640x480x4 frequency 1
|
|
// and calling ChangeDisplaySettingsEx with that configuration result with
|
|
// machine that is stucked for a long period of time
|
|
if (mode.dmDisplayFrequency == 1) {
|
|
continue;
|
|
}
|
|
|
|
if (mode.dmPelsWidth >= width && mode.dmPelsHeight >= height) {
|
|
bool replace = false;
|
|
uint32_t curr_waste = mode.dmPelsWidth * mode.dmPelsHeight - width * height;
|
|
if (curr_waste < mod_waste) {
|
|
replace = true;
|
|
} else if (curr_waste == mod_waste) {
|
|
if (mod_depth == mode.dmBitsPerPel) {
|
|
replace = mode.dmDisplayFrequency > mod_frequency;
|
|
} else if (mod_depth != depth && mode.dmBitsPerPel > mod_depth) {
|
|
replace = true;
|
|
}
|
|
}
|
|
if (replace) {
|
|
mod_waste = curr_waste;
|
|
mod_width = mode.dmPelsWidth;
|
|
mod_height = mode.dmPelsHeight;
|
|
mod_depth = mode.dmBitsPerPel;
|
|
mod_frequency = mode.dmDisplayFrequency;
|
|
}
|
|
}
|
|
}
|
|
if (mod_waste == ~0u) {
|
|
return false;
|
|
}
|
|
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_DISPLAYFREQUENCY;
|
|
mode.dmPelsWidth = mod_width;
|
|
mode.dmPelsHeight = mod_height;
|
|
mode.dmBitsPerPel = mod_depth;
|
|
mode.dmDisplayFrequency = mod_frequency;
|
|
|
|
return ChangeDisplaySettingsEx(_dev_name.c_str(), &mode, NULL, CDS_FULLSCREEN, NULL)
|
|
== DISP_CHANGE_SUCCESSFUL;
|
|
}
|
|
|
|
void WinMonitor::do_set_mode(int width, int height)
|
|
{
|
|
update_position();
|
|
if (width == _width && height == _height) {
|
|
_out_of_sync = false;
|
|
return;
|
|
}
|
|
self_monitors_change++;
|
|
if (!change_display_settings(width, height, 32) && !best_display_setting(width, height, 32)) {
|
|
_out_of_sync = true;
|
|
} else {
|
|
_out_of_sync = false;
|
|
}
|
|
self_monitors_change--;
|
|
_active = true;
|
|
update_position();
|
|
}
|
|
|
|
void WinMonitor::do_restore()
|
|
{
|
|
if (_active) {
|
|
_active = false;
|
|
self_monitors_change++;
|
|
ChangeDisplaySettingsEx(_dev_name.c_str(), NULL, NULL, 0, NULL);
|
|
self_monitors_change--;
|
|
update_position();
|
|
}
|
|
}
|
|
|
|
static MonitorsList monitors;
|
|
static Monitor* primary_monitor = NULL;
|
|
|
|
const MonitorsList& Platform::init_monitors()
|
|
{
|
|
ASSERT(monitors.empty());
|
|
|
|
int id = 0;
|
|
Monitor* mon;
|
|
DISPLAY_DEVICE device_info;
|
|
DWORD device_id = 0;
|
|
device_info.cb = sizeof(device_info);
|
|
while (EnumDisplayDevices(NULL, device_id, &device_info, 0)) {
|
|
if ((device_info.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) &&
|
|
!(device_info.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
|
|
mon = new WinMonitor(id++, device_info.DeviceName, device_info.DeviceString);
|
|
monitors.push_back(mon);
|
|
if (device_info.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
|
|
primary_monitor = mon;
|
|
}
|
|
}
|
|
device_id++;
|
|
}
|
|
return monitors;
|
|
}
|
|
|
|
void Platform::destroy_monitors()
|
|
{
|
|
primary_monitor = NULL;
|
|
while (!monitors.empty()) {
|
|
Monitor* monitor = monitors.front();
|
|
monitors.pop_front();
|
|
delete monitor;
|
|
}
|
|
}
|
|
|
|
bool Platform::is_monitors_pos_valid()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void Platform::get_app_data_dir(std::string& path, const std::string& app_name)
|
|
{
|
|
char app_data_path[MAX_PATH];
|
|
HRESULT res = SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, app_data_path);
|
|
if (res != S_OK) {
|
|
throw Exception("get user app data dir failed");
|
|
}
|
|
|
|
path = app_data_path;
|
|
path_append(path, app_name);
|
|
|
|
if (!CreateDirectoryA(path.c_str(), NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
|
|
throw Exception("create user app data dir failed");
|
|
}
|
|
}
|
|
|
|
void Platform::path_append(std::string& path, const std::string& partial_path)
|
|
{
|
|
path += "\\";
|
|
path += partial_path;
|
|
}
|
|
|
|
static void cleanup()
|
|
{
|
|
ChangeClipboardChain(platform_win, next_clipboard_viewer_win);
|
|
CloseHandle(clipboard_event);
|
|
}
|
|
|
|
void Platform::init()
|
|
{
|
|
create_message_wind();
|
|
atexit(cleanup);
|
|
}
|
|
|
|
void Platform::set_process_loop(ProcessLoop& main_process_loop)
|
|
{
|
|
main_loop = &main_process_loop;
|
|
}
|
|
|
|
WaveRecordAbstract* Platform::create_recorder(RecordClient& client,
|
|
uint32_t sampels_per_sec,
|
|
uint32_t bits_per_sample,
|
|
uint32_t channels)
|
|
{
|
|
return new WaveRecorder(client, sampels_per_sec, bits_per_sample, channels);
|
|
}
|
|
|
|
WavePlaybackAbstract* Platform::create_player(uint32_t sampels_per_sec,
|
|
uint32_t bits_per_sample,
|
|
uint32_t channels)
|
|
{
|
|
return new WavePlayer(sampels_per_sec, bits_per_sample, channels);
|
|
}
|
|
|
|
static void toggle_modifier(int key)
|
|
{
|
|
INPUT inputs[2];
|
|
memset(inputs, 0, sizeof(inputs));
|
|
inputs[0].type = inputs[1].type = INPUT_KEYBOARD;
|
|
inputs[0].ki.wVk = inputs[1].ki.wVk = key;
|
|
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(2, inputs, sizeof(INPUT));
|
|
}
|
|
|
|
uint32_t Platform::get_keyboard_lock_modifiers()
|
|
{
|
|
uint32_t modifiers = 0;
|
|
if ((GetKeyState(VK_SCROLL) & 1)) {
|
|
modifiers |= SCROLL_LOCK_MODIFIER;
|
|
}
|
|
if ((GetKeyState(VK_NUMLOCK) & 1)) {
|
|
modifiers |= NUM_LOCK_MODIFIER;
|
|
}
|
|
if ((GetKeyState(VK_CAPITAL) & 1)) {
|
|
modifiers |= CAPS_LOCK_MODIFIER;
|
|
}
|
|
return modifiers;
|
|
}
|
|
|
|
void Platform::set_keyboard_lock_modifiers(uint32_t modifiers)
|
|
{
|
|
if (((modifiers >> SCROLL_LOCK_MODIFIER_SHIFT) & 1) != (GetKeyState(VK_SCROLL) & 1)) {
|
|
toggle_modifier(VK_SCROLL);
|
|
}
|
|
|
|
if (((modifiers >> Platform::NUM_LOCK_MODIFIER_SHIFT) & 1) != (GetKeyState(VK_NUMLOCK) & 1)) {
|
|
toggle_modifier(VK_NUMLOCK);
|
|
}
|
|
|
|
if (((modifiers >> CAPS_LOCK_MODIFIER_SHIFT) & 1) != (GetKeyState(VK_CAPITAL) & 1)) {
|
|
toggle_modifier(VK_CAPITAL);
|
|
}
|
|
}
|
|
|
|
#define KEY_BIT(keymap, key, bit) (keymap[key] & 0x80 ? bit : 0)
|
|
|
|
uint32_t Platform::get_keyboard_modifiers()
|
|
{
|
|
BYTE keymap[256];
|
|
|
|
if (!GetKeyboardState(keymap)) {
|
|
return 0;
|
|
}
|
|
return KEY_BIT(keymap, VK_LSHIFT, L_SHIFT_MODIFIER) |
|
|
KEY_BIT(keymap, VK_RSHIFT, R_SHIFT_MODIFIER) |
|
|
KEY_BIT(keymap, VK_LCONTROL, L_CTRL_MODIFIER) |
|
|
KEY_BIT(keymap, VK_RCONTROL, R_CTRL_MODIFIER) |
|
|
KEY_BIT(keymap, VK_LMENU, L_ALT_MODIFIER) |
|
|
KEY_BIT(keymap, VK_RMENU, R_ALT_MODIFIER);
|
|
}
|
|
|
|
void Platform::reset_cursor_pos()
|
|
{
|
|
if (!primary_monitor) {
|
|
return;
|
|
}
|
|
SpicePoint pos = primary_monitor->get_position();
|
|
SpicePoint size = primary_monitor->get_size();
|
|
SetCursorPos(pos.x + size.x / 2, pos.y + size.y / 2);
|
|
}
|
|
|
|
class WinBaseLocalCursor: public LocalCursor {
|
|
public:
|
|
WinBaseLocalCursor() : _handle (0) {}
|
|
void set(Window window) { SetCursor(_handle);}
|
|
|
|
protected:
|
|
HCURSOR _handle;
|
|
};
|
|
|
|
class WinLocalCursor: public WinBaseLocalCursor {
|
|
public:
|
|
WinLocalCursor(CursorData* cursor_data);
|
|
~WinLocalCursor();
|
|
|
|
private:
|
|
bool _shared;
|
|
};
|
|
|
|
WinLocalCursor::WinLocalCursor(CursorData* cursor_data)
|
|
: _shared (false)
|
|
{
|
|
const SpiceCursorHeader& header = cursor_data->header();
|
|
const uint8_t* data = cursor_data->data();
|
|
int cur_size;
|
|
int bits = get_size_bits(header, cur_size);
|
|
if (!bits) {
|
|
THROW("invalid curosr type");
|
|
}
|
|
if (header.type == SPICE_CURSOR_TYPE_MONO) {
|
|
_handle = CreateCursor(NULL, header.hot_spot_x, header.hot_spot_y,
|
|
header.width, header.height, data, data + cur_size);
|
|
return;
|
|
}
|
|
ICONINFO icon;
|
|
icon.fIcon = FALSE;
|
|
icon.xHotspot = header.hot_spot_x;
|
|
icon.yHotspot = header.hot_spot_y;
|
|
icon.hbmColor = icon.hbmMask = NULL;
|
|
HDC hdc = GetDC(NULL);
|
|
|
|
switch (header.type) {
|
|
case SPICE_CURSOR_TYPE_ALPHA:
|
|
case SPICE_CURSOR_TYPE_COLOR32:
|
|
case SPICE_CURSOR_TYPE_COLOR16: {
|
|
BITMAPV5HEADER bmp_hdr;
|
|
ZeroMemory(&bmp_hdr, sizeof(bmp_hdr));
|
|
bmp_hdr.bV5Size = sizeof(bmp_hdr);
|
|
bmp_hdr.bV5Width = header.width;
|
|
bmp_hdr.bV5Height = -header.height;
|
|
bmp_hdr.bV5Planes = 1;
|
|
bmp_hdr.bV5BitCount = bits;
|
|
bmp_hdr.bV5Compression = BI_BITFIELDS;
|
|
if (bits == 32) {
|
|
bmp_hdr.bV5RedMask = 0x00FF0000;
|
|
bmp_hdr.bV5GreenMask = 0x0000FF00;
|
|
bmp_hdr.bV5BlueMask = 0x000000FF;
|
|
} else if (bits == 16) {
|
|
bmp_hdr.bV5RedMask = 0x00007C00;
|
|
bmp_hdr.bV5GreenMask = 0x000003E0;
|
|
bmp_hdr.bV5BlueMask = 0x0000001F;
|
|
}
|
|
if (header.type == SPICE_CURSOR_TYPE_ALPHA) {
|
|
bmp_hdr.bV5AlphaMask = 0xFF000000;
|
|
}
|
|
void* bmp_pixels = NULL;
|
|
icon.hbmColor = CreateDIBSection(hdc, (BITMAPINFO *)&bmp_hdr, DIB_RGB_COLORS, &bmp_pixels,
|
|
NULL, 0);
|
|
memcpy(bmp_pixels, data, cur_size);
|
|
icon.hbmMask = CreateBitmap(header.width, header.height, 1, 1,
|
|
(header.type == SPICE_CURSOR_TYPE_ALPHA) ? NULL :
|
|
(CONST VOID *)(data + cur_size));
|
|
break;
|
|
}
|
|
case SPICE_CURSOR_TYPE_COLOR4: {
|
|
BITMAPINFO* bmp_info;
|
|
bmp_info = (BITMAPINFO *)new uint8_t[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) << bits)];
|
|
ZeroMemory(bmp_info, sizeof(BITMAPINFO));
|
|
bmp_info->bmiHeader.biSize = sizeof(bmp_info->bmiHeader);
|
|
bmp_info->bmiHeader.biWidth = header.width;
|
|
bmp_info->bmiHeader.biHeight = -header.height;
|
|
bmp_info->bmiHeader.biPlanes = 1;
|
|
bmp_info->bmiHeader.biBitCount = bits;
|
|
bmp_info->bmiHeader.biCompression = BI_RGB;
|
|
memcpy(bmp_info->bmiColors, data + cur_size, sizeof(RGBQUAD) << bits);
|
|
icon.hbmColor = CreateDIBitmap(hdc, &bmp_info->bmiHeader, CBM_INIT, data,
|
|
bmp_info, DIB_RGB_COLORS);
|
|
icon.hbmMask = CreateBitmap(header.width, header.height, 1, 1,
|
|
(CONST VOID *)(data + cur_size + (sizeof(uint32_t) << bits)));
|
|
delete[] (uint8_t *)bmp_info;
|
|
break;
|
|
}
|
|
case SPICE_CURSOR_TYPE_COLOR24:
|
|
case SPICE_CURSOR_TYPE_COLOR8:
|
|
default:
|
|
LOG_WARN("unsupported cursor type %d", header.type);
|
|
_handle = LoadCursor(NULL, IDC_ARROW);
|
|
_shared = true;
|
|
ReleaseDC(NULL, hdc);
|
|
return;
|
|
}
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
|
|
if (icon.hbmColor && icon.hbmMask) {
|
|
_handle = CreateIconIndirect(&icon);
|
|
}
|
|
if (icon.hbmMask) {
|
|
DeleteObject(icon.hbmMask);
|
|
}
|
|
if (icon.hbmColor) {
|
|
DeleteObject(icon.hbmColor);
|
|
}
|
|
}
|
|
|
|
WinLocalCursor::~WinLocalCursor()
|
|
{
|
|
if (_handle && !_shared) {
|
|
DestroyCursor(_handle);
|
|
}
|
|
}
|
|
|
|
LocalCursor* Platform::create_local_cursor(CursorData* cursor_data)
|
|
{
|
|
return new WinLocalCursor(cursor_data);
|
|
}
|
|
|
|
class WinInactiveCursor: public WinBaseLocalCursor {
|
|
public:
|
|
WinInactiveCursor() { _handle = LoadCursor(NULL, IDC_NO);}
|
|
};
|
|
|
|
LocalCursor* Platform::create_inactive_cursor()
|
|
{
|
|
return new WinInactiveCursor();
|
|
}
|
|
|
|
class WinDefaultCursor: public WinBaseLocalCursor {
|
|
public:
|
|
WinDefaultCursor() { _handle = LoadCursor(NULL, IDC_ARROW);}
|
|
};
|
|
|
|
LocalCursor* Platform::create_default_cursor()
|
|
{
|
|
return new WinDefaultCursor();
|
|
}
|
|
|
|
void Platform::set_display_mode_listner(DisplayModeListener* listener)
|
|
{
|
|
}
|
|
|
|
Icon* Platform::load_icon(int id)
|
|
{
|
|
HICON icon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(id));
|
|
if (!icon) {
|
|
return NULL;
|
|
}
|
|
return new WinIcon(icon);
|
|
}
|
|
|
|
void WinPlatform::enter_modal_loop()
|
|
{
|
|
if (modal_loop_active) {
|
|
LOG_INFO("modal loop already active");
|
|
return;
|
|
}
|
|
|
|
if (set_modal_loop_timer()) {
|
|
modal_loop_active = true;
|
|
} else {
|
|
LOG_WARN("failed to create modal loop timer");
|
|
}
|
|
}
|
|
|
|
static bool set_modal_loop_timer()
|
|
{
|
|
unsigned timeout = main_loop->get_soonest_timeout();
|
|
if (timeout == INFINITE) {
|
|
timeout = MODAL_LOOP_DEFAULT_TIMEOUT; /* for cases timeouts are added after
|
|
the enterance to the loop*/
|
|
}
|
|
|
|
if (!SetTimer(platform_win, MODAL_LOOP_TIMER_ID, timeout, NULL)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WinPlatform::exit_modal_loop()
|
|
{
|
|
if (!modal_loop_active) {
|
|
LOG_INFO("not inside the loop");
|
|
return;
|
|
}
|
|
KillTimer(platform_win, MODAL_LOOP_TIMER_ID);
|
|
modal_loop_active = false;
|
|
}
|
|
|
|
int Platform::_clipboard_owner = Platform::owner_none;
|
|
|
|
void Platform::set_clipboard_owner_unlocked(int new_owner)
|
|
{
|
|
set_clipboard_owner(new_owner);
|
|
}
|
|
|
|
void Platform::set_clipboard_owner(int new_owner)
|
|
{
|
|
const char * const owner_str[] = { "none", "guest", "client" };
|
|
|
|
if (new_owner == owner_none) {
|
|
clipboard_listener->on_clipboard_release();
|
|
|
|
/* FIXME clear cached clipboard type info and data */
|
|
}
|
|
_clipboard_owner = new_owner;
|
|
LOG_INFO("new clipboard owner: %s", owner_str[new_owner]);
|
|
}
|
|
|
|
bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
|
|
{
|
|
std::set<uint32_t> grab_formats;
|
|
|
|
grab_types.clear();
|
|
for (uint32_t i = 0; i < type_count; i++) {
|
|
uint32_t format = get_clipboard_format(types[i]);
|
|
//On first supported type, open and empty the clipboard
|
|
if (format && grab_formats.empty()) {
|
|
if (!OpenClipboard(platform_win)) {
|
|
return false;
|
|
}
|
|
EmptyClipboard();
|
|
}
|
|
//For all supported type set delayed rendering
|
|
if (format) {
|
|
grab_types.insert(types[i]);
|
|
if (grab_formats.insert(format).second) {
|
|
SetClipboardData(format, NULL);
|
|
}
|
|
}
|
|
}
|
|
if (grab_formats.empty()) {
|
|
LOG_INFO("No supported clipboard types in client grab");
|
|
return false;
|
|
}
|
|
CloseClipboard();
|
|
|
|
set_clipboard_owner(owner_guest);
|
|
return true;
|
|
}
|
|
|
|
void Platform::set_clipboard_listener(ClipboardListener* listener)
|
|
{
|
|
clipboard_listener = listener ? listener : &default_clipboard_listener;
|
|
}
|
|
|
|
static HGLOBAL utf8_alloc(LPCSTR data, int size)
|
|
{
|
|
HGLOBAL handle;
|
|
LPVOID buf;
|
|
int len;
|
|
|
|
// Received utf8 string is not null-terminated
|
|
if (!(len = MultiByteToWideChar(CP_UTF8, 0, data, size, NULL, 0))) {
|
|
return NULL;
|
|
}
|
|
len++;
|
|
// Allocate and lock clipboard memory
|
|
if (!(handle = GlobalAlloc(GMEM_DDESHARE, len * sizeof(WCHAR)))) {
|
|
return NULL;
|
|
}
|
|
if (!(buf = GlobalLock(handle))) {
|
|
GlobalFree(handle);
|
|
return NULL;
|
|
}
|
|
// Translate data and set clipboard content
|
|
if (!(MultiByteToWideChar(CP_UTF8, 0, data, size, (LPWSTR)buf, len))) {
|
|
GlobalUnlock(handle);
|
|
GlobalFree(handle);
|
|
return NULL;
|
|
}
|
|
((LPWSTR)buf)[len - 1] = L'\0';
|
|
GlobalUnlock(handle);
|
|
return handle;
|
|
}
|
|
|
|
bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
|
|
{
|
|
HANDLE clip_data;
|
|
UINT format;
|
|
bool ret = false;
|
|
|
|
if (type == VD_AGENT_CLIPBOARD_NONE) {
|
|
SetEvent(clipboard_event);
|
|
return true;
|
|
}
|
|
switch (type) {
|
|
case VD_AGENT_CLIPBOARD_UTF8_TEXT:
|
|
clip_data = utf8_alloc((LPCSTR)data, size);
|
|
break;
|
|
#ifdef USE_CXIMAGE
|
|
case VD_AGENT_CLIPBOARD_IMAGE_PNG:
|
|
case VD_AGENT_CLIPBOARD_IMAGE_BMP: {
|
|
DWORD cximage_format = get_cximage_format(type);
|
|
ASSERT(cximage_format);
|
|
CxImage image((BYTE *)data, size, cximage_format);
|
|
clip_data = image.CopyToHandle();
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
LOG_INFO("Unsupported clipboard type %u", type);
|
|
return true;
|
|
}
|
|
|
|
format = get_clipboard_format(type);
|
|
if (SetClipboardData(format, clip_data)) {
|
|
SetEvent(clipboard_event);
|
|
return true;
|
|
}
|
|
// We retry clipboard open-empty-set-close only when there is a timeout in WM_RENDERFORMAT
|
|
if (!OpenClipboard(platform_win)) {
|
|
return false;
|
|
}
|
|
EmptyClipboard();
|
|
ret = !!SetClipboardData(format, clip_data);
|
|
CloseClipboard();
|
|
return ret;
|
|
}
|
|
|
|
bool Platform::on_clipboard_request(uint32_t type)
|
|
{
|
|
UINT format = get_clipboard_format(type);
|
|
HANDLE clip_data;
|
|
LPCWSTR clip_buf;
|
|
uint8_t* new_data = NULL;
|
|
long new_size;
|
|
|
|
bool ret = false;
|
|
|
|
if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(platform_win)) {
|
|
return false;
|
|
}
|
|
if (!(clip_data = GetClipboardData(format))) {
|
|
CloseClipboard();
|
|
return false;
|
|
}
|
|
|
|
switch (type) {
|
|
case VD_AGENT_CLIPBOARD_UTF8_TEXT: {
|
|
if (!(clip_buf = (LPCWSTR)GlobalLock(clip_data))) {
|
|
break;
|
|
}
|
|
size_t len = wcslen((wchar_t*)clip_buf);
|
|
new_size = WideCharToMultiByte(CP_UTF8, 0, clip_buf, len, NULL, 0, NULL, NULL);
|
|
if (!new_size) {
|
|
GlobalUnlock(clip_data);
|
|
break;
|
|
}
|
|
new_data = new uint8_t[new_size];
|
|
if (WideCharToMultiByte(CP_UTF8, 0, clip_buf, len, (LPSTR)new_data, new_size,
|
|
NULL, NULL)) {
|
|
clipboard_listener->on_clipboard_notify(type, new_data, new_size);
|
|
ret = true;
|
|
}
|
|
delete[] (uint8_t *)new_data;
|
|
GlobalUnlock(clip_data);
|
|
break;
|
|
}
|
|
#ifdef USE_CXIMAGE
|
|
case VD_AGENT_CLIPBOARD_IMAGE_PNG:
|
|
case VD_AGENT_CLIPBOARD_IMAGE_BMP: {
|
|
DWORD cximage_format = get_cximage_format(type);
|
|
ASSERT(cximage_format);
|
|
CxImage image;
|
|
if (!image.CreateFromHANDLE(clip_data)) {
|
|
LOG_INFO("Image create from handle failed");
|
|
break;
|
|
}
|
|
if (!image.Encode(new_data, new_size, cximage_format)) {
|
|
LOG_INFO("Image encode to type %u failed", type);
|
|
break;
|
|
}
|
|
LOG_INFO("Image encoded to %u bytes", new_size);
|
|
clipboard_listener->on_clipboard_notify(type, new_data, new_size);
|
|
image.FreeMemory(new_data);
|
|
ret = true;
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
LOG_INFO("Unsupported clipboard type %u", type);
|
|
}
|
|
|
|
CloseClipboard();
|
|
return ret;
|
|
}
|
|
|
|
void Platform::on_clipboard_release()
|
|
{
|
|
SetEvent(clipboard_event);
|
|
set_clipboard_owner(owner_none);
|
|
}
|
|
|
|
static bool has_console = false;
|
|
|
|
static void create_console()
|
|
{
|
|
static Mutex console_mutex;
|
|
|
|
Lock lock(console_mutex);
|
|
|
|
if (has_console) {
|
|
return;
|
|
}
|
|
|
|
AllocConsole();
|
|
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
int hConHandle = _open_osfhandle((intptr_t)h, _O_TEXT);
|
|
FILE * fp = _fdopen(hConHandle, "w");
|
|
*stdout = *fp;
|
|
|
|
h = GetStdHandle(STD_INPUT_HANDLE);
|
|
hConHandle = _open_osfhandle((intptr_t)h, _O_TEXT);
|
|
fp = _fdopen(hConHandle, "r");
|
|
*stdin = *fp;
|
|
|
|
h = GetStdHandle(STD_ERROR_HANDLE);
|
|
hConHandle = _open_osfhandle((intptr_t)h, _O_TEXT);
|
|
fp = _fdopen(hConHandle, "w");
|
|
*stderr = *fp;
|
|
|
|
has_console = true;
|
|
|
|
HWND consol_window = GetConsoleWindow();
|
|
|
|
if (consol_window) {
|
|
SetForegroundWindow(consol_window);
|
|
}
|
|
}
|
|
|
|
class ConsoleWait {
|
|
public:
|
|
~ConsoleWait()
|
|
{
|
|
if (has_console) {
|
|
Platform::term_printf("\n\nPress any key to exit...");
|
|
_getch();
|
|
}
|
|
}
|
|
|
|
} console_wait;
|
|
|
|
|
|
void Platform::term_printf(const char* format, ...)
|
|
{
|
|
if (!has_console) {
|
|
create_console();
|
|
}
|
|
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
va_end(ap);
|
|
}
|