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>
Stephen Thom authored 3 years ago
Daniel Berrange committed 3 years ago
80 | 80 | hotkeys specified here are handled by the client, it is still possible to send |
81 | 81 | these key combinations to the guest via a menu item. |
82 | 82 | |
83 | =item -K, --keymap | |
84 | ||
85 | Remap and/or block supplied keypresses to the host. All key identifiers are | |
86 | case-sensitive and follow the naming convention as defined in gdkkeysyms.h | |
87 | without the GDK_KEY_ prefix. | |
88 | ||
89 | Running the application with --debug will display keypress symbols in the | |
90 | following way: | |
91 | "Key pressed was keycode='0x63', gdk_keyname='c'" | |
92 | "Key pressed was keycode='0xffeb', gdk_keyname='Super_L'" | |
93 | ||
94 | The format for supplying a keymap is: | |
95 | <srcKeySym1>=[<destKeySym1>][+<destKeySym2][,<srckeySym2>=[<destKeySym1] | |
96 | ||
97 | To block a keypress simply assign an empty parameter to the srcKeySym. | |
98 | ||
99 | Example: | |
100 | --keymap=Super_L=,Alt_L=,1=Shift_L+F1,2=Shift_L+F2 | |
101 | ||
102 | This will block the Super_L (typically Windows Key) and ALT_L keypresses | |
103 | and remap key 1 to Shift F1, 2 to Shift F2. | |
104 | ||
83 | 105 | =item -k, --kiosk |
84 | 106 | |
85 | 107 | Start in kiosk mode. In this mode, the application will start in |
100 | 100 | hotkeys specified here are handled by the client, it is still possible to send |
101 | 101 | these key combinations to the guest via a menu item. |
102 | 102 | |
103 | =item -K, --keymap | |
104 | ||
105 | Remap and/or block supplied keypresses to the host. All key identifiers are | |
106 | case-sensitive and follow the naming convention as defined in gdkkeysyms.h | |
107 | without the GDK_KEY_ prefix. | |
108 | ||
109 | Running the application with --debug will display keypress symbols in the | |
110 | following way: | |
111 | "Key pressed was keycode='0x63', gdk_keyname='c'" | |
112 | "Key pressed was keycode='0xffeb', gdk_keyname='Super_L'" | |
113 | ||
114 | The format for supplying a keymap is: | |
115 | <srcKeySym1>=[<destKeySym1>][+<destKeySym2][,<srckeySym2>=[<destKeySym1] | |
116 | ||
117 | To block a keypress simply assign an empty parameter to the srcKeySym. | |
118 | ||
119 | Example: | |
120 | --keymap=Super_L=,Alt_L=,1=Shift_L+F1,2=Shift_L+F2 | |
121 | ||
122 | This will block the Super_L (typically Windows Key) and ALT_L keypresses | |
123 | and remap key 1 to Shift F1, 2 to Shift F2. | |
124 | ||
103 | 125 | =item -k, --kiosk |
104 | 126 | |
105 | 127 | Start in kiosk mode. In this mode, the application will start in |
162 | 162 | GdkModifierType remove_smartcard_accel_mods; |
163 | 163 | gboolean quit_on_disconnect; |
164 | 164 | gboolean supports_share_clipboard; |
165 | VirtViewerKeyMapping *keyMappings; | |
165 | 166 | }; |
166 | 167 | |
167 | 168 | |
567 | 568 | virt_viewer_app_apply_monitor_mapping(self); |
568 | 569 | } |
569 | 570 | |
571 | static | |
572 | void virt_viewer_app_set_keymap(VirtViewerApp *self, const gchar *keymap_string) | |
573 | { | |
574 | gchar **key, **keymaps, **valkey, **valuekeys = NULL; | |
575 | VirtViewerKeyMapping *keyMappingArray, *keyMappingPtr; | |
576 | guint *mappedArray, *ptrMove; | |
577 | ||
578 | if (keymap_string == NULL) { | |
579 | g_debug("keymap string is empty - nothing to do"); | |
580 | self->priv->keyMappings = NULL; | |
581 | return; | |
582 | } | |
583 | ||
584 | g_debug("keymap string set to %s", keymap_string); | |
585 | ||
586 | g_return_if_fail(VIRT_VIEWER_IS_APP(self)); | |
587 | ||
588 | g_debug("keymap command-line set to %s", keymap_string); | |
589 | if (keymap_string) { | |
590 | keymaps = g_strsplit(keymap_string, ",", -1); | |
591 | } | |
592 | ||
593 | if (!keymaps || g_strv_length(keymaps) == 0) { | |
594 | g_strfreev(keymaps); | |
595 | return; | |
596 | } | |
597 | ||
598 | keyMappingPtr = keyMappingArray = g_new0(VirtViewerKeyMapping, g_strv_length(keymaps)); | |
599 | ||
600 | g_debug("Allocated %d number of mappings", g_strv_length(keymaps)); | |
601 | ||
602 | for (key = keymaps; *key != NULL; key++) { | |
603 | gchar *srcKey = strstr(*key, "="); | |
604 | const gchar *value = (srcKey == NULL) ? NULL : (*srcKey = '\0', srcKey + 1); | |
605 | if (value == NULL) { | |
606 | g_warning("Missing mapping value for key '%s'", srcKey); | |
607 | continue; | |
608 | } | |
609 | ||
610 | // Key value must be resolved to GDK key code | |
611 | // along with mapped key which can also be void (for no action) | |
612 | guint kcode; | |
613 | kcode = gdk_keyval_from_name(*key); | |
614 | if (kcode == GDK_KEY_VoidSymbol) { | |
615 | g_warning("Unable to lookup '%s' key", *key); | |
616 | continue; | |
617 | } | |
618 | g_debug("Mapped source key '%s' to %x", *key, kcode); | |
619 | ||
620 | valuekeys = g_strsplit(value, "+", -1); | |
621 | ||
622 | keyMappingPtr->sourceKey = kcode; | |
623 | keyMappingPtr->numMappedKeys = g_strv_length(valuekeys); | |
624 | keyMappingPtr->isLast = FALSE; | |
625 | ||
626 | if (!valuekeys || g_strv_length(valuekeys) == 0) { | |
627 | g_debug("No value set for key '%s' it will be blocked", *key); | |
628 | keyMappingPtr->mappedKeys = NULL; | |
629 | keyMappingPtr++; | |
630 | g_strfreev(valuekeys); | |
631 | continue; | |
632 | } | |
633 | ||
634 | ptrMove = mappedArray = g_new0(guint, g_strv_length(valuekeys)); | |
635 | ||
636 | guint mcode; | |
637 | for (valkey = valuekeys; *valkey != NULL; valkey++) { | |
638 | g_debug("Value key to map '%s'", *valkey); | |
639 | mcode = gdk_keyval_from_name(*valkey); | |
640 | if (mcode == GDK_KEY_VoidSymbol) { | |
641 | g_warning("Unable to lookup mapped key '%s' it will be ignored", *valkey); | |
642 | } | |
643 | g_debug("Mapped dest key '%s' to %x", *valkey, mcode); | |
644 | *ptrMove++ = mcode; | |
645 | } | |
646 | keyMappingPtr->mappedKeys = mappedArray; | |
647 | keyMappingPtr++; | |
648 | g_strfreev(valuekeys); | |
649 | ||
650 | } | |
651 | keyMappingPtr--; | |
652 | keyMappingPtr->isLast=TRUE; | |
653 | ||
654 | self->priv->keyMappings = keyMappingArray; | |
655 | g_strfreev(keymaps); | |
656 | } | |
657 | ||
570 | 658 | void |
571 | 659 | virt_viewer_app_maybe_quit(VirtViewerApp *self, VirtViewerWindow *window) |
572 | 660 | { |
979 | 1067 | |
980 | 1068 | g_signal_connect(w, "hide", G_CALLBACK(viewer_window_visible_cb), self); |
981 | 1069 | g_signal_connect(w, "show", G_CALLBACK(viewer_window_visible_cb), self); |
1070 | ||
1071 | if (self->priv->keyMappings) { | |
1072 | g_object_set(window, "keymap", self->priv->keyMappings, NULL); | |
1073 | } | |
1074 | ||
982 | 1075 | return window; |
983 | 1076 | } |
984 | 1077 | |
1879 | 1972 | |
1880 | 1973 | static int opt_zoom = NORMAL_ZOOM_LEVEL; |
1881 | 1974 | static gchar *opt_hotkeys = NULL; |
1975 | static gchar *opt_keymap = NULL; | |
1882 | 1976 | static gboolean opt_version = FALSE; |
1883 | 1977 | static gboolean opt_verbose = FALSE; |
1884 | 1978 | static gboolean opt_debug = FALSE; |
2010 | 2104 | virt_viewer_app_set_debug(opt_debug); |
2011 | 2105 | virt_viewer_app_set_fullscreen(self, opt_fullscreen); |
2012 | 2106 | |
2107 | virt_viewer_app_set_keymap(self, opt_keymap); | |
2108 | ||
2013 | 2109 | self->priv->verbose = opt_verbose; |
2014 | 2110 | self->priv->quit_on_disconnect = opt_kiosk ? opt_kiosk_quit : TRUE; |
2015 | 2111 | |
2843 | 2939 | N_("Open in full screen mode (adjusts guest resolution to fit the client)"), NULL }, |
2844 | 2940 | { "hotkeys", 'H', 0, G_OPTION_ARG_STRING, &opt_hotkeys, |
2845 | 2941 | N_("Customise hotkeys"), NULL }, |
2942 | { "keymap", 'K', 0, G_OPTION_ARG_STRING, &opt_keymap, | |
2943 | N_("Remap keys format key=keymod+key e.g. F1=SHIFT+CTRL+F1,1=SHIFT+F1,ALT_L=Void"), NULL }, | |
2846 | 2944 | { "kiosk", 'k', 0, G_OPTION_ARG_NONE, &opt_kiosk, |
2847 | 2945 | N_("Enable kiosk mode"), NULL }, |
2848 | 2946 | { "kiosk-quit", '\0', 0, G_OPTION_ARG_CALLBACK, option_kiosk_quit, |
41 | 41 | GtkApplication parent; |
42 | 42 | VirtViewerAppPrivate *priv; |
43 | 43 | } VirtViewerApp; |
44 | ||
45 | typedef struct { | |
46 | guint sourceKey; | |
47 | guint numMappedKeys; | |
48 | guint *mappedKeys; | |
49 | gboolean isLast; | |
50 | } VirtViewerKeyMapping; | |
44 | 51 | |
45 | 52 | typedef struct { |
46 | 53 | GtkApplicationClass parent_class; |
85 | 85 | PROP_DISPLAY, |
86 | 86 | PROP_SUBTITLE, |
87 | 87 | PROP_APP, |
88 | PROP_KEYMAP, | |
88 | 89 | }; |
89 | 90 | |
90 | 91 | struct _VirtViewerWindowPrivate { |
113 | 114 | gboolean fullscreen; |
114 | 115 | gchar *subtitle; |
115 | 116 | gboolean initial_zoom_set; |
117 | VirtViewerKeyMapping *keyMappings; | |
116 | 118 | }; |
117 | 119 | |
118 | 120 | G_DEFINE_TYPE_WITH_PRIVATE (VirtViewerWindow, virt_viewer_window, G_TYPE_OBJECT) |
163 | 165 | g_return_if_fail(priv->app == NULL); |
164 | 166 | priv->app = g_value_get_object(value); |
165 | 167 | break; |
168 | ||
169 | case PROP_KEYMAP: | |
170 | g_free(priv->keyMappings); | |
171 | priv->keyMappings = (VirtViewerKeyMapping *)g_value_get_pointer(value); | |
172 | break; | |
166 | 173 | |
167 | 174 | default: |
168 | 175 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
312 | 319 | G_PARAM_WRITABLE | |
313 | 320 | G_PARAM_CONSTRUCT_ONLY | |
314 | 321 | G_PARAM_STATIC_STRINGS)); |
322 | g_object_class_install_property(object_class, | |
323 | PROP_KEYMAP, | |
324 | g_param_spec_pointer("keymap", | |
325 | "keymap", | |
326 | "Remapped keys", | |
327 | G_PARAM_WRITABLE | | |
328 | G_PARAM_STATIC_STRINGS)); | |
329 | ||
315 | 330 | } |
316 | 331 | |
317 | 332 | static gboolean |
1483 | 1498 | } |
1484 | 1499 | static gboolean |
1485 | 1500 | window_key_pressed (GtkWidget *widget G_GNUC_UNUSED, |
1486 | GdkEvent *event, | |
1487 | GtkWidget *display) | |
1488 | { | |
1489 | gtk_widget_grab_focus(display); | |
1490 | return gtk_widget_event(display, event); | |
1491 | } | |
1501 | GdkEvent *ev, | |
1502 | VirtViewerWindow *self) | |
1503 | { | |
1504 | GdkEventKey *event; | |
1505 | VirtViewerWindowPrivate *priv; | |
1506 | VirtViewerDisplay *display; | |
1507 | priv = self->priv; | |
1508 | display = priv->display; | |
1509 | event = (GdkEventKey *)ev; | |
1510 | ||
1511 | gtk_widget_grab_focus(GTK_WIDGET(display)); | |
1512 | ||
1513 | // Look through keymaps - if set for mappings and intercept | |
1514 | if (priv->keyMappings) { | |
1515 | VirtViewerKeyMapping *ptr, *matched; | |
1516 | ptr = priv->keyMappings; | |
1517 | matched = NULL; | |
1518 | do { | |
1519 | if (event->keyval == ptr->sourceKey) { | |
1520 | matched = ptr; | |
1521 | } | |
1522 | if (ptr->isLast) { | |
1523 | break; | |
1524 | } | |
1525 | ptr++; | |
1526 | } while (matched == NULL); | |
1527 | ||
1528 | if (matched) { | |
1529 | if (matched->mappedKeys == NULL) { | |
1530 | // Key to be ignored and not pass through to VM | |
1531 | g_debug("Blocking keypress '%s'", gdk_keyval_name(matched->sourceKey)); | |
1532 | } else { | |
1533 | g_debug("Sending through mapped keys"); | |
1534 | virt_viewer_display_send_keys(display, | |
1535 | matched->mappedKeys, matched->numMappedKeys); | |
1536 | } | |
1537 | return TRUE; | |
1538 | } | |
1539 | ||
1540 | } | |
1541 | g_debug("Key pressed was keycode='0x%x', gdk_keyname='%s'", event->keyval, gdk_keyval_name(event->keyval)); | |
1542 | return gtk_widget_event(GTK_WIDGET(display), ev); | |
1543 | } | |
1544 | ||
1492 | 1545 | |
1493 | 1546 | void |
1494 | 1547 | virt_viewer_window_set_display(VirtViewerWindow *self, VirtViewerDisplay *display) |
1516 | 1569 | gtk_widget_realize(GTK_WIDGET(display)); |
1517 | 1570 | |
1518 | 1571 | virt_viewer_signal_connect_object(priv->window, "key-press-event", |
1519 | G_CALLBACK(window_key_pressed), display, 0); | |
1572 | G_CALLBACK(window_key_pressed), self, 0); | |
1520 | 1573 | |
1521 | 1574 | /* switch back to non-display if not ready */ |
1522 | 1575 | if (!(virt_viewer_display_get_show_hint(display) & |