mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
The activex browser plugin is sending unicode16 text, where as the xpi one is sending utf8 text. After discussing this on irc we've decided that utf8 is what we want to use. So the client (this patch), and the activex will be changed to expect resp. send utf8 text as the title.
1058 lines
28 KiB
C++
1058 lines
28 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 "red_window.h"
|
|
#include "pixels_source_p.h"
|
|
#include "utils.h"
|
|
#include "debug.h"
|
|
#include <spice/protocol.h>
|
|
#include "menu.h"
|
|
#include "win_platform.h"
|
|
#include "platform_utils.h"
|
|
|
|
#include <list>
|
|
|
|
#define NATIVE_CAPTION_STYLE (WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
|
|
|
|
extern HINSTANCE instance;
|
|
|
|
static ATOM class_atom = 0;
|
|
static const LPCWSTR win_class_name = L"redc_wclass";
|
|
static HWND focus_window = NULL;
|
|
static HHOOK low_keyboard_hook = NULL;
|
|
static bool low_keyboard_hook_on = false;
|
|
static HHOOK msg_filter_hook = NULL;
|
|
typedef std::list<RedKey> KeysList;
|
|
static KeysList filtered_up_keys;
|
|
|
|
static LRESULT CALLBACK MessageFilterProc(int nCode, WPARAM wParam, LPARAM lParam);
|
|
|
|
static inline int to_red_mouse_state(WPARAM wParam)
|
|
{
|
|
return ((wParam & MK_LBUTTON) ? SPICE_MOUSE_BUTTON_MASK_LEFT : 0) |
|
|
((wParam & MK_MBUTTON) ? SPICE_MOUSE_BUTTON_MASK_MIDDLE : 0) |
|
|
((wParam & MK_RBUTTON) ? SPICE_MOUSE_BUTTON_MASK_RIGHT : 0);
|
|
}
|
|
|
|
static inline RedKey translate_key(int virtual_key, uint32_t scan, bool escape)
|
|
{
|
|
if (scan == 0) {
|
|
return REDKEY_INVALID;
|
|
}
|
|
switch (virtual_key) {
|
|
case VK_PAUSE:
|
|
return REDKEY_PAUSE;
|
|
case VK_SNAPSHOT:
|
|
return REDKEY_CTRL_PRINT_SCREEN;
|
|
case VK_NUMLOCK:
|
|
return REDKEY_NUM_LOCK;
|
|
case VK_HANGUL:
|
|
return REDKEY_KOREAN_HANGUL;
|
|
case VK_HANJA:
|
|
return REDKEY_KOREAN_HANGUL_HANJA;
|
|
case VK_PROCESSKEY:
|
|
if (scan == 0xf1) {
|
|
return REDKEY_INVALID; // prevent double key (VK_PROCESSKEY + VK_HANJA)
|
|
} else if (scan == 0xf2) {
|
|
return REDKEY_KOREAN_HANGUL;
|
|
}
|
|
default:
|
|
//todo: always use vitrtual key
|
|
if (escape) {
|
|
scan += REDKEY_ESCAPE_BASE;
|
|
}
|
|
return (RedKey)scan;
|
|
}
|
|
}
|
|
|
|
static int menu_cmd_to_app(WPARAM wparam)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void send_filtered_keys(RedWindow* window)
|
|
{
|
|
KeysList::iterator iter;
|
|
|
|
for (iter = filtered_up_keys.begin(); iter != filtered_up_keys.end(); iter++) {
|
|
window->get_listener().on_key_release(*iter);
|
|
}
|
|
filtered_up_keys.clear();
|
|
}
|
|
|
|
static inline bool is_high_surrogate(uint32_t val)
|
|
{
|
|
return val >= 0xd800 && val <= 0xdbff;
|
|
}
|
|
|
|
static inline bool is_low_surrogate(uint32_t val)
|
|
{
|
|
return val >= 0xdc00 && val <= 0xdfff;
|
|
}
|
|
|
|
static uint32_t utf16_to_utf32(uint16_t*& utf16, int& len)
|
|
{
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t val = utf16[0];
|
|
|
|
if (!is_high_surrogate(val)) {
|
|
utf16++;
|
|
len--;
|
|
return val;
|
|
}
|
|
|
|
if (len < 2) {
|
|
THROW("partial char");
|
|
}
|
|
|
|
uint32_t val2 = utf16[1];
|
|
|
|
if (!is_low_surrogate(val2)) {
|
|
THROW("invalid sequence");
|
|
}
|
|
|
|
utf16 += 2;
|
|
len -= 2;
|
|
|
|
return (((val & 0x3ff) << 10) | (val2 & 0x3ff)) + 0x10000;
|
|
}
|
|
|
|
LRESULT CALLBACK RedWindow_p::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RedWindow* window = (RedWindow*)GetWindowLong(hWnd, GWL_USERDATA);
|
|
ASSERT(window);
|
|
|
|
switch (message) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC hdc;
|
|
|
|
hdc = BeginPaint(hWnd, &ps);
|
|
SpicePoint origin = window->get_origin();
|
|
SpiceRect r;
|
|
r.left = ps.rcPaint.left - origin.x;
|
|
r.top = ps.rcPaint.top - origin.y;
|
|
r.right = ps.rcPaint.right - origin.x;
|
|
r.bottom = ps.rcPaint.bottom - origin.y;
|
|
window->get_listener().on_exposed_rect(r);
|
|
EndPaint(hWnd, &ps);
|
|
break;
|
|
}
|
|
case WM_MOUSEMOVE: {
|
|
SpicePoint origin = window->get_origin();
|
|
int x = LOWORD(lParam) - origin.x;
|
|
int y = HIWORD(lParam) - origin.y;
|
|
unsigned int buttons_state = to_red_mouse_state(wParam);
|
|
if (!window->_pointer_in_window) {
|
|
window->on_pointer_enter(x, y, buttons_state);
|
|
} else {
|
|
window->get_listener().on_pointer_motion(x, y, buttons_state);
|
|
}
|
|
break;
|
|
}
|
|
case WM_MOUSELEAVE:
|
|
window->on_pointer_leave();
|
|
break;
|
|
case WM_SETFOCUS:
|
|
window->on_focus_in();
|
|
break;
|
|
case WM_KILLFOCUS:
|
|
window->on_focus_out();
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
window->get_listener().on_mouse_button_press(SPICE_MOUSE_BUTTON_LEFT,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
window->get_listener().on_mouse_button_release(SPICE_MOUSE_BUTTON_LEFT,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
window->get_listener().on_mouse_button_press(SPICE_MOUSE_BUTTON_RIGHT,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_RBUTTONUP:
|
|
window->get_listener().on_mouse_button_release(SPICE_MOUSE_BUTTON_RIGHT,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
window->get_listener().on_mouse_button_press(SPICE_MOUSE_BUTTON_MIDDLE,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_MBUTTONUP:
|
|
window->get_listener().on_mouse_button_release(SPICE_MOUSE_BUTTON_MIDDLE,
|
|
to_red_mouse_state(wParam));
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
if (HIWORD(wParam) & 0x8000) {
|
|
window->get_listener().on_mouse_button_press(SPICE_MOUSE_BUTTON_DOWN,
|
|
to_red_mouse_state(wParam));
|
|
window->get_listener().on_mouse_button_release(SPICE_MOUSE_BUTTON_DOWN,
|
|
to_red_mouse_state(wParam));
|
|
} else {
|
|
window->get_listener().on_mouse_button_press(SPICE_MOUSE_BUTTON_UP,
|
|
to_red_mouse_state(wParam));
|
|
window->get_listener().on_mouse_button_release(SPICE_MOUSE_BUTTON_UP,
|
|
to_red_mouse_state(wParam));
|
|
}
|
|
break;
|
|
case WM_SYSKEYDOWN:
|
|
case WM_KEYDOWN: {
|
|
RedKey key = translate_key(wParam, HIWORD(lParam) & 0xff, (lParam & (1 << 24)) != 0);
|
|
window->get_listener().on_key_press(key);
|
|
|
|
BYTE key_state[256];
|
|
WCHAR buff[10];
|
|
uint16_t* str_buf = (uint16_t*)buff;
|
|
GetKeyboardState(key_state);
|
|
int n = ToUnicode(wParam, HIWORD(lParam) & 0xff, key_state, buff, 10, 0);
|
|
if (n > 0) {
|
|
uint32_t utf32;
|
|
while ((utf32 = utf16_to_utf32(str_buf, n)) != 0) {
|
|
window->get_listener().on_char(utf32);
|
|
}
|
|
}
|
|
|
|
// Allow Windows to translate Alt-F4 to WM_CLOSE message.
|
|
if (!window->_key_interception_on) {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
break;
|
|
}
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYUP: {
|
|
RedKey key = translate_key(wParam, HIWORD(lParam) & 0xff, (lParam & (1 << 24)) != 0);
|
|
window->get_listener().on_key_release(key);
|
|
break;
|
|
}
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
break;
|
|
case WM_GETMINMAXINFO: {
|
|
MINMAXINFO* info = (MINMAXINFO*)lParam;
|
|
info->ptMaxSize.x = window->_window_size.x;
|
|
info->ptMaxSize.y = window->_window_size.y;
|
|
info->ptMinTrackSize = info->ptMaxSize;
|
|
info->ptMaxTrackSize = info->ptMaxSize;
|
|
info->ptMaxPosition.x = info->ptMaxPosition.y = 0;
|
|
break;
|
|
}
|
|
case WM_SYSCOMMAND:
|
|
if (window->prossec_menu_commands(wParam & ~0x0f)) {
|
|
break;
|
|
}
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
case WM_ENTERSIZEMOVE:
|
|
case WM_ENTERMENULOOP:
|
|
ASSERT(filtered_up_keys.empty());
|
|
DBG(0, "enter modal");
|
|
window->get_listener().enter_modal_loop();
|
|
WinPlatform::enter_modal_loop();
|
|
if (msg_filter_hook) {
|
|
LOG_WARN("entering modal loop while filter hook is active");
|
|
UnhookWindowsHookEx(msg_filter_hook);
|
|
}
|
|
msg_filter_hook = SetWindowsHookEx(WH_MSGFILTER, MessageFilterProc,
|
|
GetModuleHandle(NULL), GetCurrentThreadId());
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
case WM_EXITSIZEMOVE:
|
|
case WM_EXITMENULOOP:
|
|
DBG(0, "exit modal");
|
|
window->get_listener().exit_modal_loop();
|
|
WinPlatform::exit_modal_loop();
|
|
UnhookWindowsHookEx(msg_filter_hook);
|
|
msg_filter_hook = NULL;
|
|
send_filtered_keys(window);
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
case WM_SETCURSOR:
|
|
if (!window->_pointer_in_window) {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
if (wParam == SIZE_MINIMIZED) {
|
|
window->on_minimized();
|
|
} else if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) {
|
|
window->on_restored();
|
|
}
|
|
break;
|
|
case WM_WINDOWPOSCHANGING:
|
|
window->on_pos_changing(*window);
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
default:
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ATOM register_class(HINSTANCE instance)
|
|
{
|
|
WNDCLASSEX wclass;
|
|
|
|
wclass.cbSize = sizeof(WNDCLASSEX);
|
|
wclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
wclass.lpfnWndProc = DefWindowProc;
|
|
wclass.cbClsExtra = 0;
|
|
wclass.cbWndExtra = 0;
|
|
wclass.hInstance = instance;
|
|
wclass.hIcon = NULL;
|
|
wclass.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wclass.hbrBackground = NULL;
|
|
wclass.lpszMenuName = NULL;
|
|
wclass.lpszClassName = win_class_name;
|
|
wclass.hIconSm = NULL;
|
|
return RegisterClassEx(&wclass);
|
|
}
|
|
|
|
RedWindow_p::RedWindow_p()
|
|
: _win (NULL)
|
|
, _modal_refs (0)
|
|
, _no_taskmgr_dll (NULL)
|
|
, _no_taskmgr_hook (NULL)
|
|
, _focused (false)
|
|
, _pointer_in_window (false)
|
|
, _minimized (false)
|
|
, _valid_pos (false)
|
|
, _sys_menu (NULL)
|
|
{
|
|
}
|
|
|
|
void RedWindow_p::create(RedWindow& red_window, PixelsSource_p& pixels_source)
|
|
{
|
|
HWND window;
|
|
if (!(window = CreateWindow(win_class_name, L"", NATIVE_CAPTION_STYLE, CW_USEDEFAULT,
|
|
0, CW_USEDEFAULT, 0, NULL, NULL, NULL, NULL))) {
|
|
THROW("create window failed");
|
|
}
|
|
HDC dc = GetDC(window);
|
|
if (!dc) {
|
|
THROW("get dc failed");
|
|
}
|
|
_win = window;
|
|
pixels_source.dc = dc;
|
|
|
|
int depth = GetDeviceCaps(dc, BITSPIXEL);
|
|
switch (depth) {
|
|
case 16:
|
|
_format = RedDrawable::RGB16_555;
|
|
break;
|
|
case 32:
|
|
default:
|
|
_format = RedDrawable::RGB32;
|
|
break;
|
|
}
|
|
SetWindowLong(window, GWL_USERDATA, (LONG)&red_window);
|
|
SetWindowLong(window, GWL_WNDPROC, (LONG)WindowProc);
|
|
}
|
|
|
|
void RedWindow_p::destroy(PixelsSource_p& pixels_source)
|
|
{
|
|
if (!_win) {
|
|
return;
|
|
}
|
|
|
|
ReleaseDC(_win, pixels_source.dc);
|
|
SetWindowLong(_win, GWL_WNDPROC, (LONG)DefWindowProc);
|
|
SetWindowLong(_win, GWL_USERDATA, NULL);
|
|
DestroyWindow(_win);
|
|
}
|
|
|
|
RedDrawable::Format RedWindow::get_format()
|
|
{
|
|
return _format;
|
|
}
|
|
|
|
|
|
void RedWindow_p::on_pos_changing(RedWindow& red_window)
|
|
{
|
|
if (_minimized || IsIconic(_win)) {
|
|
return;
|
|
}
|
|
SpicePoint pos = red_window.get_position();
|
|
_x = pos.x;
|
|
_y = pos.y;
|
|
_valid_pos = true;
|
|
}
|
|
|
|
void RedWindow_p::on_minimized()
|
|
{
|
|
_minimized = true;
|
|
}
|
|
|
|
void RedWindow_p::on_restored()
|
|
{
|
|
if (!_minimized) {
|
|
return;
|
|
}
|
|
_minimized = false;
|
|
if (!_valid_pos) {
|
|
return;
|
|
}
|
|
_valid_pos = false;
|
|
SetWindowPos(_win, NULL, _x, _y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
|
|
}
|
|
|
|
bool RedWindow_p::prossec_menu_commands(int cmd)
|
|
{
|
|
CommandMap::iterator iter = _commands_map.find(cmd);
|
|
if (iter == _commands_map.end()) {
|
|
return false;
|
|
}
|
|
(*iter).second.menu->get_target().do_command((*iter).second.command);
|
|
return true;
|
|
}
|
|
|
|
RedWindow::RedWindow(RedWindow::Listener& listener, int screen_id)
|
|
: _listener (listener)
|
|
, _type (TYPE_NORMAL)
|
|
, _local_cursor (NULL)
|
|
, _cursor_visible (true)
|
|
, _trace_key_interception (false)
|
|
, _key_interception_on (false)
|
|
, _menu (NULL)
|
|
{
|
|
RECT win_rect;
|
|
|
|
create(*this, *(PixelsSource_p*)get_opaque());
|
|
GetWindowRect(_win, &win_rect);
|
|
_window_size.x = win_rect.right - win_rect.left;
|
|
_window_size.y = win_rect.bottom - win_rect.top;
|
|
}
|
|
|
|
RedWindow::~RedWindow()
|
|
{
|
|
release_menu(_menu);
|
|
destroy(*(PixelsSource_p*)get_opaque());
|
|
if (_local_cursor) {
|
|
_local_cursor->unref();
|
|
}
|
|
}
|
|
|
|
void RedWindow::set_title(std::string& title)
|
|
{
|
|
SetWindowText(_win, title.c_str());
|
|
}
|
|
|
|
void RedWindow::set_icon(Icon* icon)
|
|
{
|
|
if (!icon) {
|
|
return;
|
|
}
|
|
WinIcon* w_icon = (WinIcon *)icon;
|
|
SendMessage(_win, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)w_icon->get_handle());
|
|
SendMessage(_win, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)w_icon->get_handle());
|
|
}
|
|
|
|
void RedWindow::raise()
|
|
{
|
|
SetWindowPos(_win, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
|
|
void RedWindow::position_after(RedWindow *win)
|
|
{
|
|
HWND after = NULL;
|
|
|
|
if (win) {
|
|
after = win->_win;
|
|
}
|
|
SetWindowPos(_win, after, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
|
|
static LONG to_native_style(RedWindow::Type type)
|
|
{
|
|
LONG win_style;
|
|
|
|
switch (type) {
|
|
case RedWindow::TYPE_NORMAL:
|
|
win_style = NATIVE_CAPTION_STYLE;
|
|
break;
|
|
case RedWindow::TYPE_FULLSCREEN:
|
|
win_style = 0;
|
|
break;
|
|
default:
|
|
THROW("invalid type %d", type);
|
|
}
|
|
return win_style;
|
|
}
|
|
|
|
void RedWindow::show(int screen_id)
|
|
{
|
|
if (IsIconic(_win)) {
|
|
ShowWindow(_win, SW_RESTORE);
|
|
}
|
|
|
|
const UINT set_pos_flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW |
|
|
SWP_FRAMECHANGED;
|
|
HWND pos;
|
|
|
|
SetWindowLong(_win, GWL_STYLE, to_native_style(_type));
|
|
switch (_type) {
|
|
case TYPE_NORMAL:
|
|
pos = HWND_NOTOPMOST;
|
|
break;
|
|
case TYPE_FULLSCREEN:
|
|
pos = HWND_TOPMOST;
|
|
break;
|
|
default:
|
|
THROW("invalid type %d", _type);
|
|
}
|
|
SetWindowPos(_win, pos, 0, 0, 0, 0, set_pos_flags);
|
|
}
|
|
|
|
void RedWindow::external_show()
|
|
{
|
|
LONG_PTR style = ::GetWindowLongPtr(_win, GWL_STYLE);
|
|
if ((style & WS_MINIMIZE) == WS_MINIMIZE) {
|
|
ShowWindow(_win, SW_RESTORE);
|
|
} else {
|
|
// Handle the case when hide() was called and the window is not
|
|
// visible. Since we're not the active window, the call just set the
|
|
// windows' style and doesn't show the window.
|
|
if ((style & WS_VISIBLE) != WS_VISIBLE) {
|
|
show(0);
|
|
}
|
|
// We're not the active the window, so we must be attached to the
|
|
// calling thread's message queue before focus is grabbed.
|
|
HWND front = GetForegroundWindow();
|
|
if (front != NULL) {
|
|
DWORD thread = GetWindowThreadProcessId(front, NULL);
|
|
AttachThreadInput(thread, GetCurrentThreadId(), TRUE);
|
|
SetFocus(_win);
|
|
AttachThreadInput(thread, GetCurrentThreadId(), FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedWindow::hide()
|
|
{
|
|
ShowWindow(_win, SW_HIDE);
|
|
}
|
|
|
|
static void client_to_window_size(HWND win, int width, int height, SpicePoint& win_size,
|
|
RedWindow::Type type)
|
|
{
|
|
RECT area;
|
|
|
|
SetRect(&area, 0, 0, width, height);
|
|
AdjustWindowRectEx(&area, to_native_style(type), FALSE, GetWindowLong(win, GWL_EXSTYLE));
|
|
win_size.x = area.right - area.left;
|
|
win_size.y = area.bottom - area.top;
|
|
}
|
|
|
|
void RedWindow::move_and_resize(int x, int y, int width, int height)
|
|
{
|
|
client_to_window_size(_win, width, height, _window_size, _type);
|
|
SetWindowPos(_win, NULL, x, y, _window_size.x, _window_size.y, SWP_NOACTIVATE | SWP_NOZORDER);
|
|
if (_minimized) {
|
|
_valid_pos = true;
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
}
|
|
|
|
void RedWindow::move(int x, int y)
|
|
{
|
|
SetWindowPos(_win, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
|
|
if (_minimized) {
|
|
_valid_pos = true;
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
}
|
|
|
|
void RedWindow::resize(int width, int height)
|
|
{
|
|
client_to_window_size(_win, width, height, _window_size, _type);
|
|
SetWindowPos(_win, NULL, 0, 0, _window_size.x, _window_size.y,
|
|
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
|
|
}
|
|
|
|
void RedWindow::activate()
|
|
{
|
|
SetActiveWindow(_win);
|
|
SetFocus(_win);
|
|
}
|
|
|
|
void RedWindow::minimize()
|
|
{
|
|
ShowWindow(_win, SW_SHOWMINIMIZED);
|
|
}
|
|
|
|
void RedWindow::set_mouse_position(int x, int y)
|
|
{
|
|
POINT pt;
|
|
pt.x = x + get_origin().x;
|
|
pt.y = y + get_origin().y;
|
|
ClientToScreen(_win, &pt);
|
|
SetCursorPos(pt.x, pt.y);
|
|
}
|
|
|
|
class Region_p {
|
|
public:
|
|
Region_p(HRGN region) : _region (region) {}
|
|
~Region_p() {}
|
|
|
|
void get_bbox(SpiceRect& bbox) const
|
|
{
|
|
RECT box;
|
|
|
|
if (GetRgnBox(_region, &box) == 0) {
|
|
THROW("get region bbox failed");
|
|
}
|
|
bbox.left = box.left;
|
|
bbox.right = box.right;
|
|
bbox.top = box.top;
|
|
bbox.bottom = box.bottom;
|
|
}
|
|
|
|
bool contains_point(int x, int y) const
|
|
{
|
|
return !!PtInRegion(_region, x, y);
|
|
}
|
|
|
|
private:
|
|
HRGN _region;
|
|
};
|
|
|
|
bool RedWindow::get_mouse_anchor_point(SpicePoint& pt)
|
|
{
|
|
AutoGDIObject region(CreateRectRgn(0, 0, 0, 0));
|
|
WindowDC win_dc(_win);
|
|
|
|
GetRandomRgn(*win_dc, (HRGN)region.get(), SYSRGN);
|
|
SpicePoint anchor;
|
|
Region_p region_p((HRGN)region.get());
|
|
if (!find_anchor_point(region_p, anchor)) {
|
|
return false;
|
|
}
|
|
POINT screen_pt;
|
|
screen_pt.x = anchor.x;
|
|
screen_pt.y = anchor.y;
|
|
ScreenToClient(_win, &screen_pt);
|
|
pt.x = screen_pt.x - get_origin().x;
|
|
pt.y = screen_pt.y - get_origin().y;
|
|
return true;
|
|
}
|
|
|
|
void RedWindow::cupture_mouse()
|
|
{
|
|
RECT client_rect;
|
|
POINT origin;
|
|
|
|
origin.x = origin.y = 0;
|
|
ClientToScreen(_win, &origin);
|
|
GetClientRect(_win, &client_rect);
|
|
OffsetRect(&client_rect, origin.x, origin.y);
|
|
ClipCursor(&client_rect);
|
|
}
|
|
|
|
void RedWindow::release_mouse()
|
|
{
|
|
ClipCursor(NULL);
|
|
}
|
|
|
|
void RedWindow::set_cursor(LocalCursor* local_cursor)
|
|
{
|
|
ASSERT(local_cursor);
|
|
if (_local_cursor) {
|
|
_local_cursor->unref();
|
|
}
|
|
_local_cursor = local_cursor->ref();
|
|
if (_pointer_in_window) {
|
|
_local_cursor->set(_win);
|
|
while (ShowCursor(TRUE) < 0);
|
|
}
|
|
_cursor_visible = true;
|
|
}
|
|
|
|
void RedWindow::hide_cursor()
|
|
{
|
|
if (_cursor_visible) {
|
|
if (_pointer_in_window) {
|
|
while (ShowCursor(FALSE) > -1);
|
|
}
|
|
_cursor_visible = false;
|
|
}
|
|
}
|
|
|
|
void RedWindow::show_cursor()
|
|
{
|
|
if (!_cursor_visible) {
|
|
if (_pointer_in_window) {
|
|
while (ShowCursor(TRUE) < 0);
|
|
}
|
|
_cursor_visible = true;
|
|
}
|
|
}
|
|
|
|
SpicePoint RedWindow::get_position()
|
|
{
|
|
SpicePoint position;
|
|
if (_minimized || IsIconic(_win)) {
|
|
if (_valid_pos) {
|
|
position.x = _x;
|
|
position.y = _y;
|
|
} else {
|
|
position.x = position.y = 0;
|
|
}
|
|
} else {
|
|
RECT window_rect;
|
|
GetWindowRect(_win, &window_rect);
|
|
position.x = window_rect.left;
|
|
position.y = window_rect.top;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
SpicePoint RedWindow::get_size()
|
|
{
|
|
RECT client_rect;
|
|
GetClientRect(_win, &client_rect);
|
|
SpicePoint pt = {client_rect.right - client_rect.left, client_rect.bottom - client_rect.top};
|
|
return pt;
|
|
}
|
|
|
|
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (low_keyboard_hook_on && focus_window && nCode == HC_ACTION) {
|
|
KBDLLHOOKSTRUCT *hooked = (KBDLLHOOKSTRUCT*)lParam;
|
|
DWORD dwMsg = (hooked->flags << 24) | (hooked->scanCode << 16) | 1;
|
|
|
|
if (hooked->vkCode == VK_NUMLOCK || hooked->vkCode == VK_RSHIFT) {
|
|
dwMsg &= ~(1 << 24);
|
|
SendMessage(focus_window, wParam, hooked->vkCode, dwMsg);
|
|
}
|
|
switch (hooked->vkCode) {
|
|
case VK_CAPITAL:
|
|
case VK_SCROLL:
|
|
case VK_NUMLOCK:
|
|
case VK_LSHIFT:
|
|
case VK_RSHIFT:
|
|
case VK_LCONTROL:
|
|
case VK_RCONTROL:
|
|
case VK_LMENU:
|
|
case VK_RMENU:
|
|
break;
|
|
default:
|
|
SendMessage(focus_window, wParam, hooked->vkCode, dwMsg);
|
|
return 1;
|
|
}
|
|
}
|
|
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
|
}
|
|
|
|
void RedWindow::do_start_key_interception()
|
|
{
|
|
low_keyboard_hook_on = true;
|
|
_key_interception_on = true;
|
|
_listener.on_start_key_interception();
|
|
}
|
|
|
|
void RedWindow::do_stop_key_interception()
|
|
{
|
|
low_keyboard_hook_on = false;
|
|
_key_interception_on = false;
|
|
_listener.on_stop_key_interception();
|
|
}
|
|
|
|
void RedWindow::start_key_interception()
|
|
{
|
|
if (_trace_key_interception) {
|
|
return;
|
|
}
|
|
_trace_key_interception = true;
|
|
if (_focused && _pointer_in_window) {
|
|
do_start_key_interception();
|
|
}
|
|
}
|
|
|
|
void RedWindow::stop_key_interception()
|
|
{
|
|
if (!_trace_key_interception) {
|
|
return;
|
|
}
|
|
_trace_key_interception = false;
|
|
if (_key_interception_on) {
|
|
do_stop_key_interception();
|
|
}
|
|
}
|
|
|
|
void RedWindow::init()
|
|
{
|
|
if (!(class_atom = register_class(instance))) {
|
|
THROW("register class failed");
|
|
}
|
|
low_keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc,
|
|
GetModuleHandle(NULL), 0);
|
|
}
|
|
|
|
void RedWindow::cleanup()
|
|
{
|
|
UnhookWindowsHookEx(low_keyboard_hook);
|
|
}
|
|
|
|
#ifdef USE_OGL
|
|
|
|
void RedWindow::touch_context_draw()
|
|
{
|
|
}
|
|
|
|
void RedWindow::touch_context_copy()
|
|
{
|
|
}
|
|
|
|
void RedWindow::untouch_context()
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
void RedWindow::on_focus_in()
|
|
{
|
|
_focused = true;
|
|
focus_window = _win;
|
|
get_listener().on_activate();
|
|
if (_pointer_in_window && _trace_key_interception) {
|
|
do_start_key_interception();
|
|
}
|
|
}
|
|
|
|
void RedWindow::on_focus_out()
|
|
{
|
|
if (!_focused) {
|
|
return;
|
|
}
|
|
|
|
_focused = false;
|
|
focus_window = NULL;
|
|
|
|
if (_key_interception_on) {
|
|
do_stop_key_interception();
|
|
}
|
|
get_listener().on_deactivate();
|
|
}
|
|
|
|
void RedWindow::on_pointer_enter(int x, int y, unsigned int buttons_state)
|
|
{
|
|
if (_pointer_in_window) {
|
|
return;
|
|
}
|
|
|
|
if (_cursor_visible) {
|
|
if (_local_cursor) {
|
|
_local_cursor->set(_win);
|
|
}
|
|
while (ShowCursor(TRUE) < 0);
|
|
} else {
|
|
while (ShowCursor(FALSE) > -1);
|
|
}
|
|
_pointer_in_window = true;
|
|
_listener.on_pointer_enter(x, y, buttons_state);
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = _win;
|
|
if (!TrackMouseEvent(&tme)) {
|
|
THROW("track mouse event failed");
|
|
}
|
|
if (_focused && _trace_key_interception) {
|
|
do_start_key_interception();
|
|
}
|
|
}
|
|
|
|
void RedWindow::on_pointer_leave()
|
|
{
|
|
if (!_pointer_in_window) {
|
|
return;
|
|
}
|
|
if (!_cursor_visible) {
|
|
while (ShowCursor(TRUE) < 0);
|
|
}
|
|
_pointer_in_window = false;
|
|
_listener.on_pointer_leave();
|
|
if (_key_interception_on) {
|
|
do_stop_key_interception();
|
|
}
|
|
}
|
|
|
|
static void insert_seperator(HMENU menu)
|
|
{
|
|
MENUITEMINFO item_info;
|
|
item_info.cbSize = sizeof(item_info);
|
|
item_info.fMask = MIIM_TYPE;
|
|
item_info.fType = MFT_SEPARATOR;
|
|
item_info.dwTypeData = NULL;
|
|
item_info.dwItemData = 0;
|
|
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &item_info);
|
|
}
|
|
|
|
static void utf8_to_wchar(const std::string& src, std::wstring& dest)
|
|
{
|
|
int len = MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, NULL, 0);
|
|
if (!len) {
|
|
THROW("fail to conver utf8 to wchar");
|
|
}
|
|
dest.resize(len);
|
|
MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, (wchar_t *)dest.c_str(), len);
|
|
}
|
|
|
|
static void insert_command(HMENU menu, const std::string& name, int id, int state)
|
|
{
|
|
MENUITEMINFO item_info;
|
|
item_info.cbSize = sizeof(item_info);
|
|
item_info.fMask = MIIM_ID | MIIM_STRING | MIIM_STATE;
|
|
item_info.fType = MFT_STRING;
|
|
std::wstring wname;
|
|
utf8_to_wchar(name, wname);
|
|
item_info.cch = wname.size();
|
|
item_info.dwTypeData = (wchar_t *)wname.c_str();
|
|
item_info.wID = id;
|
|
item_info.fState = MFS_ENABLED;
|
|
if (state & Menu::MENU_ITEM_STATE_CHECKED) {
|
|
item_info.fState |= MFS_CHECKED;
|
|
}
|
|
if (state & Menu::MENU_ITEM_STATE_DIM) {
|
|
item_info.fState |= MFS_DISABLED;
|
|
}
|
|
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &item_info);
|
|
}
|
|
|
|
static HMENU insert_sub_menu(HMENU menu, const std::string& name)
|
|
{
|
|
MENUITEMINFO item_info;
|
|
item_info.cbSize = sizeof(item_info);
|
|
item_info.fMask = MIIM_TYPE | MIIM_SUBMENU;
|
|
item_info.fType = MFT_STRING;
|
|
std::wstring wname;
|
|
utf8_to_wchar(name, wname);
|
|
item_info.cch = wname.size();
|
|
item_info.dwTypeData = (wchar_t *)wname.c_str();
|
|
item_info.hSubMenu = CreateMenu();
|
|
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &item_info);
|
|
return item_info.hSubMenu;
|
|
}
|
|
|
|
static int next_free_id = 1;
|
|
static const int last_id = 0x0f00;
|
|
|
|
static std::list<int> free_sys_menu_id;
|
|
|
|
static int alloc_sys_cmd_id()
|
|
{
|
|
if (!free_sys_menu_id.empty()) {
|
|
int ret = *free_sys_menu_id.begin();
|
|
free_sys_menu_id.pop_front();
|
|
return ret;
|
|
}
|
|
if (next_free_id == last_id) {
|
|
THROW("failed");
|
|
}
|
|
|
|
return next_free_id++ << 4;
|
|
}
|
|
|
|
static void free_sys_cmd_id(int id)
|
|
{
|
|
free_sys_menu_id.push_back(id);
|
|
}
|
|
|
|
static void insert_menu(Menu* menu, HMENU native, CommandMap& _commands_map)
|
|
{
|
|
int pos = 0;
|
|
|
|
for (;; pos++) {
|
|
Menu::ItemType type = menu->item_type_at(pos);
|
|
switch (type) {
|
|
case Menu::MENU_ITEM_TYPE_COMMAND: {
|
|
std::string name;
|
|
int command_id;
|
|
int state;
|
|
menu->command_at(pos, name, command_id, state);
|
|
int sys_command = alloc_sys_cmd_id();
|
|
_commands_map[sys_command] = CommandInfo(menu, command_id);
|
|
insert_command(native, name, sys_command, state);
|
|
break;
|
|
}
|
|
case Menu::MENU_ITEM_TYPE_MENU: {
|
|
AutoRef<Menu> sub_menu(menu->sub_at(pos));
|
|
HMENU native_sub = insert_sub_menu(native, (*sub_menu)->get_name());
|
|
insert_menu(*sub_menu, native_sub, _commands_map);
|
|
break;
|
|
}
|
|
case Menu::MENU_ITEM_TYPE_SEPARATOR:
|
|
insert_seperator(native);
|
|
break;
|
|
case Menu::MENU_ITEM_TYPE_INVALID:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedWindow_p::release_menu(Menu* menu)
|
|
{
|
|
if (menu) {
|
|
while (!_commands_map.empty()) {
|
|
free_sys_cmd_id((*_commands_map.begin()).first);
|
|
_commands_map.erase(_commands_map.begin());
|
|
}
|
|
GetSystemMenu(_win, TRUE);
|
|
_sys_menu = NULL;
|
|
menu->unref();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void RedWindow::set_menu(Menu* menu)
|
|
{
|
|
release_menu(_menu);
|
|
_menu = NULL;
|
|
|
|
if (!menu) {
|
|
return;
|
|
}
|
|
_menu = menu->ref();
|
|
_sys_menu = GetSystemMenu(_win, FALSE);
|
|
insert_seperator(_sys_menu);
|
|
insert_menu(_menu, _sys_menu, _commands_map);
|
|
}
|
|
|
|
static LRESULT CALLBACK MessageFilterProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (nCode >= 0)
|
|
{
|
|
MSG* msg = (MSG*)lParam;
|
|
|
|
switch (msg->message) {
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYUP: {
|
|
RedKey key = translate_key(msg->wParam, HIWORD(msg->lParam) & 0xff,
|
|
(msg->lParam & (1 << 24)) != 0);
|
|
filtered_up_keys.push_back(key);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
|
}
|