20e2adc4fc
The window tracker is filtering XEvents manually as it only requires a subset of the ones that Gdk listens to in the root window, and this is nice, but we were restricting the set a bit too much because due to this we were not notified when an xsettings manager was available, and thus in case gsd-xsettings was launched after meta-window-tracker (a normal scenario under X11), no xsetting was actually applied to the decoration windows. As per this, the default settings were used for everything and never updated, until a restart of the window-tracker. In order to be able to monitor the XSettings changes at startup, we also need to select the StructureNotifyMask as gtk always do by default. See also: https://gitlab.gnome.org/GNOME/gtk/-/blob/4.11.1/gdk/x11/gdkscreen-x11.c#L947-950 Fixes: #2580 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2948>
447 lines
13 KiB
C
447 lines
13 KiB
C
/*
|
|
* Copyright (C) 2022 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Carlos Garnacho <carlosg@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "meta-window-tracker.h"
|
|
|
|
#include "meta-frame.h"
|
|
|
|
#include <gdesktop-enums.h>
|
|
#include <gdk/x11/gdkx.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/extensions/XInput2.h>
|
|
|
|
struct _MetaWindowTracker
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GSettings *interface_settings;
|
|
|
|
GdkDisplay *display;
|
|
GHashTable *frames;
|
|
GHashTable *client_windows;
|
|
int xinput_opcode;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_DISPLAY,
|
|
N_PROPS
|
|
};
|
|
|
|
static GParamSpec *props[N_PROPS] = { 0, };
|
|
|
|
G_DEFINE_TYPE (MetaWindowTracker, meta_window_tracker, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
meta_window_tracker_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MetaWindowTracker *window_tracker = META_WINDOW_TRACKER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DISPLAY:
|
|
window_tracker->display = g_value_get_object (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_window_tracker_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MetaWindowTracker *window_tracker = META_WINDOW_TRACKER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DISPLAY:
|
|
g_value_set_object (value, window_tracker->display);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_color_scheme (MetaWindowTracker *window_tracker)
|
|
{
|
|
GDesktopColorScheme color_scheme;
|
|
gboolean is_dark;
|
|
|
|
color_scheme = g_settings_get_enum (window_tracker->interface_settings,
|
|
"color-scheme");
|
|
is_dark = color_scheme == G_DESKTOP_COLOR_SCHEME_PREFER_DARK;
|
|
|
|
g_object_set (gtk_settings_get_default (),
|
|
"gtk-application-prefer-dark-theme", is_dark,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
on_color_scheme_changed_cb (GSettings *interface_settings,
|
|
GParamSpec *pspec,
|
|
MetaWindowTracker *window_tracker)
|
|
{
|
|
update_color_scheme (window_tracker);
|
|
}
|
|
|
|
static void
|
|
set_up_frame (MetaWindowTracker *window_tracker,
|
|
Window xwindow)
|
|
{
|
|
GdkDisplay *display = window_tracker->display;
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
GdkSurface *surface;
|
|
Window xframe;
|
|
unsigned long data[1];
|
|
GtkWidget *frame;
|
|
|
|
/* Double check it's not a request for a frame of our own. */
|
|
if (g_hash_table_contains (window_tracker->frames,
|
|
GUINT_TO_POINTER (xwindow)))
|
|
return;
|
|
|
|
/* Create a frame window */
|
|
frame = meta_frame_new (xwindow);
|
|
surface = gtk_native_get_surface (GTK_NATIVE (frame));
|
|
xframe = gdk_x11_surface_get_xid (surface);
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XAddToSaveSet (xdisplay, xwindow);
|
|
|
|
data[0] = xwindow;
|
|
XChangeProperty (xdisplay,
|
|
xframe,
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_FRAME_FOR"),
|
|
XA_WINDOW,
|
|
32,
|
|
PropModeReplace,
|
|
(guchar *) data, 1);
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
{
|
|
gtk_window_destroy (GTK_WINDOW (frame));
|
|
return;
|
|
}
|
|
|
|
g_hash_table_insert (window_tracker->frames,
|
|
GUINT_TO_POINTER (xframe), frame);
|
|
g_hash_table_insert (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow), frame);
|
|
gtk_widget_set_visible (frame, TRUE);
|
|
}
|
|
|
|
static void
|
|
listen_set_up_frame (MetaWindowTracker *window_tracker,
|
|
Window xwindow)
|
|
{
|
|
GdkDisplay *display = window_tracker->display;
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
int format;
|
|
Atom type;
|
|
unsigned long nitems, bytes_after;
|
|
unsigned char *data;
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XSelectInput (xdisplay, xwindow,
|
|
PropertyChangeMask | StructureNotifyMask);
|
|
|
|
XGetWindowProperty (xdisplay,
|
|
xwindow,
|
|
gdk_x11_get_xatom_by_name_for_display (display,
|
|
"_MUTTER_NEEDS_FRAME"),
|
|
0, 1,
|
|
False, XA_CARDINAL,
|
|
&type, &format,
|
|
&nitems, &bytes_after,
|
|
(unsigned char **) &data);
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return;
|
|
|
|
if (nitems > 0 && data[0])
|
|
set_up_frame (window_tracker, xwindow);
|
|
|
|
XFree (data);
|
|
}
|
|
|
|
static void
|
|
remove_frame (MetaWindowTracker *window_tracker,
|
|
Window xwindow)
|
|
{
|
|
GdkDisplay *display = window_tracker->display;
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
GtkWidget *frame;
|
|
GdkSurface *surface;
|
|
Window xframe;
|
|
|
|
frame = g_hash_table_lookup (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow));
|
|
if (!frame)
|
|
return;
|
|
|
|
surface = gtk_native_get_surface (GTK_NATIVE (frame));
|
|
xframe = gdk_x11_surface_get_xid (surface);
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
XRemoveFromSaveSet (xdisplay, xwindow);
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
|
|
g_hash_table_remove (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow));
|
|
g_hash_table_remove (window_tracker->frames,
|
|
GUINT_TO_POINTER (xframe));
|
|
}
|
|
|
|
static gboolean
|
|
on_xevent (GdkDisplay *display,
|
|
XEvent *xevent,
|
|
gpointer user_data)
|
|
{
|
|
Window xroot = gdk_x11_display_get_xrootwindow (display);
|
|
Window xwindow = xevent->xany.window;
|
|
MetaWindowTracker *window_tracker = user_data;
|
|
GtkWidget *frame;
|
|
|
|
if (xevent->type == CreateNotify &&
|
|
xevent->xcreatewindow.parent == xroot &&
|
|
!xevent->xcreatewindow.override_redirect &&
|
|
!g_hash_table_contains (window_tracker->frames,
|
|
GUINT_TO_POINTER (xevent->xcreatewindow.window)))
|
|
{
|
|
xwindow = xevent->xcreatewindow.window;
|
|
listen_set_up_frame (window_tracker, xwindow);
|
|
}
|
|
else if (xevent->type == DestroyNotify)
|
|
{
|
|
xwindow = xevent->xdestroywindow.window;
|
|
remove_frame (window_tracker, xwindow);
|
|
}
|
|
else if (xevent->type == PropertyNotify &&
|
|
xevent->xproperty.atom ==
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_NEEDS_FRAME"))
|
|
{
|
|
if (xevent->xproperty.state == PropertyNewValue &&
|
|
!g_hash_table_contains (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow)))
|
|
set_up_frame (window_tracker, xwindow);
|
|
else if (xevent->xproperty.state == PropertyDelete &&
|
|
g_hash_table_contains (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow)))
|
|
remove_frame (window_tracker, xwindow);
|
|
}
|
|
else if (xevent->type == PropertyNotify)
|
|
{
|
|
frame = g_hash_table_lookup (window_tracker->frames,
|
|
GUINT_TO_POINTER (xwindow));
|
|
|
|
if (!frame)
|
|
{
|
|
frame = g_hash_table_lookup (window_tracker->client_windows,
|
|
GUINT_TO_POINTER (xwindow));
|
|
}
|
|
|
|
if (frame)
|
|
meta_frame_handle_xevent (META_FRAME (frame), xwindow, xevent);
|
|
}
|
|
else if (xevent->type == GenericEvent &&
|
|
xevent->xcookie.extension == window_tracker->xinput_opcode)
|
|
{
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
XIEvent *xi_event;
|
|
|
|
xi_event = (XIEvent *) xevent->xcookie.data;
|
|
|
|
if (xi_event->evtype == XI_Leave)
|
|
{
|
|
XILeaveEvent *crossing = (XILeaveEvent *) xi_event;
|
|
|
|
xwindow = crossing->event;
|
|
frame = g_hash_table_lookup (window_tracker->frames,
|
|
GUINT_TO_POINTER (xwindow));
|
|
|
|
/* When crossing from the frame to the client
|
|
* window, we may need to restore the cursor to
|
|
* its default.
|
|
*/
|
|
if (frame && crossing->detail == XINotifyInferior)
|
|
{
|
|
gdk_x11_display_error_trap_push (display);
|
|
XIUndefineCursor (xdisplay, crossing->deviceid, xwindow);
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
}
|
|
}
|
|
}
|
|
|
|
return GDK_EVENT_PROPAGATE;
|
|
}
|
|
|
|
static gboolean
|
|
query_xi_extension (MetaWindowTracker *window_tracker,
|
|
Display *xdisplay)
|
|
{
|
|
int major = 2, minor = 3;
|
|
int unused;
|
|
|
|
if (XQueryExtension (xdisplay,
|
|
"XInputExtension",
|
|
&window_tracker->xinput_opcode,
|
|
&unused,
|
|
&unused))
|
|
{
|
|
if (XIQueryVersion (xdisplay, &major, &minor) == Success)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
meta_window_tracker_constructed (GObject *object)
|
|
{
|
|
MetaWindowTracker *window_tracker = META_WINDOW_TRACKER (object);
|
|
GdkDisplay *display = window_tracker->display;
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
Window xroot = gdk_x11_display_get_xrootwindow (display);
|
|
Window *windows, ignored1, ignored2;
|
|
unsigned int i, n_windows;
|
|
|
|
G_OBJECT_CLASS (meta_window_tracker_parent_class)->constructed (object);
|
|
|
|
query_xi_extension (window_tracker, xdisplay);
|
|
|
|
XSelectInput (xdisplay, xroot,
|
|
KeyPressMask |
|
|
PropertyChangeMask |
|
|
StructureNotifyMask |
|
|
SubstructureNotifyMask);
|
|
|
|
g_signal_connect (display, "xevent",
|
|
G_CALLBACK (on_xevent), object);
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XQueryTree (xdisplay,
|
|
xroot,
|
|
&ignored1, &ignored2,
|
|
&windows, &n_windows);
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
{
|
|
g_warning ("Could not query existing windows");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < n_windows; i++)
|
|
{
|
|
XWindowAttributes attrs;
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XGetWindowAttributes (xdisplay,
|
|
windows[i],
|
|
&attrs);
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
continue;
|
|
|
|
if (attrs.override_redirect)
|
|
continue;
|
|
|
|
listen_set_up_frame (window_tracker, windows[i]);
|
|
}
|
|
|
|
XFree (windows);
|
|
}
|
|
|
|
static void
|
|
meta_window_tracker_finalize (GObject *object)
|
|
{
|
|
MetaWindowTracker *window_tracker = META_WINDOW_TRACKER (object);
|
|
|
|
g_clear_object (&window_tracker->interface_settings);
|
|
g_clear_pointer (&window_tracker->frames,
|
|
g_hash_table_unref);
|
|
g_clear_pointer (&window_tracker->client_windows,
|
|
g_hash_table_unref);
|
|
|
|
G_OBJECT_CLASS (meta_window_tracker_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
meta_window_tracker_class_init (MetaWindowTrackerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->set_property = meta_window_tracker_set_property;
|
|
object_class->get_property = meta_window_tracker_get_property;
|
|
object_class->constructed = meta_window_tracker_constructed;
|
|
object_class->finalize = meta_window_tracker_finalize;
|
|
|
|
props[PROP_DISPLAY] = g_param_spec_object ("display",
|
|
"Display",
|
|
"Display",
|
|
GDK_TYPE_DISPLAY,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_NICK |
|
|
G_PARAM_STATIC_BLURB);
|
|
|
|
g_object_class_install_properties (object_class,
|
|
G_N_ELEMENTS (props),
|
|
props);
|
|
}
|
|
|
|
static void
|
|
meta_window_tracker_init (MetaWindowTracker *window_tracker)
|
|
{
|
|
window_tracker->interface_settings = g_settings_new ("org.gnome.desktop.interface");
|
|
g_signal_connect (window_tracker->interface_settings,
|
|
"changed::color-scheme",
|
|
G_CALLBACK (on_color_scheme_changed_cb),
|
|
window_tracker);
|
|
update_color_scheme (window_tracker);
|
|
|
|
window_tracker->frames =
|
|
g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) gtk_window_destroy);
|
|
window_tracker->client_windows = g_hash_table_new (NULL, NULL);
|
|
}
|
|
|
|
MetaWindowTracker *
|
|
meta_window_tracker_new (GdkDisplay *display)
|
|
{
|
|
return g_object_new (META_TYPE_WINDOW_TRACKER,
|
|
"display", display,
|
|
NULL);
|
|
}
|