/** * FreeRDP: A Remote Desktop Protocol Implementation * X11 Keyboard Handling * * Copyright 2011 Marc-Andre Moreau * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xf_event.h" #include "xf_keyboard.h" #include #define TAG CLIENT_TAG("x11") static void xf_keyboard_modifier_map_free(xfContext* xfc) { WINPR_ASSERT(xfc); if (xfc->modifierMap) { XFreeModifiermap(xfc->modifierMap); xfc->modifierMap = NULL; } } BOOL xf_keyboard_update_modifier_map(xfContext* xfc) { WINPR_ASSERT(xfc); xf_keyboard_modifier_map_free(xfc); xfc->modifierMap = XGetModifierMapping(xfc->display); return xfc->modifierMap != NULL; } static void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* ev); static BOOL xf_sync_kbd_state(xfContext* xfc) { const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc); WINPR_ASSERT(xfc); return freerdp_input_send_synchronize_event(xfc->common.context.input, syncFlags); } static void xf_keyboard_clear(xfContext* xfc) { WINPR_ASSERT(xfc); ZeroMemory(xfc->KeyboardState, sizeof(xfc->KeyboardState)); } static BOOL xf_keyboard_action_script_init(xfContext* xfc) { wObject* obj = NULL; FILE* keyScript = NULL; char buffer[1024] = { 0 }; char command[1024] = { 0 }; const rdpSettings* settings = NULL; const char* ActionScript = NULL; WINPR_ASSERT(xfc); settings = xfc->common.context.settings; WINPR_ASSERT(settings); ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript); xfc->actionScriptExists = winpr_PathFileExists(ActionScript); if (!xfc->actionScriptExists) return FALSE; xfc->keyCombinations = ArrayList_New(TRUE); if (!xfc->keyCombinations) return FALSE; obj = ArrayList_Object(xfc->keyCombinations); WINPR_ASSERT(obj); obj->fnObjectNew = winpr_ObjectStringClone; obj->fnObjectFree = winpr_ObjectStringFree; sprintf_s(command, sizeof(command), "%s key", ActionScript); keyScript = popen(command, "r"); if (!keyScript) { xfc->actionScriptExists = FALSE; return FALSE; } while (fgets(buffer, sizeof(buffer), keyScript) != NULL) { char* context = NULL; strtok_s(buffer, "\n", &context); if (!ArrayList_Append(xfc->keyCombinations, buffer)) { ArrayList_Free(xfc->keyCombinations); xfc->actionScriptExists = FALSE; pclose(keyScript); return FALSE; } } pclose(keyScript); return xf_event_action_script_init(xfc); } static void xf_keyboard_action_script_free(xfContext* xfc) { xf_event_action_script_free(xfc); if (xfc->keyCombinations) { ArrayList_Free(xfc->keyCombinations); xfc->keyCombinations = NULL; xfc->actionScriptExists = FALSE; } } BOOL xf_keyboard_init(xfContext* xfc) { rdpSettings* settings = NULL; WINPR_ASSERT(xfc); settings = xfc->common.context.settings; WINPR_ASSERT(settings); xf_keyboard_clear(xfc); xfc->KeyboardLayout = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout); xfc->KeyboardLayout = freerdp_keyboard_init_ex( xfc->KeyboardLayout, freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList)); if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, xfc->KeyboardLayout)) return FALSE; if (!xf_keyboard_update_modifier_map(xfc)) return FALSE; xf_keyboard_action_script_init(xfc); return TRUE; } void xf_keyboard_free(xfContext* xfc) { xf_keyboard_modifier_map_free(xfc); xf_keyboard_action_script_free(xfc); } void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym) { BOOL last = 0; WINPR_ASSERT(xfc); WINPR_ASSERT(event); WINPR_ASSERT(event->keycode < ARRAYSIZE(xfc->KeyboardState)); last = xfc->KeyboardState[event->keycode]; xfc->KeyboardState[event->keycode] = TRUE; if (xf_keyboard_handle_special_keys(xfc, keysym)) return; xf_keyboard_send_key(xfc, TRUE, last, event); } void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym) { WINPR_ASSERT(xfc); WINPR_ASSERT(event); WINPR_ASSERT(event->keycode < ARRAYSIZE(xfc->KeyboardState)); BOOL last = xfc->KeyboardState[event->keycode]; xfc->KeyboardState[event->keycode] = FALSE; xf_keyboard_handle_special_keys_release(xfc, keysym); xf_keyboard_send_key(xfc, FALSE, last, event); } void xf_keyboard_release_all_keypress(xfContext* xfc) { WINPR_ASSERT(xfc); for (size_t keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++) { if (xfc->KeyboardState[keycode]) { const DWORD rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode); // release tab before releasing the windows key. // this stops the start menu from opening on unfocus event. if (rdp_scancode == RDP_SCANCODE_LWIN) freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE, RDP_SCANCODE_TAB); freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE, rdp_scancode); xfc->KeyboardState[keycode] = FALSE; } } xf_sync_kbd_state(xfc); } BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym) { KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState)); return xfc->KeyboardState[keycode]; } void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* event) { DWORD rdp_scancode = 0; rdpInput* input = NULL; WINPR_ASSERT(xfc); WINPR_ASSERT(event); input = xfc->common.context.input; WINPR_ASSERT(input); rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->keycode); if (rdp_scancode == RDP_SCANCODE_PAUSE && !xf_keyboard_key_pressed(xfc, XK_Control_L) && !xf_keyboard_key_pressed(xfc, XK_Control_R)) { /* Pause without Ctrl has to be sent as a series of keycodes * in a single input PDU. Pause only happens on "press"; * no code is sent on "release". */ if (down) { freerdp_input_send_keyboard_pause_event(input); } } else { if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnicodeInput)) { wchar_t buffer[32] = { 0 }; int xwc = -1; switch (rdp_scancode) { case RDP_SCANCODE_RETURN: break; default: { XIM xim = XOpenIM(xfc->display, 0, 0, 0); XIC xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL); KeySym ignore = { 0 }; Status return_status = 0; XKeyEvent ev = *event; ev.type = KeyPress; xwc = XwcLookupString(xic, &ev, buffer, ARRAYSIZE(buffer), &ignore, &return_status); } break; } if (xwc < 1) { if (rdp_scancode == RDP_SCANCODE_UNKNOWN) WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode); else freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode); } else freerdp_input_send_unicode_keyboard_event(input, down ? 0 : KBD_FLAGS_RELEASE, buffer[0]); } else if (rdp_scancode == RDP_SCANCODE_UNKNOWN) WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode); else freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode); if ((rdp_scancode == RDP_SCANCODE_CAPSLOCK) && (down == FALSE)) { xf_sync_kbd_state(xfc); } } } int xf_keyboard_read_keyboard_state(xfContext* xfc) { int dummy = 0; Window wdummy = 0; UINT32 state = 0; if (!xfc->remote_app && xfc->window) { XQueryPointer(xfc->display, xfc->window->handle, &wdummy, &wdummy, &dummy, &dummy, &dummy, &dummy, &state); } else { XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &wdummy, &wdummy, &dummy, &dummy, &dummy, &dummy, &state); } return state; } static int xf_keyboard_get_keymask(xfContext* xfc, int keysym) { int keysymMask = 0; KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); if (keycode == NoSymbol) return 0; WINPR_ASSERT(xfc->modifierMap); for (int modifierpos = 0; modifierpos < 8; modifierpos++) { int offset = xfc->modifierMap->max_keypermod * modifierpos; for (int key = 0; key < xfc->modifierMap->max_keypermod; key++) { if (xfc->modifierMap->modifiermap[offset + key] == keycode) { keysymMask |= 1 << modifierpos; } } } return keysymMask; } BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, int keysym) { int keysymMask = xf_keyboard_get_keymask(xfc, keysym); if (!keysymMask) return FALSE; return (state & keysymMask) ? TRUE : FALSE; } static BOOL xf_keyboard_set_key_state(xfContext* xfc, BOOL on, int keysym) { if (!xfc->xkbAvailable) return FALSE; const int keysymMask = xf_keyboard_get_keymask(xfc, keysym); if (!keysymMask) { return FALSE; } return XkbLockModifiers(xfc->display, XkbUseCoreKbd, keysymMask, on ? keysymMask : 0); } UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc) { UINT32 toggleKeysState = 0; const int state = xf_keyboard_read_keyboard_state(xfc); if (xf_keyboard_get_key_state(xfc, state, XK_Scroll_Lock)) toggleKeysState |= KBD_SYNC_SCROLL_LOCK; if (xf_keyboard_get_key_state(xfc, state, XK_Num_Lock)) toggleKeysState |= KBD_SYNC_NUM_LOCK; if (xf_keyboard_get_key_state(xfc, state, XK_Caps_Lock)) toggleKeysState |= KBD_SYNC_CAPS_LOCK; if (xf_keyboard_get_key_state(xfc, state, XK_Kana_Lock)) toggleKeysState |= KBD_SYNC_KANA_LOCK; return toggleKeysState; } static void xk_keyboard_update_modifier_keys(xfContext* xfc) { const int keysyms[] = { XK_Shift_L, XK_Shift_R, XK_Alt_L, XK_Alt_R, XK_Control_L, XK_Control_R, XK_Super_L, XK_Super_R }; xf_keyboard_clear(xfc); const int state = xf_keyboard_read_keyboard_state(xfc); for (size_t i = 0; i < ARRAYSIZE(keysyms); i++) { if (xf_keyboard_get_key_state(xfc, state, keysyms[i])) { const KeyCode keycode = XKeysymToKeycode(xfc->display, keysyms[i]); WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState)); xfc->KeyboardState[keycode] = TRUE; } } } void xf_keyboard_focus_in(xfContext* xfc) { UINT32 state = 0; Window w = None; int d = 0; int x = 0; int y = 0; WINPR_ASSERT(xfc); if (!xfc->display || !xfc->window) return; rdpInput* input = xfc->common.context.input; WINPR_ASSERT(input); const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc); freerdp_input_send_focus_in_event(input, syncFlags); xk_keyboard_update_modifier_keys(xfc); /* finish with a mouse pointer position like mstsc.exe if required */ if (xfc->remote_app || !xfc->window) return; if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state)) { if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height)) { xf_event_adjust_coordinates(xfc, &x, &y); freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y); } } } static int xf_keyboard_execute_action_script(xfContext* xfc, XF_MODIFIER_KEYS* mod, KeySym keysym) { int status = 1; BOOL match = FALSE; char buffer[1024] = { 0 }; char command[2048] = { 0 }; char combination[1024] = { 0 }; if (!xfc->actionScriptExists) return 1; if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R) || (keysym == XK_Alt_L) || (keysym == XK_Alt_R) || (keysym == XK_Control_L) || (keysym == XK_Control_R)) { return 1; } const char* keyStr = XKeysymToString(keysym); if (keyStr == 0) { return 1; } if (mod->Shift) winpr_str_append("Shift", combination, sizeof(combination), "+"); if (mod->Ctrl) winpr_str_append("Ctrl", combination, sizeof(combination), "+"); if (mod->Alt) winpr_str_append("Alt", combination, sizeof(combination), "+"); if (mod->Super) winpr_str_append("Super", combination, sizeof(combination), "+"); winpr_str_append(keyStr, combination, sizeof(combination), NULL); const size_t count = ArrayList_Count(xfc->keyCombinations); for (size_t index = 0; index < count; index++) { const char* keyCombination = (const char*)ArrayList_GetItem(xfc->keyCombinations, index); if (_stricmp(keyCombination, combination) == 0) { match = TRUE; break; } } if (!match) return 1; const char* ActionScript = freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ActionScript); sprintf_s(command, sizeof(command), "%s key %s", ActionScript, combination); FILE* keyScript = popen(command, "r"); if (!keyScript) return -1; while (fgets(buffer, sizeof(buffer), keyScript) != NULL) { char* context = NULL; strtok_s(buffer, "\n", &context); if (strcmp(buffer, "key-local") == 0) status = 0; } if (pclose(keyScript) == -1) status = -1; return status; } static int xk_keyboard_get_modifier_keys(xfContext* xfc, XF_MODIFIER_KEYS* mod) { mod->LeftShift = xf_keyboard_key_pressed(xfc, XK_Shift_L); mod->RightShift = xf_keyboard_key_pressed(xfc, XK_Shift_R); mod->Shift = mod->LeftShift || mod->RightShift; mod->LeftAlt = xf_keyboard_key_pressed(xfc, XK_Alt_L); mod->RightAlt = xf_keyboard_key_pressed(xfc, XK_Alt_R); mod->Alt = mod->LeftAlt || mod->RightAlt; mod->LeftCtrl = xf_keyboard_key_pressed(xfc, XK_Control_L); mod->RightCtrl = xf_keyboard_key_pressed(xfc, XK_Control_R); mod->Ctrl = mod->LeftCtrl || mod->RightCtrl; mod->LeftSuper = xf_keyboard_key_pressed(xfc, XK_Super_L); mod->RightSuper = xf_keyboard_key_pressed(xfc, XK_Super_R); mod->Super = mod->LeftSuper || mod->RightSuper; return 0; } BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym) { XF_MODIFIER_KEYS mod = { 0 }; xk_keyboard_get_modifier_keys(xfc, &mod); // remember state of RightCtrl to ungrab keyboard if next action is release of RightCtrl // do not return anything such that the key could be used by client if ungrab is not the goal if (keysym == XK_Control_R) { if (mod.RightCtrl && !xfc->wasRightCtrlAlreadyPressed) { // Right Ctrl is pressed, getting ready to ungrab xfc->ungrabKeyboardWithRightCtrl = TRUE; xfc->wasRightCtrlAlreadyPressed = TRUE; } } else { // some other key has been pressed, abort ungrabbing if (xfc->ungrabKeyboardWithRightCtrl) xfc->ungrabKeyboardWithRightCtrl = FALSE; } if (!xf_keyboard_execute_action_script(xfc, &mod, keysym)) { return TRUE; } if (!xfc->remote_app && xfc->fullscreen_toggle) { if (keysym == XK_Return) { if (mod.Ctrl && mod.Alt) { /* Ctrl-Alt-Enter: toggle full screen */ xf_toggle_fullscreen(xfc); return TRUE; } } } if ((keysym == XK_c) || (keysym == XK_C)) { if (mod.Ctrl && mod.Alt) { /* Ctrl-Alt-C: toggle control */ if (freerdp_client_encomsp_toggle_control(xfc->common.encomsp)) return TRUE; } } #if 0 /* set to 1 to enable multi touch gesture simulation via keyboard */ #ifdef WITH_XRENDER if (!xfc->remote_app && freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MultiTouchGestures)) { rdpContext* ctx = &xfc->common.context; if (mod.Ctrl && mod.Alt) { int pdx = 0; int pdy = 0; int zdx = 0; int zdy = 0; switch (keysym) { case XK_0: /* Ctrl-Alt-0: Reset scaling and panning */{ const UINT32 sessionWidth = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopWidth); const UINT32 sessionHeight = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopHeight); xfc->scaledWidth = sessionWidth; xfc->scaledHeight = sessionHeight; xfc->offset_x = 0; xfc->offset_y = 0; if (!xfc->fullscreen && (sessionWidth != xfc->window->width || sessionHeight != xfc->window->height)) { xf_ResizeDesktopWindow(xfc, xfc->window, sessionWidth, sessionHeight); } xf_draw_screen(xfc, 0, 0, sessionWidth, sessionHeight); return TRUE; } case XK_1: /* Ctrl-Alt-1: Zoom in */ zdx = zdy = 10; break; case XK_2: /* Ctrl-Alt-2: Zoom out */ zdx = zdy = -10; break; case XK_3: /* Ctrl-Alt-3: Pan left */ pdx = -10; break; case XK_4: /* Ctrl-Alt-4: Pan right */ pdx = 10; break; case XK_5: /* Ctrl-Alt-5: Pan up */ pdy = -10; break; case XK_6: /* Ctrl-Alt-6: Pan up */ pdy = 10; break; } if (pdx != 0 || pdy != 0) { PanningChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = pdx; e.dy = pdy; PubSub_OnPanningChange(ctx->pubSub, xfc, &e); return TRUE; } if (zdx != 0 || zdy != 0) { ZoomingChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = zdx; e.dy = zdy; PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); return TRUE; } } } #endif /* WITH_XRENDER defined */ #endif /* pinch/zoom/pan simulation */ return FALSE; } void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym) { if (keysym != XK_Control_R) return; xfc->wasRightCtrlAlreadyPressed = FALSE; if (!xfc->ungrabKeyboardWithRightCtrl) return; // all requirements for ungrab are fulfilled, ungrabbing now XF_MODIFIER_KEYS mod = { 0 }; xk_keyboard_get_modifier_keys(xfc, &mod); if (!mod.RightCtrl) { if (!xfc->fullscreen) { freerdp_client_encomsp_toggle_control(xfc->common.encomsp); } xfc->mouse_active = FALSE; xf_ungrab(xfc); } // ungrabbed xfc->ungrabKeyboardWithRightCtrl = FALSE; } BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) { xfContext* xfc = (xfContext*)context; xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_SCROLL_LOCK, XK_Scroll_Lock); xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_NUM_LOCK, XK_Num_Lock); xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_CAPS_LOCK, XK_Caps_Lock); xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_KANA_LOCK, XK_Kana_Lock); return TRUE; } BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, UINT32 imeConvMode) { if (!context) return FALSE; WLog_WARN(TAG, "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 ", imeConvMode=%08" PRIx32 ") ignored", imeId, imeState, imeConvMode); return TRUE; } BOOL xf_ungrab(xfContext* xfc) { WINPR_ASSERT(xfc); XUngrabKeyboard(xfc->display, CurrentTime); XUngrabPointer(xfc->display, CurrentTime); xfc->common.mouse_grabbed = FALSE; return TRUE; }