src: Support modifier-only hotkey for release-cursor

This is implemented by switching from GTK accelerators to using the
display's grab sequence handling when a modifier-only hotkey is
configured.

Signed-off-by: Paul Donohue <git@PaulSD.com>
This commit is contained in:
Paul Donohue 2021-04-25 11:58:44 -04:00 committed by Daniel P. Berrangé
parent 26f5f2a3f2
commit f0cc7103be
8 changed files with 235 additions and 95 deletions

View File

@ -131,7 +131,6 @@ struct _VirtViewerAppPrivate {
GResource *resource;
gboolean direct;
gboolean verbose;
gboolean enable_accel;
gboolean authretry;
gboolean started;
gboolean fullscreen;
@ -166,6 +165,7 @@ struct _VirtViewerAppPrivate {
GKeyFile *config;
gchar *config_file;
gchar *release_cursor_display_hotkey;
gchar **insert_smartcard_accel;
gchar **remove_smartcard_accel;
gchar **usb_device_reset_accel;
@ -185,7 +185,7 @@ enum {
PROP_GURI,
PROP_FULLSCREEN,
PROP_TITLE,
PROP_ENABLE_ACCEL,
PROP_RELEASE_CURSOR_DISPLAY_HOTKEY,
PROP_KIOSK,
PROP_QUIT_ON_DISCONNECT,
PROP_UUID,
@ -1930,8 +1930,8 @@ virt_viewer_app_get_property (GObject *object, guint property_id,
g_value_set_string(value, virt_viewer_app_get_title(self));
break;
case PROP_ENABLE_ACCEL:
g_value_set_boolean(value, virt_viewer_app_get_enable_accel(self));
case PROP_RELEASE_CURSOR_DISPLAY_HOTKEY:
g_value_set_string(value, virt_viewer_app_get_release_cursor_display_hotkey(self));
break;
case PROP_KIOSK:
@ -2001,8 +2001,8 @@ virt_viewer_app_set_property (GObject *object, guint property_id,
priv->title = g_value_dup_string(value);
break;
case PROP_ENABLE_ACCEL:
virt_viewer_app_set_enable_accel(self, g_value_get_boolean(value));
case PROP_RELEASE_CURSOR_DISPLAY_HOTKEY:
virt_viewer_app_set_release_cursor_display_hotkey(self, g_value_dup_string(value));
break;
case PROP_KIOSK:
@ -2089,6 +2089,8 @@ virt_viewer_app_dispose (GObject *object)
priv->uuid = NULL;
g_free(priv->config_file);
priv->config_file = NULL;
g_free(priv->release_cursor_display_hotkey);
priv->release_cursor_display_hotkey = NULL;
g_strfreev(priv->insert_smartcard_accel);
priv->insert_smartcard_accel = NULL;
g_strfreev(priv->remove_smartcard_accel);
@ -2496,6 +2498,7 @@ virt_viewer_app_on_application_startup(GApplication *app)
virt_viewer_app_set_kiosk(self, opt_kiosk);
priv->release_cursor_display_hotkey = NULL;
hotkey_names = g_new(gchar*, G_N_ELEMENTS(hotkey_defaults) + 1);
for (i = 0 ; i < G_N_ELEMENTS(hotkey_defaults); i++) {
hotkey_names[i] = g_strdup(hotkey_defaults[i].name);
@ -2699,14 +2702,13 @@ virt_viewer_app_class_init (VirtViewerAppClass *klass)
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_ENABLE_ACCEL,
g_param_spec_boolean("enable-accel",
"Enable Accel",
"Enable accelerators",
FALSE,
G_PARAM_CONSTRUCT |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
PROP_RELEASE_CURSOR_DISPLAY_HOTKEY,
g_param_spec_string("release-cursor-display-hotkey",
"Release Cursor Display Hotkey",
"Display-managed hotkey to ungrab keyboard and mouse",
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_KIOSK,
@ -2828,21 +2830,24 @@ gboolean virt_viewer_app_get_direct(VirtViewerApp *self)
return priv->direct;
}
gboolean
virt_viewer_app_get_enable_accel(VirtViewerApp *self)
gchar*
virt_viewer_app_get_release_cursor_display_hotkey(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
return priv->enable_accel;
return priv->release_cursor_display_hotkey;
}
void
virt_viewer_app_set_enable_accel(VirtViewerApp *self, gboolean enable)
virt_viewer_app_set_release_cursor_display_hotkey(VirtViewerApp *self, const gchar *hotkey)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
priv->enable_accel = enable;
g_object_notify(G_OBJECT(self), "enable-accel");
g_free(priv->release_cursor_display_hotkey);
priv->release_cursor_display_hotkey = g_strdup(hotkey);
g_object_notify(G_OBJECT(self), "release-cursor-display-hotkey");
}
gchar**
@ -2863,6 +2868,7 @@ virt_viewer_app_clear_hotkeys(VirtViewerApp *self)
}
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
virt_viewer_app_set_release_cursor_display_hotkey(self, "Control_L+Alt_L");
VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
g_strfreev(priv->insert_smartcard_accel);
priv->insert_smartcard_accel = NULL;
@ -2909,7 +2915,18 @@ virt_viewer_app_set_hotkey(VirtViewerApp *self, const gchar *hotkey_name,
accels[0] = hotkey;
gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
}
if (accel_key == 0 && accel_mods == 0) {
if (g_str_equal(hotkey_name, "release-cursor")) {
if (accel_key == 0) {
/* GTK does not support using modifiers as hotkeys without any non-modifiers
* (eg. CTRL+ALT), however the displays do support this for the grab sequence.
*/
virt_viewer_app_set_release_cursor_display_hotkey(self, hotkey);
g_free(accel);
return;
}
virt_viewer_app_set_release_cursor_display_hotkey(self, NULL);
}
if (accel_key == 0) {
g_warning("Invalid hotkey '%s' for '%s'", hotkey, hotkey_name);
g_free(accel);
return;
@ -2953,7 +2970,6 @@ virt_viewer_app_set_hotkeys(VirtViewerApp *self, const gchar *hotkeys_str)
if (!hotkeys || g_strv_length(hotkeys) == 0) {
g_strfreev(hotkeys);
virt_viewer_app_set_enable_accel(self, FALSE);
return;
}
@ -2970,8 +2986,6 @@ virt_viewer_app_set_hotkeys(VirtViewerApp *self, const gchar *hotkeys_str)
virt_viewer_app_set_hotkey(self, *hotkey, value);
}
g_strfreev(hotkeys);
virt_viewer_app_set_enable_accel(self, TRUE);
}
void

View File

@ -73,6 +73,8 @@ gboolean virt_viewer_app_initial_connect(VirtViewerApp *self, GError **error);
gboolean virt_viewer_app_get_direct(VirtViewerApp *self);
void virt_viewer_app_set_direct(VirtViewerApp *self, gboolean direct);
char** virt_viewer_app_get_hotkey_names(void);
gchar* virt_viewer_app_get_release_cursor_display_hotkey(VirtViewerApp *self);
void virt_viewer_app_set_release_cursor_display_hotkey(VirtViewerApp *self, const gchar *hotkey);
void virt_viewer_app_set_hotkey(VirtViewerApp *self, const gchar *hotkey_name, const gchar *hotkey);
void virt_viewer_app_set_hotkeys(VirtViewerApp *self, const gchar *hotkeys);
void virt_viewer_app_set_attach(VirtViewerApp *self, gboolean attach);
@ -96,13 +98,11 @@ void virt_viewer_app_set_connect_info(VirtViewerApp *self,
void virt_viewer_app_show_status(VirtViewerApp *self, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3);
void virt_viewer_app_show_display(VirtViewerApp *self);
GList* virt_viewer_app_get_windows(VirtViewerApp *self);
gboolean virt_viewer_app_get_enable_accel(VirtViewerApp *self);
VirtViewerSession* virt_viewer_app_get_session(VirtViewerApp *self);
gboolean virt_viewer_app_get_fullscreen(VirtViewerApp *app);
void virt_viewer_app_clear_hotkeys(VirtViewerApp *app);
GList* virt_viewer_app_get_initial_displays(VirtViewerApp* self);
gint virt_viewer_app_get_initial_monitor_for_display(VirtViewerApp* self, gint display);
void virt_viewer_app_set_enable_accel(VirtViewerApp *app, gboolean enable);
void virt_viewer_app_show_preferences(VirtViewerApp *app, GtkWidget *parent);
gboolean virt_viewer_app_get_session_cancelled(VirtViewerApp *self);

View File

@ -233,31 +233,26 @@ zoom_level_changed(VirtViewerDisplaySpice *self,
}
static void
enable_accel_changed(VirtViewerApp *app,
GParamSpec *pspec G_GNUC_UNUSED,
VirtViewerDisplaySpice *self)
release_cursor_display_hotkey_changed(VirtViewerApp *app,
GParamSpec *pspec G_GNUC_UNUSED,
VirtViewerDisplaySpice *self)
{
gboolean kiosk;
guint accel_key = 0;
GdkModifierType accel_mods = 0;
gchar **accels;
if (virt_viewer_app_get_enable_accel(app)){
accels = gtk_application_get_accels_for_action(GTK_APPLICATION(app), "win.release-cursor");
if (accels[0])
gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
g_strfreev(accels);
}
gchar *hotkey;
g_object_get(app, "kiosk", &kiosk, NULL);
hotkey = virt_viewer_app_get_release_cursor_display_hotkey(app);
if (accel_key || accel_mods || kiosk) {
SpiceGrabSequence *seq = spice_grab_sequence_new(0, NULL);
if (kiosk || hotkey == NULL) {
/* disable default grab sequence */
SpiceGrabSequence *seq = spice_grab_sequence_new(0, NULL);
spice_display_set_grab_keys(self->display, seq);
spice_grab_sequence_free(seq);
} else {
spice_display_set_grab_keys(self->display, NULL);
hotkey = spice_hotkey_to_display_hotkey(hotkey);
SpiceGrabSequence *seq = spice_grab_sequence_new_from_string(hotkey);
g_free(hotkey);
spice_display_set_grab_keys(self->display, seq);
spice_grab_sequence_free(seq);
}
}
@ -337,8 +332,8 @@ virt_viewer_display_spice_new(VirtViewerSessionSpice *session,
app = virt_viewer_session_get_app(VIRT_VIEWER_SESSION(session));
virt_viewer_signal_connect_object(app, "notify::enable-accel",
G_CALLBACK(enable_accel_changed), self, 0);
virt_viewer_signal_connect_object(app, "notify::release-cursor-display-hotkey",
G_CALLBACK(release_cursor_display_hotkey_changed), self, 0);
virt_viewer_signal_connect_object(self, "notify::fullscreen",
G_CALLBACK(resize_policy_changed), app, 0);
virt_viewer_signal_connect_object(self, "notify::auto-resize",
@ -346,7 +341,7 @@ virt_viewer_display_spice_new(VirtViewerSessionSpice *session,
virt_viewer_signal_connect_object(self, "notify::zoom-level",
G_CALLBACK(zoom_level_changed), app, 0);
resize_policy_changed(self, NULL, app);
enable_accel_changed(app, NULL, self);
release_cursor_display_hotkey_changed(app, NULL, self);
return GTK_WIDGET(self);
}

View File

@ -192,31 +192,26 @@ virt_viewer_display_vnc_resize_desktop(VncDisplay *vnc G_GNUC_UNUSED,
static void
enable_accel_changed(VirtViewerApp *app,
GParamSpec *pspec G_GNUC_UNUSED,
VncDisplay *vnc)
release_cursor_display_hotkey_changed(VirtViewerApp *app,
GParamSpec *pspec G_GNUC_UNUSED,
VncDisplay *vnc)
{
gboolean kiosk;
guint accel_key = 0;
GdkModifierType accel_mods = 0;
gchar **accels;
if (virt_viewer_app_get_enable_accel(app)){
accels = gtk_application_get_accels_for_action(GTK_APPLICATION(app), "win.release-cursor");
if (accels[0])
gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
g_strfreev(accels);
}
gchar *hotkey;
g_object_get(app, "kiosk", &kiosk, NULL);
hotkey = virt_viewer_app_get_release_cursor_display_hotkey(app);
if (accel_key || accel_mods || kiosk) {
VncGrabSequence *seq = vnc_grab_sequence_new(0, NULL);
if(kiosk || hotkey == NULL) {
/* disable default grab sequence */
VncGrabSequence *seq = vnc_grab_sequence_new(0, NULL);
vnc_display_set_grab_keys(vnc, seq);
vnc_grab_sequence_free(seq);
} else {
vnc_display_set_grab_keys(vnc, NULL);
hotkey = spice_hotkey_to_display_hotkey(hotkey);
VncGrabSequence *seq = vnc_grab_sequence_new_from_string(hotkey);
g_free(hotkey);
vnc_display_set_grab_keys(vnc, seq);
vnc_grab_sequence_free(seq);
}
}
@ -293,9 +288,9 @@ virt_viewer_display_vnc_new(VirtViewerSessionVnc *session,
G_CALLBACK(virt_viewer_display_vnc_initialized), self);
app = virt_viewer_session_get_app(VIRT_VIEWER_SESSION(session));
virt_viewer_signal_connect_object(app, "notify::enable-accel",
G_CALLBACK(enable_accel_changed), self->vnc, 0);
enable_accel_changed(app, NULL, self->vnc);
virt_viewer_signal_connect_object(app, "notify::release-cursor-display-hotkey",
G_CALLBACK(release_cursor_display_hotkey_changed), self->vnc, 0);
release_cursor_display_hotkey_changed(app, NULL, self->vnc);
#ifdef HAVE_VNC_REMOTE_RESIZE
virt_viewer_signal_connect_object(self, "notify::auto-resize",

View File

@ -970,8 +970,6 @@ virt_viewer_file_fill_app(VirtViewerFile* self, VirtViewerApp *app, GError **err
}
}
virt_viewer_app_set_enable_accel(app, TRUE);
if (virt_viewer_file_is_set(self, "fullscreen"))
g_object_set(G_OBJECT(app), "fullscreen",
virt_viewer_file_get_fullscreen(self), NULL);

View File

@ -434,6 +434,133 @@ spice_hotkey_to_gtk_accelerator(const gchar *key)
return accel;
}
static gchar *
spice_key_to_gdk_key(const gchar *spice_key)
{
guint i;
gchar *key = g_strdup(spice_key);
static const struct {
const char *spice;
const char *gdk;
} keys[] = {
{ "alt", "Alt_L" },
{ "lalt", "Alt_L" },
{ "leftalt", "Alt_L" },
{ "left-alt", "Alt_L" },
{ "ralt", "Alt_R" },
{ "rightalt", "Alt_R" },
{ "right-alt", "Alt_R" },
{ "ctrl", "Control_L" },
{ "ctl", "Control_L" },
{ "control", "Control_L" },
{ "lctrl", "Control_L" },
{ "leftctrl", "Control_L" },
{ "left-ctrl", "Control_L" },
{ "rctrl", "Control_R" },
{ "rightctrl", "Control_R" },
{ "right-ctrl", "Control_R" },
{ "cmd", "Control_L" },
{ "lcmd", "Control_L" },
{ "leftcmd", "Control_L" },
{ "left-cmd", "Control_L" },
{ "rcmd", "Control_R" },
{ "rightcmd", "Control_R" },
{ "right-cmd", "Control_R" },
{ "shift", "Shift_L" },
{ "shft", "Shift_L" },
{ "lshift", "Shift_L" },
{ "leftshift", "Shift_L" },
{ "left-shift", "Shift_L" },
{ "rshift", "Shift_R" },
{ "rightshift", "Shift_R" },
{ "right-shift", "Shift_R" },
{ "win", "Super_L" },
{ "lwin", "Super_L" },
{ "leftwin", "Super_L" },
{ "left-win", "Super_L" },
{ "rwin", "Super_R" },
{ "rightwin", "Super_R" },
{ "right-win", "Super_R" },
{ "super", "Super_L" },
{ "hyper", "Hyper_L" },
{ "meta", "Meta_L" },
{ "esc", "Escape" },
{ "escape", "Escape" },
{ "ins", "Insert" },
{ "insert", "Insert" },
{ "del", "Delete" },
{ "delete", "Delete" },
{ "pgup", "Page_Up" },
{ "pageup", "Page_Up" },
{ "pgdn", "Page_Down" },
{ "pagedown", "Page_Down" },
{ "home", "Home" },
{ "end", "End" },
{ "space", "space" },
{ "enter", "Return" },
{ "tab", "Tab" },
{ "f1", "F1" },
{ "f2", "F2" },
{ "f3", "F3" },
{ "f4", "F4" },
{ "f5", "F5" },
{ "f6", "F6" },
{ "f7", "F7" },
{ "f8", "F8" },
{ "f9", "F9" },
{ "f10", "F10" },
{ "f11", "F11" },
{ "f12", "F12" },
};
if (key[0] == '<' && key[strlen(key)-1] == '>') {
gchar *tmp = key;
key = g_strndup(key+1, strlen(key)-2);
g_free(tmp);
}
for (i = 0; i < G_N_ELEMENTS(keys); ++i) {
if (g_ascii_strcasecmp(keys[i].spice, key) == 0) {
g_free(key);
return g_strdup(keys[i].gdk);
}
}
return key;
}
gchar*
spice_hotkey_to_display_hotkey(const gchar *key)
{
gchar *new_key, **k, **keyv;
keyv = g_strsplit(key, "+", -1);
g_return_val_if_fail(keyv != NULL, NULL);
for (k = keyv; *k != NULL; k++) {
gchar *tmp = *k;
*k = spice_key_to_gdk_key(tmp);
g_free(tmp);
}
new_key = g_strjoinv("+", keyv);
g_strfreev(keyv);
return new_key;
}
static gboolean str_is_empty(const gchar *str)
{
return ((str == NULL) || (str[0] == '\0'));

View File

@ -54,6 +54,7 @@ gulong virt_viewer_signal_connect_object(gpointer instance,
GConnectFlags connect_flags);
gchar* spice_hotkey_to_gtk_accelerator(const gchar *key);
gchar* spice_hotkey_to_display_hotkey(const gchar *key);
gint virt_viewer_compare_buildid(const gchar *s1, const gchar *s2);
/* monitor alignment */

View File

@ -224,7 +224,7 @@ virt_viewer_window_constructed(GObject *object)
if (G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed)
G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed(object);
g_signal_connect(self->app, "notify::enable-accel",
g_signal_connect(self->app, "notify::release-cursor-display-hotkey",
G_CALLBACK(rebuild_combo_menu), object);
rebuild_combo_menu(NULL, NULL, object);
}
@ -872,34 +872,48 @@ virt_viewer_window_get_keycombo_menu(VirtViewerWindow *self)
}
}
if (virt_viewer_app_get_enable_accel(self->app)) {
gchar **accelactions = gtk_application_list_action_descriptions(GTK_APPLICATION(self->app));
gchar **accelactions = gtk_application_list_action_descriptions(GTK_APPLICATION(self->app));
sectionitems = g_menu_new();
section = g_menu_item_new_section(NULL, G_MENU_MODEL(sectionitems));
g_menu_append_item(menu, section);
sectionitems = g_menu_new();
section = g_menu_item_new_section(NULL, G_MENU_MODEL(sectionitems));
g_menu_append_item(menu, section);
for (i = 0; accelactions[i] != NULL; i++) {
gchar **accels = gtk_application_get_accels_for_action(GTK_APPLICATION(self->app),
accelactions[i]);
for (j = 0; accels[j] != NULL; j++) {
for (i = 0; accelactions[i] != NULL; i++) {
if (g_str_equal(accelactions[i], "win.release-cursor")) {
gchar *display_hotkey = virt_viewer_app_get_release_cursor_display_hotkey(self->app);
if (display_hotkey) {
gchar *accel = spice_hotkey_to_gtk_accelerator(display_hotkey);
guint accel_key;
GdkModifierType accel_mods;
gtk_accelerator_parse(accels[j], &accel_key, &accel_mods);
gtk_accelerator_parse(accel, &accel_key, &accel_mods);
guint *keys = accel_key_to_keys(accel_key, accel_mods);
gchar *label = gtk_accelerator_get_label(accel_key, accel_mods);
gchar *label = spice_hotkey_to_display_hotkey(display_hotkey);
virt_viewer_menu_add_combo(self, sectionitems, keys, label);
g_free(label);
g_free(keys);
}
g_strfreev(accels);
}
g_strfreev(accelactions);
gchar **accels = gtk_application_get_accels_for_action(GTK_APPLICATION(self->app),
accelactions[i]);
for (j = 0; accels[j] != NULL; j++) {
guint accel_key;
GdkModifierType accel_mods;
gtk_accelerator_parse(accels[j], &accel_key, &accel_mods);
guint *keys = accel_key_to_keys(accel_key, accel_mods);
gchar *label = gtk_accelerator_get_label(accel_key, accel_mods);
virt_viewer_menu_add_combo(self, sectionitems, keys, label);
g_free(label);
g_free(keys);
}
g_strfreev(accels);
}
g_strfreev(accelactions);
return menu;
}
@ -921,9 +935,7 @@ virt_viewer_window_disable_modifiers(VirtViewerWindow *self)
/* This stops global accelerators like Ctrl+Q == Quit */
for (accels = self->accel_list ; accels ; accels = accels->next) {
if (virt_viewer_app_get_enable_accel(self->app) &&
self->accel_group == accels->data &&
!self->kiosk)
if (self->accel_group == accels->data && !self->kiosk)
continue;
gtk_window_remove_accel_group(GTK_WINDOW(self->window), accels->data);
}
@ -1292,25 +1304,25 @@ virt_viewer_window_update_title(VirtViewerWindow *self)
if (self->grabbed) {
gchar *label;
gchar *display_hotkey;
guint accel_key = 0;
GdkModifierType accel_mods = 0;
gchar **accels;
if (virt_viewer_app_get_enable_accel(self->app)) {
display_hotkey = virt_viewer_app_get_release_cursor_display_hotkey(self->app);
if (display_hotkey) {
label = spice_hotkey_to_display_hotkey(display_hotkey);
} else {
accels = gtk_application_get_accels_for_action(GTK_APPLICATION(self->app), "win.release-cursor");
if (accels[0])
gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
g_strfreev(accels);
}
if (accel_key || accel_mods) {
g_debug("release-cursor accel key: key=%u, mods=%x", accel_key, accel_mods);
label = gtk_accelerator_get_label(accel_key, accel_mods);
} else {
label = g_strdup(_("Ctrl_L+Alt_L"));
}
grabhint = g_strdup_printf(_("(Press %s to release pointer)"), label);
g_free(label);
if (self->subtitle) {
/* translators:
@ -1330,8 +1342,6 @@ virt_viewer_window_update_title(VirtViewerWindow *self)
grabhint,
g_get_application_name());
}
g_free(label);
} else if (self->subtitle) {
/* translators:
* This is "<subtitle> - <appname>"