d643eb5c6f
All X11 surfaces created by gtk4 claim to support XDND via the XdndAware property. This was leading some clients, e.g. Qt, to consider the frame window as drop target instead of the client window. Avoid this issue by removing the XdndAware property again after gtk has created the surface. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2715 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2978>
601 lines
17 KiB
C
601 lines
17 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-frame.h"
|
|
|
|
#include "meta-frame-content.h"
|
|
#include "meta-frame-header.h"
|
|
|
|
#include <gdk/x11/gdkx.h>
|
|
#include <X11/Xatom.h>
|
|
|
|
struct _MetaFrame
|
|
{
|
|
GtkWindow parent_instance;
|
|
GtkWidget *content;
|
|
Atom atom__NET_WM_VISIBLE_NAME;
|
|
Atom atom__NET_WM_NAME;
|
|
Atom atom__MOTIF_WM_HINTS;
|
|
Atom atom__NET_WM_STATE;
|
|
Atom atom__NET_WM_STATE_FULLSCREEN;
|
|
|
|
char *net_wm_visible_name;
|
|
char *net_wm_name;
|
|
char *wm_name;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
unsigned long decorations;
|
|
long input_mode;
|
|
unsigned long status;
|
|
} MotifWmHints;
|
|
|
|
#define MWM_HINTS_FUNCTIONS (1L << 0)
|
|
|
|
#define MWM_FUNC_ALL (1L << 0)
|
|
#define MWM_FUNC_RESIZE (1L << 1)
|
|
#define MWM_FUNC_MINIMIZE (1L << 3)
|
|
#define MWM_FUNC_MAXIMIZE (1L << 4)
|
|
#define MWM_FUNC_CLOSE (1L << 5)
|
|
|
|
G_DEFINE_TYPE (MetaFrame, meta_frame, GTK_TYPE_WINDOW)
|
|
|
|
static void
|
|
meta_frame_constructed (GObject *object)
|
|
{
|
|
MetaFrame *frame = META_FRAME (object);
|
|
GdkDisplay *display;
|
|
|
|
display = gtk_widget_get_display (GTK_WIDGET (object));
|
|
|
|
frame->atom__NET_WM_VISIBLE_NAME =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_VISIBLE_NAME");
|
|
frame->atom__NET_WM_NAME =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME");
|
|
frame->atom__MOTIF_WM_HINTS =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_MOTIF_WM_HINTS");
|
|
frame->atom__NET_WM_STATE =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE");
|
|
frame->atom__NET_WM_STATE_FULLSCREEN =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE_FULLSCREEN");
|
|
|
|
G_OBJECT_CLASS (meta_frame_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
meta_frame_finalize (GObject *object)
|
|
{
|
|
MetaFrame *frame = META_FRAME (object);
|
|
|
|
g_free (frame->net_wm_visible_name);
|
|
g_free (frame->net_wm_name);
|
|
g_free (frame->wm_name);
|
|
|
|
G_OBJECT_CLASS (meta_frame_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
meta_frame_class_init (MetaFrameClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->constructed = meta_frame_constructed;
|
|
object_class->finalize = meta_frame_finalize;
|
|
}
|
|
|
|
static gboolean
|
|
client_window_has_wm_protocol (MetaFrame *frame,
|
|
Window client_window,
|
|
Atom protocol)
|
|
{
|
|
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
Atom *wm_protocols, wm_protocols_atom;
|
|
int format;
|
|
Atom type;
|
|
unsigned long i, nitems, bytes_after;
|
|
gboolean found = FALSE;
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
wm_protocols_atom =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS");
|
|
|
|
if (XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
|
|
client_window,
|
|
wm_protocols_atom,
|
|
0, G_MAXLONG, False,
|
|
XA_ATOM,
|
|
&type, &format,
|
|
&nitems, &bytes_after,
|
|
(unsigned char **) &wm_protocols) != Success)
|
|
{
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
return FALSE;
|
|
}
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return FALSE;
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
if (wm_protocols[i] == protocol)
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
XFree (wm_protocols);
|
|
|
|
return found;
|
|
}
|
|
|
|
static gboolean
|
|
on_frame_close_request (GtkWindow *window,
|
|
gpointer user_data)
|
|
{
|
|
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (window));
|
|
GtkWidget *content;
|
|
XClientMessageEvent ev = { 0 };
|
|
Window client_xwindow;
|
|
Atom delete_window_atom;
|
|
|
|
content = gtk_window_get_child (window);
|
|
if (!content)
|
|
return FALSE;
|
|
|
|
client_xwindow =
|
|
meta_frame_content_get_window (META_FRAME_CONTENT (content));
|
|
|
|
delete_window_atom =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "WM_DELETE_WINDOW");
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
if (client_window_has_wm_protocol (META_FRAME (window),
|
|
client_xwindow,
|
|
delete_window_atom))
|
|
{
|
|
ev.type = ClientMessage;
|
|
ev.window = client_xwindow;
|
|
ev.message_type =
|
|
gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS");
|
|
ev.format = 32;
|
|
ev.data.l[0] = delete_window_atom;
|
|
ev.data.l[1] = 0; /* FIXME: missing timestamp */
|
|
|
|
XSendEvent (gdk_x11_display_get_xdisplay (display),
|
|
client_xwindow, False, 0, (XEvent*) &ev);
|
|
}
|
|
else
|
|
{
|
|
XKillClient (gdk_x11_display_get_xdisplay (display),
|
|
client_xwindow);
|
|
}
|
|
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
meta_frame_init (MetaFrame *frame)
|
|
{
|
|
g_signal_connect (frame, "close-request",
|
|
G_CALLBACK (on_frame_close_request), NULL);
|
|
}
|
|
|
|
static void
|
|
meta_frame_update_extents (MetaFrame *frame,
|
|
GtkBorder border)
|
|
{
|
|
GtkWindow *window = GTK_WINDOW (frame);
|
|
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
GdkSurface *surface;
|
|
Window xframe;
|
|
unsigned long data[4];
|
|
|
|
surface = gtk_native_get_surface (GTK_NATIVE (window));
|
|
if (!surface)
|
|
return;
|
|
|
|
data[0] = border.left;
|
|
data[1] = border.right;
|
|
data[2] = border.top;
|
|
data[3] = border.bottom;
|
|
|
|
xframe = gdk_x11_surface_get_xid (surface);
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XChangeProperty (gdk_x11_display_get_xdisplay (display),
|
|
xframe,
|
|
gdk_x11_get_xatom_by_name_for_display (display, "_MUTTER_FRAME_EXTENTS"),
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(guchar *) &data, 4);
|
|
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
}
|
|
|
|
static void
|
|
on_border_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
MetaFrame *frame = user_data;
|
|
GtkWidget *content;
|
|
GtkBorder border;
|
|
|
|
content = gtk_window_get_child (GTK_WINDOW (frame));
|
|
border = meta_frame_content_get_border (META_FRAME_CONTENT (content));
|
|
meta_frame_update_extents (frame, border);
|
|
}
|
|
|
|
static char *
|
|
get_utf8_string_prop (GtkWindow *window,
|
|
Window client_window,
|
|
Atom prop)
|
|
{
|
|
MetaFrame *frame = META_FRAME (window);
|
|
GdkDisplay *display;
|
|
char *str = NULL;
|
|
int format;
|
|
Atom type;
|
|
unsigned long nitems, bytes_after;
|
|
|
|
display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
if (XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
|
|
client_window,
|
|
prop,
|
|
0, G_MAXLONG, False,
|
|
gdk_x11_get_xatom_by_name_for_display (display,
|
|
"UTF8_STRING"),
|
|
&type, &format,
|
|
&nitems, &bytes_after,
|
|
(unsigned char **) &str) != Success)
|
|
{
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
return NULL;
|
|
}
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return NULL;
|
|
|
|
return str;
|
|
}
|
|
|
|
static void
|
|
update_frame_title (MetaFrame *frame)
|
|
{
|
|
const char *title = NULL;
|
|
|
|
if (frame->net_wm_visible_name)
|
|
title = frame->net_wm_visible_name;
|
|
else if (frame->net_wm_name)
|
|
title = frame->net_wm_name;
|
|
else if (frame->wm_name)
|
|
title = frame->wm_name;
|
|
else
|
|
title = "";
|
|
|
|
gtk_window_set_title (GTK_WINDOW (frame), title);
|
|
}
|
|
|
|
static void
|
|
frame_sync_net_wm_visible_name (GtkWindow *window,
|
|
Window client_window)
|
|
{
|
|
MetaFrame *frame = META_FRAME (window);
|
|
|
|
g_clear_pointer (&frame->net_wm_visible_name, g_free);
|
|
frame->net_wm_visible_name =
|
|
get_utf8_string_prop (window, client_window, frame->atom__NET_WM_VISIBLE_NAME);
|
|
update_frame_title (frame);
|
|
}
|
|
|
|
static void
|
|
frame_sync_net_wm_name (GtkWindow *window,
|
|
Window client_window)
|
|
{
|
|
MetaFrame *frame = META_FRAME (window);
|
|
|
|
g_clear_pointer (&frame->net_wm_visible_name, g_free);
|
|
frame->net_wm_name =
|
|
get_utf8_string_prop (window, client_window, frame->atom__NET_WM_NAME);
|
|
update_frame_title (frame);
|
|
}
|
|
|
|
static char *
|
|
text_property_to_utf8 (GdkDisplay *display,
|
|
const XTextProperty *prop)
|
|
{
|
|
Display *xdisplay;
|
|
char *ret = NULL;
|
|
char **local_list = NULL;
|
|
int count = 0;
|
|
int res;
|
|
|
|
xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
|
|
res = XmbTextPropertyToTextList (xdisplay, prop,
|
|
&local_list, &count);
|
|
if (res == XNoMemory || res == XLocaleNotSupported || res == XConverterNotFound)
|
|
goto out;
|
|
|
|
if (count == 0)
|
|
goto out;
|
|
|
|
if (!g_utf8_validate (local_list[0], -1, NULL))
|
|
ret = NULL;
|
|
else
|
|
ret = g_strdup (local_list[0]);
|
|
|
|
out:
|
|
XFreeStringList (local_list);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
frame_sync_wm_name (GtkWindow *window,
|
|
Window client_window)
|
|
{
|
|
MetaFrame *frame = META_FRAME (window);
|
|
GdkDisplay *display;
|
|
XTextProperty text_prop;
|
|
int retval;
|
|
|
|
display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
retval = XGetWMName (gdk_x11_display_get_xdisplay (display),
|
|
client_window,
|
|
&text_prop);
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return;
|
|
if (retval == 0)
|
|
return;
|
|
|
|
frame->wm_name = text_property_to_utf8 (display, &text_prop);
|
|
update_frame_title (frame);
|
|
XFree (text_prop.value);
|
|
}
|
|
|
|
static void
|
|
frame_sync_motif_wm_hints (GtkWindow *window,
|
|
Window client_window)
|
|
{
|
|
MetaFrame *frame = META_FRAME (window);
|
|
GdkDisplay *display;
|
|
MotifWmHints *mwm_hints = NULL;
|
|
int format;
|
|
Atom type;
|
|
unsigned long nitems, bytes_after;
|
|
gboolean deletable = TRUE;
|
|
|
|
display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
if (XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
|
|
client_window,
|
|
frame->atom__MOTIF_WM_HINTS,
|
|
0, sizeof (MotifWmHints) / sizeof (long),
|
|
False, AnyPropertyType,
|
|
&type, &format,
|
|
&nitems, &bytes_after,
|
|
(unsigned char **) &mwm_hints) != Success)
|
|
{
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
return;
|
|
}
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return;
|
|
|
|
if (mwm_hints &&
|
|
(mwm_hints->flags & MWM_HINTS_FUNCTIONS) != 0)
|
|
{
|
|
if ((mwm_hints->functions & MWM_FUNC_ALL) == 0)
|
|
deletable = (mwm_hints->functions & MWM_FUNC_CLOSE) != 0;
|
|
else
|
|
deletable = (mwm_hints->functions & MWM_FUNC_CLOSE) == 0;
|
|
}
|
|
|
|
gtk_window_set_deletable (window, deletable);
|
|
g_free (mwm_hints);
|
|
}
|
|
|
|
static void
|
|
frame_sync_wm_normal_hints (GtkWindow *frame,
|
|
Window client_window)
|
|
{
|
|
GdkDisplay *display;
|
|
XSizeHints size_hints;
|
|
long nitems;
|
|
gboolean resizable = TRUE;
|
|
|
|
display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
if (XGetWMNormalHints (gdk_x11_display_get_xdisplay (display),
|
|
client_window,
|
|
&size_hints,
|
|
&nitems) != Success)
|
|
{
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
return;
|
|
}
|
|
|
|
if (gdk_x11_display_error_trap_pop (display))
|
|
return;
|
|
|
|
if (nitems > 0)
|
|
{
|
|
resizable = ((size_hints.flags & PMinSize) == 0 ||
|
|
(size_hints.flags & PMaxSize) == 0 ||
|
|
size_hints.min_width != size_hints.max_width ||
|
|
size_hints.min_height != size_hints.max_height);
|
|
}
|
|
|
|
gtk_window_set_resizable (frame, resizable);
|
|
}
|
|
|
|
static void
|
|
frame_sync_wm_state (MetaFrame *frame,
|
|
Window client_window)
|
|
{
|
|
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
Atom *data = NULL, type;
|
|
int format;
|
|
unsigned long i, nitems, bytes_after;
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
XGetWindowProperty (xdisplay,
|
|
client_window,
|
|
frame->atom__NET_WM_STATE,
|
|
0, 32,
|
|
False, XA_ATOM,
|
|
&type, &format,
|
|
&nitems, &bytes_after,
|
|
(unsigned char **) &data);
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
if (data[i] == frame->atom__NET_WM_STATE_FULLSCREEN)
|
|
gtk_window_fullscreen (GTK_WINDOW (frame));
|
|
}
|
|
|
|
gdk_x11_display_error_trap_pop_ignored (display);
|
|
|
|
XFree (data);
|
|
}
|
|
|
|
GtkWidget *
|
|
meta_frame_new (Window window)
|
|
{
|
|
GdkDisplay *display;
|
|
GtkWidget *frame, *header, *content;
|
|
GdkSurface *surface;
|
|
int frame_height = 0;
|
|
double scale;
|
|
|
|
frame = g_object_new (META_TYPE_FRAME, NULL);
|
|
|
|
header = meta_frame_header_new ();
|
|
|
|
gtk_window_set_titlebar (GTK_WINDOW (frame), header);
|
|
|
|
content = meta_frame_content_new (window);
|
|
gtk_window_set_child (GTK_WINDOW (frame), content);
|
|
|
|
g_signal_connect (content, "notify::border",
|
|
G_CALLBACK (on_border_changed), frame);
|
|
|
|
gtk_widget_realize (GTK_WIDGET (frame));
|
|
surface = gtk_native_get_surface (GTK_NATIVE (frame));
|
|
gdk_x11_surface_set_frame_sync_enabled (surface, TRUE);
|
|
|
|
frame_sync_wm_state (META_FRAME (frame), window);
|
|
|
|
if (!gtk_window_is_fullscreen (GTK_WINDOW (frame)))
|
|
{
|
|
gtk_widget_measure (header,
|
|
GTK_ORIENTATION_VERTICAL, 1,
|
|
&frame_height,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
scale = gdk_surface_get_scale_factor (gtk_native_get_surface (GTK_NATIVE (frame)));
|
|
|
|
meta_frame_update_extents (META_FRAME (frame),
|
|
(GtkBorder) {
|
|
0, 0,
|
|
frame_height * scale, 0,
|
|
});
|
|
|
|
frame_sync_net_wm_visible_name (GTK_WINDOW (frame), window);
|
|
frame_sync_net_wm_name (GTK_WINDOW (frame), window);
|
|
frame_sync_wm_name (GTK_WINDOW (frame), window);
|
|
frame_sync_motif_wm_hints (GTK_WINDOW (frame), window);
|
|
frame_sync_wm_normal_hints (GTK_WINDOW (frame), window);
|
|
|
|
/* Disable XDND support on the frame window, because it can cause some clients
|
|
* to try use it instead of the client window as drop target */
|
|
display = gtk_widget_get_display (GTK_WIDGET (frame));
|
|
XDeleteProperty (gdk_x11_display_get_xdisplay (display),
|
|
gdk_x11_surface_get_xid (surface),
|
|
gdk_x11_get_xatom_by_name_for_display (display, "XdndAware"));
|
|
|
|
return frame;
|
|
}
|
|
|
|
void
|
|
meta_frame_handle_xevent (MetaFrame *frame,
|
|
Window window,
|
|
XEvent *xevent)
|
|
{
|
|
GtkWidget *content;
|
|
gboolean is_frame, is_content;
|
|
GdkSurface *surface;
|
|
|
|
surface = gtk_native_get_surface (GTK_NATIVE (frame));
|
|
if (!surface)
|
|
return;
|
|
|
|
content = gtk_window_get_child (GTK_WINDOW (frame));
|
|
if (!content)
|
|
return;
|
|
|
|
is_frame = window == gdk_x11_surface_get_xid (surface);
|
|
is_content =
|
|
window == meta_frame_content_get_window (META_FRAME_CONTENT (content));
|
|
|
|
if (!is_frame && !is_content)
|
|
return;
|
|
|
|
if (is_content && xevent->type == PropertyNotify)
|
|
{
|
|
if (xevent->xproperty.atom == frame->atom__NET_WM_VISIBLE_NAME)
|
|
frame_sync_net_wm_visible_name (GTK_WINDOW (frame), xevent->xproperty.window);
|
|
else if (xevent->xproperty.atom == frame->atom__NET_WM_NAME)
|
|
frame_sync_net_wm_name (GTK_WINDOW (frame), xevent->xproperty.window);
|
|
else if (xevent->xproperty.atom == XA_WM_NAME)
|
|
frame_sync_wm_name (GTK_WINDOW (frame), xevent->xproperty.window);
|
|
else if (xevent->xproperty.atom == frame->atom__MOTIF_WM_HINTS)
|
|
frame_sync_motif_wm_hints (GTK_WINDOW (frame), xevent->xproperty.window);
|
|
else if (xevent->xproperty.atom == XA_WM_NORMAL_HINTS)
|
|
frame_sync_wm_normal_hints (GTK_WINDOW (frame), xevent->xproperty.window);
|
|
}
|
|
}
|