From b80250e483e20bc2b66aa0117af0c442875c1b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 3 Jul 2019 16:47:54 +0200 Subject: [PATCH] tests: Add "accept_take_focus" command When used it setups an X11 event monitor that replies to WM_TAKE_FOCUS ClientMessage's with a XSetInputFocus request. It can only be used by x11 clients on windows that have WM_TAKE_FOCUS atom set and that does not accept input. https://gitlab.gnome.org/GNOME/mutter/merge_requests/669 --- src/tests/test-client.c | 108 +++++++++++++++++++++++++++++++++++++++- src/tests/test-runner.c | 19 +++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/tests/test-client.c b/src/tests/test-client.c index 264be0489..83a5ce485 100644 --- a/src/tests/test-client.c +++ b/src/tests/test-client.c @@ -33,6 +33,7 @@ static gboolean wayland; GHashTable *windows; GQuark event_source_quark; GQuark event_handlers_quark; +GQuark can_take_focus_quark; typedef void (*XEventHandler) (GtkWidget *window, XEvent *event); @@ -63,6 +64,7 @@ lookup_window (const char *window_id) typedef struct { GSource base; + GSource **self_ref; GPollFD event_poll_fd; Display *xdisplay; } XClientEventSource; @@ -120,10 +122,19 @@ x_event_source_dispatch (GSource *source, return TRUE; } +static void +x_event_source_finalize (GSource *source) +{ + XClientEventSource *x_source = (XClientEventSource *) source; + + *x_source->self_ref = NULL; +} + static GSourceFuncs x_event_funcs = { x_event_source_prepare, x_event_source_check, x_event_source_dispatch, + x_event_source_finalize, }; static GSource* @@ -138,6 +149,7 @@ ensure_xsource_handler (GdkDisplay *gdkdisplay) source = g_source_new (&x_event_funcs, sizeof (XClientEventSource)); x_source = (XClientEventSource *) source; + x_source->self_ref = &source; x_source->xdisplay = xdisplay; x_source->event_poll_fd.fd = ConnectionNumber (xdisplay); x_source->event_poll_fd.events = G_IO_IN; @@ -163,6 +175,15 @@ window_has_x11_event_handler (GtkWidget *window, return g_list_find (handlers, handler) != NULL; } +static void +unref_and_maybe_destroy_gsource (GSource *source) +{ + g_source_unref (source); + + if (source->ref_count == 1) + g_source_destroy (source); +} + static void window_add_x11_event_handler (GtkWidget *window, XEventHandler handler) @@ -175,7 +196,7 @@ window_add_x11_event_handler (GtkWidget *window, source = ensure_xsource_handler (gtk_widget_get_display (window)); g_object_set_qdata_full (G_OBJECT (window), event_source_quark, source, - (GDestroyNotify) g_source_unref); + (GDestroyNotify) unref_and_maybe_destroy_gsource); handlers = g_list_append (handlers, handler); g_object_set_qdata (G_OBJECT (window), event_handlers_quark, handlers); @@ -196,6 +217,31 @@ window_remove_x11_event_handler (GtkWidget *window, g_object_set_qdata (G_OBJECT (window), event_handlers_quark, handlers); } +static void +handle_take_focus (GtkWidget *window, + XEvent *xevent) +{ + GdkWindow *gdkwindow = gtk_widget_get_window (window); + GdkDisplay *display = gtk_widget_get_display (window); + Atom wm_protocols = + gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS"); + Atom wm_take_focus = + gdk_x11_get_xatom_by_name_for_display (display, "WM_TAKE_FOCUS"); + + if (xevent->xany.type != ClientMessage || + xevent->xany.window != GDK_WINDOW_XID (gdkwindow)) + return; + + if (xevent->xclient.message_type == wm_protocols && + xevent->xclient.data.l[0] == wm_take_focus) + { + XSetInputFocus (xevent->xany.display, + GDK_WINDOW_XID (gdkwindow), + RevertToParent, + xevent->xclient.data.l[1]); + } +} + static void process_line (const char *line) { @@ -264,6 +310,9 @@ process_line (const char *line) gtk_window_set_title (GTK_WINDOW (window), title); g_free (title); + g_object_set_qdata (G_OBJECT (window), can_take_focus_quark, + GUINT_TO_POINTER (TRUE)); + gtk_widget_realize (window); if (!wayland) @@ -350,6 +399,14 @@ process_line (const char *line) goto out; } + if (!wayland && + window_has_x11_event_handler (window, handle_take_focus)) + { + g_print ("Impossible to use %s for windows accepting take focus", + argv[1]); + goto out; + } + gboolean enabled = g_ascii_strcasecmp (argv[2], "true") == 0; gtk_window_set_accept_focus (GTK_WINDOW (window), enabled); } @@ -374,6 +431,13 @@ process_line (const char *line) goto out; } + if (window_has_x11_event_handler (window, handle_take_focus)) + { + g_print ("Impossible to change %s for windows accepting take focus", + argv[1]); + goto out; + } + GdkDisplay *display = gdk_display_get_default (); GdkWindow *gdkwindow = gtk_widget_get_window (window); Display *xdisplay = gdk_x11_display_get_xdisplay (display); @@ -399,10 +463,51 @@ process_line (const char *line) new_protocols[n++] = wm_take_focus; XSetWMProtocols (xdisplay, xwindow, new_protocols, n); + g_object_set_qdata (G_OBJECT (window), can_take_focus_quark, + GUINT_TO_POINTER (add)); XFree (new_protocols); XFree (protocols); } + else if (strcmp (argv[0], "accept_take_focus") == 0) + { + if (argc != 3) + { + g_print ("usage: %s [true|false]", argv[0]); + goto out; + } + + GtkWidget *window = lookup_window (argv[1]); + if (!window) + { + g_print ("unknown window %s", argv[1]); + goto out; + } + + if (wayland) + { + g_print ("%s not supported under wayland", argv[0]); + goto out; + } + + if (gtk_window_get_accept_focus (GTK_WINDOW (window))) + { + g_print ("%s not supported for input windows", argv[0]); + goto out; + } + + if (!g_object_get_qdata (G_OBJECT (window), can_take_focus_quark)) + { + g_print ("%s not supported for windows with no WM_TAKE_FOCUS set", + argv[0]); + goto out; + } + + if (g_ascii_strcasecmp (argv[2], "true") == 0) + window_add_x11_event_handler (window, handle_take_focus); + else + window_remove_x11_event_handler (window, handle_take_focus); + } else if (strcmp (argv[0], "show") == 0) { if (argc != 2) @@ -669,6 +774,7 @@ main(int argc, char **argv) g_free, NULL); event_source_quark = g_quark_from_static_string ("event-source"); event_handlers_quark = g_quark_from_static_string ("event-handlers"); + can_take_focus_quark = g_quark_from_static_string ("can-take-focus"); GInputStream *raw_in = g_unix_input_stream_new (0, FALSE); GDataInputStream *in = g_data_input_stream_new (raw_in); diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c index fdf4395bf..377b256c2 100644 --- a/src/tests/test-runner.c +++ b/src/tests/test-runner.c @@ -497,6 +497,25 @@ test_case_do (TestCase *test, NULL)) return FALSE; } + else if (strcmp (argv[0], "accept_take_focus") == 0) + { + if (argc != 3 || + (g_ascii_strcasecmp (argv[2], "true") != 0 && + g_ascii_strcasecmp (argv[2], "false") != 0)) + BAD_COMMAND("usage: %s / [true|false]", + argv[0]); + + TestClient *client; + const char *window_id; + if (!test_case_parse_window_id (test, argv[1], &client, &window_id, error)) + return FALSE; + + if (!test_client_do (client, error, + argv[0], window_id, + argv[2], + NULL)) + return FALSE; + } else if (strcmp (argv[0], "show") == 0) { if (argc != 2)