gdk: Add gdk_toplevel_inhibit_system_shortcuts API
authorOlivier Fourdan <ofourdan@redhat.com>
Fri, 20 Mar 2020 14:17:41 +0000 (15:17 +0100)
committerOlivier Fourdan <ofourdan@redhat.com>
Mon, 30 Mar 2020 16:25:36 +0000 (18:25 +0200)
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

gdk/gdksurfaceprivate.h
gdk/gdktoplevel.c
gdk/gdktoplevel.h
gdk/gdktoplevelprivate.h
tests/meson.build
tests/testinhibitshortcuts.c [new file with mode: 0644]

index 0c94b5a80f0fad0eb970d7e7ab34178d17992d6d..cbc6e53094f52a4861035f344c8b2741b7c8757a 100644 (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
index 35ea7f52f617dfcc7445733993c221b3f494a538..96e49826273af9c0b277f3708cd609621350336a 100644 (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);
+}
index 720d0dea72425f61790add4e299f935f41f1df3b..863c26f3e8e982b1c9d5b4fdf54861034bf0aeb9 100644 (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__ */
index ac3c5a4ab32e2bab9f0ff3dd249ff2b4686cb235..c54d0ac503d1ffb9f94599150a72f6594d28d550 100644 (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;
 
index abf681fcc351f0a4825c56447b7ee8d722bf5cd8..6d57a63c9ff0ea77acffe6780e69af1b8b42b4c8 100644 (file)
@@ -125,6 +125,7 @@ gtk_tests = [
   ['testblur'],
   ['testtexture'],
   ['testwindowdrag'],
+  ['testinhibitshortcuts'],
   ['testtexthistory', ['../gtk/gtktexthistory.c']],
 ]
 
diff --git a/tests/testinhibitshortcuts.c b/tests/testinhibitshortcuts.c
new file mode 100644 (file)
index 0000000..fa746ed
--- /dev/null
@@ -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;
+}