36c2523d68
* add generic function to check for X extensions * detect x keyboard support * add callbacks and client code to set/clear keyboard indicators
604 lines
13 KiB
C
604 lines
13 KiB
C
/**
|
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
|
* X11 Keyboard Handling
|
|
*
|
|
* Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <winpr/crt.h>
|
|
#include <winpr/path.h>
|
|
#include <winpr/collections.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/keysym.h>
|
|
#include <X11/XKBlib.h>
|
|
|
|
#include <freerdp/locale/keyboard.h>
|
|
|
|
#include "xf_event.h"
|
|
|
|
#include "xf_keyboard.h"
|
|
|
|
#include <freerdp/log.h>
|
|
#define TAG CLIENT_TAG("x11")
|
|
|
|
int xf_keyboard_action_script_init(xfContext* xfc)
|
|
{
|
|
int exitCode;
|
|
FILE* keyScript;
|
|
char* keyCombination;
|
|
char buffer[1024] = { 0 };
|
|
char command[1024] = { 0 };
|
|
|
|
if (xfc->actionScript)
|
|
{
|
|
free(xfc->actionScript);
|
|
xfc->actionScript = NULL;
|
|
}
|
|
|
|
if (PathFileExistsA("/usr/share/freerdp/action.sh"))
|
|
xfc->actionScript = _strdup("/usr/share/freerdp/action.sh");
|
|
|
|
if (!xfc->actionScript)
|
|
return 0;
|
|
|
|
xfc->keyCombinations = ArrayList_New(TRUE);
|
|
ArrayList_Object(xfc->keyCombinations)->fnObjectFree = free;
|
|
|
|
sprintf_s(command, sizeof(command), "%s key", xfc->actionScript);
|
|
|
|
keyScript = popen(command, "r");
|
|
|
|
if (keyScript < 0)
|
|
{
|
|
free(xfc->actionScript);
|
|
xfc->actionScript = NULL;
|
|
return 0;
|
|
}
|
|
|
|
while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
|
|
{
|
|
strtok(buffer, "\n");
|
|
keyCombination = _strdup(buffer);
|
|
ArrayList_Add(xfc->keyCombinations, keyCombination);
|
|
}
|
|
|
|
exitCode = pclose(keyScript);
|
|
|
|
xf_event_action_script_init(xfc);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void xf_keyboard_action_script_free(xfContext* xfc)
|
|
{
|
|
xf_event_action_script_free(xfc);
|
|
|
|
if (xfc->keyCombinations)
|
|
{
|
|
ArrayList_Free(xfc->keyCombinations);
|
|
xfc->keyCombinations = NULL;
|
|
}
|
|
|
|
if (xfc->actionScript)
|
|
{
|
|
free(xfc->actionScript);
|
|
xfc->actionScript = NULL;
|
|
}
|
|
}
|
|
|
|
void xf_keyboard_init(xfContext* xfc)
|
|
{
|
|
xf_keyboard_clear(xfc);
|
|
|
|
xfc->KeyboardLayout = xfc->instance->settings->KeyboardLayout;
|
|
xfc->KeyboardLayout = freerdp_keyboard_init(xfc->KeyboardLayout);
|
|
xfc->instance->settings->KeyboardLayout = xfc->KeyboardLayout;
|
|
|
|
if (xfc->modifierMap)
|
|
XFreeModifiermap(xfc->modifierMap);
|
|
|
|
xfc->modifierMap = XGetModifierMapping(xfc->display);
|
|
|
|
xf_keyboard_action_script_init(xfc);
|
|
}
|
|
|
|
void xf_keyboard_free(xfContext* xfc)
|
|
{
|
|
if (xfc->modifierMap)
|
|
{
|
|
XFreeModifiermap(xfc->modifierMap);
|
|
xfc->modifierMap = NULL;
|
|
}
|
|
|
|
xf_keyboard_action_script_free(xfc);
|
|
}
|
|
|
|
void xf_keyboard_clear(xfContext* xfc)
|
|
{
|
|
ZeroMemory(xfc->KeyboardState, 256 * sizeof(BOOL));
|
|
}
|
|
|
|
void xf_keyboard_key_press(xfContext* xfc, BYTE keycode, KeySym keysym)
|
|
{
|
|
if (keycode < 8)
|
|
return;
|
|
|
|
xfc->KeyboardState[keycode] = keysym;
|
|
|
|
if (xf_keyboard_handle_special_keys(xfc, keysym))
|
|
return;
|
|
|
|
xf_keyboard_send_key(xfc, TRUE, keycode);
|
|
}
|
|
|
|
void xf_keyboard_key_release(xfContext* xfc, BYTE keycode)
|
|
{
|
|
if (keycode < 8)
|
|
return;
|
|
|
|
xfc->KeyboardState[keycode] = NoSymbol;
|
|
|
|
xf_keyboard_send_key(xfc, FALSE, keycode);
|
|
}
|
|
|
|
void xf_keyboard_release_all_keypress(xfContext* xfc)
|
|
{
|
|
int keycode;
|
|
DWORD rdp_scancode;
|
|
|
|
for (keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++)
|
|
{
|
|
if (xfc->KeyboardState[keycode] != NoSymbol)
|
|
{
|
|
rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode);
|
|
freerdp_input_send_keyboard_event_ex(xfc->instance->input, FALSE, rdp_scancode);
|
|
xfc->KeyboardState[keycode] = NoSymbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym)
|
|
{
|
|
KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
|
|
return (xfc->KeyboardState[keycode] == keysym);
|
|
}
|
|
|
|
void xf_keyboard_send_key(xfContext* xfc, BOOL down, BYTE keycode)
|
|
{
|
|
DWORD rdp_scancode;
|
|
rdpInput* input;
|
|
|
|
input = xfc->instance->input;
|
|
rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode);
|
|
|
|
if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
|
|
{
|
|
WLog_ERR(TAG, "Unknown key with X keycode 0x%02x", keycode);
|
|
}
|
|
else 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
|
|
{
|
|
freerdp_input_send_keyboard_event_ex(input, down, rdp_scancode);
|
|
|
|
if ((rdp_scancode == RDP_SCANCODE_CAPSLOCK) && (down == FALSE))
|
|
{
|
|
UINT32 syncFlags;
|
|
syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
|
|
input->SynchronizeEvent(input, syncFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
int xf_keyboard_read_keyboard_state(xfContext* xfc)
|
|
{
|
|
int dummy;
|
|
Window wdummy;
|
|
UINT32 state = 0;
|
|
|
|
if (!xfc->remote_app)
|
|
{
|
|
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 modifierpos, key, keysymMask = 0;
|
|
KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
|
|
|
|
if (keycode == NoSymbol)
|
|
return 0;
|
|
|
|
for (modifierpos = 0; modifierpos < 8; modifierpos++)
|
|
{
|
|
int offset = xfc->modifierMap->max_keypermod * modifierpos;
|
|
|
|
for (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)
|
|
{
|
|
int keysymMask;
|
|
|
|
if (!xfc->xkbAvailable)
|
|
return FALSE;
|
|
|
|
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)
|
|
{
|
|
int state;
|
|
UINT32 toggleKeysState = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
void xf_keyboard_focus_in(xfContext* xfc)
|
|
{
|
|
rdpInput* input;
|
|
UINT32 syncFlags;
|
|
int dummy, mouseX, mouseY;
|
|
Window wdummy;
|
|
UINT32 state = 0;
|
|
|
|
if (xfc->display && xfc->window)
|
|
{
|
|
input = xfc->instance->input;
|
|
syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
|
|
XQueryPointer(xfc->display, xfc->window->handle, &wdummy, &wdummy, &mouseX, &mouseY, &dummy, &dummy, &state);
|
|
input->FocusInEvent(input, syncFlags, mouseX, mouseY);
|
|
}
|
|
}
|
|
|
|
int xf_keyboard_execute_action_script(xfContext* xfc, XF_MODIFIER_KEYS* mod, KeySym keysym)
|
|
{
|
|
int index;
|
|
int count;
|
|
int exitCode;
|
|
int status = 1;
|
|
FILE* keyScript;
|
|
const char* keyStr;
|
|
BOOL match = FALSE;
|
|
char* keyCombination;
|
|
char buffer[1024] = { 0 };
|
|
char command[1024] = { 0 };
|
|
char combination[1024] = { 0 };
|
|
|
|
if (!xfc->actionScript)
|
|
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;
|
|
}
|
|
|
|
keyStr = XKeysymToString(keysym);
|
|
|
|
if (mod->Shift)
|
|
strcat(combination, "Shift+");
|
|
|
|
if (mod->Ctrl)
|
|
strcat(combination, "Ctrl+");
|
|
|
|
if (mod->Alt)
|
|
strcat(combination, "Alt+");
|
|
|
|
strcat(combination, keyStr);
|
|
|
|
count = ArrayList_Count(xfc->keyCombinations);
|
|
|
|
for (index = 0; index < count; index++)
|
|
{
|
|
keyCombination = (char*) ArrayList_GetItem(xfc->keyCombinations, index);
|
|
|
|
if (_stricmp(keyCombination, combination) == 0)
|
|
{
|
|
match = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!match)
|
|
return 1;
|
|
|
|
sprintf_s(command, sizeof(command), "%s key %s",
|
|
xfc->actionScript, combination);
|
|
|
|
keyScript = popen(command, "r");
|
|
|
|
if (keyScript < 0)
|
|
return -1;
|
|
|
|
while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
|
|
{
|
|
strtok(buffer, "\n");
|
|
|
|
if (strcmp(buffer, "key-local") == 0)
|
|
status = 0;
|
|
}
|
|
|
|
exitCode = pclose(keyScript);
|
|
|
|
return status;
|
|
}
|
|
|
|
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);
|
|
|
|
if (!xf_keyboard_execute_action_script(xfc, &mod, keysym))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if(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 */
|
|
xf_toggle_control(xfc);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_period)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
/* Zoom In (scale larger) */
|
|
|
|
double s = xfc->settings->ScalingFactor;
|
|
|
|
s += 0.1;
|
|
|
|
if (s > 2.0)
|
|
s = 2.0;
|
|
|
|
xfc->settings->ScalingFactor = s;
|
|
|
|
xfc->currentWidth = xfc->originalWidth * s;
|
|
xfc->currentHeight = xfc->originalHeight * s;
|
|
|
|
xf_transform_window(xfc);
|
|
|
|
{
|
|
ResizeWindowEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.width = (int) xfc->originalWidth * xfc->settings->ScalingFactor;
|
|
e.height = (int) xfc->originalHeight * xfc->settings->ScalingFactor;
|
|
PubSub_OnResizeWindow(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
xf_draw_screen_scaled(xfc, 0, 0, 0, 0, FALSE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_comma)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
/* Zoom Out (scale smaller) */
|
|
|
|
double s = xfc->settings->ScalingFactor;
|
|
|
|
s -= 0.1;
|
|
|
|
if (s < 0.5)
|
|
s = 0.5;
|
|
|
|
xfc->settings->ScalingFactor = s;
|
|
|
|
xfc->currentWidth = xfc->originalWidth * s;
|
|
xfc->currentHeight = xfc->originalHeight * s;
|
|
|
|
xf_transform_window(xfc);
|
|
|
|
{
|
|
ResizeWindowEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.width = (int) xfc->originalWidth * xfc->settings->ScalingFactor;
|
|
e.height = (int) xfc->originalHeight * xfc->settings->ScalingFactor;
|
|
PubSub_OnResizeWindow(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
|
|
xf_draw_screen_scaled(xfc, 0, 0, 0, 0, FALSE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_KP_4)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
|
|
{
|
|
PanningChangeEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.XPan = -5;
|
|
e.YPan = 0;
|
|
PubSub_OnPanningChange(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_KP_6)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
|
|
{
|
|
PanningChangeEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.XPan = 5;
|
|
e.YPan = 0;
|
|
PubSub_OnPanningChange(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_KP_8)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
{
|
|
PanningChangeEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.XPan = 0;
|
|
e.YPan = -5;
|
|
PubSub_OnPanningChange(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (keysym == XK_KP_2)
|
|
{
|
|
if (mod.Ctrl && mod.Alt)
|
|
{
|
|
{
|
|
PanningChangeEventArgs e;
|
|
|
|
EventArgsInit(&e, "xfreerdp");
|
|
e.XPan = 0;
|
|
e.YPan = 5;
|
|
PubSub_OnPanningChange(((rdpContext*) xfc)->pubSub, xfc, &e);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void 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);
|
|
}
|