Added -K --keymap commandline option that allows user to block certain keypresses or to remap keypresses being sent to the underlying spice or vnc widget

Signed-off-by: Stephen Thom <sthom@williamhill.co.uk>
This commit is contained in:
Stephen Thom 2020-06-05 14:12:19 +00:00 committed by Daniel Berrange
parent 27ea968c70
commit fedaa5f89a
5 changed files with 207 additions and 5 deletions

View File

@ -81,6 +81,28 @@ Note that hotkeys for which no binding is given are disabled. Although the
hotkeys specified here are handled by the client, it is still possible to send
these key combinations to the guest via a menu item.
=item -K, --keymap
Remap and/or block supplied keypresses to the host. All key identifiers are
case-sensitive and follow the naming convention as defined in gdkkeysyms.h
without the GDK_KEY_ prefix.
Running the application with --debug will display keypress symbols in the
following way:
"Key pressed was keycode='0x63', gdk_keyname='c'"
"Key pressed was keycode='0xffeb', gdk_keyname='Super_L'"
The format for supplying a keymap is:
<srcKeySym1>=[<destKeySym1>][+<destKeySym2][,<srckeySym2>=[<destKeySym1]
To block a keypress simply assign an empty parameter to the srcKeySym.
Example:
--keymap=Super_L=,Alt_L=,1=Shift_L+F1,2=Shift_L+F2
This will block the Super_L (typically Windows Key) and ALT_L keypresses
and remap key 1 to Shift F1, 2 to Shift F2.
=item -k, --kiosk
Start in kiosk mode. In this mode, the application will start in

View File

@ -101,6 +101,28 @@ Note that hotkeys for which no binding is given are disabled. Although the
hotkeys specified here are handled by the client, it is still possible to send
these key combinations to the guest via a menu item.
=item -K, --keymap
Remap and/or block supplied keypresses to the host. All key identifiers are
case-sensitive and follow the naming convention as defined in gdkkeysyms.h
without the GDK_KEY_ prefix.
Running the application with --debug will display keypress symbols in the
following way:
"Key pressed was keycode='0x63', gdk_keyname='c'"
"Key pressed was keycode='0xffeb', gdk_keyname='Super_L'"
The format for supplying a keymap is:
<srcKeySym1>=[<destKeySym1>][+<destKeySym2][,<srckeySym2>=[<destKeySym1]
To block a keypress simply assign an empty parameter to the srcKeySym.
Example:
--keymap=Super_L=,Alt_L=,1=Shift_L+F1,2=Shift_L+F2
This will block the Super_L (typically Windows Key) and ALT_L keypresses
and remap key 1 to Shift F1, 2 to Shift F2.
=item -k, --kiosk
Start in kiosk mode. In this mode, the application will start in

View File

@ -163,6 +163,7 @@ struct _VirtViewerAppPrivate {
GdkModifierType remove_smartcard_accel_mods;
gboolean quit_on_disconnect;
gboolean supports_share_clipboard;
VirtViewerKeyMapping *keyMappings;
};
@ -568,6 +569,93 @@ void virt_viewer_app_set_uuid_string(VirtViewerApp *self, const gchar *uuid_stri
virt_viewer_app_apply_monitor_mapping(self);
}
static
void virt_viewer_app_set_keymap(VirtViewerApp *self, const gchar *keymap_string)
{
gchar **key, **keymaps, **valkey, **valuekeys = NULL;
VirtViewerKeyMapping *keyMappingArray, *keyMappingPtr;
guint *mappedArray, *ptrMove;
if (keymap_string == NULL) {
g_debug("keymap string is empty - nothing to do");
self->priv->keyMappings = NULL;
return;
}
g_debug("keymap string set to %s", keymap_string);
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
g_debug("keymap command-line set to %s", keymap_string);
if (keymap_string) {
keymaps = g_strsplit(keymap_string, ",", -1);
}
if (!keymaps || g_strv_length(keymaps) == 0) {
g_strfreev(keymaps);
return;
}
keyMappingPtr = keyMappingArray = g_new0(VirtViewerKeyMapping, g_strv_length(keymaps));
g_debug("Allocated %d number of mappings", g_strv_length(keymaps));
for (key = keymaps; *key != NULL; key++) {
gchar *srcKey = strstr(*key, "=");
const gchar *value = (srcKey == NULL) ? NULL : (*srcKey = '\0', srcKey + 1);
if (value == NULL) {
g_warning("Missing mapping value for key '%s'", srcKey);
continue;
}
// Key value must be resolved to GDK key code
// along with mapped key which can also be void (for no action)
guint kcode;
kcode = gdk_keyval_from_name(*key);
if (kcode == GDK_KEY_VoidSymbol) {
g_warning("Unable to lookup '%s' key", *key);
continue;
}
g_debug("Mapped source key '%s' to %x", *key, kcode);
valuekeys = g_strsplit(value, "+", -1);
keyMappingPtr->sourceKey = kcode;
keyMappingPtr->numMappedKeys = g_strv_length(valuekeys);
keyMappingPtr->isLast = FALSE;
if (!valuekeys || g_strv_length(valuekeys) == 0) {
g_debug("No value set for key '%s' it will be blocked", *key);
keyMappingPtr->mappedKeys = NULL;
keyMappingPtr++;
g_strfreev(valuekeys);
continue;
}
ptrMove = mappedArray = g_new0(guint, g_strv_length(valuekeys));
guint mcode;
for (valkey = valuekeys; *valkey != NULL; valkey++) {
g_debug("Value key to map '%s'", *valkey);
mcode = gdk_keyval_from_name(*valkey);
if (mcode == GDK_KEY_VoidSymbol) {
g_warning("Unable to lookup mapped key '%s' it will be ignored", *valkey);
}
g_debug("Mapped dest key '%s' to %x", *valkey, mcode);
*ptrMove++ = mcode;
}
keyMappingPtr->mappedKeys = mappedArray;
keyMappingPtr++;
g_strfreev(valuekeys);
}
keyMappingPtr--;
keyMappingPtr->isLast=TRUE;
self->priv->keyMappings = keyMappingArray;
g_strfreev(keymaps);
}
void
virt_viewer_app_maybe_quit(VirtViewerApp *self, VirtViewerWindow *window)
{
@ -980,6 +1068,11 @@ virt_viewer_app_window_new(VirtViewerApp *self, gint nth)
g_signal_connect(w, "hide", G_CALLBACK(viewer_window_visible_cb), self);
g_signal_connect(w, "show", G_CALLBACK(viewer_window_visible_cb), self);
if (self->priv->keyMappings) {
g_object_set(window, "keymap", self->priv->keyMappings, NULL);
}
return window;
}
@ -1880,6 +1973,7 @@ gboolean virt_viewer_app_start(VirtViewerApp *self, GError **error)
static int opt_zoom = NORMAL_ZOOM_LEVEL;
static gchar *opt_hotkeys = NULL;
static gchar *opt_keymap = NULL;
static gboolean opt_version = FALSE;
static gboolean opt_verbose = FALSE;
static gboolean opt_debug = FALSE;
@ -2011,6 +2105,8 @@ virt_viewer_app_on_application_startup(GApplication *app)
virt_viewer_app_set_debug(opt_debug);
virt_viewer_app_set_fullscreen(self, opt_fullscreen);
virt_viewer_app_set_keymap(self, opt_keymap);
self->priv->verbose = opt_verbose;
self->priv->quit_on_disconnect = opt_kiosk ? opt_kiosk_quit : TRUE;
@ -2844,6 +2940,8 @@ virt_viewer_app_add_option_entries(G_GNUC_UNUSED VirtViewerApp *self,
N_("Open in full screen mode (adjusts guest resolution to fit the client)"), NULL },
{ "hotkeys", 'H', 0, G_OPTION_ARG_STRING, &opt_hotkeys,
N_("Customise hotkeys"), NULL },
{ "keymap", 'K', 0, G_OPTION_ARG_STRING, &opt_keymap,
N_("Remap keys format key=keymod+key e.g. F1=SHIFT+CTRL+F1,1=SHIFT+F1,ALT_L=Void"), NULL },
{ "kiosk", 'k', 0, G_OPTION_ARG_NONE, &opt_kiosk,
N_("Enable kiosk mode"), NULL },
{ "kiosk-quit", '\0', 0, G_OPTION_ARG_CALLBACK, option_kiosk_quit,

View File

@ -43,6 +43,13 @@ typedef struct {
VirtViewerAppPrivate *priv;
} VirtViewerApp;
typedef struct {
guint sourceKey;
guint numMappedKeys;
guint *mappedKeys;
gboolean isLast;
} VirtViewerKeyMapping;
typedef struct {
GtkApplicationClass parent_class;

View File

@ -86,6 +86,7 @@ enum {
PROP_DISPLAY,
PROP_SUBTITLE,
PROP_APP,
PROP_KEYMAP,
};
struct _VirtViewerWindowPrivate {
@ -114,6 +115,7 @@ struct _VirtViewerWindowPrivate {
gboolean fullscreen;
gchar *subtitle;
gboolean initial_zoom_set;
VirtViewerKeyMapping *keyMappings;
};
G_DEFINE_TYPE_WITH_PRIVATE (VirtViewerWindow, virt_viewer_window, G_TYPE_OBJECT)
@ -165,6 +167,11 @@ virt_viewer_window_set_property (GObject *object, guint property_id,
priv->app = g_value_get_object(value);
break;
case PROP_KEYMAP:
g_free(priv->keyMappings);
priv->keyMappings = (VirtViewerKeyMapping *)g_value_get_pointer(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
@ -313,6 +320,14 @@ virt_viewer_window_class_init (VirtViewerWindowClass *klass)
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_KEYMAP,
g_param_spec_pointer("keymap",
"keymap",
"Remapped keys",
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS));
}
static gboolean
@ -1484,13 +1499,51 @@ display_show_hint(VirtViewerDisplay *display,
}
static gboolean
window_key_pressed (GtkWidget *widget G_GNUC_UNUSED,
GdkEvent *event,
GtkWidget *display)
GdkEvent *ev,
VirtViewerWindow *self)
{
gtk_widget_grab_focus(display);
return gtk_widget_event(display, event);
GdkEventKey *event;
VirtViewerWindowPrivate *priv;
VirtViewerDisplay *display;
priv = self->priv;
display = priv->display;
event = (GdkEventKey *)ev;
gtk_widget_grab_focus(GTK_WIDGET(display));
// Look through keymaps - if set for mappings and intercept
if (priv->keyMappings) {
VirtViewerKeyMapping *ptr, *matched;
ptr = priv->keyMappings;
matched = NULL;
do {
if (event->keyval == ptr->sourceKey) {
matched = ptr;
}
if (ptr->isLast) {
break;
}
ptr++;
} while (matched == NULL);
if (matched) {
if (matched->mappedKeys == NULL) {
// Key to be ignored and not pass through to VM
g_debug("Blocking keypress '%s'", gdk_keyval_name(matched->sourceKey));
} else {
g_debug("Sending through mapped keys");
virt_viewer_display_send_keys(display,
matched->mappedKeys, matched->numMappedKeys);
}
return TRUE;
}
}
g_debug("Key pressed was keycode='0x%x', gdk_keyname='%s'", event->keyval, gdk_keyval_name(event->keyval));
return gtk_widget_event(GTK_WIDGET(display), ev);
}
void
virt_viewer_window_set_display(VirtViewerWindow *self, VirtViewerDisplay *display)
{
@ -1517,7 +1570,7 @@ virt_viewer_window_set_display(VirtViewerWindow *self, VirtViewerDisplay *displa
gtk_widget_realize(GTK_WIDGET(display));
virt_viewer_signal_connect_object(priv->window, "key-press-event",
G_CALLBACK(window_key_pressed), display, 0);
G_CALLBACK(window_key_pressed), self, 0);
/* switch back to non-display if not ready */
if (!(virt_viewer_display_get_show_hint(display) &