/*
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 .
*/
#include "common.h"
#include "red_window.h"
#include "pixels_source_p.h"
#include "utils.h"
#include "debug.h"
#include
#include "menu.h"
#include "win_platform.h"
#include "platform_utils.h"
#include
#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 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::wstring& 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 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 >> 4);
}
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