e95499038d
It returns non-0 if there are any hints in the WM_NORMAL_HINTS
property, 0 if there are none.
Fixes the mouse cursor changing to the resize shape over the decorations
of non-resizable windows.
Fixes: c7b3d8c607
("frames: Push error traps around various X11 calls")
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3096>
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))
|
|
{
|
|
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);
|
|
}
|
|
}
|