mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-27 07:29:32 +00:00
Make sure we process the XFixesSetSelectionOwnerNotify event caused by us setting the clipboard owner to none, directly after setting the owner to none. Otherwise we may end up changing the clipboard owner to none, after it has already been re-owned because the XFixesSetSelectionOwnerNotify event to owner none is event is still pending when we set the new owner, and then changes the owner back to none once processed messing up our clipboard ownership state tracking. I saw this happening when doing copy twice in succession inside the guest.
1078 lines
30 KiB
C++
1078 lines
30 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"
|
|
#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;
|
|
|
|
// clipboard_changer says whether the client was the last one to change cliboard, for loop
|
|
// prevention. It's initialized to true so we ignore the first clipboard change event which
|
|
// happens right when we call SetClipboardViewer().
|
|
static bool clipboard_changer = true;
|
|
|
|
static const int CLIPBOARD_TIMEOUT_MS = 10000;
|
|
|
|
typedef struct ClipboardFormat {
|
|
uint32_t format;
|
|
uint32_t type;
|
|
} ClipboardFormat;
|
|
|
|
static ClipboardFormat clipboard_formats[] = {
|
|
{CF_UNICODETEXT, VD_AGENT_CLIPBOARD_UTF8_TEXT},
|
|
{CF_DIB, VD_AGENT_CLIPBOARD_BITMAP},
|
|
{0, 0}};
|
|
|
|
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) {
|
|
ClipboardFormat* iter;
|
|
|
|
for (iter = clipboard_formats; iter->type && iter->format != format; iter++);
|
|
return iter->type;
|
|
}
|
|
|
|
static uint32_t get_clipboard_format(uint32_t type) {
|
|
ClipboardFormat* iter;
|
|
|
|
for (iter = clipboard_formats; iter->format && iter->type != type; iter++);
|
|
return iter->format;
|
|
}
|
|
|
|
static uint32_t get_available_clipboard_type()
|
|
{
|
|
uint32_t type = 0;
|
|
|
|
for (ClipboardFormat* iter = clipboard_formats; iter->format && !type; iter++) {
|
|
if (IsClipboardFormatAvailable(iter->format)) {
|
|
type = iter->type;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
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 (!clipboard_changer) {
|
|
uint32_t type = get_available_clipboard_type();
|
|
if (type) {
|
|
clipboard_listener->on_clipboard_grab(&type, 1);
|
|
} else {
|
|
LOG_INFO("Unsupported clipboard format");
|
|
}
|
|
} else {
|
|
clipboard_changer = false;
|
|
}
|
|
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;
|
|
DWORD mod_height;
|
|
DWORD mod_depth;
|
|
DWORD mod_frequency;
|
|
|
|
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 == ~0) {
|
|
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()
|
|
{
|
|
int 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(int new_owner)
|
|
{
|
|
if (new_owner == owner_none) {
|
|
clipboard_listener->on_clipboard_release();
|
|
|
|
/* FIXME clear cached clipboard type info and data */
|
|
}
|
|
_clipboard_owner = new_owner;
|
|
}
|
|
|
|
bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
|
|
{
|
|
/* FIXME use all types rather then just the first one */
|
|
uint32_t format = get_clipboard_format(types[0]);
|
|
|
|
if (!format) {
|
|
LOG_INFO("Unsupported clipboard type %u", types[0]);
|
|
return false;
|
|
}
|
|
if (!OpenClipboard(platform_win)) {
|
|
return false;
|
|
}
|
|
clipboard_changer = true;
|
|
EmptyClipboard();
|
|
SetClipboardData(format, NULL);
|
|
CloseClipboard();
|
|
return true;
|
|
}
|
|
|
|
void Platform::set_clipboard_listener(ClipboardListener* listener)
|
|
{
|
|
clipboard_listener = listener ? listener : &default_clipboard_listener;
|
|
}
|
|
|
|
bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
|
|
{
|
|
HGLOBAL clip_data;
|
|
LPVOID clip_buf;
|
|
int clip_size;
|
|
int clip_len;
|
|
UINT format;
|
|
bool ret = false;
|
|
|
|
// Get the required clipboard size
|
|
switch (type) {
|
|
case VD_AGENT_CLIPBOARD_UTF8_TEXT:
|
|
// Received utf8 string is not null-terminated
|
|
if (!(clip_len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, NULL, 0))) {
|
|
return false;
|
|
}
|
|
clip_len++;
|
|
clip_size = clip_len * sizeof(WCHAR);
|
|
break;
|
|
case VD_AGENT_CLIPBOARD_BITMAP:
|
|
clip_size = size;
|
|
break;
|
|
default:
|
|
LOG_INFO("Unsupported clipboard type %u", type);
|
|
return false;
|
|
}
|
|
|
|
// Allocate and lock clipboard memory
|
|
if (!(clip_data = GlobalAlloc(GMEM_DDESHARE, clip_size))) {
|
|
return false;
|
|
}
|
|
if (!(clip_buf = GlobalLock(clip_data))) {
|
|
GlobalFree(clip_data);
|
|
return false;
|
|
}
|
|
|
|
// Translate data and set clipboard content
|
|
switch (type) {
|
|
case VD_AGENT_CLIPBOARD_UTF8_TEXT:
|
|
ret = !!MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, (LPWSTR)clip_buf, clip_len);
|
|
((LPWSTR)clip_buf)[clip_len - 1] = L'\0';
|
|
break;
|
|
case VD_AGENT_CLIPBOARD_BITMAP:
|
|
memcpy(clip_buf, data, size);
|
|
ret = true;
|
|
break;
|
|
}
|
|
GlobalUnlock(clip_data);
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
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;
|
|
LPVOID clip_buf;
|
|
bool ret = false;
|
|
|
|
if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(platform_win)) {
|
|
return false;
|
|
}
|
|
if (!(clip_data = GetClipboardData(format)) || !(clip_buf = GlobalLock(clip_data))) {
|
|
CloseClipboard();
|
|
return false;
|
|
}
|
|
|
|
switch (type) {
|
|
case VD_AGENT_CLIPBOARD_UTF8_TEXT: {
|
|
size_t len = wcslen((wchar_t*)clip_buf);
|
|
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, len, NULL, 0, NULL, NULL);
|
|
if (!utf8_size) {
|
|
break;
|
|
}
|
|
uint8_t* utf8_data = new uint8_t[utf8_size];
|
|
if (WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, len, (LPSTR)utf8_data, utf8_size,
|
|
NULL, NULL)) {
|
|
clipboard_listener->on_clipboard_notify(type, utf8_data, utf8_size);
|
|
ret = true;
|
|
}
|
|
delete[] (uint8_t *)utf8_data;
|
|
break;
|
|
}
|
|
case VD_AGENT_CLIPBOARD_BITMAP: {
|
|
size_t clip_size = GlobalSize(clip_data);
|
|
if (!clip_size) {
|
|
break;
|
|
}
|
|
clipboard_listener->on_clipboard_notify(type, (uint8_t*)clip_buf, clip_size);
|
|
ret = true;
|
|
break;
|
|
}
|
|
default:
|
|
LOG_INFO("Unsupported clipboard type %u", type);
|
|
}
|
|
|
|
GlobalUnlock(clip_data);
|
|
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);
|
|
}
|