gdk: Add gdk_toplevel_inhibit_system_shortcuts API

With the removal of grabs from the public API, we need a replacement API
to let applications bypass system keyboard shortcuts.

A typical use case for this API is remote desktop or virtual machine
viewers which need to inhibit the default system keyboard shortcuts so
that the remote session or virtual host gets those instead of the local
environment.

Close: https://gitlab.gnome.org/GNOME/gtk/issues/982
This commit is contained in:
Olivier Fourdan 2020-03-20 15:17:41 +01:00
parent da47ccaf3c
commit 3e1f59af61
6 changed files with 197 additions and 0 deletions

View File

@ -102,6 +102,9 @@ struct _GdkSurface
GdkDrawContext *paint_context;
cairo_region_t *opaque_region;
guint shortcuts_inhibited : 1;
GdkSeat *current_shortcuts_inhibited_seat;
};
struct _GdkSurfaceClass

View File

@ -73,6 +73,17 @@ gdk_toplevel_default_supports_edge_constraints (GdkToplevel *toplevel)
return FALSE;
}
static void
gdk_toplevel_default_inhibit_system_shortcuts (GdkToplevel *toplevel,
GdkEvent *event)
{
}
static void
gdk_toplevel_default_restore_system_shortcuts (GdkToplevel *toplevel)
{
}
static void
gdk_toplevel_default_init (GdkToplevelInterface *iface)
{
@ -82,6 +93,8 @@ gdk_toplevel_default_init (GdkToplevelInterface *iface)
iface->focus = gdk_toplevel_default_focus;
iface->show_window_menu = gdk_toplevel_default_show_window_menu;
iface->supports_edge_constraints = gdk_toplevel_default_supports_edge_constraints;
iface->inhibit_system_shortcuts = gdk_toplevel_default_inhibit_system_shortcuts;
iface->restore_system_shortcuts = gdk_toplevel_default_restore_system_shortcuts;
g_object_interface_install_property (iface,
g_param_spec_flags ("state",
@ -137,6 +150,12 @@ gdk_toplevel_default_init (GdkToplevelInterface *iface)
GDK_TYPE_FULLSCREEN_MODE,
GDK_FULLSCREEN_ON_CURRENT_MONITOR,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
g_object_interface_install_property (iface,
g_param_spec_boolean ("shortcuts-inhibited",
"Shortcuts inhibited",
"Whether keyboard shortcuts are inhibited",
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
}
guint
@ -152,6 +171,7 @@ gdk_toplevel_install_properties (GObjectClass *object_class,
g_object_class_override_property (object_class, first_prop + GDK_TOPLEVEL_PROP_DECORATED, "decorated");
g_object_class_override_property (object_class, first_prop + GDK_TOPLEVEL_PROP_DELETABLE, "deletable");
g_object_class_override_property (object_class, first_prop + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE, "fullscreen-mode");
g_object_class_override_property (object_class, first_prop + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED, "shortcuts-inhibited");
return GDK_TOPLEVEL_NUM_PROPERTIES;
}
@ -439,3 +459,56 @@ gdk_toplevel_supports_edge_constraints (GdkToplevel *toplevel)
return GDK_TOPLEVEL_GET_IFACE (toplevel)->supports_edge_constraints (toplevel);
}
/**
* gdk_toplevel_inhibit_system_shortcuts:
* @toplevel: the #GdkToplevel requesting system keyboard shortcuts
* @event: (nullable): the #GdkEvent that is triggering the inhibit
* request, or %NULL if none is available.
*
* Requests that the @toplevel inhibit the system shortcuts, asking the
* desktop environment/windowing system to let all keyboard events reach
* the surface, as long as it is focused, instead of triggering system
* actions.
*
* If granted, the rerouting remains active until the default shortcuts
* processing is restored with gdk_toplevel_restore_system_shortcuts(),
* or the request is revoked by the desktop enviroment, windowing system
* or the user.
*
* A typical use case for this API is remote desktop or virtual machine
* viewers which need to inhibit the default system keyboard shortcuts
* so that the remote session or virtual host gets those instead of the
* local environment.
*
* The windowing system or desktop environment may ask the user to grant
* or deny the request or even choose to ignore the request entirely.
*
* The caller can be notified whenever the request is granted or revoked
* by listening to the GdkToplevel::shortcuts-inhibited property.
*
*/
void
gdk_toplevel_inhibit_system_shortcuts (GdkToplevel *toplevel,
GdkEvent *event)
{
g_return_if_fail (GDK_IS_TOPLEVEL (toplevel));
GDK_TOPLEVEL_GET_IFACE (toplevel)->inhibit_system_shortcuts (toplevel,
event);
}
/**
* gdk_toplevel_restore_system_shortcuts:
* @toplevel: a #GdkToplevel
*
* Restore default system keyboard shortcuts which were previously
* requested to be inhibited by gdk_toplevel_inhibit_system_shortcuts().
*/
void
gdk_toplevel_restore_system_shortcuts (GdkToplevel *toplevel)
{
g_return_if_fail (GDK_IS_TOPLEVEL (toplevel));
GDK_TOPLEVEL_GET_IFACE (toplevel)->restore_system_shortcuts (toplevel);
}

View File

@ -24,6 +24,7 @@
#error "Only <gdk/gdk.h> can be included directly."
#endif
#include <gdk/gdkseat.h>
#include <gdk/gdksurface.h>
#include <gdk/gdktoplevellayout.h>
@ -87,6 +88,14 @@ void gdk_toplevel_set_deletable (GdkToplevel *toplevel,
GDK_AVAILABLE_IN_ALL
gboolean gdk_toplevel_supports_edge_constraints (GdkToplevel *toplevel);
GDK_AVAILABLE_IN_ALL
void gdk_toplevel_inhibit_system_shortcuts (GdkToplevel *toplevel,
GdkEvent *event);
GDK_AVAILABLE_IN_ALL
void gdk_toplevel_restore_system_shortcuts (GdkToplevel *toplevel);
G_END_DECLS
#endif /* __GDK_TOPLEVEL_H__ */

View File

@ -21,6 +21,9 @@ struct _GdkToplevelInterface
gboolean (* show_window_menu) (GdkToplevel *toplevel,
GdkEvent *event);
gboolean (* supports_edge_constraints) (GdkToplevel *toplevel);
void (* inhibit_system_shortcuts) (GdkToplevel *toplevel,
GdkEvent *event);
void (* restore_system_shortcuts) (GdkToplevel *toplevel);
};
typedef enum
@ -34,6 +37,7 @@ typedef enum
GDK_TOPLEVEL_PROP_DECORATED,
GDK_TOPLEVEL_PROP_DELETABLE,
GDK_TOPLEVEL_PROP_FULLSCREEN_MODE,
GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED,
GDK_TOPLEVEL_NUM_PROPERTIES
} GdkToplevelProperties;

View File

@ -125,6 +125,7 @@ gtk_tests = [
['testblur'],
['testtexture'],
['testwindowdrag'],
['testinhibitshortcuts'],
['testtexthistory', ['../gtk/gtktexthistory.c']],
]

View File

@ -0,0 +1,107 @@
/* testinhibitshortcuts.c
Copyright (C) 2017 Red Hat
Author: Olivier Fourdan <ofourdan@redhat.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtk/gtk.h>
static void
on_shortcuts_inhibit_change (GdkSurface *surface, GParamSpec *pspec, gpointer data)
{
GtkWidget *button = GTK_WIDGET (data);
gboolean button_active;
gboolean shortcuts_inhibited;
g_object_get (GDK_TOPLEVEL (surface), "shortcuts-inhibited", &shortcuts_inhibited, NULL);
gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (button), FALSE);
button_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
if (button_active != shortcuts_inhibited)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), shortcuts_inhibited);
}
static void
on_button_toggle (GtkWidget *button, gpointer data)
{
GdkSurface *surface = GDK_SURFACE (data);
GdkEvent *event;
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
gdk_toplevel_restore_system_shortcuts (GDK_TOPLEVEL (surface));
return;
}
gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (button), TRUE);
event = gtk_get_current_event ();
gdk_toplevel_inhibit_system_shortcuts (GDK_TOPLEVEL (surface), event);
}
static void
quit_cb (GtkWidget *widget,
gpointer user_data)
{
gboolean *done = user_data;
*done = TRUE;
g_main_context_wakeup (NULL);
}
int
main (int argc, char *argv[])
{
GdkSurface *surface;
GtkWidget *window;
GtkWidget *button;
GtkWidget *vbox;
GtkWidget *text_view;
gboolean done = FALSE;
gtk_init ();
window = gtk_window_new ();
g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
gtk_widget_realize (window);
surface = gtk_native_get_surface (gtk_widget_get_native (window));
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
gtk_container_add (GTK_CONTAINER (window), vbox);
text_view = gtk_text_view_new ();
gtk_widget_set_hexpand (text_view, TRUE);
gtk_widget_set_vexpand (text_view, TRUE);
gtk_container_add (GTK_CONTAINER (vbox), text_view);
button = gtk_check_button_new_with_label ("Inhibit system keyboard shorcuts");
gtk_container_add (GTK_CONTAINER (vbox), button);
g_signal_connect (G_OBJECT (button), "toggled",
G_CALLBACK (on_button_toggle), surface);
g_signal_connect (G_OBJECT (surface), "notify::shortcuts-inhibited",
G_CALLBACK (on_shortcuts_inhibit_change), button);
gtk_widget_show (window);
while (!done)
g_main_context_iteration (NULL, TRUE);
return 0;
}