/*
* 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 .
*
* Author: Carlos Garnacho
*/
#include "config.h"
#include "meta-window-tracker.h"
#include "meta-frame.h"
#include
#include
#include
#include
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);
}