/*
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
#include
#include
#include
#include
#ifdef USE_OGL
#include
#include
#include
#include
#endif // USE_OGL
#include
#include "red_window.h"
#include "utils.h"
#include "debug.h"
#include "platform.h"
#include "x_platform.h"
#include "pixels_source_p.h"
#include
#include "region.h"
#ifdef USE_OGL
#include "gl_utils.h"
#include "red_pixmap_gl.h"
#endif // USE_OGL
#include "x_icon.h"
#define X_RETRIES 10
#define X_RETRY_DELAY_MICRO (1000 * 100)
#define RAISE_RETRIES X_RETRIES
#define Z_POSITION_RETRIES X_RETRIES
#define GET_POSITION_RETRIES X_RETRIES
#define GET_VIS_REGION_RETRIES X_RETRIES
#define MOUSE_GRAB_RETRIES X_RETRIES
static Display* x_display = NULL;
static XContext user_data_context;
static bool using_evdev = false;
static XIC x_input_context = NULL;
static Atom wm_protocol_atom;
static Atom wm_delete_window_atom;
static Atom wm_desktop;
static Atom wm_current_desktop;
static Atom wm_state;
static Atom wm_state_above;
static Atom wm_state_fullscreen;
static Atom wm_user_time;
static RedWindow* focus_window;
static unsigned long focus_serial = 0;
#define USE_X11_KEYCODE
#ifdef USE_X11_KEYCODE
enum EvdevKeyCode {
EVDEV_KEYCODE_ESCAPE = 9,
EVDEV_KEYCODE_1,
EVDEV_KEYCODE_2,
EVDEV_KEYCODE_3,
EVDEV_KEYCODE_4,
EVDEV_KEYCODE_5,
EVDEV_KEYCODE_6,
EVDEV_KEYCODE_7,
EVDEV_KEYCODE_8,
EVDEV_KEYCODE_9,
EVDEV_KEYCODE_0,
EVDEV_KEYCODE_MINUS,
EVDEV_KEYCODE_EQUAL,
EVDEV_KEYCODE_BACK_SPACE,
EVDEV_KEYCODE_TAB,
EVDEV_KEYCODE_Q,
EVDEV_KEYCODE_W,
EVDEV_KEYCODE_E,
EVDEV_KEYCODE_R,
EVDEV_KEYCODE_T,
EVDEV_KEYCODE_Y,
EVDEV_KEYCODE_U,
EVDEV_KEYCODE_I,
EVDEV_KEYCODE_O,
EVDEV_KEYCODE_P,
EVDEV_KEYCODE_L_BRACKET,
EVDEV_KEYCODE_R_BRACKET,
EVDEV_KEYCODE_RETURN,
EVDEV_KEYCODE_L_CONTROL,
EVDEV_KEYCODE_A,
EVDEV_KEYCODE_S,
EVDEV_KEYCODE_D,
EVDEV_KEYCODE_F,
EVDEV_KEYCODE_G,
EVDEV_KEYCODE_H,
EVDEV_KEYCODE_J,
EVDEV_KEYCODE_K,
EVDEV_KEYCODE_L,
EVDEV_KEYCODE_SEMICOLON,
EVDEV_KEYCODE_APOSTROPH,
EVDEV_KEYCODE_BACKQUAT,
EVDEV_KEYCODE_L_SHIFT,
EVDEV_KEYCODE_BACKSLASH,
EVDEV_KEYCODE_Z,
EVDEV_KEYCODE_X,
EVDEV_KEYCODE_C,
EVDEV_KEYCODE_V,
EVDEV_KEYCODE_B,
EVDEV_KEYCODE_N,
EVDEV_KEYCODE_M,
EVDEV_KEYCODE_COMMA,
EVDEV_KEYCODE_PERIOD,
EVDEV_KEYCODE_SLASH,
EVDEV_KEYCODE_R_SHIFT,
EVDEV_KEYCODE_PAD_MULTIPLY,
EVDEV_KEYCODE_L_ALT,
EVDEV_KEYCODE_SPACE,
EVDEV_KEYCODE_CAPS_LOCK,
EVDEV_KEYCODE_F1,
EVDEV_KEYCODE_F2,
EVDEV_KEYCODE_F3,
EVDEV_KEYCODE_F4,
EVDEV_KEYCODE_F5,
EVDEV_KEYCODE_F6,
EVDEV_KEYCODE_F7,
EVDEV_KEYCODE_F8,
EVDEV_KEYCODE_F9,
EVDEV_KEYCODE_F10,
EVDEV_KEYCODE_NUM_LOCK,
EVDEV_KEYCODE_SCROLL_LOCK,
EVDEV_KEYCODE_PAD_7,
EVDEV_KEYCODE_PAD_8,
EVDEV_KEYCODE_PAD_9,
EVDEV_KEYCODE_PAD_SUBTRACT,
EVDEV_KEYCODE_PAD_4,
EVDEV_KEYCODE_PAD_5,
EVDEV_KEYCODE_PAD_6,
EVDEV_KEYCODE_PAD_ADD,
EVDEV_KEYCODE_PAD_1,
EVDEV_KEYCODE_PAD_2,
EVDEV_KEYCODE_PAD_3,
EVDEV_KEYCODE_PAD_0,
EVDEV_KEYCODE_PAD_DEL,
EVDEV_KEYCODE_EUROPEAN = 94,
EVDEV_KEYCODE_F11,
EVDEV_KEYCODE_F12,
EVDEV_KEYCODE_JAPANESE_BACKSLASH,
EVDEV_KEYCODE_JAPANESE_HENKAN = 100,
EVDEV_KEYCODE_JAPANESE_HIRAGANA_KATAKANA,
EVDEV_KEYCODE_JAPANESE_MUHENKAN,
EVDEV_KEYCODE_PAD_ENTER = 104,
EVDEV_KEYCODE_R_CONTROL,
EVDEV_KEYCODE_PAD_DEVIDE,
EVDEV_KEYCODE_PRINT,
EVDEV_KEYCODE_R_ALT,
EVDEV_KEYCODE_HOME = 110,
EVDEV_KEYCODE_UP,
EVDEV_KEYCODE_PAGE_UP,
EVDEV_KEYCODE_LEFT,
EVDEV_KEYCODE_RIGHT,
EVDEV_KEYCODE_END,
EVDEV_KEYCODE_DOWN,
EVDEV_KEYCODE_PAGE_DOWN,
EVDEV_KEYCODE_INSERT,
EVDEV_KEYCODE_DELETE,
EVDEV_KEYCODE_PAUSE = 127,
EVDEV_KEYCODE_HANGUL = 130,
EVDEV_KEYCODE_HANGUL_HANJA,
EVDEV_KEYCODE_YEN,
EVDEV_KEYCODE_L_COMMAND,
EVDEV_KEYCODE_R_COMMAND,
EVDEV_KEYCODE_MENU,
};
enum KbdKeyCode {
KBD_KEYCODE_ESCAPE = 9,
KBD_KEYCODE_1,
KBD_KEYCODE_2,
KBD_KEYCODE_3,
KBD_KEYCODE_4,
KBD_KEYCODE_5,
KBD_KEYCODE_6,
KBD_KEYCODE_7,
KBD_KEYCODE_8,
KBD_KEYCODE_9,
KBD_KEYCODE_0,
KBD_KEYCODE_MINUS,
KBD_KEYCODE_EQUAL,
KBD_KEYCODE_BACK_SPACE,
KBD_KEYCODE_TAB,
KBD_KEYCODE_Q,
KBD_KEYCODE_W,
KBD_KEYCODE_E,
KBD_KEYCODE_R,
KBD_KEYCODE_T,
KBD_KEYCODE_Y,
KBD_KEYCODE_U,
KBD_KEYCODE_I,
KBD_KEYCODE_O,
KBD_KEYCODE_P,
KBD_KEYCODE_L_BRACKET,
KBD_KEYCODE_R_BRACKET,
KBD_KEYCODE_RETURN,
KBD_KEYCODE_L_CONTROL,
KBD_KEYCODE_A,
KBD_KEYCODE_S,
KBD_KEYCODE_D,
KBD_KEYCODE_F,
KBD_KEYCODE_G,
KBD_KEYCODE_H,
KBD_KEYCODE_J,
KBD_KEYCODE_K,
KBD_KEYCODE_L,
KBD_KEYCODE_SEMICOLON,
KBD_KEYCODE_APOSTROPH,
KBD_KEYCODE_BACKQUAT,
KBD_KEYCODE_L_SHIFT,
KBD_KEYCODE_BACKSLASH,
KBD_KEYCODE_Z,
KBD_KEYCODE_X,
KBD_KEYCODE_C,
KBD_KEYCODE_V,
KBD_KEYCODE_B,
KBD_KEYCODE_N,
KBD_KEYCODE_M,
KBD_KEYCODE_COMMA,
KBD_KEYCODE_PERIOD,
KBD_KEYCODE_SLASH,
KBD_KEYCODE_R_SHIFT,
KBD_KEYCODE_PAD_MULTIPLY,
KBD_KEYCODE_L_ALT,
KBD_KEYCODE_SPACE,
KBD_KEYCODE_CAPS_LOCK,
KBD_KEYCODE_F1,
KBD_KEYCODE_F2,
KBD_KEYCODE_F3,
KBD_KEYCODE_F4,
KBD_KEYCODE_F5,
KBD_KEYCODE_F6,
KBD_KEYCODE_F7,
KBD_KEYCODE_F8,
KBD_KEYCODE_F9,
KBD_KEYCODE_F10,
KBD_KEYCODE_NUM_LOCK,
KBD_KEYCODE_SCROLL_LOCK,
KBD_KEYCODE_PAD_7,
KBD_KEYCODE_PAD_8,
KBD_KEYCODE_PAD_9,
KBD_KEYCODE_PAD_SUBTRACT,
KBD_KEYCODE_PAD_4,
KBD_KEYCODE_PAD_5,
KBD_KEYCODE_PAD_6,
KBD_KEYCODE_PAD_ADD,
KBD_KEYCODE_PAD_1,
KBD_KEYCODE_PAD_2,
KBD_KEYCODE_PAD_3,
KBD_KEYCODE_PAD_0,
KBD_KEYCODE_PAD_DEL,
KBD_KEYCODE_EUROPEAN = 94,
KBD_KEYCODE_F11,
KBD_KEYCODE_F12,
KBD_KEYCODE_HOME,
KBD_KEYCODE_UP,
KBD_KEYCODE_PAGE_UP,
KBD_KEYCODE_LEFT,
KBD_KEYCODE_RIGHT = 102,
KBD_KEYCODE_END,
KBD_KEYCODE_DOWN,
KBD_KEYCODE_PAGE_DOWN,
KBD_KEYCODE_INSERT,
KBD_KEYCODE_DELETE,
KBD_KEYCODE_PAD_ENTER,
KBD_KEYCODE_R_CONTROL,
KBD_KEYCODE_PAUSE,
KBD_KEYCODE_PRINT,
KBD_KEYCODE_PAD_DEVIDE,
KBD_KEYCODE_R_ALT,
KBD_KEYCODE_L_COMMAND = 115,
KBD_KEYCODE_R_COMMAND,
KBD_KEYCODE_MENU,
KBD_KEYCODE_JAPANESE_HENKAN = 129,
KBD_KEYCODE_JAPANESE_MUHENKAN = 131,
KBD_KEYCODE_YEN = 133,
KBD_KEYCODE_JAPANESE_HIRAGANA_KATAKANA = 208,
KBD_KEYCODE_HANGUL_HANJA,
KBD_KEYCODE_HANGUL,
KBD_KEYCODE_JAPANESE_BACKSLASH,
};
static void query_keyboard()
{
XkbDescPtr kbd_desk = XkbGetKeyboard(x_display, XkbAllComponentsMask, XkbUseCoreKbd);
if (!kbd_desk) {
LOG_WARN("get keyboard failed");
return;
}
char* keycodes = XGetAtomName(x_display, kbd_desk->names->keycodes);
if (keycodes) {
if (strstr(keycodes, "evdev")) {
using_evdev = true;
}
XFree(keycodes);
} else {
LOG_WARN("get name failed");
}
XkbFreeClientMap(kbd_desk, XkbAllComponentsMask, True);
}
static RedKey keycode_map[256];
#define INIT_MAP \
KEYMAP(KEYCODE_ESCAPE, REDKEY_ESCAPE); \
KEYMAP(KEYCODE_1, REDKEY_1); \
KEYMAP(KEYCODE_2, REDKEY_2); \
KEYMAP(KEYCODE_3, REDKEY_3); \
KEYMAP(KEYCODE_4, REDKEY_4); \
KEYMAP(KEYCODE_5, REDKEY_5); \
KEYMAP(KEYCODE_6, REDKEY_6); \
KEYMAP(KEYCODE_7, REDKEY_7); \
KEYMAP(KEYCODE_8, REDKEY_8); \
KEYMAP(KEYCODE_9, REDKEY_9); \
KEYMAP(KEYCODE_0, REDKEY_0); \
KEYMAP(KEYCODE_MINUS, REDKEY_MINUS); \
KEYMAP(KEYCODE_EQUAL, REDKEY_EQUALS); \
KEYMAP(KEYCODE_BACK_SPACE, REDKEY_BACKSPACE); \
KEYMAP(KEYCODE_TAB, REDKEY_TAB); \
KEYMAP(KEYCODE_Q, REDKEY_Q); \
KEYMAP(KEYCODE_W, REDKEY_W); \
KEYMAP(KEYCODE_E, REDKEY_E); \
KEYMAP(KEYCODE_R, REDKEY_R); \
KEYMAP(KEYCODE_T, REDKEY_T); \
KEYMAP(KEYCODE_Y, REDKEY_Y); \
KEYMAP(KEYCODE_U, REDKEY_U); \
KEYMAP(KEYCODE_I, REDKEY_I); \
KEYMAP(KEYCODE_O, REDKEY_O); \
KEYMAP(KEYCODE_P, REDKEY_P); \
KEYMAP(KEYCODE_L_BRACKET, REDKEY_L_BRACKET); \
KEYMAP(KEYCODE_R_BRACKET, REDKEY_R_BRACKET); \
KEYMAP(KEYCODE_RETURN, REDKEY_ENTER); \
KEYMAP(KEYCODE_L_CONTROL, REDKEY_L_CTRL); \
KEYMAP(KEYCODE_A, REDKEY_A); \
KEYMAP(KEYCODE_S, REDKEY_S); \
KEYMAP(KEYCODE_D, REDKEY_D); \
KEYMAP(KEYCODE_F, REDKEY_F); \
KEYMAP(KEYCODE_G, REDKEY_G); \
KEYMAP(KEYCODE_H, REDKEY_H); \
KEYMAP(KEYCODE_J, REDKEY_J); \
KEYMAP(KEYCODE_K, REDKEY_K); \
KEYMAP(KEYCODE_L, REDKEY_L); \
KEYMAP(KEYCODE_SEMICOLON, REDKEY_SEMICOLON); \
KEYMAP(KEYCODE_APOSTROPH, REDKEY_QUOTE); \
KEYMAP(KEYCODE_BACKQUAT, REDKEY_BACK_QUOTE); \
KEYMAP(KEYCODE_L_SHIFT, REDKEY_L_SHIFT); \
KEYMAP(KEYCODE_BACKSLASH, REDKEY_BACK_SLASH); \
KEYMAP(KEYCODE_Z, REDKEY_Z); \
KEYMAP(KEYCODE_X, REDKEY_X); \
KEYMAP(KEYCODE_C, REDKEY_C); \
KEYMAP(KEYCODE_V, REDKEY_V); \
KEYMAP(KEYCODE_B, REDKEY_B); \
KEYMAP(KEYCODE_N, REDKEY_N); \
KEYMAP(KEYCODE_M, REDKEY_M); \
KEYMAP(KEYCODE_COMMA, REDKEY_COMMA); \
KEYMAP(KEYCODE_PERIOD, REDKEY_PERIOD); \
KEYMAP(KEYCODE_SLASH, REDKEY_SLASH); \
KEYMAP(KEYCODE_R_SHIFT, REDKEY_R_SHIFT); \
KEYMAP(KEYCODE_PAD_MULTIPLY, REDKEY_PAD_MULTIPLY); \
KEYMAP(KEYCODE_L_ALT, REDKEY_L_ALT); \
KEYMAP(KEYCODE_SPACE, REDKEY_SPACE); \
KEYMAP(KEYCODE_CAPS_LOCK, REDKEY_CAPS_LOCK); \
KEYMAP(KEYCODE_F1, REDKEY_F1); \
KEYMAP(KEYCODE_F2, REDKEY_F2); \
KEYMAP(KEYCODE_F3, REDKEY_F3); \
KEYMAP(KEYCODE_F4, REDKEY_F4); \
KEYMAP(KEYCODE_F5, REDKEY_F5); \
KEYMAP(KEYCODE_F6, REDKEY_F6); \
KEYMAP(KEYCODE_F7, REDKEY_F7); \
KEYMAP(KEYCODE_F8, REDKEY_F8); \
KEYMAP(KEYCODE_F9, REDKEY_F9); \
KEYMAP(KEYCODE_F10, REDKEY_F10); \
KEYMAP(KEYCODE_NUM_LOCK, REDKEY_NUM_LOCK); \
KEYMAP(KEYCODE_SCROLL_LOCK, REDKEY_SCROLL_LOCK); \
KEYMAP(KEYCODE_PAD_7, REDKEY_PAD_7); \
KEYMAP(KEYCODE_PAD_8, REDKEY_PAD_8); \
KEYMAP(KEYCODE_PAD_9, REDKEY_PAD_9); \
KEYMAP(KEYCODE_PAD_SUBTRACT, REDKEY_PAD_MINUS); \
KEYMAP(KEYCODE_PAD_4, REDKEY_PAD_4); \
KEYMAP(KEYCODE_PAD_5, REDKEY_PAD_5); \
KEYMAP(KEYCODE_PAD_6, REDKEY_PAD_6); \
KEYMAP(KEYCODE_PAD_ADD, REDKEY_PAD_PLUS); \
KEYMAP(KEYCODE_PAD_1, REDKEY_PAD_1); \
KEYMAP(KEYCODE_PAD_2, REDKEY_PAD_2); \
KEYMAP(KEYCODE_PAD_3, REDKEY_PAD_3); \
KEYMAP(KEYCODE_PAD_0, REDKEY_PAD_0); \
KEYMAP(KEYCODE_PAD_DEL, REDKEY_PAD_POINT); \
KEYMAP(KEYCODE_EUROPEAN, REDKEY_EUROPEAN); \
KEYMAP(KEYCODE_F11, REDKEY_F11); \
KEYMAP(KEYCODE_F12, REDKEY_F12); \
KEYMAP(KEYCODE_JAPANESE_BACKSLASH, REDKEY_JAPANESE_BACKSLASH); \
KEYMAP(KEYCODE_JAPANESE_HENKAN, REDKEY_JAPANESE_HENKAN); \
KEYMAP(KEYCODE_JAPANESE_HIRAGANA_KATAKANA, REDKEY_JAPANESE_HIRAGANA_KATAKANA); \
KEYMAP(KEYCODE_JAPANESE_MUHENKAN, REDKEY_JAPANESE_MUHENKAN); \
KEYMAP(KEYCODE_PAD_ENTER, REDKEY_PAD_ENTER); \
KEYMAP(KEYCODE_R_CONTROL, REDKEY_R_CTRL); \
KEYMAP(KEYCODE_PAD_DEVIDE, REDKEY_PAD_DIVIDE); \
KEYMAP(KEYCODE_PRINT, REDKEY_CTRL_PRINT_SCREEN); \
KEYMAP(KEYCODE_R_ALT, REDKEY_R_ALT); \
KEYMAP(KEYCODE_HOME, REDKEY_HOME); \
KEYMAP(KEYCODE_UP, REDKEY_UP); \
KEYMAP(KEYCODE_PAGE_UP, REDKEY_PAGEUP); \
KEYMAP(KEYCODE_LEFT, REDKEY_LEFT); \
KEYMAP(KEYCODE_RIGHT, REDKEY_RIGHT); \
KEYMAP(KEYCODE_END, REDKEY_END); \
KEYMAP(KEYCODE_DOWN, REDKEY_DOWN); \
KEYMAP(KEYCODE_PAGE_DOWN, REDKEY_PAGEDOWN); \
KEYMAP(KEYCODE_INSERT, REDKEY_INSERT); \
KEYMAP(KEYCODE_DELETE, REDKEY_DELETE); \
KEYMAP(KEYCODE_PAUSE, REDKEY_PAUSE); \
\
KEYMAP(KEYCODE_YEN, REDKEY_JAPANESE_YEN); \
KEYMAP(KEYCODE_L_COMMAND, REDKEY_LEFT_CMD); \
KEYMAP(KEYCODE_R_COMMAND, REDKEY_RIGHT_CMD); \
KEYMAP(KEYCODE_MENU, REDKEY_MENU); \
KEYMAP(KEYCODE_HANGUL, REDKEY_KOREAN_HANGUL); \
KEYMAP(KEYCODE_HANGUL_HANJA, REDKEY_KOREAN_HANGUL_HANJA);
static void init_evdev_map()
{
#define KEYMAP(key_code, red_key) keycode_map[EVDEV_##key_code] = red_key
INIT_MAP;
#undef KEYMAP
}
static void init_kbd_map()
{
#define KEYMAP(key_code, red_key) keycode_map[KBD_##key_code] = red_key
INIT_MAP;
#undef KEYMAP
}
static void init_key_map()
{
query_keyboard();
memset(keycode_map, 0, sizeof(keycode_map));
if (using_evdev) {
LOG_INFO("using evdev mapping");
init_evdev_map();
} else {
LOG_INFO("using kbd mapping");
init_kbd_map();
}
}
static inline RedKey to_red_key_code(unsigned int keycode)
{
if (keycode > 255) {
return REDKEY_INVALID;
}
return keycode_map[keycode];
}
#else
static RedKey key_table_0xff[256]; //miscellany
static RedKey key_table_0x00[256]; //Latin 1
static RedKey key_table_0xfe[256]; //Keyboard (XKB) Extension
#define INIT_KEY(x, red) key_table_0xff[x & 0xff] = red;
static void init_key_table_0xff()
{
for (int i = 0; i < sizeof(key_table_0xff) / sizeof(key_table_0xff[0]); i++) {
key_table_0xff[i] = REDKEY_INVALID;
}
INIT_KEY(XK_Escape, REDKEY_ESCAPE);
INIT_KEY(XK_BackSpace, REDKEY_BACKSPACE);
INIT_KEY(XK_Tab, REDKEY_TAB);
INIT_KEY(XK_Return, REDKEY_ENTER);
INIT_KEY(XK_Control_L, REDKEY_L_CTRL);
INIT_KEY(XK_Shift_L, REDKEY_L_SHIFT);
INIT_KEY(XK_Shift_R, REDKEY_R_SHIFT);
INIT_KEY(XK_KP_Multiply, REDKEY_PAD_MULTIPLY);
INIT_KEY(XK_Alt_L, REDKEY_L_ALT);
INIT_KEY(XK_Caps_Lock, REDKEY_CAPS_LOCK);
INIT_KEY(XK_F1, REDKEY_F1);
INIT_KEY(XK_F2, REDKEY_F2);
INIT_KEY(XK_F3, REDKEY_F3);
INIT_KEY(XK_F4, REDKEY_F4);
INIT_KEY(XK_F5, REDKEY_F5);
INIT_KEY(XK_F6, REDKEY_F6);
INIT_KEY(XK_F7, REDKEY_F7);
INIT_KEY(XK_F8, REDKEY_F8);
INIT_KEY(XK_F9, REDKEY_F9);
INIT_KEY(XK_F10, REDKEY_F10);
INIT_KEY(XK_Num_Lock, REDKEY_NUM_LOCK);
INIT_KEY(XK_Scroll_Lock, REDKEY_SCROLL_LOCK);
INIT_KEY(XK_KP_7, REDKEY_PAD_7);
INIT_KEY(XK_KP_Home, REDKEY_PAD_7);
INIT_KEY(XK_KP_8, REDKEY_PAD_8);
INIT_KEY(XK_KP_Up, REDKEY_PAD_8);
INIT_KEY(XK_KP_9, REDKEY_PAD_9);
INIT_KEY(XK_KP_Page_Up, REDKEY_PAD_9);
INIT_KEY(XK_KP_Subtract, REDKEY_PAD_MINUS);
INIT_KEY(XK_KP_4, REDKEY_PAD_4);
INIT_KEY(XK_KP_Left, REDKEY_PAD_4);
INIT_KEY(XK_KP_5, REDKEY_PAD_5);
INIT_KEY(XK_KP_Begin, REDKEY_PAD_5);
INIT_KEY(XK_KP_6, REDKEY_PAD_6);
INIT_KEY(XK_KP_Right, REDKEY_PAD_6);
INIT_KEY(XK_KP_Add, REDKEY_PAD_PLUS);
INIT_KEY(XK_KP_1, REDKEY_PAD_1);
INIT_KEY(XK_KP_End, REDKEY_PAD_1);
INIT_KEY(XK_KP_2, REDKEY_PAD_2);
INIT_KEY(XK_KP_Down, REDKEY_PAD_2);
INIT_KEY(XK_KP_3, REDKEY_PAD_3);
INIT_KEY(XK_KP_Page_Down, REDKEY_PAD_3);
INIT_KEY(XK_KP_0, REDKEY_PAD_0);
INIT_KEY(XK_KP_Insert, REDKEY_PAD_0);
INIT_KEY(XK_KP_Decimal, REDKEY_PAD_POINT);
INIT_KEY(XK_KP_Delete, REDKEY_PAD_POINT);
INIT_KEY(XK_F11, REDKEY_F11);
INIT_KEY(XK_F12, REDKEY_F12);
INIT_KEY(XK_KP_Enter, REDKEY_PAD_ENTER);
INIT_KEY(XK_Control_R, REDKEY_R_CTRL);
INIT_KEY(XK_KP_Divide, REDKEY_PAD_DIVIDE);
INIT_KEY(XK_Print, REDKEY_CTRL_PRINT_SCREEN);
INIT_KEY(XK_Home, REDKEY_HOME);
INIT_KEY(XK_Up, REDKEY_UP);
INIT_KEY(XK_Page_Up, REDKEY_PAGEUP);
INIT_KEY(XK_Left, REDKEY_LEFT);
INIT_KEY(XK_Right, REDKEY_RIGHT);
INIT_KEY(XK_End, REDKEY_END);
INIT_KEY(XK_Down, REDKEY_DOWN);
INIT_KEY(XK_Page_Down, REDKEY_PAGEDOWN);
INIT_KEY(XK_Insert, REDKEY_INSERT);
INIT_KEY(XK_Delete, REDKEY_DELETE);
INIT_KEY(XK_Super_L, REDKEY_LEFT_CMD);
INIT_KEY(XK_Super_R, REDKEY_RIGHT_CMD);
INIT_KEY(XK_Menu, REDKEY_MENU);
INIT_KEY(XK_Pause, REDKEY_CTRL_BREAK);
}
#undef INIT_KEY
#define INIT_KEY(x, red) key_table_0x00[x & 0xff] = red;
static void init_key_table_0x00()
{
for (int i = 0; i < sizeof(key_table_0x00) / sizeof(key_table_0x00[0]); i++) {
key_table_0x00[i] = REDKEY_INVALID;
}
INIT_KEY(XK_1, REDKEY_1);
INIT_KEY(XK_2, REDKEY_2);
INIT_KEY(XK_3, REDKEY_3);
INIT_KEY(XK_4, REDKEY_4);
INIT_KEY(XK_5, REDKEY_5);
INIT_KEY(XK_6, REDKEY_6);
INIT_KEY(XK_7, REDKEY_7);
INIT_KEY(XK_8, REDKEY_8);
INIT_KEY(XK_9, REDKEY_9);
INIT_KEY(XK_0, REDKEY_0);
INIT_KEY(XK_minus, REDKEY_MINUS);
INIT_KEY(XK_equal, REDKEY_EQUALS);
INIT_KEY(XK_q, REDKEY_Q);
INIT_KEY(XK_w, REDKEY_W);
INIT_KEY(XK_e, REDKEY_E);
INIT_KEY(XK_r, REDKEY_R);
INIT_KEY(XK_t, REDKEY_T);
INIT_KEY(XK_y, REDKEY_Y);
INIT_KEY(XK_u, REDKEY_U);
INIT_KEY(XK_i, REDKEY_I);
INIT_KEY(XK_o, REDKEY_O);
INIT_KEY(XK_p, REDKEY_P);
INIT_KEY(XK_bracketleft, REDKEY_L_BRACKET);
INIT_KEY(XK_bracketright, REDKEY_R_BRACKET);
INIT_KEY(XK_a, REDKEY_A);
INIT_KEY(XK_s, REDKEY_S);
INIT_KEY(XK_e, REDKEY_E);
INIT_KEY(XK_d, REDKEY_D);
INIT_KEY(XK_f, REDKEY_F);
INIT_KEY(XK_g, REDKEY_G);
INIT_KEY(XK_h, REDKEY_H);
INIT_KEY(XK_j, REDKEY_J);
INIT_KEY(XK_k, REDKEY_K);
INIT_KEY(XK_l, REDKEY_L);
INIT_KEY(XK_semicolon, REDKEY_SEMICOLON);
INIT_KEY(XK_quoteright, REDKEY_QUOTE);
INIT_KEY(XK_quoteleft, REDKEY_BACK_QUOTE);
INIT_KEY(XK_backslash, REDKEY_BACK_SLASH);
INIT_KEY(XK_z, REDKEY_Z);
INIT_KEY(XK_x, REDKEY_X);
INIT_KEY(XK_c, REDKEY_C);
INIT_KEY(XK_v, REDKEY_V);
INIT_KEY(XK_b, REDKEY_B);
INIT_KEY(XK_n, REDKEY_N);
INIT_KEY(XK_m, REDKEY_M);
INIT_KEY(XK_comma, REDKEY_COMMA);
INIT_KEY(XK_period, REDKEY_PERIOD);
INIT_KEY(XK_slash, REDKEY_SLASH);
INIT_KEY(XK_space, REDKEY_SPACE);
}
#undef INIT_KEY
#define INIT_KEY(x, red) key_table_0xfe[x & 0xff] = red;
static void init_key_table_0xfe()
{
for (int i = 0; i < sizeof(key_table_0xfe) / sizeof(key_table_0xfe[0]); i++) {
key_table_0xfe[i] = REDKEY_INVALID;
}
INIT_KEY(XK_ISO_Level3_Shift, REDKEY_R_ALT);
}
static inline RedKey to_red_key_code(unsigned int keycode)
{
KeySym sym = XKeycodeToKeysym(x_display, keycode, 0);
RedKey red_key;
if (sym == NoSymbol) {
DBG(0, "no symbol for %d", keycode);
}
switch (sym >> 8) {
case 0x00:
red_key = key_table_0x00[sym & 0xff];
break;
case 0xff:
red_key = key_table_0xff[sym & 0xff];
break;
case 0xfe:
red_key = key_table_0xfe[sym & 0xff];
break;
default:
DBG(0, "unsupported key set %lu", (sym >> 8));
return REDKEY_INVALID;
}
if (red_key == REDKEY_INVALID) {
DBG(0, "no valid key mapping for keycode %u", keycode);
}
return red_key;
}
#endif
static inline int to_red_buttons_state(unsigned int state)
{
return ((state & Button1Mask) ? SPICE_MOUSE_BUTTON_MASK_LEFT : 0) |
((state & Button2Mask) ? SPICE_MOUSE_BUTTON_MASK_MIDDLE : 0) |
((state & Button3Mask) ? SPICE_MOUSE_BUTTON_MASK_RIGHT : 0);
}
static inline SpiceMouseButton to_red_button(unsigned int botton, unsigned int& state, bool press)
{
unsigned int mask = 0;
SpiceMouseButton ret;
switch (botton) {
case Button1:
mask = SPICE_MOUSE_BUTTON_MASK_LEFT;
ret = SPICE_MOUSE_BUTTON_LEFT;
break;
case Button2:
mask = SPICE_MOUSE_BUTTON_MASK_MIDDLE;
ret = SPICE_MOUSE_BUTTON_MIDDLE;
break;
case Button3:
mask = SPICE_MOUSE_BUTTON_MASK_RIGHT;
ret = SPICE_MOUSE_BUTTON_RIGHT;
break;
case Button4:
ret = SPICE_MOUSE_BUTTON_UP;
break;
case Button5:
ret = SPICE_MOUSE_BUTTON_DOWN;
break;
default:
ret = SPICE_MOUSE_BUTTON_INVALID;
}
if (press) {
state |= mask;
} else {
state &= ~mask;
}
return ret;
}
void RedWindow_p::handle_key_press_event(RedWindow& window, XKeyEvent* event)
{
static int buf_size = 0;
static wchar_t* utf32_buf = NULL;
KeySym key_sym;
Status status;
int len;
window.get_listener().on_key_press(to_red_key_code(event->keycode));
if (x_input_context != NULL) {
for (;;) {
len = XwcLookupString(x_input_context, event, utf32_buf, buf_size, &key_sym, &status);
if (status != XBufferOverflow) {
break;
}
if (utf32_buf) {
delete [] utf32_buf;
}
utf32_buf = new wchar_t[len];
buf_size = len;
}
switch (status) {
case XLookupChars:
case XLookupBoth: {
uint32_t* now = (uint32_t*)utf32_buf;
uint32_t* end = now + len;
for (; now < end; now++) {
window.get_listener().on_char(*now);
}
break;
}
case XLookupNone:
case XLookupKeySym:
break;
default:
THROW("unexpected status %d", status);
}
} else { /* no XIM */
unsigned char buffer[16];
int i;
len = XLookupString(event, (char *)buffer, sizeof(buffer), NULL, NULL);
for (i = 0; i < len; i++) {
window.get_listener().on_char((uint32_t)buffer[i]);
}
}
}
void RedWindow_p::win_proc(XEvent& event)
{
XPointer window_pointer;
RedWindow* red_window;
if (XFindContext(x_display, event.xany.window, user_data_context, &window_pointer)) {
THROW("no user data");
}
red_window = (RedWindow*)window_pointer;
switch (event.type) {
case MotionNotify: {
SpicePoint size = red_window->get_size();
if (event.xmotion.x >= 0 && event.xmotion.y >= 0 &&
event.xmotion.x < size.x && event.xmotion.y < size.y) {
SpicePoint origin = red_window->get_origin();
red_window->get_listener().on_pointer_motion(event.xmotion.x - origin.x,
event.xmotion.y - origin.y,
to_red_buttons_state(event.xmotion.state));
}
break;
}
case KeyPress:
red_window->handle_key_press_event(*red_window, &event.xkey);
break;
case KeyRelease: {
RedKey key = to_red_key_code(event.xkey.keycode);
XEvent next_event;
if (XCheckWindowEvent(x_display, red_window->_win, ~long(0), &next_event)) {
XPutBackEvent(x_display, &next_event);
if ((next_event.type == KeyPress) &&
(event.xkey.keycode == next_event.xkey.keycode) &&
(event.xkey.time == next_event.xkey.time)) {
break;
}
}
if (key != REDKEY_KOREAN_HANGUL && key != REDKEY_KOREAN_HANGUL_HANJA) {
red_window->get_listener().on_key_release(key);
}
break;
}
case ButtonPress: {
unsigned int state = to_red_buttons_state(event.xbutton.state);
SpiceMouseButton button = to_red_button(event.xbutton.button, state, true);
if (button == SPICE_MOUSE_BUTTON_INVALID) {
DBG(0, "ButtonPress: invalid button %u", event.xbutton.button);
break;
}
red_window->get_listener().on_mouse_button_press(button, state);
break;
}
case ButtonRelease: {
unsigned int state = to_red_buttons_state(event.xbutton.state);
SpiceMouseButton button = to_red_button(event.xbutton.button, state, false);
if (button == SPICE_MOUSE_BUTTON_INVALID) {
DBG(0, "ButtonRelease: invalid button %u", event.xbutton.button);
break;
}
red_window->get_listener().on_mouse_button_release(button, state);
break;
}
case Expose: {
SpicePoint origin;
SpiceRect area;
origin = red_window->get_origin();
area.left = event.xexpose.x - origin.x;
area.right = area.left + event.xexpose.width;
area.top = event.xexpose.y - origin.y;
area.bottom = area.top + event.xexpose.height;
red_window->get_listener().on_exposed_rect(area);
break;
}
case FocusIn:
if (event.xany.serial < focus_serial) {
DBG(0, "Ignored FocusIn win=%p (serial=%d, Last foucs serial=%d)",
red_window, event.xany.serial, focus_serial);
break;
}
if (!red_window->_ignore_foucs) {
RedWindow* prev_focus_window = focus_window;
focus_window = red_window;
focus_serial = event.xany.serial;
if (prev_focus_window && (red_window != prev_focus_window)) {
prev_focus_window->on_focus_out();
}
red_window->on_focus_in();
} else {
red_window->_shadow_foucs_state = true;
memcpy(&red_window->_shadow_focus_event, &event, sizeof(XEvent));
}
break;
case FocusOut:
if (event.xany.serial <= focus_serial) {
DBG(0, "Ignored FocusOut win=%p (serial=%d, Last foucs serial=%d)",
red_window, event.xany.serial, focus_serial);
break;
}
if (!red_window->_ignore_foucs) {
focus_serial = event.xany.serial;
if (red_window != focus_window) {
break;
}
focus_window = NULL;
red_window->on_focus_out();
} else {
red_window->_shadow_foucs_state = false;
memcpy(&red_window->_shadow_focus_event, &event, sizeof(XEvent));
}
break;
case ConfigureNotify:
break;
case ClientMessage:
if (event.xclient.message_type == wm_protocol_atom) {
ASSERT(event.xclient.format == 32);
if (event.xclient.data.l[0] == wm_delete_window_atom) {
DBG(0, "wm_delete_window");
Platform::send_quit_request();
}
}
break;
case DestroyNotify:
break;
case MapNotify:
red_window->set_visibale(true);
break;
case UnmapNotify:
red_window->set_visibale(false);
break;
case EnterNotify:
if (!red_window->_ignore_pointer) {
SpicePoint origin = red_window->get_origin();
red_window->on_pointer_enter(event.xcrossing.x - origin.x, event.xcrossing.y - origin.y,
to_red_buttons_state(event.xcrossing.state));
} else {
red_window->_shadow_pointer_state = true;
memcpy(&red_window->_shadow_pointer_event, &event, sizeof(XEvent));
}
break;
case LeaveNotify:
if (!red_window->_ignore_pointer) {
red_window->on_pointer_leave();
} else {
red_window->_shadow_pointer_state = false;
memcpy(&red_window->_shadow_pointer_event, &event, sizeof(XEvent));
}
break;
}
}
void RedWindow_p::sync(bool shadowed)
{
if (shadowed) {
_ignore_foucs = true;
_ignore_pointer = true;
_shadow_foucs_state = _focused;
_shadow_pointer_state = _pointer_in_window;
_shadow_focus_event.xany.serial = 0;
}
XSync(x_display, False);
XEvent event;
while (XCheckWindowEvent(x_display, _win, ~long(0), &event)) {
win_proc(event);
}
if (!shadowed) {
return;
}
_ignore_foucs = false;
_ignore_pointer = false;
if (_shadow_foucs_state != _focused) {
DBG(0, "put back shadowed focus event");
XPutBackEvent(x_display, &_shadow_focus_event);
} else if (_shadow_focus_event.xany.serial > 0) {
focus_serial = _shadow_focus_event.xany.serial;
}
if (_shadow_pointer_state != _pointer_in_window) {
DBG(0, "put back shadowed pointer event");
XPutBackEvent(x_display, &_shadow_pointer_event);
}
}
void RedWindow_p::wait_for_reparent()
{
XEvent event;
for (int i = 0; i < 50; i++) {
if (XCheckTypedWindowEvent(x_display, _win, ReparentNotify, &event)) {
return;
}
usleep(20 * 1000);
XSync(x_display, False);
}
DBG(0, "failed");
}
void RedWindow_p::wait_for_map()
{
bool wait_parent = _expect_parent;
while (!_visibale) {
XEvent event;
XWindowEvent(x_display, _win, ~0, &event);
switch (event.type) {
case ReparentNotify:
wait_parent = false;
break;
case MapNotify:
_visibale = true;
break;
default:
//todo: post state messages to app message queue instead of
// calling win_proc
win_proc(event);
}
}
if (wait_parent) {
wait_for_reparent();
}
}
void RedWindow_p::wait_for_unmap()
{
bool wait_parent = _expect_parent;
while (_visibale) {
XEvent event;
XWindowEvent(x_display, _win, ~0, &event);
switch (event.type) {
case ReparentNotify:
wait_parent = false;
break;
case UnmapNotify:
_visibale = false;
break;
//default:
// win_proc(event);
}
}
if (wait_parent) {
wait_for_reparent();
}
}
#ifdef USE_OGL
void RedWindow_p::set_glx(int width, int height)
{
if (_glcont_copy) {
XSync(x_display, False);
glXMakeCurrent(x_display, _win, _glcont_copy);
//glDrawBuffer(GL_FRONT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width, 0, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0, 0, width, height);
glColor3f(1, 1, 1);
glEnable(GL_TEXTURE_2D);
GLC_ERROR_TEST_FINISH;
}
}
#endif // USE_OGL
void RedWindow_p::set_minmax(PixelsSource_p& pix_source, int width, int height)
{
//todo: auto res
XSizeHints* size_hints = XAllocSizeHints();
ASSERT(size_hints);
size_hints->flags = PMinSize | PMaxSize;
size_hints->min_width = size_hints->max_width = width;
size_hints->min_height = size_hints->max_height = height;
XSetWMNormalHints(x_display, _win, size_hints);
XFree(size_hints);
pix_source.x_drawable.height = height;
pix_source.x_drawable.width = width;
}
Cursor RedWindow_p::create_invisible_cursor(Window window)
{
XColor color;
char data[1] = {0};
Window root_window = RootWindow(x_display, DefaultScreen(x_display));
Pixmap blank = XCreateBitmapFromData(x_display, root_window, data, 1, 1);
Cursor cursor = XCreatePixmapCursor(x_display, blank, blank, &color, &color, 0, 0);
XFreePixmap(x_display, blank);
return cursor;
}
RedWindow_p::RedWindow_p()
: _win (None)
#ifdef USE_OGL
, _glcont_copy (NULL)
#endif // USE_OGL
, _icon (NULL)
, _focused (false)
, _ignore_foucs (false)
, _pointer_in_window (false)
, _ignore_pointer (false)
{
}
void RedWindow_p::destroy(RedWindow& red_window, PixelsSource_p& pix_source)
{
if (_win == None) {
return;
}
if (focus_window == &red_window) {
focus_window = NULL;
red_window.on_focus_out();
}
XPlatform::cleare_win_proc(_win);
XSelectInput(x_display, _win, 0);
XSync(x_display, False);
XEvent event;
while (XCheckWindowEvent(x_display, _win, ~long(0), &event));
Window window = _win;
_win = None;
XFreeCursor(x_display, _invisible_cursor);
_invisible_cursor = None;
XDeleteContext(x_display, window, user_data_context);
#ifdef USE_OGL
if (_glcont_copy) {
glXDestroyContext(x_display, _glcont_copy);
_glcont_copy = NULL;
}
#endif // USE_OGL
XDestroyWindow(x_display, window);
XFreeColormap(x_display, _colormap);
XFreeGC(x_display, pix_source.x_drawable.gc);
pix_source.x_drawable.gc = NULL;
pix_source.x_drawable.drawable = None;
if (_icon) {
_icon->unref();
}
}
void RedWindow_p::create(RedWindow& red_window, PixelsSource_p& pix_source, int x, int y,
unsigned int width, unsigned int height, int in_screen)
{
Window window = None;
Cursor cursor = None;
GC gc = NULL;
Window root_window = RootWindow(x_display, in_screen);
XSetWindowAttributes win_attributes;
unsigned long mask = CWBorderPixel | CWEventMask;
win_attributes.border_pixel = 1;
win_attributes.event_mask = StructureNotifyMask | SubstructureNotifyMask | ExposureMask |
KeyPressMask | KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | PointerMotionMask | FocusChangeMask |
EnterWindowMask | LeaveWindowMask;
_colormap = XCreateColormap(x_display, root_window, XPlatform::get_vinfo()[in_screen]->visual,
AllocNone);
win_attributes.colormap = _colormap;
mask |= CWColormap;
window = XCreateWindow(x_display, root_window, x, y,
width, height, 0, XPlatform::get_vinfo()[in_screen]->depth,
InputOutput, XPlatform::get_vinfo()[in_screen]->visual, mask,
&win_attributes);
if (!window) {
THROW("create X window failed");
}
try {
if (XSaveContext(x_display, window, user_data_context, (XPointer)&red_window)) {
THROW("set win usr data failed");
}
XSetWMProtocols(x_display, window, &wm_delete_window_atom, 1);
XGCValues gc_vals;
if (!(gc = XCreateGC(x_display, window, 0, &gc_vals))) {
THROW("create gc failed");
}
if (!(cursor = create_invisible_cursor(window))) {
THROW("create invisible cursor failed");
}
XPlatform::set_win_proc(window, win_proc);
} catch (...) {
if (gc) {
XFreeGC(x_display, gc);
}
XDeleteContext(x_display, window, user_data_context);
XDestroyWindow(x_display, window);
if (cursor != None) {
XFreeCursor(x_display, cursor);
}
throw;
}
_screen = in_screen;
_win = window;
_invisible_cursor = cursor;
_show_pos.x = x;
_show_pos.y = y;
_visibale = false;
_expect_parent = false;
pix_source.type = PIXELS_SOURCE_TYPE_X_DRAWABLE;
pix_source.x_drawable.drawable = window;
pix_source.x_drawable.screen = _screen;
pix_source.x_drawable.gc = gc;
set_minmax(pix_source, width, height);
sync();
}
void RedWindow_p::migrate(RedWindow& red_window, PixelsSource_p& pix_source, int to_screen)
{
if (to_screen == _screen) {
return;
}
XWindowAttributes attrib;
if (!XGetWindowAttributes(x_display, _win, &attrib)) {
THROW("get attributes failed");
}
XTextProperty text_pro;
bool valid_title = XGetWMName(x_display, _win, &text_pro) && text_pro.value;
destroy(red_window, pix_source);
create(red_window, pix_source, _show_pos.x, _show_pos.y, attrib.width, attrib.height,
to_screen);
if (valid_title) {
XSetWMName(x_display, _win, &text_pro);
XFree(text_pro.value); //???
}
if (_icon) {
AutoRef red(_icon->ref());
red_window.set_icon(_icon);
}
}
void RedWindow_p::move_to_current_desktop()
{
Window root = RootWindow(x_display, _screen);
Atom actual_type_return;
int actual_format_return;
unsigned long bytes_after_return;
unsigned long nitems_return;
unsigned char *prop_return;
long desktop = ~long(0);
if (XGetWindowProperty(x_display, root, wm_current_desktop, 0, 1, False, AnyPropertyType,
&actual_type_return, &actual_format_return, &nitems_return,
&bytes_after_return, &prop_return) == Success &&
actual_type_return != None && actual_format_return == 32) {
desktop = *(uint32_t *)prop_return;
} else {
DBG(0, "get current desktop failed");
}
XEvent xevent;
xevent.type = ClientMessage;
xevent.xclient.window = _win;
xevent.xclient.message_type = wm_desktop;
xevent.xclient.format = 32;
xevent.xclient.data.l[0] = desktop;
xevent.xclient.data.l[1] = 0;
xevent.xclient.data.l[2] = 0;
xevent.xclient.data.l[3] = 0;
xevent.xclient.data.l[4] = 0;
if (!XSendEvent(x_display, root, False, SubstructureNotifyMask | SubstructureRedirectMask,
&xevent)) {
DBG(0, "failed");
}
}
RedWindow::RedWindow(RedWindow::Listener& listener, int screen)
: _listener (listener)
, _type (TYPE_NORMAL)
, _local_cursor (NULL)
, _cursor_visible (true)
, _trace_key_interception (false)
, _key_interception_on (false)
, _menu (NULL)
{
ASSERT(x_display);
create(*this, *(PixelsSource_p*)get_opaque(), 0, 0, 200, 200,
(screen == DEFAULT_SCREEN) ? DefaultScreen(x_display) : screen);
}
RedWindow::~RedWindow()
{
destroy(*this, *(PixelsSource_p*)get_opaque());
if (_local_cursor) {
_local_cursor->unref();
}
}
void RedWindow::set_title(std::wstring& title)
{
XTextProperty text_prop;
wchar_t *name = const_cast(title.c_str());
int r;
if (_win) {
r = XwcTextListToTextProperty(x_display, &name, 1, XStringStyle, &text_prop);
if (r >= 0) {
XSetWMName(x_display, _win, &text_prop);
XFree(text_prop.value);
} else {
LOG_WARN("XwcTextListToTextProperty Error %d", r);
}
}
}
void RedWindow::set_icon(Icon* icon)
{
if (_icon) {
_icon->unref();
_icon = NULL;
}
if (!icon) {
return;
}
_icon = icon->ref();
XWMHints* wm_hints;
if (_win == None || !(wm_hints = XAllocWMHints())) {
return;
}
try {
XIcon* xicon = (XIcon*)icon;
xicon->get_pixmaps(_screen, wm_hints->icon_pixmap, wm_hints->icon_mask);
wm_hints->flags = IconPixmapHint | IconMaskHint;
XSetWMHints(x_display, _win, wm_hints);
} catch (...) {
}
XFree(wm_hints);
}
static XErrorHandler old_error_handler = NULL;
static unsigned char x_error = Success;
static int x_error_handler(Display* display, XErrorEvent* error_event)
{
x_error = error_event->error_code;
if (error_event->error_code == BadWindow) {
return 0;
}
ASSERT(old_error_handler);
XSetErrorHandler(old_error_handler);
old_error_handler(display, error_event);
old_error_handler = NULL;
return 0;
}
class AutoXErrorHandler {
public:
AutoXErrorHandler()
{
ASSERT(old_error_handler == NULL);
XSync(x_display, False);
x_error = Success;
old_error_handler = XSetErrorHandler(x_error_handler);
}
~AutoXErrorHandler()
{
if (old_error_handler) {
XSetErrorHandler(old_error_handler);
old_error_handler = NULL;
}
}
};
static Window get_window_for_reposition(Window window)
{
for (;;) {
Window root;
Window parent;
Window* childrens;
unsigned int num_childrens;
if (!XQueryTree(x_display, window, &root, &parent, &childrens, &num_childrens)) {
return None;
}
if (childrens) {
XFree(childrens);
}
if (parent == root) {
break;
}
window = parent;
}
return window;
}
void RedWindow::raise()
{
AutoXErrorHandler auto_error_handler;
int raise_retries = RAISE_RETRIES;
for (;; --raise_retries) {
Window window = get_window_for_reposition(_win);
if (window != None) {
x_error = Success;
XRaiseWindow(x_display, window);
if (x_error == Success) {
break;
}
if (x_error != BadWindow) {
THROW("XRaiseWindow failed");
}
}
if (!raise_retries) {
THROW("failed");
}
usleep(X_RETRY_DELAY_MICRO);
}
if (raise_retries < RAISE_RETRIES) {
DBG(0, "retries %d", (RAISE_RETRIES - raise_retries));
}
sync();
}
void RedWindow::position_after(RedWindow *after)
{
if (!after || after->_screen != _screen) {
raise();
return;
}
AutoXErrorHandler auto_error_handler;
int position_retries = Z_POSITION_RETRIES;
for (;; --position_retries) {
Window sibling = get_window_for_reposition(after->get_window());
Window self = get_window_for_reposition(_win);
if (sibling != None && self != None) {
XWindowChanges changes;
changes.sibling = sibling;
changes.stack_mode = Below;
x_error = Success;
XConfigureWindow(x_display, self, CWSibling | CWStackMode, &changes);
if (x_error == Success) {
break;
}
if (x_error != BadWindow) {
THROW("XConfigureWindow failed");
}
}
if (!position_retries) {
THROW("failed");
}
usleep(X_RETRY_DELAY_MICRO);
}
if (position_retries < Z_POSITION_RETRIES) {
DBG(0, "retries %d", (Z_POSITION_RETRIES - position_retries));
}
}
void RedWindow::show(int screen_id)
{
if (_visibale) {
return;
}
bool wait_parent;
if (screen_id != _screen) {
_listener.pre_migrate();
migrate(*this, *(PixelsSource_p*)get_opaque(), screen_id);
_listener.post_migrate();
}
if (_type == TYPE_FULLSCREEN) {
Atom state[2];
state[0] = wm_state_above;
state[1] = wm_state_fullscreen;
XChangeProperty(x_display, _win, wm_state, XA_ATOM, 32, PropModeReplace,
(unsigned char*)state, 2);
wait_parent = false;
} else {
XDeleteProperty(x_display, _win, wm_state);
wait_parent = true;
}
XMapWindow(x_display, _win);
move_to_current_desktop();
_expect_parent = wait_parent;
wait_for_map();
move(_show_pos.x, _show_pos.y);
}
static bool get_prop_32(Window win, Atom prop, uint32_t &val)
{
Atom actual_type_return;
int actual_format_return;
unsigned long bytes_after_return;
unsigned long nitems_return;
unsigned char *prop_return;
if (XGetWindowProperty(x_display, win, prop, 0, 1, False, AnyPropertyType,
&actual_type_return, &actual_format_return,
&nitems_return, &bytes_after_return, &prop_return) == Success &&
nitems_return == 1 &&
actual_type_return != None &&
actual_format_return == 32) {
val = *(uint32_t *)prop_return;
return true;
}
return false;
}
void RedWindow::external_show()
{
Atom atom;
DBG(0, "");
show(_screen);
raise();
activate();
atom = XInternAtom(x_display, "_NET_ACTIVE_WINDOW", true);
if (atom != None) {
Window root;
XEvent xev;
long eventmask;
xev.xclient.type = ClientMessage;
xev.xclient.serial = 0;
xev.xclient.send_event = True;
xev.xclient.window = _win;
xev.xclient.message_type = atom;
xev.xclient.format = 32;
xev.xclient.data.l[0] = 1;
uint32_t user_time;
if (get_prop_32(_win, wm_user_time, user_time)) {
xev.xclient.data.l[1] = user_time;
} else {
xev.xclient.data.l[1] = 0;
}
xev.xclient.data.l[2] = 0;
xev.xclient.data.l[3] = 0;
xev.xclient.data.l[4] = 0;
root = RootWindow(x_display, _screen),
eventmask = SubstructureRedirectMask | SubstructureNotifyMask;
XSendEvent(x_display, root, False, eventmask, &xev);
}
}
void RedWindow::hide()
{
if (!_visibale) {
return;
}
on_pointer_leave();
on_focus_out();
XUnmapWindow(x_display, _win);
_show_pos = get_position();
wait_for_unmap();
ASSERT(!_focused);
ASSERT(!_pointer_in_window);
_expect_parent = false;
}
static void send_expose(Window window, int width, int height)
{
XExposeEvent event;
event.type = Expose;
event.display = x_display;
event.window = window;
event.x = 0;
event.y = 0;
event.width = width;
event.height = height;
event.count = 0;
XSendEvent(x_display, window, False, ExposureMask, (XEvent *)&event);
}
void RedWindow::move_and_resize(int x, int y, int width, int height)
{
set_minmax(*(PixelsSource_p*)get_opaque(), width, height);
XMoveResizeWindow(x_display, _win, x, y, width, height);
_show_pos.x = x;
_show_pos.y = y;
if (_visibale) {
send_expose(_win, width, height);
}
}
void RedWindow::move(int x, int y)
{
XMoveWindow(x_display, _win, x, y);
_show_pos.x = x;
_show_pos.y = y;
}
void RedWindow::resize(int width, int height)
{
set_minmax(*(PixelsSource_p*)get_opaque(), width, height);
XResizeWindow(x_display, _win, width, height);
if (_visibale) {
send_expose(_win, width, height);
}
}
void RedWindow::activate()
{
//todo: use _NET_ACTIVE_WINDOW
XSetInputFocus(x_display, _win, RevertToParent, CurrentTime);
}
void RedWindow::minimize()
{
XIconifyWindow(x_display, _win, _screen);
sync();
}
static bool __get_position(Window window, SpicePoint& pos)
{
pos.x = pos.y = 0;
for (;;) {
XWindowAttributes attrib;
Window root;
Window parent;
Window* childrens;
unsigned int num_childrens;
if (!XGetWindowAttributes(x_display, window, &attrib)) {
return false;
}
pos.x += attrib.x;
pos.y += attrib.y;
if (!XQueryTree(x_display, window, &root, &parent, &childrens, &num_childrens)) {
return false;
}
if (childrens) {
XFree(childrens);
}
if (parent == None) {
break;
}
window = parent;
}
return true;
}
SpicePoint RedWindow::get_position()
{
SpicePoint pos;
AutoXErrorHandler auto_error_handler;
int get_position_retries = GET_POSITION_RETRIES;
for (;; --get_position_retries) {
if (__get_position(_win, pos)) {
break;
}
if (!get_position_retries) {
THROW("failed");
}
usleep(X_RETRY_DELAY_MICRO);
}
if (get_position_retries < GET_POSITION_RETRIES) {
DBG(0, "retries %d", (GET_POSITION_RETRIES - get_position_retries));
}
return pos;
}
void RedWindow::do_start_key_interception()
{
// Working with KDE: XGrabKeyboard generate focusout and focusin events
// while we have the focus. This behavior trigger infinite recursive. for
// that reason we temporary disable focus event handling. Same happens
// LeaveNotify and EnterNotify.
ASSERT(_focused);
XGrabKeyboard(x_display, _win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
sync(true);
_listener.on_start_key_interception();
_key_interception_on = true;
}
void RedWindow::do_stop_key_interception()
{
XUngrabKeyboard(x_display, CurrentTime);
sync(true);
_key_interception_on = false;
_listener.on_stop_key_interception();
}
void RedWindow::start_key_interception()
{
if (_trace_key_interception) {
return;
}
_trace_key_interception = true;
if (_pointer_in_window && _focused) {
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::set_cursor(LocalCursor* local_cursor)
{
ASSERT(local_cursor);
if (_local_cursor) {
_local_cursor->unref();
}
_local_cursor = local_cursor->ref();
_local_cursor->set(_win);
_cursor_visible = true;
}
void RedWindow::show_cursor()
{
if (!_cursor_visible) {
if (_local_cursor) {
_local_cursor->set(_win);
}
_cursor_visible = true;
}
}
void RedWindow::hide_cursor()
{
if (_cursor_visible) {
XDefineCursor(x_display, _win, _invisible_cursor);
_cursor_visible = false;
}
}
void RedWindow::release_mouse()
{
XUngrabPointer(x_display, CurrentTime);
sync(true);
}
void RedWindow::cupture_mouse()
{
int grab_retries = MOUSE_GRAB_RETRIES;
XSync(x_display, False);
for (;; --grab_retries) {
int result = XGrabPointer(x_display, _win, True, 0,
GrabModeAsync, GrabModeAsync,
_win, None, CurrentTime);
if (result == GrabSuccess) {
break;
}
if (!grab_retries) {
THROW("grab pointer failed (%d)", result);
}
usleep(X_RETRY_DELAY_MICRO);
DBG(0, "grab failed result=%d", result);
}
sync();
}
void RedWindow::set_mouse_position(int x, int y)
{
XWarpPointer(x_display, None, _win, 0, 0, 0, 0, x + get_origin().x, y + get_origin().y);
}
SpicePoint RedWindow::get_size()
{
XWindowAttributes attrib;
XGetWindowAttributes(x_display, _win, &attrib);
SpicePoint size;
size.x = attrib.width;
size.y = attrib.height;
return size;
}
static void window_area_from_attributes(SpiceRect& area, XWindowAttributes& attrib)
{
area.left = attrib.x;
area.right = area.left + attrib.width;
area.top = attrib.y;
area.bottom = area.top + attrib.height;
}
#define FAIL_ON_BAD_WINDOW(error, format, ...) \
if (error) { \
if ((x_error) == BadWindow) { \
return NULL; \
} \
THROW(format, ## __VA_ARGS__); \
}
static QRegion *get_visibale_region(Window window)
{
QRegion* region = new QRegion;
region_init(region);
XWindowAttributes attrib;
if (!XGetWindowAttributes(x_display, window, &attrib)) {
return NULL;
}
if (attrib.map_state != IsViewable) {
DBG(0, "not viewable");
return region;
}
SpiceRect window_area;
window_area_from_attributes(window_area, attrib);
window_area.right -= window_area.left;
window_area.bottom -= window_area.top;
window_area.top = window_area.left = 0;
region_add(region, &window_area);
Window prev = None;
Window root;
Window parent;
Window* childrens;
unsigned int num_childrens;
AutoXErrorHandler auto_error_handler;
for (;;) {
FAIL_ON_BAD_WINDOW(!XQueryTree(x_display, window, &root, &parent, &childrens,
&num_childrens),
"%s: query X tree failed", __FUNCTION__);
for (int i = num_childrens - 1; i >= 0 && childrens[i] != prev; i--) {
FAIL_ON_BAD_WINDOW(!XGetWindowAttributes(x_display, childrens[i], &attrib),
"%s: get win attributes failed", __FUNCTION__);
if (attrib.map_state == IsViewable) {
window_area_from_attributes(window_area, attrib);
window_area.left -= attrib.border_width;
window_area.right += attrib.border_width;
window_area.top -= attrib.border_width;
window_area.bottom += attrib.border_width;
region_remove(region, &window_area);
}
}
if (childrens) {
XFree(childrens);
}
FAIL_ON_BAD_WINDOW(!XGetWindowAttributes(x_display, window, &attrib),
"%s: get win attributes failed", __FUNCTION__);
window_area_from_attributes(window_area, attrib);
region_offset(region, window_area.left, window_area.top);
if (parent == None) {
break;
}
FAIL_ON_BAD_WINDOW(!XGetWindowAttributes(x_display, parent, &attrib),
"%s: get win attributes failed", __FUNCTION__);
window_area_from_attributes(window_area, attrib);
window_area.right -= window_area.left;
window_area.bottom -= window_area.top;
window_area.top = window_area.left = 0;
QRegion parent_region;
region_init(&parent_region);
region_add(&parent_region, &window_area);
region_and(region, &parent_region);
region_destroy(&parent_region);
prev = window;
window = parent;
}
//todo: intersect with monitors
return region;
}
class Region_p {
public:
Region_p(QRegion* region) : _region (region) {}
~Region_p() { delete _region;}
void get_bbox(SpiceRect& bbox) const
{
if (region_is_empty(_region)) {
bbox.left = bbox.right = bbox.top = bbox.bottom = 0;
} else {
bbox.left = _region->extents.x1;
bbox.top = _region->extents.y1;
bbox.right = _region->extents.x2;
bbox.bottom = _region->extents.y2;
}
}
bool contains_point(int x, int y) const
{
return region_contains_point(_region, x, y);
}
private:
QRegion* _region;
};
bool RedWindow::get_mouse_anchor_point(SpicePoint& pt)
{
QRegion* vis_region;
int vis_region_retries = GET_VIS_REGION_RETRIES;
while (!(vis_region = get_visibale_region(_win))) {
if (!vis_region_retries) {
THROW("get visible region failed");
}
--vis_region_retries;
usleep(X_RETRY_DELAY_MICRO);
}
if (vis_region_retries < GET_VIS_REGION_RETRIES) {
DBG(0, "retries %d", (GET_VIS_REGION_RETRIES - vis_region_retries));
}
Region_p region(vis_region);
if (!find_anchor_point(region, pt)) {
return false;
}
SpicePoint position = get_position();
pt.x -= (position.x + get_origin().x);
pt.y -= (position.y + get_origin().y);
return true;
}
#ifdef USE_OGL
RedGlContext RedWindow::create_context_gl()
{
if (XPlatform::get_fbconfig()[_screen]) {
return glXCreateContext(x_display, XPlatform::get_vinfo()[_screen], NULL, GL_TRUE);
}
return NULL;
}
RedPbuffer RedWindow::create_pbuff(int width, int height)
{
GLXPbuffer pbuff;
GLXFBConfig** fb_config;
int pbuf_attr[] = { GLX_PRESERVED_CONTENTS, True,
GLX_PBUFFER_WIDTH, width,
GLX_PBUFFER_HEIGHT, height,
GLX_LARGEST_PBUFFER, False,
0, 0 };
fb_config = XPlatform::get_fbconfig();
pbuff = glXCreatePbuffer(XPlatform::get_display(), fb_config[_screen][0],
pbuf_attr);
return pbuff;
}
void RedWindow::untouch_context()
{
glXMakeCurrent(x_display, 0, 0);
}
void RedWindow::set_type_gl()
{
PixelsSource_p *pix_source = (PixelsSource_p*)get_opaque();
pix_source->type = PIXELS_SOURCE_TYPE_GL_DRAWABLE;
}
void RedWindow::unset_type_gl()
{
PixelsSource_p *pix_source = (PixelsSource_p*)get_opaque();
pix_source->type = PIXELS_SOURCE_TYPE_X_DRAWABLE;
}
void RedWindow::set_gl_context(RedGlContext context)
{
PixelsSource_p *pix_source = (PixelsSource_p*)get_opaque();
pix_source->x_drawable.context = context;
}
void RedWindow::set_render_pbuff(RedPbuffer pbuff)
{
PixelsSource_p *pix_source = (PixelsSource_p*)get_opaque();
pix_source->x_drawable.rendertype = RENDER_TYPE_PBUFF;
pix_source->x_drawable.pbuff = pbuff;
}
void RedWindow::set_render_fbo(GLuint fbo)
{
PixelsSource_p *pix_source = (PixelsSource_p*)get_opaque();
pix_source->x_drawable.rendertype = RENDER_TYPE_FBO;
pix_source->x_drawable.fbo = fbo;
}
#endif // USE_OGL
int RedWindow::get_screen_num()
{
return _screen;
}
RedDrawable::Format RedWindow::get_format()
{
return XPlatform::get_screen_format(_screen);
}
void RedWindow::on_focus_in()
{
if (_focused) {
return;
}
_focused = true;
if (x_input_context) {
XwcResetIC(x_input_context);
}
XPlatform::on_focus_in();
get_listener().on_activate();
if (_trace_key_interception && _pointer_in_window) {
do_start_key_interception();
}
}
void RedWindow::on_focus_out()
{
if (!_focused) {
return;
}
_focused = false;
if (_key_interception_on) {
do_stop_key_interception();
}
get_listener().on_deactivate();
XPlatform::on_focus_out();
}
void RedWindow::on_pointer_enter(int x, int y, unsigned int buttons_state)
{
if (_pointer_in_window) {
return;
}
_pointer_in_window = true;
_listener.on_pointer_enter(x, y, buttons_state);
if (_focused && _trace_key_interception) {
do_start_key_interception();
}
}
void RedWindow::on_pointer_leave()
{
if (!_pointer_in_window) {
return;
}
_pointer_in_window = false;
_listener.on_pointer_leave();
if (_key_interception_on) {
do_stop_key_interception();
}
}
void RedWindow::set_menu(Menu* menu)
{
}
void RedWindow::init()
{
x_display = XPlatform::get_display();
x_input_context = XPlatform::get_input_context();
ASSERT(x_display);
user_data_context = XUniqueContext();
wm_protocol_atom = XInternAtom(x_display, "WM_PROTOCOLS", False);
wm_delete_window_atom = XInternAtom(x_display, "WM_DELETE_WINDOW", False);
wm_desktop = XInternAtom(x_display, "_NET_WM_DESKTOP", False);
wm_current_desktop = XInternAtom(x_display, "_NET_CURRENT_DESKTOP", False);
wm_state = XInternAtom(x_display, "_NET_WM_STATE", False);
wm_state_above = XInternAtom(x_display, "_NET_WM_STATE_ABOVE", False);
wm_state_fullscreen = XInternAtom(x_display, "_NET_WM_STATE_FULLSCREEN", False);
wm_user_time = XInternAtom(x_display, "_NET_WM_USER_TIME", False);
#ifdef USE_X11_KEYCODE
init_key_map();
#else
init_key_table_0xff();
init_key_table_0x00();
init_key_table_0xfe();
#endif
}
void RedWindow::cleanup()
{
}