diff options
author | Alexander Larsson <alexl@redhat.com> | 2018-03-20 11:46:11 +0100 |
---|---|---|
committer | Alexander Larsson <alexl@redhat.com> | 2018-03-20 11:46:11 +0100 |
commit | 890080ebf7a12199d5500fbbf6a0daf06cc2d35f (patch) | |
tree | 477903a53b4cfe06172df68207f50e614bd89319 /gdk/x11/gdksurface-x11.c | |
parent | 391727bd0dff0f1d2d937f125bb08a541da02348 (diff) | |
download | gtk+-890080ebf7a12199d5500fbbf6a0daf06cc2d35f.tar.gz |
GdkWindow -> GdkSurface: File renames
Rename all *window.[ch] source files.
This is an automatic operation, done by the following commands:
for i in $(git ls-files gdk | grep window); do
git mv $i $(echo $i | sed s/window/surface/);
git sed -f g $(basename $i) $(basename $i | sed s/window/surface/) ;
done
git checkout NEWS* po-properties po
Diffstat (limited to 'gdk/x11/gdksurface-x11.c')
-rw-r--r-- | gdk/x11/gdksurface-x11.c | 4948 |
1 files changed, 4948 insertions, 0 deletions
diff --git a/gdk/x11/gdksurface-x11.c b/gdk/x11/gdksurface-x11.c new file mode 100644 index 0000000000..21f1d14bf1 --- /dev/null +++ b/gdk/x11/gdksurface-x11.c @@ -0,0 +1,4948 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 1995-2007 Peter Mattis, Spencer Kimball, + * Josh MacDonald, Ryan Lortie + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include "config.h" + +#include "gdksurface-x11.h" + +#include "gdksurface.h" +#include "gdksurfaceimpl.h" +#include "gdkvisual-x11.h" +#include "gdkinternals.h" +#include "gdkdeviceprivate.h" +#include "gdkframeclockprivate.h" +#include "gdkasync.h" +#include "gdkeventsource.h" +#include "gdkdisplay-x11.h" +#include "gdkglcontext-x11.h" +#include "gdkprivate-x11.h" +#include "gdktextureprivate.h" +#include "gdk-private.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <netinet/in.h> +#include <unistd.h> + +#include <cairo-xlib.h> + +#include "MwmUtil.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> + +#include <X11/extensions/shape.h> + +#ifdef HAVE_XKB +#include <X11/XKBlib.h> +#endif + +#ifdef HAVE_XCOMPOSITE +#include <X11/extensions/Xcomposite.h> +#endif + +#ifdef HAVE_XFIXES +#include <X11/extensions/Xfixes.h> +#endif + +#ifdef HAVE_XDAMAGE +#include <X11/extensions/Xdamage.h> +#endif + +const int _gdk_x11_event_mask_table[21] = +{ + ExposureMask, + PointerMotionMask, + PointerMotionHintMask, + ButtonMotionMask, + Button1MotionMask, + Button2MotionMask, + Button3MotionMask, + ButtonPressMask, + ButtonReleaseMask, + KeyPressMask, + KeyReleaseMask, + EnterWindowMask, + LeaveWindowMask, + FocusChangeMask, + StructureNotifyMask, + PropertyChangeMask, + VisibilityChangeMask, + 0, /* PROXIMITY_IN */ + 0, /* PROXIMTY_OUT */ + SubstructureNotifyMask, + ButtonPressMask /* SCROLL; on X mouse wheel events is treated as mouse button 4/5 */ +}; + +const gint _gdk_x11_event_mask_table_size = G_N_ELEMENTS (_gdk_x11_event_mask_table); + +/* Forward declarations */ +static void gdk_x11_surface_apply_fullscreen_mode (GdkSurface *window); +static gboolean gdk_surface_icon_name_set (GdkSurface *window); +static void set_wm_name (GdkDisplay *display, + Window xwindow, + const gchar *name); +static void move_to_current_desktop (GdkSurface *window); + +static void gdk_surface_impl_x11_finalize (GObject *object); + +#define WINDOW_IS_TOPLEVEL_OR_FOREIGN(window) \ + (GDK_SURFACE_TYPE (window) == GDK_SURFACE_TOPLEVEL || \ + GDK_SURFACE_TYPE (window) == GDK_SURFACE_TEMP || \ + GDK_SURFACE_TYPE (window) == GDK_SURFACE_FOREIGN) + +#define WINDOW_IS_TOPLEVEL(window) \ + (GDK_SURFACE_TYPE (window) == GDK_SURFACE_TOPLEVEL || \ + GDK_SURFACE_TYPE (window) == GDK_SURFACE_TEMP) + +/* Return whether time1 is considered later than time2 as far as xserver + * time is concerned. Accounts for wraparound. + */ +#define XSERVER_TIME_IS_LATER(time1, time2) \ + ( (( time1 > time2 ) && ( time1 - time2 < ((guint32)-1)/2 )) || \ + (( time1 < time2 ) && ( time2 - time1 > ((guint32)-1)/2 )) \ + ) + +struct _GdkX11Surface { + GdkSurface parent; +}; + +struct _GdkX11SurfaceClass { + GdkSurfaceClass parent_class; +}; + +G_DEFINE_TYPE (GdkX11Surface, gdk_x11_surface, GDK_TYPE_SURFACE) + +static void +gdk_x11_surface_class_init (GdkX11SurfaceClass *x11_surface_class) +{ +} + +static void +gdk_x11_surface_init (GdkX11Surface *x11_surface) +{ +} + + +G_DEFINE_TYPE (GdkSurfaceImplX11, gdk_surface_impl_x11, GDK_TYPE_SURFACE_IMPL) + +static void +gdk_surface_impl_x11_init (GdkSurfaceImplX11 *impl) +{ + impl->window_scale = 1; + impl->frame_sync_enabled = TRUE; +} + +GdkToplevelX11 * +_gdk_x11_surface_get_toplevel (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl; + + g_return_val_if_fail (GDK_IS_SURFACE (window), NULL); + + if (!WINDOW_IS_TOPLEVEL (window)) + return NULL; + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (!impl->toplevel) + { + impl->toplevel = g_new0 (GdkToplevelX11, 1); + impl->toplevel->have_focused = FALSE; + } + + return impl->toplevel; +} + +/** + * _gdk_x11_surface_update_size: + * @impl: a #GdkSurfaceImplX11. + * + * Updates the state of the window (in particular the drawable's + * cairo surface) when its size has changed. + **/ +void +_gdk_x11_surface_update_size (GdkSurfaceImplX11 *impl) +{ + if (impl->cairo_surface) + { + cairo_xlib_surface_set_size (impl->cairo_surface, + impl->unscaled_width, impl->unscaled_height); + } +} + +static void +gdk_x11_surface_get_unscaled_size (GdkSurface *window, + int *unscaled_width, + int *unscaled_height) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (unscaled_width) + *unscaled_width = impl->unscaled_width; + + if (unscaled_height) + *unscaled_height = impl->unscaled_height; +} + +static gboolean +gdk_x11_surface_supports_edge_constraints (GdkSurface *window) +{ + return gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_GTK_EDGE_CONSTRAINTS")); +} + +static void +set_sync_counter(Display *display, + XSyncCounter counter, + gint64 value) +{ + XSyncValue sync_value; + + XSyncIntsToValue (&sync_value, + value & G_GINT64_CONSTANT(0xFFFFFFFF), + value >> 32); + XSyncSetCounter (display, counter, sync_value); +} + +static void +window_pre_damage (GdkSurface *window) +{ + GdkSurface *toplevel_window = gdk_surface_get_toplevel (window); + GdkSurfaceImplX11 *impl; + + if (!toplevel_window || !WINDOW_IS_TOPLEVEL (toplevel_window)) + return; + + impl = GDK_SURFACE_IMPL_X11 (toplevel_window->impl); + + if (impl->toplevel->in_frame && + impl->toplevel->current_counter_value % 2 == 0) + { + impl->toplevel->current_counter_value += 1; + set_sync_counter (GDK_SURFACE_XDISPLAY (impl->wrapper), + impl->toplevel->extended_update_counter, + impl->toplevel->current_counter_value); + } +} + +static void +on_surface_changed (void *data) +{ + GdkSurface *window = data; + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (impl->tracking_damage) + window_pre_damage (window); +} + +/* We want to know when cairo drawing causes damage to the window, + * so we engage in the _NET_WM_FRAME_DRAWN protocol with the + * window only when there actually is drawing. To do that we use + * a technique (hack) suggested by Uli Schlachter - if we set + * a dummy "mime data" on the cairo surface (this facility is + * used to attach JPEG data to an imager), then cairo wil flush + * and remove the mime data before making any changes to the window. + */ + +static void +hook_surface_changed (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (impl->cairo_surface) + { + cairo_surface_set_mime_data (impl->cairo_surface, + "x-gdk/change-notify", + (unsigned char *)"X", + 1, + on_surface_changed, + window); + impl->tracking_damage = 1; + } +} + +static void +unhook_surface_changed (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (impl->cairo_surface) + { + impl->tracking_damage = 0; + cairo_surface_set_mime_data (impl->cairo_surface, + "x-gdk/change-notify", + NULL, 0, + NULL, NULL); + } +} + +static void +gdk_x11_surface_predict_presentation_time (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + GdkFrameClock *clock; + GdkFrameTimings *timings; + gint64 presentation_time; + gint64 refresh_interval; + + if (!WINDOW_IS_TOPLEVEL (window)) + return; + + clock = gdk_surface_get_frame_clock (window); + + timings = gdk_frame_clock_get_current_timings (clock); + + gdk_frame_clock_get_refresh_info (clock, + timings->frame_time, + &refresh_interval, &presentation_time); + + if (presentation_time != 0) + { + if (timings->slept_before) + { + presentation_time += refresh_interval; + } + else + { + if (presentation_time < timings->frame_time + refresh_interval / 2) + presentation_time += refresh_interval; + } + } + else + { + if (timings->slept_before) + presentation_time = timings->frame_time + refresh_interval + refresh_interval / 2; + else + presentation_time = timings->frame_time + refresh_interval; + } + + if (presentation_time < impl->toplevel->throttled_presentation_time) + presentation_time = impl->toplevel->throttled_presentation_time; + + timings->predicted_presentation_time = presentation_time; +} + +static void +gdk_x11_surface_begin_frame (GdkSurface *window, + gboolean force_frame) +{ + GdkSurfaceImplX11 *impl; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (!WINDOW_IS_TOPLEVEL (window) || + impl->toplevel->extended_update_counter == None) + return; + + impl->toplevel->in_frame = TRUE; + + if (impl->toplevel->configure_counter_value != 0 && + impl->toplevel->configure_counter_value_is_extended) + { + impl->toplevel->current_counter_value = impl->toplevel->configure_counter_value; + if ((impl->toplevel->current_counter_value % 2) == 1) + impl->toplevel->current_counter_value += 1; + + impl->toplevel->configure_counter_value = 0; + + window_pre_damage (window); + } + else if (force_frame) + { + /* When mapping the window, we really want to freeze the + rendering of the window by the compositor until we've + actually painted something into the window's buffer. */ + window_pre_damage (window); + } + else + { + hook_surface_changed (window); + } +} + +static void +gdk_x11_surface_end_frame (GdkSurface *window) +{ + GdkFrameClock *clock; + GdkFrameTimings *timings; + GdkSurfaceImplX11 *impl; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (!WINDOW_IS_TOPLEVEL (window) || + impl->toplevel->extended_update_counter == None || + !impl->toplevel->in_frame) + return; + + clock = gdk_surface_get_frame_clock (window); + timings = gdk_frame_clock_get_current_timings (clock); + + impl->toplevel->in_frame = FALSE; + + if (impl->toplevel->current_counter_value % 2 == 1) + { + if (GDK_DISPLAY_DEBUG_CHECK (gdk_surface_get_display (window), FRAMES)) + { + XImage *image = XGetImage (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + 0, 0, 1, 1, + (1 << 24) - 1, + ZPixmap); + XDestroyImage (image); + } + + /* An increment of 3 means that the frame was not drawn as fast as possible, + * but rather at a particular time. This can trigger different handling from + * the compositor. + */ + if (timings->slept_before) + impl->toplevel->current_counter_value += 3; + else + impl->toplevel->current_counter_value += 1; + + set_sync_counter(GDK_SURFACE_XDISPLAY (impl->wrapper), + impl->toplevel->extended_update_counter, + impl->toplevel->current_counter_value); + + if (impl->frame_sync_enabled && + gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_WM_FRAME_DRAWN"))) + { + impl->toplevel->frame_pending = TRUE; + _gdk_frame_clock_freeze (gdk_surface_get_frame_clock (window)); + timings->cookie = impl->toplevel->current_counter_value; + } + } + + unhook_surface_changed (window); + + if (impl->toplevel->configure_counter_value != 0 && + !impl->toplevel->configure_counter_value_is_extended) + { + set_sync_counter (GDK_SURFACE_XDISPLAY (window), + impl->toplevel->update_counter, + impl->toplevel->configure_counter_value); + + impl->toplevel->configure_counter_value = 0; + } + + if (!impl->toplevel->frame_pending) + timings->complete = TRUE; +} + +/***************************************************** + * X11 specific implementations of generic functions * + *****************************************************/ + +static cairo_surface_t * +gdk_x11_create_cairo_surface (GdkSurfaceImplX11 *impl, + int width, + int height) +{ + Visual *visual; + + visual = gdk_x11_display_get_window_visual (GDK_X11_DISPLAY (gdk_surface_get_display (impl->wrapper))); + return cairo_xlib_surface_create (GDK_SURFACE_XDISPLAY (impl->wrapper), + GDK_SURFACE_IMPL_X11 (impl)->xid, + visual, + width, height); +} + +static cairo_surface_t * +gdk_x11_ref_cairo_surface (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (GDK_SURFACE_DESTROYED (window)) + return NULL; + + if (!impl->cairo_surface) + { + impl->cairo_surface = gdk_x11_create_cairo_surface (impl, + gdk_surface_get_width (window) * impl->window_scale, + gdk_surface_get_height (window) * impl->window_scale); + cairo_surface_set_device_scale (impl->cairo_surface, impl->window_scale, impl->window_scale); + + if (WINDOW_IS_TOPLEVEL (window) && impl->toplevel->in_frame) + hook_surface_changed (window); + } + + cairo_surface_reference (impl->cairo_surface); + + return impl->cairo_surface; +} + +static void +gdk_surface_impl_x11_finalize (GObject *object) +{ + GdkSurface *wrapper; + GdkSurfaceImplX11 *impl; + + g_return_if_fail (GDK_IS_SURFACE_IMPL_X11 (object)); + + impl = GDK_SURFACE_IMPL_X11 (object); + + wrapper = impl->wrapper; + + if (WINDOW_IS_TOPLEVEL (wrapper) && impl->toplevel->in_frame) + unhook_surface_changed (wrapper); + + _gdk_x11_surface_grab_check_destroy (wrapper); + + if (!GDK_SURFACE_DESTROYED (wrapper)) + { + GdkDisplay *display = GDK_SURFACE_DISPLAY (wrapper); + + _gdk_x11_display_remove_window (display, impl->xid); + if (impl->toplevel && impl->toplevel->focus_window) + _gdk_x11_display_remove_window (display, impl->toplevel->focus_window); + } + + g_free (impl->toplevel); + + if (impl->cursor) + g_object_unref (impl->cursor); + + G_OBJECT_CLASS (gdk_surface_impl_x11_parent_class)->finalize (object); +} + +typedef struct { + GdkDisplay *display; + Pixmap pixmap; +} FreePixmapData; + +static void +free_pixmap (gpointer datap) +{ + FreePixmapData *data = datap; + + if (!gdk_display_is_closed (data->display)) + { + XFreePixmap (GDK_DISPLAY_XDISPLAY (data->display), + data->pixmap); + } + + g_object_unref (data->display); + g_slice_free (FreePixmapData, data); +} + +static void +attach_free_pixmap_handler (cairo_surface_t *surface, + GdkDisplay *display, + Pixmap pixmap) +{ + static const cairo_user_data_key_t key; + FreePixmapData *data; + + data = g_slice_new (FreePixmapData); + data->display = g_object_ref (display); + data->pixmap = pixmap; + + cairo_surface_set_user_data (surface, &key, data, free_pixmap); +} + +/* Cairo does not guarantee we get an xlib surface if we call + * cairo_surface_create_similar(). In some cases however, we must use a + * pixmap or bitmap in the X11 API. + * These functions ensure an Xlib surface. + */ +cairo_surface_t * +_gdk_x11_display_create_bitmap_surface (GdkDisplay *display, + int width, + int height) +{ + cairo_surface_t *surface; + Pixmap pixmap; + + pixmap = XCreatePixmap (GDK_DISPLAY_XDISPLAY (display), + GDK_SCREEN_XROOTWIN (GDK_X11_DISPLAY (display)->screen), + width, height, 1); + surface = cairo_xlib_surface_create_for_bitmap (GDK_DISPLAY_XDISPLAY (display), + pixmap, + GDK_X11_SCREEN (GDK_X11_DISPLAY (display)->screen)->xscreen, + width, height); + attach_free_pixmap_handler (surface, display, pixmap); + + return surface; +} + +/* Create a surface backed with a pixmap without alpha on the same screen as window */ +static cairo_surface_t * +gdk_x11_surface_create_pixmap_surface (GdkSurface *window, + int width, + int height) +{ + GdkDisplay *display; + Display *dpy; + cairo_surface_t *surface; + Pixmap pixmap; + + display = gdk_surface_get_display (window); + dpy = GDK_DISPLAY_XDISPLAY (display); + + pixmap = XCreatePixmap (dpy, + GDK_SURFACE_XID (window), + width, height, + DefaultDepth (dpy, DefaultScreen (dpy))); + surface = cairo_xlib_surface_create (dpy, + pixmap, + DefaultVisual (dpy, DefaultScreen (dpy)), + width, height); + attach_free_pixmap_handler (surface, display, pixmap); + + return surface; +} + +static void +set_wm_protocols (GdkSurface *window) +{ + GdkDisplay *display = gdk_surface_get_display (window); + Atom protocols[4]; + int n = 0; + + protocols[n++] = gdk_x11_get_xatom_by_name_for_display (display, "WM_DELETE_WINDOW"); + protocols[n++] = gdk_x11_get_xatom_by_name_for_display (display, "WM_TAKE_FOCUS"); + protocols[n++] = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_PING"); + +#ifdef HAVE_XSYNC + if (GDK_X11_DISPLAY (display)->use_sync) + protocols[n++] = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_SYNC_REQUEST"); +#endif + + XSetWMProtocols (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), protocols, n); +} + +static const gchar * +get_default_title (void) +{ + const char *title; + + title = g_get_application_name (); + if (!title) + title = g_get_prgname (); + if (!title) + title = ""; + + return title; +} + +static void +check_leader_window_title (GdkDisplay *display) +{ + GdkX11Display *display_x11 = GDK_X11_DISPLAY (display); + + if (display_x11->leader_window && !display_x11->leader_window_title_set) + { + set_wm_name (display, + display_x11->leader_window, + get_default_title ()); + + display_x11->leader_window_title_set = TRUE; + } +} + +static Window +create_focus_window (GdkDisplay *display, + XID parent) +{ + GdkX11Display *display_x11; + GdkEventMask event_mask; + Display *xdisplay; + Window focus_window; + XSetWindowAttributes attrs; + + xdisplay = GDK_DISPLAY_XDISPLAY (display); + display_x11 = GDK_X11_DISPLAY (display); + + focus_window = XCreateWindow (xdisplay, parent, + -1, -1, 1, 1, 0, + 0, /* depth */ + InputOnly, + CopyFromParent, + 0, &attrs); + + event_mask = (GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_FOCUS_CHANGE_MASK); + + gdk_x11_event_source_select_events ((GdkEventSource *) display_x11->event_source, + focus_window, + event_mask, 0); + + XMapWindow (xdisplay, focus_window); + + return focus_window; +} + +static void +ensure_sync_counter (GdkSurface *window) +{ +#ifdef HAVE_XSYNC + if (!GDK_SURFACE_DESTROYED (window)) + { + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + GdkToplevelX11 *toplevel = _gdk_x11_surface_get_toplevel (window); + + if (toplevel && + toplevel->update_counter == None && + GDK_X11_DISPLAY (display)->use_sync) + { + Display *xdisplay = GDK_DISPLAY_XDISPLAY (display); + XSyncValue value; + Atom atom; + XID counters[2]; + + XSyncIntToValue (&value, 0); + + toplevel->update_counter = XSyncCreateCounter (xdisplay, value); + toplevel->extended_update_counter = XSyncCreateCounter (xdisplay, value); + + atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_SYNC_REQUEST_COUNTER"); + + counters[0] = toplevel->update_counter; + counters[1] = toplevel->extended_update_counter; + XChangeProperty (xdisplay, GDK_SURFACE_XID (window), + atom, XA_CARDINAL, + 32, PropModeReplace, + (guchar *)counters, 2); + + toplevel->current_counter_value = 0; + } + } +#endif +} + +static void +setup_toplevel_window (GdkSurface *window, + GdkX11Screen *x11_screen) +{ + GdkToplevelX11 *toplevel = _gdk_x11_surface_get_toplevel (window); + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + GdkDisplay *display = gdk_surface_get_display (window); + Display *xdisplay = GDK_SURFACE_XDISPLAY (window); + XID xid = GDK_SURFACE_XID (window); + XSizeHints size_hints; + long pid; + Window leader_window; + + set_wm_protocols (window); + + if (!window->input_only) + { + /* The focus window is off the visible area, and serves to receive key + * press events so they don't get sent to child windows. + */ + toplevel->focus_window = create_focus_window (display, xid); + _gdk_x11_display_add_window (x11_screen->display, + &toplevel->focus_window, + window); + } + + check_leader_window_title (x11_screen->display); + + /* FIXME: Is there any point in doing this? Do any WM's pay + * attention to PSize, and even if they do, is this the + * correct value??? + */ + size_hints.flags = PSize; + size_hints.width = window->width * impl->window_scale; + size_hints.height = window->height * impl->window_scale; + + XSetWMNormalHints (xdisplay, xid, &size_hints); + + /* This will set WM_CLIENT_MACHINE and WM_LOCALE_NAME */ + XSetWMProperties (xdisplay, xid, NULL, NULL, NULL, 0, NULL, NULL, NULL); + + pid = getpid (); + XChangeProperty (xdisplay, xid, + gdk_x11_get_xatom_by_name_for_display (x11_screen->display, "_NET_WM_PID"), + XA_CARDINAL, 32, + PropModeReplace, + (guchar *)&pid, 1); + + leader_window = GDK_X11_DISPLAY (x11_screen->display)->leader_window; + if (!leader_window) + leader_window = xid; + XChangeProperty (xdisplay, xid, + gdk_x11_get_xatom_by_name_for_display (x11_screen->display, "WM_CLIENT_LEADER"), + XA_WINDOW, 32, PropModeReplace, + (guchar *) &leader_window, 1); + + if (toplevel->focus_window != None) + XChangeProperty (xdisplay, xid, + gdk_x11_get_xatom_by_name_for_display (x11_screen->display, "_NET_WM_USER_TIME_WINDOW"), + XA_WINDOW, 32, PropModeReplace, + (guchar *) &toplevel->focus_window, 1); + + if (!window->focus_on_map) + gdk_x11_surface_set_user_time (window, 0); + else if (GDK_X11_DISPLAY (x11_screen->display)->user_time != 0) + gdk_x11_surface_set_user_time (window, GDK_X11_DISPLAY (x11_screen->display)->user_time); + + ensure_sync_counter (window); + + /* Start off in a frozen state - we'll finish this when we first paint */ + gdk_x11_surface_begin_frame (window, TRUE); +} + +static void +on_frame_clock_before_paint (GdkFrameClock *clock, + GdkSurface *window) +{ + gdk_x11_surface_predict_presentation_time (window); + gdk_x11_surface_begin_frame (window, FALSE); +} + +static void +on_frame_clock_after_paint (GdkFrameClock *clock, + GdkSurface *window) +{ + gdk_x11_surface_end_frame (window); + +} + +static void +connect_frame_clock (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl; + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + if (WINDOW_IS_TOPLEVEL (window) && !impl->frame_clock_connected) + { + GdkFrameClock *frame_clock = gdk_surface_get_frame_clock (window); + + g_signal_connect (frame_clock, "before-paint", + G_CALLBACK (on_frame_clock_before_paint), window); + g_signal_connect (frame_clock, "after-paint", + G_CALLBACK (on_frame_clock_after_paint), window); + + impl->frame_clock_connected = TRUE; + } +} + +void +_gdk_x11_display_create_window_impl (GdkDisplay *display, + GdkSurface *window, + GdkSurface *real_parent, + GdkEventMask event_mask, + GdkSurfaceAttr *attributes) +{ + GdkSurfaceImplX11 *impl; + GdkX11Screen *x11_screen; + GdkX11Display *display_x11; + + Window xparent; + Visual *xvisual; + Display *xdisplay; + + XSetWindowAttributes xattributes; + long xattributes_mask; + XClassHint *class_hint; + + unsigned int class; + int depth; + + int abs_x; + int abs_y; + + display_x11 = GDK_X11_DISPLAY (display); + x11_screen = GDK_X11_SCREEN (display_x11->screen); + if (real_parent) + xparent = GDK_SURFACE_XID (real_parent); + else + xparent = GDK_SCREEN_XROOTWIN (x11_screen); + + impl = g_object_new (GDK_TYPE_SURFACE_IMPL_X11, NULL); + window->impl = GDK_SURFACE_IMPL (impl); + impl->wrapper = GDK_SURFACE (window); + impl->window_scale = x11_screen->window_scale; + + xdisplay = x11_screen->xdisplay; + + xattributes_mask = 0; + + xvisual = gdk_x11_display_get_window_visual (display_x11); + + impl->override_redirect = FALSE; + + /* Sanity checks */ + switch (window->window_type) + { + case GDK_SURFACE_TOPLEVEL: + case GDK_SURFACE_TEMP: + if (window->parent) + { + /* The common code warns for this case */ + xparent = GDK_SCREEN_XROOTWIN (x11_screen); + } + break; + + case GDK_SURFACE_CHILD: + default: + g_assert_not_reached (); + break; + } + + if (!window->input_only) + { + class = InputOutput; + + xattributes.background_pixel = BlackPixel (xdisplay, x11_screen->screen_num); + + xattributes.border_pixel = BlackPixel (xdisplay, x11_screen->screen_num); + xattributes_mask |= CWBorderPixel | CWBackPixel; + + xattributes.bit_gravity = NorthWestGravity; + xattributes_mask |= CWBitGravity; + + xattributes.colormap = gdk_x11_display_get_window_colormap (display_x11); + xattributes_mask |= CWColormap; + + if (window->window_type == GDK_SURFACE_TEMP) + { + xattributes.save_under = True; + xattributes.override_redirect = True; + xattributes.cursor = None; + xattributes_mask |= CWSaveUnder | CWOverrideRedirect; + + impl->override_redirect = TRUE; + } + + depth = gdk_x11_display_get_window_depth (display_x11); + } + else + { + class = InputOnly; + + if (window->window_type == GDK_SURFACE_TEMP) + { + xattributes.override_redirect = True; + xattributes_mask |= CWOverrideRedirect; + + impl->override_redirect = TRUE; + } + + depth = 0; + } + + if (window->width * impl->window_scale > 32767 || + window->height * impl->window_scale > 32767) + { + g_warning ("Native Windows wider or taller than 32767 pixels are not supported"); + + if (window->width * impl->window_scale > 32767) + window->width = 32767 / impl->window_scale; + if (window->height * impl->window_scale > 32767) + window->height = 32767 / impl->window_scale; + } + + impl->unscaled_width = window->width * impl->window_scale; + impl->unscaled_height = window->height * impl->window_scale; + + if (window->parent) + { + abs_x = window->parent->abs_x; + abs_y = window->parent->abs_y; + } + else + { + abs_x = 0; + abs_y = 0; + } + + impl->xid = XCreateWindow (xdisplay, xparent, + (window->x + abs_x) * impl->window_scale, + (window->y + abs_y) * impl->window_scale, + window->width * impl->window_scale, window->height * impl->window_scale, + 0, depth, class, xvisual, + xattributes_mask, &xattributes); + + g_object_ref (window); + _gdk_x11_display_add_window (x11_screen->display, &impl->xid, window); + + switch (GDK_SURFACE_TYPE (window)) + { + case GDK_SURFACE_TOPLEVEL: + case GDK_SURFACE_TEMP: + gdk_surface_set_title (window, get_default_title ()); + + class_hint = XAllocClassHint (); + class_hint->res_name = (char *) g_get_prgname (); + class_hint->res_class = (char *) display_x11->program_class; + XSetClassHint (xdisplay, impl->xid, class_hint); + XFree (class_hint); + + setup_toplevel_window (window, x11_screen); + break; + + case GDK_SURFACE_CHILD: + default: + break; + } + + gdk_x11_event_source_select_events ((GdkEventSource *) display_x11->event_source, + GDK_SURFACE_XID (window), event_mask, + StructureNotifyMask | PropertyChangeMask); + + connect_frame_clock (window); + + gdk_surface_freeze_toplevel_updates (window); +} + +static GdkEventMask +x_event_mask_to_gdk_event_mask (long mask) +{ + GdkEventMask event_mask = 0; + int i; + + for (i = 0; i < _gdk_x11_event_mask_table_size; i++) + { + if (mask & _gdk_x11_event_mask_table[i]) + event_mask |= 1 << (i + 1); + } + + return event_mask; +} + +/** + * gdk_x11_surface_foreign_new_for_display: + * @display: (type GdkX11Display): the #GdkDisplay where the window handle comes from. + * @window: an Xlib Window + * + * Wraps a native window in a #GdkSurface. The function will try to + * look up the window using gdk_x11_surface_lookup_for_display() first. + * If it does not find it there, it will create a new window. + * + * This may fail if the window has been destroyed. If the window + * was already known to GDK, a new reference to the existing + * #GdkSurface is returned. + * + * Returns: (transfer full): a #GdkSurface wrapper for the native + * window, or %NULL if the window has been destroyed. The wrapper + * will be newly created, if one doesn’t exist already. + */ +GdkSurface * +gdk_x11_surface_foreign_new_for_display (GdkDisplay *display, + Window window) +{ + GdkX11Screen *screen; + GdkSurface *win; + GdkSurfaceImplX11 *impl; + GdkX11Display *display_x11; + XWindowAttributes attrs; + Window root, parent; + Window *children = NULL; + guint nchildren; + gboolean result; + + g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); + + display_x11 = GDK_X11_DISPLAY (display); + + if ((win = gdk_x11_surface_lookup_for_display (display, window)) != NULL) + return g_object_ref (win); + + gdk_x11_display_error_trap_push (display); + result = XGetWindowAttributes (display_x11->xdisplay, window, &attrs); + if (gdk_x11_display_error_trap_pop (display) || !result) + return NULL; + + /* FIXME: This is pretty expensive. + * Maybe the caller should supply the parent + */ + gdk_x11_display_error_trap_push (display); + result = XQueryTree (display_x11->xdisplay, window, &root, &parent, &children, &nchildren); + if (gdk_x11_display_error_trap_pop (display) || !result) + return NULL; + + if (children) + XFree (children); + + screen = _gdk_x11_display_screen_for_xrootwin (display, root); + if (screen == NULL) + return NULL; + + win = _gdk_display_create_window (display); + win->impl = g_object_new (GDK_TYPE_SURFACE_IMPL_X11, NULL); + win->impl_window = win; + + impl = GDK_SURFACE_IMPL_X11 (win->impl); + impl->wrapper = win; + impl->window_scale = GDK_X11_SCREEN (screen)->window_scale; + + /* Always treat foreigns as toplevels */ + win->parent = NULL; + + impl->xid = window; + + win->x = attrs.x / impl->window_scale; + win->y = attrs.y / impl->window_scale; + impl->unscaled_width = attrs.width; + impl->unscaled_height = attrs.height; + win->width = attrs.width / impl->window_scale; + win->height = attrs.height / impl->window_scale; + win->window_type = GDK_SURFACE_FOREIGN; + win->destroyed = FALSE; + + win->event_mask = x_event_mask_to_gdk_event_mask (attrs.your_event_mask); + + if (attrs.map_state == IsUnmapped) + win->state = GDK_SURFACE_STATE_WITHDRAWN; + else + win->state = 0; + win->viewable = TRUE; + + g_object_ref (win); + _gdk_x11_display_add_window (display, &GDK_SURFACE_XID (win), win); + + /* Update the clip region, etc */ + _gdk_surface_update_size (win); + + return win; +} + +static void +gdk_toplevel_x11_free_contents (GdkDisplay *display, + GdkToplevelX11 *toplevel) +{ + if (toplevel->icon_pixmap) + { + cairo_surface_destroy (toplevel->icon_pixmap); + toplevel->icon_pixmap = NULL; + } + if (toplevel->icon_mask) + { + cairo_surface_destroy (toplevel->icon_mask); + toplevel->icon_mask = NULL; + } + if (toplevel->group_leader) + { + g_object_unref (toplevel->group_leader); + toplevel->group_leader = NULL; + } +#ifdef HAVE_XSYNC + if (toplevel->update_counter != None) + { + XSyncDestroyCounter (GDK_DISPLAY_XDISPLAY (display), + toplevel->update_counter); + XSyncDestroyCounter (GDK_DISPLAY_XDISPLAY (display), + toplevel->extended_update_counter); + toplevel->update_counter = None; + toplevel->extended_update_counter = None; + + toplevel->current_counter_value = 0; + } +#endif +} + +static void +gdk_x11_surface_destroy (GdkSurface *window, + gboolean recursing, + gboolean foreign_destroy) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + toplevel = _gdk_x11_surface_get_toplevel (window); + if (toplevel) + gdk_toplevel_x11_free_contents (GDK_SURFACE_DISPLAY (window), toplevel); + + unhook_surface_changed (window); + + if (impl->cairo_surface) + { + cairo_surface_finish (impl->cairo_surface); + cairo_surface_destroy (impl->cairo_surface); + impl->cairo_surface = NULL; + } + + if (!recursing && !foreign_destroy) + XDestroyWindow (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XID (window)); +} + +/* This function is called when the XWindow is really gone. + */ +static void +gdk_x11_surface_destroy_notify (GdkSurface *window) +{ + GdkSurfaceImplX11 *window_impl; + + window_impl = GDK_SURFACE_IMPL_X11 ((window)->impl); + + if (!GDK_SURFACE_DESTROYED (window)) + { + if (GDK_SURFACE_TYPE(window) != GDK_SURFACE_FOREIGN) + g_warning ("GdkSurface %#lx unexpectedly destroyed", GDK_SURFACE_XID (window)); + + _gdk_surface_destroy (window, TRUE); + } + + _gdk_x11_display_remove_window (GDK_SURFACE_DISPLAY (window), GDK_SURFACE_XID (window)); + if (window_impl->toplevel && window_impl->toplevel->focus_window) + _gdk_x11_display_remove_window (GDK_SURFACE_DISPLAY (window), window_impl->toplevel->focus_window); + + _gdk_x11_surface_grab_check_destroy (window); + + g_object_unref (window); +} + +static void +update_wm_hints (GdkSurface *window, + gboolean force) +{ + GdkToplevelX11 *toplevel = _gdk_x11_surface_get_toplevel (window); + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + XWMHints wm_hints; + + if (!force && + !toplevel->is_leader && + window->state & GDK_SURFACE_STATE_WITHDRAWN) + return; + + wm_hints.flags = StateHint | InputHint; + wm_hints.input = window->accept_focus ? True : False; + wm_hints.initial_state = NormalState; + + if (window->state & GDK_SURFACE_STATE_ICONIFIED) + { + wm_hints.flags |= StateHint; + wm_hints.initial_state = IconicState; + } + + if (toplevel->icon_pixmap) + { + wm_hints.flags |= IconPixmapHint; + wm_hints.icon_pixmap = cairo_xlib_surface_get_drawable (toplevel->icon_pixmap); + } + + if (toplevel->icon_mask) + { + wm_hints.flags |= IconMaskHint; + wm_hints.icon_mask = cairo_xlib_surface_get_drawable (toplevel->icon_mask); + } + + wm_hints.flags |= WindowGroupHint; + if (toplevel->group_leader && !GDK_SURFACE_DESTROYED (toplevel->group_leader)) + { + wm_hints.flags |= WindowGroupHint; + wm_hints.window_group = GDK_SURFACE_XID (toplevel->group_leader); + } + else + wm_hints.window_group = GDK_X11_DISPLAY (display)->leader_window; + + if (toplevel->urgency_hint) + wm_hints.flags |= XUrgencyHint; + + XSetWMHints (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + &wm_hints); +} + +static void +set_initial_hints (GdkSurface *window) +{ + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + Display *xdisplay = GDK_DISPLAY_XDISPLAY (display); + Window xwindow = GDK_SURFACE_XID (window); + GdkToplevelX11 *toplevel; + Atom atoms[9]; + gint i; + + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (!toplevel) + return; + + update_wm_hints (window, TRUE); + + /* We set the spec hints regardless of whether the spec is supported, + * since it can't hurt and it's kind of expensive to check whether + * it's supported. + */ + + i = 0; + + if (window->state & GDK_SURFACE_STATE_MAXIMIZED) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_MAXIMIZED_VERT"); + ++i; + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_MAXIMIZED_HORZ"); + ++i; + toplevel->have_maxhorz = toplevel->have_maxvert = TRUE; + } + + if (window->state & GDK_SURFACE_STATE_ABOVE) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_ABOVE"); + ++i; + } + + if (window->state & GDK_SURFACE_STATE_BELOW) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_BELOW"); + ++i; + } + + if (window->state & GDK_SURFACE_STATE_STICKY) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_STICKY"); + ++i; + toplevel->have_sticky = TRUE; + } + + if (window->state & GDK_SURFACE_STATE_FULLSCREEN) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_FULLSCREEN"); + ++i; + toplevel->have_fullscreen = TRUE; + } + + if (window->modal_hint) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_MODAL"); + ++i; + } + + if (toplevel->skip_taskbar_hint) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_SKIP_TASKBAR"); + ++i; + } + + if (toplevel->skip_pager_hint) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_SKIP_PAGER"); + ++i; + } + + if (window->state & GDK_SURFACE_STATE_ICONIFIED) + { + atoms[i] = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_WM_STATE_HIDDEN"); + ++i; + toplevel->have_hidden = TRUE; + } + + if (i > 0) + { + XChangeProperty (xdisplay, + xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE"), + XA_ATOM, 32, PropModeReplace, + (guchar*) atoms, i); + } + else + { + XDeleteProperty (xdisplay, + xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE")); + } + + if (window->state & GDK_SURFACE_STATE_STICKY) + { + atoms[0] = 0xFFFFFFFF; + XChangeProperty (xdisplay, + xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP"), + XA_CARDINAL, 32, PropModeReplace, + (guchar*) atoms, 1); + toplevel->on_all_desktops = TRUE; + } + else + { + XDeleteProperty (xdisplay, + xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP")); + } + + toplevel->map_serial = NextRequest (xdisplay); +} + +static void +gdk_surface_x11_show (GdkSurface *window, gboolean already_mapped) +{ + GdkDisplay *display; + GdkX11Display *display_x11; + GdkToplevelX11 *toplevel; + Display *xdisplay = GDK_SURFACE_XDISPLAY (window); + Window xwindow = GDK_SURFACE_XID (window); + + if (!already_mapped) + set_initial_hints (window); + + if (WINDOW_IS_TOPLEVEL (window)) + { + display = gdk_surface_get_display (window); + display_x11 = GDK_X11_DISPLAY (display); + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (toplevel->user_time != 0 && + display_x11->user_time != 0 && + XSERVER_TIME_IS_LATER (display_x11->user_time, toplevel->user_time)) + gdk_x11_surface_set_user_time (window, display_x11->user_time); + } + + XMapWindow (xdisplay, xwindow); + + /* Fullscreen on current monitor is the default, no need to apply this mode + * when mapping a window. This also ensures that the default behavior remains + * consistent with pre-fullscreen mode implementation. + */ + if (window->fullscreen_mode != GDK_FULLSCREEN_ON_CURRENT_MONITOR) + gdk_x11_surface_apply_fullscreen_mode (window); +} + +static void +gdk_surface_x11_hide (GdkSurface *window) +{ + /* We'll get the unmap notify eventually, and handle it then, + * but checking here makes things more consistent if we are + * just doing stuff ourself. + */ + _gdk_x11_surface_grab_check_unmap (window, + NextRequest (GDK_SURFACE_XDISPLAY (window))); + + /* You can't simply unmap toplevel windows. */ + switch (window->window_type) + { + case GDK_SURFACE_TOPLEVEL: + case GDK_SURFACE_TEMP: /* ? */ + gdk_surface_withdraw (window); + return; + + case GDK_SURFACE_FOREIGN: + case GDK_SURFACE_CHILD: + default: + break; + } + + _gdk_surface_clear_update_area (window); + + XUnmapWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window)); +} + +static void +gdk_surface_x11_withdraw (GdkSurface *window) +{ + if (!window->destroyed) + { + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_synthesize_window_state (window, + 0, + GDK_SURFACE_STATE_WITHDRAWN); + + g_assert (!GDK_SURFACE_IS_MAPPED (window)); + + XWithdrawWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), 0); + } +} + +static inline void +window_x11_move (GdkSurface *window, + gint x, + gint y) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + XMoveWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + x * impl->window_scale, y * impl->window_scale); + + if (impl->override_redirect) + { + window->x = x; + window->y = y; + } +} + +static inline void +window_x11_resize (GdkSurface *window, + gint width, + gint height) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (width < 1) + width = 1; + + if (height < 1) + height = 1; + + window_pre_damage (window); + + XResizeWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + width * impl->window_scale, height * impl->window_scale); + + if (impl->override_redirect) + { + impl->unscaled_width = width * impl->window_scale; + impl->unscaled_height = height * impl->window_scale; + window->width = width; + window->height = height; + _gdk_x11_surface_update_size (GDK_SURFACE_IMPL_X11 (window->impl)); + } + else + { + if (width * impl->window_scale != impl->unscaled_width || height * impl->window_scale != impl->unscaled_height) + window->resize_count += 1; + } +} + +static inline void +window_x11_move_resize (GdkSurface *window, + gint x, + gint y, + gint width, + gint height) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (width < 1) + width = 1; + + if (height < 1) + height = 1; + + window_pre_damage (window); + + XMoveResizeWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + x * impl->window_scale, y * impl->window_scale, + width * impl->window_scale, height * impl->window_scale); + + if (impl->override_redirect) + { + window->x = x; + window->y = y; + + impl->unscaled_width = width * impl->window_scale; + impl->unscaled_height = height * impl->window_scale; + window->width = width; + window->height = height; + + _gdk_x11_surface_update_size (GDK_SURFACE_IMPL_X11 (window->impl)); + } + else + { + if (width * impl->window_scale != impl->unscaled_width || height * impl->window_scale != impl->unscaled_height) + window->resize_count += 1; + } +} + +static void +gdk_surface_x11_move_resize (GdkSurface *window, + gboolean with_move, + gint x, + gint y, + gint width, + gint height) +{ + if (with_move && (width < 0 && height < 0)) + window_x11_move (window, x, y); + else + { + if (with_move) + window_x11_move_resize (window, x, y, width, height); + else + window_x11_resize (window, width, height); + } +} + +void +_gdk_x11_surface_set_window_scale (GdkSurface *window, + int scale) +{ + GdkSurfaceImplX11 *impl; + GdkToplevelX11 *toplevel; + GdkSurfaceHints geom_mask; + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + impl->window_scale = scale; + if (impl->cairo_surface) + cairo_surface_set_device_scale (impl->cairo_surface, impl->window_scale, impl->window_scale); + _gdk_surface_update_size (window); + + toplevel = _gdk_x11_surface_get_toplevel (window); + if (toplevel && window->window_type != GDK_SURFACE_FOREIGN) + { + /* These are affected by window scale: */ + geom_mask = toplevel->last_geometry_hints_mask & + (GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC); + if (geom_mask) + gdk_surface_set_geometry_hints (window, + &toplevel->last_geometry_hints, + geom_mask); + } + + if (window->window_type == GDK_SURFACE_FOREIGN) + XMoveWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + window->x * impl->window_scale, + window->y * impl->window_scale); + else + { + if (impl->override_redirect) + { + impl->unscaled_width = window->width * impl->window_scale; + impl->unscaled_height = window->height * impl->window_scale; + } + + XResizeWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + window->width * impl->window_scale, + window->height * impl->window_scale); + } + + gdk_surface_invalidate_rect (window, NULL, TRUE); +} + +static void +gdk_surface_x11_raise (GdkSurface *window) +{ + XRaiseWindow (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XID (window)); +} + +static void +gdk_surface_x11_restack_toplevel (GdkSurface *window, + GdkSurface *sibling, + gboolean above) +{ + XWindowChanges changes; + + changes.sibling = GDK_SURFACE_XID (sibling); + changes.stack_mode = above ? Above : Below; + XReconfigureWMWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + gdk_x11_screen_get_screen_number (GDK_SURFACE_SCREEN (window)), + CWStackMode | CWSibling, &changes); +} + +static void +gdk_surface_x11_lower (GdkSurface *window) +{ + XLowerWindow (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XID (window)); +} + +/** + * gdk_x11_surface_move_to_current_desktop: + * @window: (type GdkX11Surface): a #GdkSurface + * + * Moves the window to the correct workspace when running under a + * window manager that supports multiple workspaces, as described + * in the [Extended Window Manager Hints](http://www.freedesktop.org/Standards/wm-spec) specification. + * Will not do anything if the window is already on all workspaces. + */ +void +gdk_x11_surface_move_to_current_desktop (GdkSurface *window) +{ + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_IS_SURFACE (window)); + g_return_if_fail (GDK_SURFACE_TYPE (window) != GDK_SURFACE_CHILD); + + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (toplevel->on_all_desktops) + return; + + move_to_current_desktop (window); +} + +static void +move_to_current_desktop (GdkSurface *window) +{ + guint32 desktop; + + desktop = gdk_x11_screen_get_current_desktop (GDK_SURFACE_SCREEN (window)); + gdk_x11_surface_move_to_desktop (window, desktop); +} + +static guint32 +get_netwm_cardinal_property (GdkSurface *window, + const gchar *name) +{ + GdkX11Screen *x11_screen = GDK_SURFACE_SCREEN (window); + GdkAtom atom; + guint32 prop = 0; + Atom type; + gint format; + gulong nitems; + gulong bytes_after; + guchar *data; + + atom = g_intern_static_string (name); + + if (!gdk_x11_screen_supports_net_wm_hint (x11_screen, atom)) + return 0; + + XGetWindowProperty (x11_screen->xdisplay, + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (GDK_SURFACE_DISPLAY (window), name), + 0, G_MAXLONG, + False, XA_CARDINAL, &type, &format, &nitems, + &bytes_after, &data); + if (type == XA_CARDINAL) + { + prop = *(gulong *)data; + XFree (data); + } + + return prop; +} + +/** + * gdk_x11_surface_get_desktop: + * @window: (type GdkX11Surface): a #GdkSurface + * + * Gets the number of the workspace @window is on. + * + * Returns: the current workspace of @window + */ +guint32 +gdk_x11_surface_get_desktop (GdkSurface *window) +{ + g_return_val_if_fail (GDK_IS_SURFACE (window), 0); + + return get_netwm_cardinal_property (window, "_NET_WM_DESKTOP"); +} + +/** + * gdk_x11_surface_move_to_desktop: + * @window: (type GdkX11Surface): a #GdkSurface + * @desktop: the number of the workspace to move the window to + * + * Moves the window to the given workspace when running unde a + * window manager that supports multiple workspaces, as described + * in the [Extended Window Manager Hints](http://www.freedesktop.org/Standards/wm-spec) specification. + */ +void +gdk_x11_surface_move_to_desktop (GdkSurface *window, + guint32 desktop) +{ + GdkAtom atom; + XClientMessageEvent xclient; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + atom = g_intern_static_string ("_NET_WM_DESKTOP"); + if (!gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), atom)) + return; + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.serial = 0; + xclient.send_event = True; + xclient.window = GDK_SURFACE_XID (window); + xclient.message_type = gdk_x11_atom_to_xatom_for_display (GDK_SURFACE_DISPLAY (window), atom); + xclient.format = 32; + + xclient.data.l[0] = desktop; + xclient.data.l[1] = 1; /* source indication */ + xclient.data.l[2] = 0; + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + + XSendEvent (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XROOTWIN (window), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); +} + +static void +gdk_x11_surface_focus (GdkSurface *window, + guint32 timestamp) +{ + GdkDisplay *display; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = GDK_SURFACE_DISPLAY (window); + + if (gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_ACTIVE_WINDOW"))) + { + XClientMessageEvent xclient; + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_ACTIVE_WINDOW"); + xclient.format = 32; + xclient.data.l[0] = 1; /* requestor type; we're an app */ + xclient.data.l[1] = timestamp; + xclient.data.l[2] = None; /* currently active window */ + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + + XSendEvent (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); + } + else + { + XRaiseWindow (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window)); + + /* There is no way of knowing reliably whether we are viewable; + * so trap errors asynchronously around the XSetInputFocus call + */ + gdk_x11_display_error_trap_push (display); + XSetInputFocus (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + RevertToParent, + timestamp); + gdk_x11_display_error_trap_pop_ignored (display); + } +} + +static void +gdk_x11_surface_set_type_hint (GdkSurface *window, + GdkSurfaceTypeHint hint) +{ + GdkDisplay *display; + Atom atom; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = gdk_surface_get_display (window); + + switch (hint) + { + case GDK_SURFACE_TYPE_HINT_DIALOG: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DIALOG"); + break; + case GDK_SURFACE_TYPE_HINT_MENU: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_MENU"); + break; + case GDK_SURFACE_TYPE_HINT_TOOLBAR: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + break; + case GDK_SURFACE_TYPE_HINT_UTILITY: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_UTILITY"); + break; + case GDK_SURFACE_TYPE_HINT_SPLASHSCREEN: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_SPLASH"); + break; + case GDK_SURFACE_TYPE_HINT_DOCK: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK"); + break; + case GDK_SURFACE_TYPE_HINT_DESKTOP: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP"); + break; + case GDK_SURFACE_TYPE_HINT_DROPDOWN_MENU: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + break; + case GDK_SURFACE_TYPE_HINT_POPUP_MENU: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_POPUP_MENU"); + break; + case GDK_SURFACE_TYPE_HINT_TOOLTIP: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); + break; + case GDK_SURFACE_TYPE_HINT_NOTIFICATION: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NOTIFICATION"); + break; + case GDK_SURFACE_TYPE_HINT_COMBO: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_COMBO"); + break; + case GDK_SURFACE_TYPE_HINT_DND: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DND"); + break; + default: + g_warning ("Unknown hint %d passed to gdk_surface_set_type_hint", hint); + /* Fall thru */ + case GDK_SURFACE_TYPE_HINT_NORMAL: + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NORMAL"); + break; + } + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"), + XA_ATOM, 32, PropModeReplace, + (guchar *)&atom, 1); +} + +static GdkSurfaceTypeHint +gdk_x11_surface_get_type_hint (GdkSurface *window) +{ + GdkDisplay *display; + GdkSurfaceTypeHint type; + Atom type_return; + gint format_return; + gulong nitems_return; + gulong bytes_after_return; + guchar *data = NULL; + + g_return_val_if_fail (GDK_IS_SURFACE (window), GDK_SURFACE_TYPE_HINT_NORMAL); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return GDK_SURFACE_TYPE_HINT_NORMAL; + + type = GDK_SURFACE_TYPE_HINT_NORMAL; + + display = gdk_surface_get_display (window); + + if (XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"), + 0, G_MAXLONG, False, XA_ATOM, &type_return, + &format_return, &nitems_return, &bytes_after_return, + &data) == Success) + { + if ((type_return == XA_ATOM) && (format_return == 32) && + (data) && (nitems_return == 1)) + { + Atom atom = *(Atom*)data; + + if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DIALOG")) + type = GDK_SURFACE_TYPE_HINT_DIALOG; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_MENU")) + type = GDK_SURFACE_TYPE_HINT_MENU; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_TOOLBAR")) + type = GDK_SURFACE_TYPE_HINT_TOOLBAR; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_UTILITY")) + type = GDK_SURFACE_TYPE_HINT_UTILITY; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_SPLASH")) + type = GDK_SURFACE_TYPE_HINT_SPLASHSCREEN; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK")) + type = GDK_SURFACE_TYPE_HINT_DOCK; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP")) + type = GDK_SURFACE_TYPE_HINT_DESKTOP; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU")) + type = GDK_SURFACE_TYPE_HINT_DROPDOWN_MENU; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_POPUP_MENU")) + type = GDK_SURFACE_TYPE_HINT_POPUP_MENU; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_TOOLTIP")) + type = GDK_SURFACE_TYPE_HINT_TOOLTIP; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NOTIFICATION")) + type = GDK_SURFACE_TYPE_HINT_NOTIFICATION; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_COMBO")) + type = GDK_SURFACE_TYPE_HINT_COMBO; + else if (atom == gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DND")) + type = GDK_SURFACE_TYPE_HINT_DND; + } + + if (type_return != None && data != NULL) + XFree (data); + } + + return type; +} + +static void +gdk_wmspec_change_state (gboolean add, + GdkSurface *window, + GdkAtom state1, + GdkAtom state2) +{ + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + XClientMessageEvent xclient; + +#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define _NET_WM_STATE_ADD 1 /* add/set property */ +#define _NET_WM_STATE_TOGGLE 2 /* toggle property */ + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE"); + xclient.format = 32; + xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xclient.data.l[1] = gdk_x11_atom_to_xatom_for_display (display, state1); + xclient.data.l[2] = gdk_x11_atom_to_xatom_for_display (display, state2); + xclient.data.l[3] = 1; /* source indication */ + xclient.data.l[4] = 0; + + XSendEvent (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); +} + +static void +gdk_x11_surface_set_modal_hint (GdkSurface *window, + gboolean modal) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + window->modal_hint = modal; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (modal, window, + g_intern_static_string ("_NET_WM_STATE_MODAL"), + NULL); +} + +static void +gdk_x11_surface_set_skip_taskbar_hint (GdkSurface *window, + gboolean skips_taskbar) +{ + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_SURFACE_TYPE (window) != GDK_SURFACE_CHILD); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + toplevel = _gdk_x11_surface_get_toplevel (window); + toplevel->skip_taskbar_hint = skips_taskbar; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (skips_taskbar, window, + g_intern_static_string ("_NET_WM_STATE_SKIP_TASKBAR"), + NULL); +} + +static void +gdk_x11_surface_set_skip_pager_hint (GdkSurface *window, + gboolean skips_pager) +{ + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_SURFACE_TYPE (window) != GDK_SURFACE_CHILD); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + toplevel = _gdk_x11_surface_get_toplevel (window); + toplevel->skip_pager_hint = skips_pager; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (skips_pager, window, + g_intern_static_string ("_NET_WM_STATE_SKIP_PAGER"), + NULL); +} + +static void +gdk_x11_surface_set_urgency_hint (GdkSurface *window, + gboolean urgent) +{ + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_SURFACE_TYPE (window) != GDK_SURFACE_CHILD); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + toplevel = _gdk_x11_surface_get_toplevel (window); + toplevel->urgency_hint = urgent; + + update_wm_hints (window, FALSE); +} + +static void +gdk_x11_surface_set_geometry_hints (GdkSurface *window, + const GdkGeometry *geometry, + GdkSurfaceHints geom_mask) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + XSizeHints size_hints; + GdkToplevelX11 *toplevel; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + toplevel = _gdk_x11_surface_get_toplevel (window); + if (toplevel) + { + if (geometry) + toplevel->last_geometry_hints = *geometry; + toplevel->last_geometry_hints_mask = geom_mask; + } + + size_hints.flags = 0; + + if (geom_mask & GDK_HINT_POS) + { + size_hints.flags |= PPosition; + /* We need to initialize the following obsolete fields because KWM + * apparently uses these fields if they are non-zero. + * #@#!#!$!. + */ + size_hints.x = 0; + size_hints.y = 0; + } + + if (geom_mask & GDK_HINT_USER_POS) + { + size_hints.flags |= USPosition; + } + + if (geom_mask & GDK_HINT_USER_SIZE) + { + size_hints.flags |= USSize; + } + + if (geom_mask & GDK_HINT_MIN_SIZE) + { + size_hints.flags |= PMinSize; + size_hints.min_width = geometry->min_width * impl->window_scale; + size_hints.min_height = geometry->min_height * impl->window_scale; + } + + if (geom_mask & GDK_HINT_MAX_SIZE) + { + size_hints.flags |= PMaxSize; + size_hints.max_width = MAX (geometry->max_width, 1) * impl->window_scale; + size_hints.max_height = MAX (geometry->max_height, 1) * impl->window_scale; + } + + if (geom_mask & GDK_HINT_BASE_SIZE) + { + size_hints.flags |= PBaseSize; + size_hints.base_width = geometry->base_width * impl->window_scale; + size_hints.base_height = geometry->base_height * impl->window_scale; + } + + if (geom_mask & GDK_HINT_RESIZE_INC) + { + size_hints.flags |= PResizeInc; + size_hints.width_inc = geometry->width_inc * impl->window_scale; + size_hints.height_inc = geometry->height_inc * impl->window_scale; + } + else if (impl->window_scale > 1) + { + size_hints.flags |= PResizeInc; + size_hints.width_inc = impl->window_scale; + size_hints.height_inc = impl->window_scale; + } + + if (geom_mask & GDK_HINT_ASPECT) + { + size_hints.flags |= PAspect; + if (geometry->min_aspect <= 1) + { + size_hints.min_aspect.x = 65536 * geometry->min_aspect; + size_hints.min_aspect.y = 65536; + } + else + { + size_hints.min_aspect.x = 65536; + size_hints.min_aspect.y = 65536 / geometry->min_aspect;; + } + if (geometry->max_aspect <= 1) + { + size_hints.max_aspect.x = 65536 * geometry->max_aspect; + size_hints.max_aspect.y = 65536; + } + else + { + size_hints.max_aspect.x = 65536; + size_hints.max_aspect.y = 65536 / geometry->max_aspect;; + } + } + + if (geom_mask & GDK_HINT_WIN_GRAVITY) + { + size_hints.flags |= PWinGravity; + size_hints.win_gravity = geometry->win_gravity; + } + + /* FIXME: Would it be better to delete this property if + * geom_mask == 0? It would save space on the server + */ + XSetWMNormalHints (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + &size_hints); +} + +static void +gdk_surface_get_geometry_hints (GdkSurface *window, + GdkGeometry *geometry, + GdkSurfaceHints *geom_mask) +{ + GdkSurfaceImplX11 *impl; + XSizeHints *size_hints; + glong junk_supplied_mask = 0; + + g_return_if_fail (GDK_IS_SURFACE (window)); + g_return_if_fail (geometry != NULL); + g_return_if_fail (geom_mask != NULL); + + *geom_mask = 0; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + size_hints = XAllocSizeHints (); + if (!size_hints) + return; + + if (!XGetWMNormalHints (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + size_hints, + &junk_supplied_mask)) + size_hints->flags = 0; + + if (size_hints->flags & PMinSize) + { + *geom_mask |= GDK_HINT_MIN_SIZE; + geometry->min_width = size_hints->min_width / impl->window_scale; + geometry->min_height = size_hints->min_height / impl->window_scale; + } + + if (size_hints->flags & PMaxSize) + { + *geom_mask |= GDK_HINT_MAX_SIZE; + geometry->max_width = MAX (size_hints->max_width, 1) / impl->window_scale; + geometry->max_height = MAX (size_hints->max_height, 1) / impl->window_scale; + } + + if (size_hints->flags & PResizeInc) + { + *geom_mask |= GDK_HINT_RESIZE_INC; + geometry->width_inc = size_hints->width_inc / impl->window_scale; + geometry->height_inc = size_hints->height_inc / impl->window_scale; + } + + if (size_hints->flags & PAspect) + { + *geom_mask |= GDK_HINT_ASPECT; + + geometry->min_aspect = (gdouble) size_hints->min_aspect.x / (gdouble) size_hints->min_aspect.y; + geometry->max_aspect = (gdouble) size_hints->max_aspect.x / (gdouble) size_hints->max_aspect.y; + } + + if (size_hints->flags & PWinGravity) + { + *geom_mask |= GDK_HINT_WIN_GRAVITY; + geometry->win_gravity = size_hints->win_gravity; + } + + XFree (size_hints); +} + +static gboolean +utf8_is_latin1 (const gchar *str) +{ + const char *p = str; + + while (*p) + { + gunichar ch = g_utf8_get_char (p); + + if (ch > 0xff) + return FALSE; + + p = g_utf8_next_char (p); + } + + return TRUE; +} + +/* Set the property to @utf8_str as STRING if the @utf8_str is fully + * convertable to STRING, otherwise, set it as compound text + */ +static void +set_text_property (GdkDisplay *display, + Window xwindow, + Atom property, + const gchar *utf8_str) +{ + gchar *prop_text = NULL; + Atom prop_type; + gint prop_length; + gint prop_format; + gboolean is_compound_text; + + if (utf8_is_latin1 (utf8_str)) + { + prop_type = XA_STRING; + prop_text = _gdk_x11_display_utf8_to_string_target (display, utf8_str); + prop_length = prop_text ? strlen (prop_text) : 0; + prop_format = 8; + is_compound_text = FALSE; + } + else + { + GdkAtom gdk_type; + + gdk_x11_display_utf8_to_compound_text (display, + utf8_str, &gdk_type, &prop_format, + (guchar **)&prop_text, &prop_length); + prop_type = gdk_x11_atom_to_xatom_for_display (display, gdk_type); + is_compound_text = TRUE; + } + + if (prop_text) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + xwindow, + property, + prop_type, prop_format, + PropModeReplace, (guchar *)prop_text, + prop_length); + + if (is_compound_text) + gdk_x11_free_compound_text ((guchar *)prop_text); + else + g_free (prop_text); + } +} + +/* Set WM_NAME and _NET_WM_NAME + */ +static void +set_wm_name (GdkDisplay *display, + Window xwindow, + const gchar *name) +{ + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (guchar *)name, strlen (name)); + + set_text_property (display, xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "WM_NAME"), + name); +} + +static void +gdk_x11_surface_set_title (GdkSurface *window, + const gchar *title) +{ + GdkDisplay *display; + Display *xdisplay; + Window xwindow; + + g_return_if_fail (title != NULL); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = gdk_surface_get_display (window); + xdisplay = GDK_DISPLAY_XDISPLAY (display); + xwindow = GDK_SURFACE_XID (window); + + set_wm_name (display, xwindow, title); + + if (!gdk_surface_icon_name_set (window)) + { + XChangeProperty (xdisplay, xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_ICON_NAME"), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (guchar *)title, strlen (title)); + + set_text_property (display, xwindow, + gdk_x11_get_xatom_by_name_for_display (display, "WM_ICON_NAME"), + title); + } +} + +static void +gdk_x11_surface_set_role (GdkSurface *window, + const gchar *role) +{ + GdkDisplay *display; + + display = gdk_surface_get_display (window); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (role) + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "WM_WINDOW_ROLE"), + XA_STRING, 8, PropModeReplace, (guchar *)role, strlen (role)); + else + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "WM_WINDOW_ROLE")); +} + +static void +gdk_x11_surface_set_startup_id (GdkSurface *window, + const gchar *startup_id) +{ + GdkDisplay *display; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + display = gdk_surface_get_display (window); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (startup_id) + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_STARTUP_ID"), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (unsigned char *)startup_id, strlen (startup_id)); + else + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_STARTUP_ID")); +} + +static void +gdk_x11_surface_set_transient_for (GdkSurface *window, + GdkSurface *parent) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + /* XSetTransientForHint() doesn't allow unsetting, so do it manually */ + if (parent && !GDK_SURFACE_DESTROYED (parent)) + XSetTransientForHint (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + GDK_SURFACE_XID (parent)); + else + XDeleteProperty (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (GDK_SURFACE_DISPLAY (window), "WM_TRANSIENT_FOR")); +} + +GdkCursor * +_gdk_x11_surface_get_cursor (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl; + + g_return_val_if_fail (GDK_IS_SURFACE (window), NULL); + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + return impl->cursor; +} + +static void +gdk_surface_x11_get_geometry (GdkSurface *window, + gint *x, + gint *y, + gint *width, + gint *height) +{ + GdkSurfaceImplX11 *impl; + Window root; + gint tx; + gint ty; + guint twidth; + guint theight; + guint tborder_width; + guint tdepth; + + if (!GDK_SURFACE_DESTROYED (window)) + { + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + XGetGeometry (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + &root, &tx, &ty, &twidth, &theight, &tborder_width, &tdepth); + + if (x) + *x = tx / impl->window_scale; + if (y) + *y = ty / impl->window_scale; + if (width) + *width = twidth / impl->window_scale; + if (height) + *height = theight / impl->window_scale; + } +} + +static void +gdk_surface_x11_get_root_coords (GdkSurface *window, + gint x, + gint y, + gint *root_x, + gint *root_y) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + Window child; + gint tx; + gint ty; + + XTranslateCoordinates (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + GDK_SURFACE_XROOTWIN (window), + x * impl->window_scale, y * impl->window_scale, &tx, &ty, + &child); + + if (root_x) + *root_x = tx / impl->window_scale; + if (root_y) + *root_y = ty / impl->window_scale; +} + +static void +gdk_x11_surface_get_frame_extents (GdkSurface *window, + GdkRectangle *rect) +{ + GdkDisplay *display; + GdkSurfaceImplX11 *impl; + Window xwindow; + Window xparent; + Window root; + Window child; + Window *children; + guchar *data; + Window *vroots; + Atom type_return; + guint nchildren; + guint nvroots; + gulong nitems_return; + gulong bytes_after_return; + gint format_return; + gint i; + guint ww, wh, wb, wd; + gint wx, wy; + gboolean got_frame_extents = FALSE; + + g_return_if_fail (rect != NULL); + + rect->x = 0; + rect->y = 0; + rect->width = 1; + rect->height = 1; + + while (window->parent && (window->parent)->parent) + window = window->parent; + + impl = GDK_SURFACE_IMPL_X11 (window->impl); + + /* Refine our fallback answer a bit using local information */ + rect->x = window->x * impl->window_scale; + rect->y = window->y * impl->window_scale; + rect->width = window->width * impl->window_scale; + rect->height = window->height * impl->window_scale; + + if (GDK_SURFACE_DESTROYED (window) || impl->override_redirect) + return; + + nvroots = 0; + vroots = NULL; + + display = gdk_surface_get_display (window); + + gdk_x11_display_error_trap_push (display); + + xwindow = GDK_SURFACE_XID (window); + + /* first try: use _NET_FRAME_EXTENTS */ + if (gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_FRAME_EXTENTS")) && + XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), xwindow, + gdk_x11_get_xatom_by_name_for_display (display, + "_NET_FRAME_EXTENTS"), + 0, G_MAXLONG, False, XA_CARDINAL, &type_return, + &format_return, &nitems_return, &bytes_after_return, + &data) + == Success) + { + if ((type_return == XA_CARDINAL) && (format_return == 32) && + (nitems_return == 4) && (data)) + { + gulong *ldata = (gulong *) data; + got_frame_extents = TRUE; + + /* try to get the real client window geometry */ + if (XGetGeometry (GDK_DISPLAY_XDISPLAY (display), xwindow, + &root, &wx, &wy, &ww, &wh, &wb, &wd) && + XTranslateCoordinates (GDK_DISPLAY_XDISPLAY (display), + xwindow, root, 0, 0, &wx, &wy, &child)) + { + rect->x = wx; + rect->y = wy; + rect->width = ww; + rect->height = wh; + } + + /* _NET_FRAME_EXTENTS format is left, right, top, bottom */ + rect->x -= ldata[0]; + rect->y -= ldata[2]; + rect->width += ldata[0] + ldata[1]; + rect->height += ldata[2] + ldata[3]; + } + + if (data) + XFree (data); + } + + if (got_frame_extents) + goto out; + + /* no frame extents property available, which means we either have a WM that + is not EWMH compliant or is broken - try fallback and walk up the window + tree to get our window's parent which hopefully is the window frame */ + + /* use NETWM_VIRTUAL_ROOTS if available */ + root = GDK_SURFACE_XROOTWIN (window); + + if (gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_VIRTUAL_ROOTS")) && + XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), root, + gdk_x11_get_xatom_by_name_for_display (display, + "_NET_VIRTUAL_ROOTS"), + 0, G_MAXLONG, False, XA_WINDOW, &type_return, + &format_return, &nitems_return, &bytes_after_return, + &data) + == Success) + { + if ((type_return == XA_WINDOW) && (format_return == 32) && (data)) + { + nvroots = nitems_return; + vroots = (Window *)data; + } + } + + xparent = GDK_SURFACE_XID (window); + + do + { + xwindow = xparent; + + if (!XQueryTree (GDK_DISPLAY_XDISPLAY (display), xwindow, + &root, &xparent, + &children, &nchildren)) + goto out; + + if (children) + XFree (children); + + /* check virtual roots */ + for (i = 0; i < nvroots; i++) + { + if (xparent == vroots[i]) + { + root = xparent; + break; + } + } + } + while (xparent != root); + + if (XGetGeometry (GDK_DISPLAY_XDISPLAY (display), xwindow, + &root, &wx, &wy, &ww, &wh, &wb, &wd)) + { + rect->x = wx; + rect->y = wy; + rect->width = ww; + rect->height = wh; + } + + out: + if (vroots) + XFree (vroots); + + /* Here we extend the size to include the extra pixels if we round x/y down + as well as round the size up when we divide by scale so that the returned + size is guaranteed to cover the real pixels, but it may overshoot a bit + in case the window is not positioned/sized according to the scale */ + rect->width = (rect->width + rect->x % impl->window_scale + impl->window_scale - 1) / impl->window_scale; + rect->height = (rect->height + rect->y % impl->window_scale + impl->window_scale - 1) / impl->window_scale; + rect->x = rect->x / impl->window_scale; + rect->y = rect->y / impl->window_scale; + gdk_x11_display_error_trap_pop_ignored (display); +} + +static gboolean +gdk_surface_x11_get_device_state (GdkSurface *window, + GdkDevice *device, + gdouble *x, + gdouble *y, + GdkModifierType *mask) +{ + GdkSurface *child; + + if (GDK_SURFACE_DESTROYED (window)) + return FALSE; + + /*HIDPI: handle coords here?*/ + GDK_DEVICE_GET_CLASS (device)->query_state (device, window, + &child, + NULL, NULL, + x, y, mask); + return child != NULL; +} + +static GdkEventMask +gdk_surface_x11_get_events (GdkSurface *window) +{ + XWindowAttributes attrs; + GdkEventMask event_mask; + GdkEventMask filtered; + + if (GDK_SURFACE_DESTROYED (window)) + return 0; + else + { + XGetWindowAttributes (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + &attrs); + event_mask = x_event_mask_to_gdk_event_mask (attrs.your_event_mask); + /* if property change was filtered out before, keep it filtered out */ + filtered = GDK_STRUCTURE_MASK | GDK_PROPERTY_CHANGE_MASK; + window->event_mask = event_mask & ((window->event_mask & filtered) | ~filtered); + + return event_mask; + } +} +static void +gdk_surface_x11_set_events (GdkSurface *window, + GdkEventMask event_mask) +{ + long xevent_mask = 0; + + if (!GDK_SURFACE_DESTROYED (window)) + { + GdkX11Display *display_x11; + + if (GDK_SURFACE_XID (window) != GDK_SURFACE_XROOTWIN (window)) + xevent_mask = StructureNotifyMask | PropertyChangeMask; + + display_x11 = GDK_X11_DISPLAY (gdk_surface_get_display (window)); + gdk_x11_event_source_select_events ((GdkEventSource *) display_x11->event_source, + GDK_SURFACE_XID (window), event_mask, + xevent_mask); + } +} + +static inline void +do_shape_combine_region (GdkSurface *window, + const cairo_region_t *shape_region, + gint offset_x, + gint offset_y, + gint shape) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (GDK_SURFACE_DESTROYED (window)) + return; + + if (shape_region == NULL) + { + /* Use NULL mask to unset the shape */ + if (shape == ShapeBounding + ? gdk_display_supports_shapes (GDK_SURFACE_DISPLAY (window)) + : gdk_display_supports_input_shapes (GDK_SURFACE_DISPLAY (window))) + { + XShapeCombineMask (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + shape, + 0, 0, + None, + ShapeSet); + } + return; + } + + if (shape == ShapeBounding + ? gdk_display_supports_shapes (GDK_SURFACE_DISPLAY (window)) + : gdk_display_supports_input_shapes (GDK_SURFACE_DISPLAY (window))) + { + gint n_rects = 0; + XRectangle *xrects = NULL; + + _gdk_x11_region_get_xrectangles (shape_region, + 0, 0, impl->window_scale, + &xrects, &n_rects); + + XShapeCombineRectangles (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + shape, + offset_x * impl->window_scale, + offset_y * impl->window_scale, + xrects, n_rects, + ShapeSet, + YXBanded); + + g_free (xrects); + } +} + +static void +gdk_surface_x11_shape_combine_region (GdkSurface *window, + const cairo_region_t *shape_region, + gint offset_x, + gint offset_y) +{ + do_shape_combine_region (window, shape_region, offset_x, offset_y, ShapeBounding); +} + +static void +gdk_surface_x11_input_shape_combine_region (GdkSurface *window, + const cairo_region_t *shape_region, + gint offset_x, + gint offset_y) +{ +#ifdef ShapeInput + do_shape_combine_region (window, shape_region, offset_x, offset_y, ShapeInput); +#endif +} + +static void +gdk_x11_surface_set_accept_focus (GdkSurface *window, + gboolean accept_focus) +{ + accept_focus = accept_focus != FALSE; + + if (window->accept_focus != accept_focus) + { + window->accept_focus = accept_focus; + + if (!GDK_SURFACE_DESTROYED (window) && + WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + update_wm_hints (window, FALSE); + } +} + +static void +gdk_x11_surface_set_focus_on_map (GdkSurface *window, + gboolean focus_on_map) +{ + focus_on_map = focus_on_map != FALSE; + + if (window->focus_on_map != focus_on_map) + { + window->focus_on_map = focus_on_map; + + if ((!GDK_SURFACE_DESTROYED (window)) && + (!window->focus_on_map) && + WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + gdk_x11_surface_set_user_time (window, 0); + } +} + +/** + * gdk_x11_surface_set_user_time: + * @window: (type GdkX11Surface): A toplevel #GdkSurface + * @timestamp: An XServer timestamp to which the property should be set + * + * The application can use this call to update the _NET_WM_USER_TIME + * property on a toplevel window. This property stores an Xserver + * time which represents the time of the last user input event + * received for this window. This property may be used by the window + * manager to alter the focus, stacking, and/or placement behavior of + * windows when they are mapped depending on whether the new window + * was created by a user action or is a "pop-up" window activated by a + * timer or some other event. + * + * Note that this property is automatically updated by GDK, so this + * function should only be used by applications which handle input + * events bypassing GDK. + **/ +void +gdk_x11_surface_set_user_time (GdkSurface *window, + guint32 timestamp) +{ + GdkDisplay *display; + GdkX11Display *display_x11; + GdkToplevelX11 *toplevel; + glong timestamp_long = (glong)timestamp; + Window xid; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = gdk_surface_get_display (window); + display_x11 = GDK_X11_DISPLAY (display); + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (!toplevel) + { + g_warning ("gdk_surface_set_user_time called on non-toplevel\n"); + return; + } + + if (toplevel->focus_window != None && + gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_WM_USER_TIME_WINDOW"))) + xid = toplevel->focus_window; + else + xid = GDK_SURFACE_XID (window); + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), xid, + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_USER_TIME"), + XA_CARDINAL, 32, PropModeReplace, + (guchar *)×tamp_long, 1); + + if (timestamp_long != GDK_CURRENT_TIME && + (display_x11->user_time == GDK_CURRENT_TIME || + XSERVER_TIME_IS_LATER (timestamp_long, display_x11->user_time))) + display_x11->user_time = timestamp_long; + + if (toplevel) + toplevel->user_time = timestamp_long; +} + +/** + * gdk_x11_surface_set_utf8_property: + * @window: (type GdkX11Surface): a #GdkSurface + * @name: Property name, will be interned as an X atom + * @value: (allow-none): Property value, or %NULL to delete + * + * This function modifies or removes an arbitrary X11 window + * property of type UTF8_STRING. If the given @window is + * not a toplevel window, it is ignored. + */ +void +gdk_x11_surface_set_utf8_property (GdkSurface *window, + const gchar *name, + const gchar *value) +{ + GdkDisplay *display; + + if (!WINDOW_IS_TOPLEVEL (window)) + return; + + display = gdk_surface_get_display (window); + + if (value != NULL) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, name), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (guchar *)value, strlen (value)); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, name)); + } +} + +static void +gdk_x11_surface_set_shadow_width (GdkSurface *window, + int left, + int right, + int top, + int bottom) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + Atom frame_extents; + gulong data[4] = { + left * impl->window_scale, + right * impl->window_scale, + top * impl->window_scale, + bottom * impl->window_scale + }; + + frame_extents = gdk_x11_get_xatom_by_name_for_display (gdk_surface_get_display (window), + "_GTK_FRAME_EXTENTS"); + XChangeProperty (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + frame_extents, XA_CARDINAL, + 32, PropModeReplace, + (guchar *) &data, 4); +} + +/** + * gdk_x11_surface_set_theme_variant: + * @window: (type GdkX11Surface): a #GdkSurface + * @variant: the theme variant to export + * + * GTK+ applications can request a dark theme variant. In order to + * make other applications - namely window managers using GTK+ for + * themeing - aware of this choice, GTK+ uses this function to + * export the requested theme variant as _GTK_THEME_VARIANT property + * on toplevel windows. + * + * Note that this property is automatically updated by GTK+, so this + * function should only be used by applications which do not use GTK+ + * to create toplevel windows. + */ +void +gdk_x11_surface_set_theme_variant (GdkSurface *window, + const char *variant) +{ + gdk_x11_surface_set_utf8_property (window, "_GTK_THEME_VARIANT", + variant ? variant : ""); +} + +#define GDK_SELECTION_MAX_SIZE(display) \ + MIN(262144, \ + XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) == 0 \ + ? XMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100 \ + : XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100) + +static void +gdk_surface_update_icon (GdkSurface *window, + GList *icon_list) +{ + GdkToplevelX11 *toplevel; + GdkTexture *best_icon; + GList *tmp_list; + int best_size; + + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (toplevel->icon_pixmap != NULL) + { + cairo_surface_destroy (toplevel->icon_pixmap); + toplevel->icon_pixmap = NULL; + } + + if (toplevel->icon_mask != NULL) + { + cairo_surface_destroy (toplevel->icon_mask); + toplevel->icon_mask = NULL; + } + +#define IDEAL_SIZE 48 + + best_size = G_MAXINT; + best_icon = NULL; + for (tmp_list = icon_list; tmp_list; tmp_list = tmp_list->next) + { + GdkTexture *texture = tmp_list->data; + int this; + + /* average width and height - if someone passes in a rectangular + * icon they deserve what they get. + */ + this = gdk_texture_get_width (texture) + gdk_texture_get_height (texture); + this /= 2; + + if (best_icon == NULL) + { + best_icon = texture; + best_size = this; + } + else + { + /* icon is better if it's 32 pixels or larger, and closer to + * the ideal size than the current best. + */ + if (this >= 32 && + (ABS (best_size - IDEAL_SIZE) < + ABS (this - IDEAL_SIZE))) + { + best_icon = texture; + best_size = this; + } + } + } + + if (best_icon) + { + int width = gdk_texture_get_width (best_icon); + int height = gdk_texture_get_height (best_icon); + cairo_surface_t *surface; + cairo_t *cr; + + toplevel->icon_pixmap = gdk_x11_surface_create_pixmap_surface (window, width, height); + + surface = gdk_texture_download_surface (best_icon); + + cr = cairo_create (toplevel->icon_pixmap); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface (cr, surface, 0, 0); + if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA) + { + /* Saturate the image, so it has bilevel alpha */ + cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA); + cairo_paint (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SATURATE); + cairo_paint (cr); + cairo_pop_group_to_source (cr); + } + cairo_paint (cr); + cairo_destroy (cr); + + if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA) + { + GdkDisplay *display = gdk_surface_get_display (window); + + toplevel->icon_mask = _gdk_x11_display_create_bitmap_surface (display, width, height); + + cr = cairo_create (toplevel->icon_mask); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_destroy (cr); + } + + cairo_surface_destroy (surface); + } + + update_wm_hints (window, FALSE); +} + +static void +gdk_x11_surface_set_icon_list (GdkSurface *window, + GList *textures) +{ + gulong *data; + gulong *p; + gint size; + GList *l; + gint width, height; + GdkTexture *texture; + GdkDisplay *display; + gint i, n; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = gdk_surface_get_display (window); + + size = 0; + n = 0; + for (l = textures; l != NULL; l = l->next) + { + texture = l->data; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + /* silently ignore overlarge icons */ + if (size + 2 + width * height > GDK_SELECTION_MAX_SIZE(display)) + break; + + n++; + size += 2 + width * height; + } + + data = g_malloc (size * sizeof (gulong)); + + p = data; + for (l = textures; l != NULL && n > 0; l = l->next) + { + texture = l->data; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + *p++ = width; + *p++ = height; + + gdk_texture_download (texture, (guchar *) p, width * 4); + if (sizeof (gulong) > 4) + { + i = width * height; + while (i-- > 0) + p[i] = ((guint32 *) p)[i]; + } + + p += width * height; + n--; + } + + if (size > 0) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_ICON"), + XA_CARDINAL, 32, + PropModeReplace, + (guchar*) data, size); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_ICON")); + } + + g_free (data); + + gdk_surface_update_icon (window, textures); +} + +static gboolean +gdk_surface_icon_name_set (GdkSurface *window) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (window), + g_quark_from_static_string ("gdk-icon-name-set"))); +} + +static void +gdk_x11_surface_set_icon_name (GdkSurface *window, + const gchar *name) +{ + GdkDisplay *display; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + display = gdk_surface_get_display (window); + + g_object_set_qdata (G_OBJECT (window), g_quark_from_static_string ("gdk-icon-name-set"), + GUINT_TO_POINTER (name != NULL)); + + if (name != NULL) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_ICON_NAME"), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (guchar *)name, strlen (name)); + + set_text_property (display, GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "WM_ICON_NAME"), + name); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_ICON_NAME")); + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "WM_ICON_NAME")); + } +} + +static void +gdk_x11_surface_iconify (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + XIconifyWindow (GDK_SURFACE_XDISPLAY (window), + GDK_SURFACE_XID (window), + gdk_x11_screen_get_screen_number (GDK_SURFACE_SCREEN (window))); + } + else + { + /* Flip our client side flag, the real work happens on map. */ + gdk_synthesize_window_state (window, + 0, + GDK_SURFACE_STATE_ICONIFIED); + gdk_wmspec_change_state (TRUE, window, + g_intern_static_string ("_NET_WM_STATE_HIDDEN"), + NULL); + } +} + +static void +gdk_x11_surface_deiconify (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + gdk_surface_show (window); + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_HIDDEN"), + NULL); + } + else + { + /* Flip our client side flag, the real work happens on map. */ + gdk_synthesize_window_state (window, + GDK_SURFACE_STATE_ICONIFIED, + 0); + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_HIDDEN"), + NULL); + } +} + +static void +gdk_x11_surface_stick (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + /* "stick" means stick to all desktops _and_ do not scroll with the + * viewport. i.e. glue to the monitor glass in all cases. + */ + + XClientMessageEvent xclient; + + /* Request stick during viewport scroll */ + gdk_wmspec_change_state (TRUE, window, + g_intern_static_string ("_NET_WM_STATE_STICKY"), + NULL); + + /* Request desktop 0xFFFFFFFF */ + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.display = GDK_SURFACE_XDISPLAY (window); + xclient.message_type = gdk_x11_get_xatom_by_name_for_display (GDK_SURFACE_DISPLAY (window), + "_NET_WM_DESKTOP"); + xclient.format = 32; + + xclient.data.l[0] = 0xFFFFFFFF; + xclient.data.l[1] = 0; + xclient.data.l[2] = 0; + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + + XSendEvent (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); + } + else + { + /* Flip our client side flag, the real work happens on map. */ + gdk_synthesize_window_state (window, + 0, + GDK_SURFACE_STATE_STICKY); + } +} + +static void +gdk_x11_surface_unstick (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + /* Request unstick from viewport */ + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_STICKY"), + NULL); + + move_to_current_desktop (window); + } + else + { + /* Flip our client side flag, the real work happens on map. */ + gdk_synthesize_window_state (window, + GDK_SURFACE_STATE_STICKY, + 0); + + } +} + +static void +gdk_x11_surface_maximize (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (TRUE, window, + g_intern_static_string ("_NET_WM_STATE_MAXIMIZED_VERT"), + g_intern_static_string ("_NET_WM_STATE_MAXIMIZED_HORZ")); + else + gdk_synthesize_window_state (window, + 0, + GDK_SURFACE_STATE_MAXIMIZED); +} + +static void +gdk_x11_surface_unmaximize (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_MAXIMIZED_VERT"), + g_intern_static_string ("_NET_WM_STATE_MAXIMIZED_HORZ")); + else + gdk_synthesize_window_state (window, + GDK_SURFACE_STATE_MAXIMIZED, + 0); +} + +static void +gdk_x11_surface_apply_fullscreen_mode (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + /* _NET_WM_FULLSCREEN_MONITORS gives an indication to the window manager as + * to which monitors so span across when the window is fullscreen, but it's + * not a state in itself so this would have no effect if the window is not + * mapped. + */ + + if (GDK_SURFACE_IS_MAPPED (window)) + { + XClientMessageEvent xclient; + gint monitors[4]; + gint i; + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.display = GDK_SURFACE_XDISPLAY (window); + xclient.format = 32; + + switch (window->fullscreen_mode) + { + case GDK_FULLSCREEN_ON_CURRENT_MONITOR: + + /* FIXME: This is not part of the EWMH spec! + * + * There is no documented mechanism to remove the property + * _NET_WM_FULLSCREEN_MONITORS once set, so we use use a set of + * invalid, largest possible value. + * + * When given values larger than actual possible monitor values, most + * window managers who support the _NET_WM_FULLSCREEN_MONITORS spec + * will simply unset _NET_WM_FULLSCREEN_MONITORS and revert to their + * default behavior. + * + * Successfully tested on mutter/metacity, kwin, compiz and xfwm4. + * + * Note, this (non documented) mechanism is unlikely to be an issue + * as it's used only for transitionning back from "all monitors" to + * "current monitor" mode. + * + * Applications who don't change the default mode won't trigger this + * mechanism. + */ + for (i = 0; i < 4; ++i) + xclient.data.l[i] = G_MAXLONG; + + break; + + case GDK_FULLSCREEN_ON_ALL_MONITORS: + + _gdk_x11_screen_get_edge_monitors (GDK_SURFACE_SCREEN (window), + &monitors[0], + &monitors[1], + &monitors[2], + &monitors[3]); + /* Translate all 4 monitors from the GDK set into XINERAMA indices */ + for (i = 0; i < 4; ++i) + { + xclient.data.l[i] = monitors[i]; + /* Sanity check, if XINERAMA is not available, we could have invalid + * negative values for the XINERAMA indices. + */ + if (xclient.data.l[i] < 0) + { + g_warning ("gdk_x11_surface_apply_fullscreen_mode: Invalid XINERAMA monitor index"); + return; + } + } + break; + + default: + g_warning ("gdk_x11_surface_apply_fullscreen_mode: Unhandled fullscreen mode %d", + window->fullscreen_mode); + return; + } + + /* Send fullscreen monitors client message */ + xclient.data.l[4] = 1; /* source indication */ + xclient.message_type = gdk_x11_get_xatom_by_name_for_display (GDK_SURFACE_DISPLAY (window), + "_NET_WM_FULLSCREEN_MONITORS"); + XSendEvent (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); + } +} + +static void +gdk_x11_surface_fullscreen (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + gdk_wmspec_change_state (TRUE, window, + g_intern_static_string ("_NET_WM_STATE_FULLSCREEN"), + NULL); + /* Actual XRandR layout may have change since we computed the fullscreen + * monitors in GDK_FULLSCREEN_ON_ALL_MONITORS mode. + */ + if (window->fullscreen_mode == GDK_FULLSCREEN_ON_ALL_MONITORS) + gdk_x11_surface_apply_fullscreen_mode (window); + } + else + gdk_synthesize_window_state (window, + 0, + GDK_SURFACE_STATE_FULLSCREEN); +} + +static void +gdk_x11_surface_fullscreen_on_monitor (GdkSurface *window, + GdkMonitor *monitor) +{ + GdkRectangle geom; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + gdk_monitor_get_geometry (monitor, &geom); + gdk_surface_move (window, geom.x, geom.y); + + gdk_surface_set_fullscreen_mode (window, GDK_FULLSCREEN_ON_CURRENT_MONITOR); + gdk_x11_surface_fullscreen (window); +} + +static void +gdk_x11_surface_unfullscreen (GdkSurface *window) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_FULLSCREEN"), + NULL); + + else + gdk_synthesize_window_state (window, + GDK_SURFACE_STATE_FULLSCREEN, + 0); +} + +static void +gdk_x11_surface_set_keep_above (GdkSurface *window, + gboolean setting) +{ + g_return_if_fail (GDK_IS_SURFACE (window)); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + if (setting) + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_BELOW"), + NULL); + gdk_wmspec_change_state (setting, window, + g_intern_static_string ("_NET_WM_STATE_ABOVE"), + NULL); + } + else + gdk_synthesize_window_state (window, + setting ? GDK_SURFACE_STATE_BELOW : GDK_SURFACE_STATE_ABOVE, + setting ? GDK_SURFACE_STATE_ABOVE : 0); +} + +static void +gdk_x11_surface_set_keep_below (GdkSurface *window, gboolean setting) +{ + g_return_if_fail (GDK_IS_SURFACE (window)); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + if (GDK_SURFACE_IS_MAPPED (window)) + { + if (setting) + gdk_wmspec_change_state (FALSE, window, + g_intern_static_string ("_NET_WM_STATE_ABOVE"), + NULL); + gdk_wmspec_change_state (setting, window, + g_intern_static_string ("_NET_WM_STATE_BELOW"), + NULL); + } + else + gdk_synthesize_window_state (window, + setting ? GDK_SURFACE_STATE_ABOVE : GDK_SURFACE_STATE_BELOW, + setting ? GDK_SURFACE_STATE_BELOW : 0); +} + +static GdkSurface * +gdk_x11_surface_get_group (GdkSurface *window) +{ + GdkToplevelX11 *toplevel; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL (window)) + return NULL; + + toplevel = _gdk_x11_surface_get_toplevel (window); + + return toplevel->group_leader; +} + +static void +gdk_x11_surface_set_group (GdkSurface *window, + GdkSurface *leader) +{ + GdkToplevelX11 *toplevel; + + g_return_if_fail (GDK_IS_SURFACE (window)); + g_return_if_fail (GDK_SURFACE_TYPE (window) != GDK_SURFACE_CHILD); + g_return_if_fail (leader == NULL || GDK_IS_SURFACE (leader)); + + if (GDK_SURFACE_DESTROYED (window) || + (leader != NULL && GDK_SURFACE_DESTROYED (leader)) || + !WINDOW_IS_TOPLEVEL (window)) + return; + + toplevel = _gdk_x11_surface_get_toplevel (window); + + if (leader == NULL) + leader = gdk_display_get_default_group (gdk_surface_get_display (window)); + + if (toplevel->group_leader != leader) + { + if (toplevel->group_leader) + g_object_unref (toplevel->group_leader); + toplevel->group_leader = g_object_ref (leader); + (_gdk_x11_surface_get_toplevel (leader))->is_leader = TRUE; + } + + update_wm_hints (window, FALSE); +} + +static MotifWmHints * +gdk_surface_get_mwm_hints (GdkSurface *window) +{ + GdkDisplay *display; + Atom hints_atom = None; + guchar *data; + Atom type; + gint format; + gulong nitems; + gulong bytes_after; + + if (GDK_SURFACE_DESTROYED (window)) + return NULL; + + display = gdk_surface_get_display (window); + + hints_atom = gdk_x11_get_xatom_by_name_for_display (display, _XA_MOTIF_WM_HINTS); + + XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XID (window), + hints_atom, 0, sizeof (MotifWmHints)/sizeof (long), + False, AnyPropertyType, &type, &format, &nitems, + &bytes_after, &data); + + if (type == None) + return NULL; + + return (MotifWmHints *)data; +} + +static void +gdk_surface_set_mwm_hints (GdkSurface *window, + MotifWmHints *new_hints) +{ + GdkDisplay *display; + Atom hints_atom = None; + guchar *data; + MotifWmHints *hints; + Atom type; + gint format; + gulong nitems; + gulong bytes_after; + + if (GDK_SURFACE_DESTROYED (window)) + return; + + display = gdk_surface_get_display (window); + + hints_atom = gdk_x11_get_xatom_by_name_for_display (display, _XA_MOTIF_WM_HINTS); + + XGetWindowProperty (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XID (window), + hints_atom, 0, sizeof (MotifWmHints)/sizeof (long), + False, AnyPropertyType, &type, &format, &nitems, + &bytes_after, &data); + + if (type == None) + hints = new_hints; + else + { + hints = (MotifWmHints *)data; + + if (new_hints->flags & MWM_HINTS_FUNCTIONS) + { + hints->flags |= MWM_HINTS_FUNCTIONS; + hints->functions = new_hints->functions; + } + if (new_hints->flags & MWM_HINTS_DECORATIONS) + { + hints->flags |= MWM_HINTS_DECORATIONS; + hints->decorations = new_hints->decorations; + } + } + + XChangeProperty (GDK_SURFACE_XDISPLAY (window), GDK_SURFACE_XID (window), + hints_atom, hints_atom, 32, PropModeReplace, + (guchar *)hints, sizeof (MotifWmHints)/sizeof (long)); + + if (hints != new_hints) + XFree (hints); +} + +static void +gdk_x11_surface_set_decorations (GdkSurface *window, + GdkWMDecoration decorations) +{ + MotifWmHints hints; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + /* initialize to zero to avoid writing uninitialized data to socket */ + memset(&hints, 0, sizeof(hints)); + hints.flags = MWM_HINTS_DECORATIONS; + hints.decorations = decorations; + + gdk_surface_set_mwm_hints (window, &hints); +} + +static gboolean +gdk_x11_surface_get_decorations(GdkSurface *window, + GdkWMDecoration *decorations) +{ + MotifWmHints *hints; + gboolean result = FALSE; + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return FALSE; + + hints = gdk_surface_get_mwm_hints (window); + + if (hints) + { + if (hints->flags & MWM_HINTS_DECORATIONS) + { + if (decorations) + *decorations = hints->decorations; + result = TRUE; + } + + XFree (hints); + } + + return result; +} + +static void +gdk_x11_surface_set_functions (GdkSurface *window, + GdkWMFunction functions) +{ + MotifWmHints hints; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + /* initialize to zero to avoid writing uninitialized data to socket */ + memset(&hints, 0, sizeof(hints)); + hints.flags = MWM_HINTS_FUNCTIONS; + hints.functions = functions; + + gdk_surface_set_mwm_hints (window, &hints); +} + +cairo_region_t * +_gdk_x11_xwindow_get_shape (Display *xdisplay, + Window window, + gint scale, + gint shape_type) +{ + cairo_region_t *shape; + GdkRectangle *rl; + XRectangle *xrl; + gint rn, ord, i; + + shape = NULL; + rn = 0; + + /* Note that XShapeGetRectangles returns NULL in two situations: + * - the server doesn't support the SHAPE extension + * - the shape is empty + * + * Since we can't discriminate these here, we always return + * an empty shape. It is the callers responsibility to check + * whether the server supports the SHAPE extensions beforehand. + */ + xrl = XShapeGetRectangles (xdisplay, window, shape_type, &rn, &ord); + + if (rn == 0) + return cairo_region_create (); /* Empty */ + + if (ord != YXBanded) + { + /* This really shouldn't happen with any xserver, as they + * generally convert regions to YXBanded internally + */ + g_warning ("non YXBanded shape masks not supported"); + XFree (xrl); + return NULL; + } + + /* NOTE: The scale divisions here may lose some precision if someone + else set the shape to be non-scale precision */ + rl = g_new (GdkRectangle, rn); + for (i = 0; i < rn; i++) + { + rl[i].x = xrl[i].x / scale; + rl[i].y = xrl[i].y / scale; + rl[i].width = xrl[i].width / scale; + rl[i].height = xrl[i].height / scale; + } + XFree (xrl); + + shape = cairo_region_create_rectangles (rl, rn); + g_free (rl); + + return shape; +} + +/* From the WM spec */ +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + +static void +wmspec_send_message (GdkDisplay *display, + GdkSurface *window, + gint root_x, + gint root_y, + gint action, + gint button) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + XClientMessageEvent xclient; + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.message_type = + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_MOVERESIZE"); + xclient.format = 32; + xclient.data.l[0] = root_x * impl->window_scale; + xclient.data.l[1] = root_y * impl->window_scale; + xclient.data.l[2] = action; + xclient.data.l[3] = button; + xclient.data.l[4] = 1; /* source indication */ + + XSendEvent (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); +} + +static void +handle_wmspec_button_release (GdkDisplay *display, + const XEvent *xevent) +{ + GdkX11Display *display_x11 = GDK_X11_DISPLAY (display); + GdkSurface *window; + +#if defined (HAVE_XGENERICEVENTS) && defined (XINPUT_2) + XIEvent *xiev = (XIEvent *) xevent->xcookie.data; + XIDeviceEvent *xidev = (XIDeviceEvent *) xiev; + + if (xevent->xany.type == GenericEvent) + window = gdk_x11_surface_lookup_for_display (display, xidev->event); + else +#endif + window = gdk_x11_surface_lookup_for_display (display, xevent->xany.window); + + if (display_x11->wm_moveresize_button != 0 && window != NULL) + { + if ((xevent->xany.type == ButtonRelease && + xevent->xbutton.button == display_x11->wm_moveresize_button) +#if defined (HAVE_XGENERICEVENTS) && defined (XINPUT_2) + || + (xevent->xany.type == GenericEvent && + xiev->evtype == XI_ButtonRelease && + xidev->detail == display_x11->wm_moveresize_button) +#endif + ) + { + display_x11->wm_moveresize_button = 0; + wmspec_send_message (display, window, 0, 0, _NET_WM_MOVERESIZE_CANCEL, 0); + } + } +} + +static void +wmspec_moveresize (GdkSurface *window, + gint direction, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + + if (button != 0) + gdk_seat_ungrab (gdk_device_get_seat (device)); /* Release passive grab */ + GDK_X11_DISPLAY (display)->wm_moveresize_button = button; + + wmspec_send_message (display, window, root_x, root_y, direction, button); +} + +static void +wmspec_resize_drag (GdkSurface *window, + GdkSurfaceEdge edge, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + gint direction; + + if (button == 0) + direction = _NET_WM_MOVERESIZE_SIZE_KEYBOARD; + else + switch (edge) + { + /* Let the compiler turn a switch into a table, instead + * of doing the table manually, this way is easier to verify. + */ + case GDK_SURFACE_EDGE_NORTH_WEST: + direction = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; + break; + + case GDK_SURFACE_EDGE_NORTH: + direction = _NET_WM_MOVERESIZE_SIZE_TOP; + break; + + case GDK_SURFACE_EDGE_NORTH_EAST: + direction = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + break; + + case GDK_SURFACE_EDGE_WEST: + direction = _NET_WM_MOVERESIZE_SIZE_LEFT; + break; + + case GDK_SURFACE_EDGE_EAST: + direction = _NET_WM_MOVERESIZE_SIZE_RIGHT; + break; + + case GDK_SURFACE_EDGE_SOUTH_WEST: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + break; + + case GDK_SURFACE_EDGE_SOUTH: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOM; + break; + + case GDK_SURFACE_EDGE_SOUTH_EAST: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + break; + + default: + g_warning ("gdk_surface_begin_resize_drag: bad resize edge %d!", + edge); + return; + } + + wmspec_moveresize (window, direction, device, button, root_x, root_y, timestamp); +} + +typedef struct _MoveResizeData MoveResizeData; + +struct _MoveResizeData +{ + GdkDisplay *display; + + GdkSurface *moveresize_window; + GdkSurface *moveresize_emulation_window; + gboolean is_resize; + GdkSurfaceEdge resize_edge; + GdkDevice *device; + gint moveresize_button; + gint moveresize_x; + gint moveresize_y; + gint moveresize_orig_x; + gint moveresize_orig_y; + gint moveresize_orig_width; + gint moveresize_orig_height; + GdkSurfaceHints moveresize_geom_mask; + GdkGeometry moveresize_geometry; + Time moveresize_process_time; + XEvent *moveresize_pending_event; +}; + +static MoveResizeData * +get_move_resize_data (GdkDisplay *display, + gboolean create) +{ + MoveResizeData *mv_resize; + static GQuark move_resize_quark = 0; + + if (!move_resize_quark) + move_resize_quark = g_quark_from_static_string ("gdk-window-moveresize"); + + mv_resize = g_object_get_qdata (G_OBJECT (display), move_resize_quark); + + if (!mv_resize && create) + { + mv_resize = g_new0 (MoveResizeData, 1); + mv_resize->display = display; + + g_object_set_qdata (G_OBJECT (display), move_resize_quark, mv_resize); + } + + return mv_resize; +} + +static void +check_maximize (MoveResizeData *mv_resize, + gdouble x_root, + gdouble y_root) +{ + GdkSurfaceState state; + gint y; + + if (mv_resize->is_resize) + return; + + state = gdk_surface_get_state (mv_resize->moveresize_window); + + if (state & GDK_SURFACE_STATE_MAXIMIZED) + return; + + y = mv_resize->moveresize_orig_y + (y_root - mv_resize->moveresize_y); + + if (y < 10) + gdk_surface_maximize (mv_resize->moveresize_window); +} + +static void +check_unmaximize (MoveResizeData *mv_resize, + gdouble x_root, + gdouble y_root) +{ + GdkSurfaceState state; + gint dx, dy; + + if (mv_resize->is_resize) + return; + + state = gdk_surface_get_state (mv_resize->moveresize_window); + + if ((state & (GDK_SURFACE_STATE_MAXIMIZED | GDK_SURFACE_STATE_TILED)) == 0) + return; + + dx = x_root - mv_resize->moveresize_x; + dy = y_root - mv_resize->moveresize_y; + + if (ABS (dx) > 20 || ABS (dy) > 20) + gdk_surface_unmaximize (mv_resize->moveresize_window); +} + +static void +update_pos (MoveResizeData *mv_resize, + gint new_root_x, + gint new_root_y) +{ + gint dx, dy; + + check_unmaximize (mv_resize, new_root_x, new_root_y); + dx = new_root_x - mv_resize->moveresize_x; + dy = new_root_y - mv_resize->moveresize_y; + + if (mv_resize->is_resize) + { + gint x, y, w, h; + + x = mv_resize->moveresize_orig_x; + y = mv_resize->moveresize_orig_y; + + w = mv_resize->moveresize_orig_width; + h = mv_resize->moveresize_orig_height; + + switch (mv_resize->resize_edge) + { + case GDK_SURFACE_EDGE_NORTH_WEST: + x += dx; + y += dy; + w -= dx; + h -= dy; + break; + case GDK_SURFACE_EDGE_NORTH: + y += dy; + h -= dy; + break; + case GDK_SURFACE_EDGE_NORTH_EAST: + y += dy; + h -= dy; + w += dx; + break; + case GDK_SURFACE_EDGE_SOUTH_WEST: + h += dy; + x += dx; + w -= dx; + break; + case GDK_SURFACE_EDGE_SOUTH_EAST: + w += dx; + h += dy; + break; + case GDK_SURFACE_EDGE_SOUTH: + h += dy; + break; + case GDK_SURFACE_EDGE_EAST: + w += dx; + break; + case GDK_SURFACE_EDGE_WEST: + x += dx; + w -= dx; + break; + default: + break; + } + + x = MAX (x, 0); + y = MAX (y, 0); + w = MAX (w, 1); + h = MAX (h, 1); + + if (mv_resize->moveresize_geom_mask) + { + gdk_surface_constrain_size (&mv_resize->moveresize_geometry, + mv_resize->moveresize_geom_mask, + w, h, &w, &h); + } + + gdk_surface_move_resize (mv_resize->moveresize_window, x, y, w, h); + } + else + { + gint x, y; + + x = mv_resize->moveresize_orig_x + dx; + y = mv_resize->moveresize_orig_y + dy; + + gdk_surface_move (mv_resize->moveresize_window, x, y); + } +} + +static void +finish_drag (MoveResizeData *mv_resize) +{ + gdk_surface_destroy (mv_resize->moveresize_emulation_window); + mv_resize->moveresize_emulation_window = NULL; + g_clear_object (&mv_resize->moveresize_window); + g_clear_pointer (&mv_resize->moveresize_pending_event, g_free); +} + +static int +lookahead_motion_predicate (Display *xdisplay, + XEvent *event, + XPointer arg) +{ + gboolean *seen_release = (gboolean *)arg; + GdkDisplay *display = gdk_x11_lookup_xdisplay (xdisplay); + MoveResizeData *mv_resize = get_move_resize_data (display, FALSE); + + if (*seen_release) + return False; + + switch (event->xany.type) + { + case ButtonRelease: + *seen_release = TRUE; + break; + case MotionNotify: + mv_resize->moveresize_process_time = event->xmotion.time; + break; + default: + break; + } + + return False; +} + +static gboolean +moveresize_lookahead (MoveResizeData *mv_resize, + const XEvent *event) +{ + XEvent tmp_event; + gboolean seen_release = FALSE; + + if (mv_resize->moveresize_process_time) + { + if (event->xmotion.time == mv_resize->moveresize_process_time) + { + mv_resize->moveresize_process_time = 0; + return TRUE; + } + else + return FALSE; + } + + XCheckIfEvent (event->xany.display, &tmp_event, + lookahead_motion_predicate, (XPointer) & seen_release); + + return mv_resize->moveresize_process_time == 0; +} + +gboolean +_gdk_x11_moveresize_handle_event (const XEvent *event) +{ + guint button_mask = 0; + GdkDisplay *display = gdk_x11_lookup_xdisplay (event->xany.display); + MoveResizeData *mv_resize = get_move_resize_data (display, FALSE); + GdkSurfaceImplX11 *impl; + + if (!mv_resize || !mv_resize->moveresize_window) + { + handle_wmspec_button_release (display, event); + return FALSE; + } + + impl = GDK_SURFACE_IMPL_X11 (mv_resize->moveresize_window->impl); + + if (mv_resize->moveresize_button != 0) + button_mask = GDK_BUTTON1_MASK << (mv_resize->moveresize_button - 1); + + switch (event->xany.type) + { + case MotionNotify: + if (mv_resize->moveresize_window->resize_count > 0) + { + if (mv_resize->moveresize_pending_event) + *mv_resize->moveresize_pending_event = *event; + else + mv_resize->moveresize_pending_event = + g_memdup (event, sizeof (XEvent)); + + break; + } + if (!moveresize_lookahead (mv_resize, event)) + break; + + update_pos (mv_resize, + event->xmotion.x_root / impl->window_scale, + event->xmotion.y_root / impl->window_scale); + + /* This should never be triggered in normal cases, but in the + * case where the drag started without an implicit grab being + * in effect, we could miss the release if it occurs before + * we grab the pointer; this ensures that we will never + * get a permanently stuck grab. + */ + if ((event->xmotion.state & button_mask) == 0) + { + check_maximize (mv_resize, + event->xmotion.x_root / impl->window_scale, + event->xmotion.y_root / impl->window_scale); + finish_drag (mv_resize); + } + break; + + case ButtonRelease: + update_pos (mv_resize, + event->xbutton.x_root / impl->window_scale, + event->xbutton.y_root / impl->window_scale); + + if (event->xbutton.button == mv_resize->moveresize_button) + { + check_maximize (mv_resize, + event->xmotion.x_root / impl->window_scale, + event->xmotion.y_root / impl->window_scale); + finish_drag (mv_resize); + } + break; + +#if defined (HAVE_XGENERICEVENTS) && defined (XINPUT_2) + case GenericEvent: + { + /* we just assume this is an XI2 event */ + XIEvent *ev = (XIEvent *) event->xcookie.data; + XIDeviceEvent *xev = (XIDeviceEvent *)ev; + gint state; + switch (ev->evtype) + { + case XI_Motion: + update_pos (mv_resize, xev->root_x / impl->window_scale, xev->root_y / impl->window_scale); + state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group); + if ((state & button_mask) == 0) + { + check_maximize (mv_resize, + xev->root_x / impl->window_scale, + xev->root_y / impl->window_scale); + finish_drag (mv_resize); + } + break; + + case XI_ButtonRelease: + update_pos (mv_resize, xev->root_x / impl->window_scale, xev->root_y / impl->window_scale); + if (xev->detail == mv_resize->moveresize_button) + { + check_maximize (mv_resize, + xev->root_x / impl->window_scale, + xev->root_y / impl->window_scale); + finish_drag (mv_resize); + } + break; + default: + break; + } + } + break; +#endif + + default: + break; + + } + return TRUE; +} + +gboolean +_gdk_x11_moveresize_configure_done (GdkDisplay *display, + GdkSurface *window) +{ + XEvent *tmp_event; + MoveResizeData *mv_resize = get_move_resize_data (display, FALSE); + + if (!mv_resize || window != mv_resize->moveresize_window) + return FALSE; + + if (mv_resize->moveresize_pending_event) + { + tmp_event = mv_resize->moveresize_pending_event; + mv_resize->moveresize_pending_event = NULL; + _gdk_x11_moveresize_handle_event (tmp_event); + g_free (tmp_event); + } + + return TRUE; +} + +static void +create_moveresize_window (MoveResizeData *mv_resize, + guint32 timestamp) +{ + GdkGrabStatus status; + + g_assert (mv_resize->moveresize_emulation_window == NULL); + + mv_resize->moveresize_emulation_window = gdk_surface_new_temp (mv_resize->display); + gdk_surface_show (mv_resize->moveresize_emulation_window); + + status = gdk_seat_grab (gdk_device_get_seat (mv_resize->device), + mv_resize->moveresize_emulation_window, + GDK_SEAT_CAPABILITY_POINTER, FALSE, + NULL, NULL, NULL, NULL); + + if (status != GDK_GRAB_SUCCESS) + { + /* If this fails, some other client has grabbed the window + * already. + */ + finish_drag (mv_resize); + } + + mv_resize->moveresize_process_time = 0; +} + +/* + Calculate mv_resize->moveresize_orig_x and mv_resize->moveresize_orig_y + so that calling XMoveWindow with these coordinates will not move the + window. + Note that this depends on the WM to implement ICCCM-compliant reference + point handling. +*/ +static void +calculate_unmoving_origin (MoveResizeData *mv_resize) +{ + GdkRectangle rect; + gint width, height; + + if (mv_resize->moveresize_geom_mask & GDK_HINT_WIN_GRAVITY && + mv_resize->moveresize_geometry.win_gravity == GDK_GRAVITY_STATIC) + { + gdk_surface_get_origin (mv_resize->moveresize_window, + &mv_resize->moveresize_orig_x, + &mv_resize->moveresize_orig_y); + } + else + { + gdk_surface_get_frame_extents (mv_resize->moveresize_window, &rect); + gdk_surface_get_geometry (mv_resize->moveresize_window, + NULL, NULL, &width, &height); + + switch (mv_resize->moveresize_geometry.win_gravity) + { + case GDK_GRAVITY_NORTH_WEST: + mv_resize->moveresize_orig_x = rect.x; + mv_resize->moveresize_orig_y = rect.y; + break; + case GDK_GRAVITY_NORTH: + mv_resize->moveresize_orig_x = rect.x + rect.width / 2 - width / 2; + mv_resize->moveresize_orig_y = rect.y; + break; + case GDK_GRAVITY_NORTH_EAST: + mv_resize->moveresize_orig_x = rect.x + rect.width - width; + mv_resize->moveresize_orig_y = rect.y; + break; + case GDK_GRAVITY_WEST: + mv_resize->moveresize_orig_x = rect.x; + mv_resize->moveresize_orig_y = rect.y + rect.height / 2 - height / 2; + break; + case GDK_GRAVITY_CENTER: + mv_resize->moveresize_orig_x = rect.x + rect.width / 2 - width / 2; + mv_resize->moveresize_orig_y = rect.y + rect.height / 2 - height / 2; + break; + case GDK_GRAVITY_EAST: + mv_resize->moveresize_orig_x = rect.x + rect.width - width; + mv_resize->moveresize_orig_y = rect.y + rect.height / 2 - height / 2; + break; + case GDK_GRAVITY_SOUTH_WEST: + mv_resize->moveresize_orig_x = rect.x; + mv_resize->moveresize_orig_y = rect.y + rect.height - height; + break; + case GDK_GRAVITY_SOUTH: + mv_resize->moveresize_orig_x = rect.x + rect.width / 2 - width / 2; + mv_resize->moveresize_orig_y = rect.y + rect.height - height; + break; + case GDK_GRAVITY_SOUTH_EAST: + mv_resize->moveresize_orig_x = rect.x + rect.width - width; + mv_resize->moveresize_orig_y = rect.y + rect.height - height; + break; + case GDK_GRAVITY_STATIC: + default: + mv_resize->moveresize_orig_x = rect.x; + mv_resize->moveresize_orig_y = rect.y; + break; + } + } +} + +static void +emulate_resize_drag (GdkSurface *window, + GdkSurfaceEdge edge, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + MoveResizeData *mv_resize = get_move_resize_data (GDK_SURFACE_DISPLAY (window), TRUE); + + if (mv_resize->moveresize_window != NULL) + return; /* already a drag operation in progress */ + + mv_resize->is_resize = TRUE; + mv_resize->moveresize_button = button; + mv_resize->resize_edge = edge; + mv_resize->device = device; + mv_resize->moveresize_x = root_x; + mv_resize->moveresize_y = root_y; + mv_resize->moveresize_window = g_object_ref (window); + + mv_resize->moveresize_orig_width = gdk_surface_get_width (window); + mv_resize->moveresize_orig_height = gdk_surface_get_height (window); + + mv_resize->moveresize_geom_mask = 0; + gdk_surface_get_geometry_hints (window, + &mv_resize->moveresize_geometry, + &mv_resize->moveresize_geom_mask); + + calculate_unmoving_origin (mv_resize); + + create_moveresize_window (mv_resize, timestamp); +} + +static void +emulate_move_drag (GdkSurface *window, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + MoveResizeData *mv_resize = get_move_resize_data (GDK_SURFACE_DISPLAY (window), TRUE); + + if (mv_resize->moveresize_window != NULL) + return; /* already a drag operation in progress */ + + mv_resize->is_resize = FALSE; + mv_resize->device = device; + mv_resize->moveresize_button = button; + mv_resize->moveresize_x = root_x; + mv_resize->moveresize_y = root_y; + + mv_resize->moveresize_window = g_object_ref (window); + + calculate_unmoving_origin (mv_resize); + + create_moveresize_window (mv_resize, timestamp); +} + +static gboolean +_should_perform_ewmh_drag (GdkSurface *window, + GdkDevice *device) +{ + GdkPointerSurfaceInfo *info; + GdkDisplay *display; + + display = gdk_surface_get_display (window); + info = _gdk_display_get_pointer_info (display, device); + + if ((!info->last_slave || gdk_device_get_source (info->last_slave) != GDK_SOURCE_TOUCHSCREEN) && + gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_NET_WM_MOVERESIZE"))) + return TRUE; + + return FALSE; +} + +static void +gdk_x11_surface_begin_resize_drag (GdkSurface *window, + GdkSurfaceEdge edge, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window)) + return; + + /* Avoid EWMH for touch devices */ + if (_should_perform_ewmh_drag (window, device)) + wmspec_resize_drag (window, edge, device, button, root_x, root_y, timestamp); + else + emulate_resize_drag (window, edge, device, button, root_x, root_y, timestamp); +} + +static void +gdk_x11_surface_begin_move_drag (GdkSurface *window, + GdkDevice *device, + gint button, + gint root_x, + gint root_y, + guint32 timestamp) +{ + gint direction; + + if (GDK_SURFACE_DESTROYED (window) || !WINDOW_IS_TOPLEVEL (window)) + return; + + if (button == 0) + direction = _NET_WM_MOVERESIZE_MOVE_KEYBOARD; + else + direction = _NET_WM_MOVERESIZE_MOVE; + + /* Avoid EWMH for touch devices */ + if (_should_perform_ewmh_drag (window, device)) + wmspec_moveresize (window, direction, device, button, root_x, root_y, timestamp); + else + emulate_move_drag (window, device, button, root_x, root_y, timestamp); +} + +static gboolean +gdk_x11_surface_beep (GdkSurface *window) +{ + GdkDisplay *display; + + display = GDK_SURFACE_DISPLAY (window); + +#ifdef HAVE_XKB + if (GDK_X11_DISPLAY (display)->use_xkb) + { + XkbBell (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + 0, + None); + return TRUE; + } +#endif + + return FALSE; +} + +static void +gdk_x11_surface_set_opacity (GdkSurface *window, + gdouble opacity) +{ + GdkDisplay *display; + gulong cardinal; + + g_return_if_fail (GDK_IS_SURFACE (window)); + + if (GDK_SURFACE_DESTROYED (window) || + !WINDOW_IS_TOPLEVEL (window)) + return; + + display = gdk_surface_get_display (window); + + if (opacity < 0) + opacity = 0; + else if (opacity > 1) + opacity = 1; + + cardinal = opacity * 0xffffffff; + + if (cardinal == 0xffffffff) + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_OPACITY")); + else + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_OPACITY"), + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &cardinal, 1); +} + +static Bool +timestamp_predicate (Display *display, + XEvent *xevent, + XPointer arg) +{ + Window xwindow = GPOINTER_TO_UINT (arg); + GdkDisplay *gdk_display = gdk_x11_lookup_xdisplay (display); + + if (xevent->type == PropertyNotify && + xevent->xproperty.window == xwindow && + xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (gdk_display, + "GDK_TIMESTAMP_PROP")) + return True; + + return False; +} + +/** + * gdk_x11_get_server_time: + * @window: (type GdkX11Surface): a #GdkSurface, used for communication + * with the server. The window must have + * GDK_PROPERTY_CHANGE_MASK in its events mask or a hang will + * result. + * + * Routine to get the current X server time stamp. + * + * Returns: the time stamp. + **/ +guint32 +gdk_x11_get_server_time (GdkSurface *window) +{ + Display *xdisplay; + Window xwindow; + guchar c = 'a'; + XEvent xevent; + Atom timestamp_prop_atom; + + g_return_val_if_fail (GDK_IS_SURFACE (window), 0); + g_return_val_if_fail (!GDK_SURFACE_DESTROYED (window), 0); + + xdisplay = GDK_SURFACE_XDISPLAY (window); + xwindow = GDK_SURFACE_XID (window); + timestamp_prop_atom = + gdk_x11_get_xatom_by_name_for_display (GDK_SURFACE_DISPLAY (window), + "GDK_TIMESTAMP_PROP"); + + XChangeProperty (xdisplay, xwindow, timestamp_prop_atom, + timestamp_prop_atom, + 8, PropModeReplace, &c, 1); + + XIfEvent (xdisplay, &xevent, + timestamp_predicate, GUINT_TO_POINTER(xwindow)); + + return xevent.xproperty.time; +} + +/** + * gdk_x11_surface_get_xid: + * @window: (type GdkX11Surface): a native #GdkSurface. + * + * Returns the X resource (window) belonging to a #GdkSurface. + * + * Returns: the ID of @drawable’s X resource. + **/ +XID +gdk_x11_surface_get_xid (GdkSurface *window) +{ + if (!GDK_SURFACE_IS_X11 (window) || + !_gdk_surface_has_impl (window)) + { + g_warning (G_STRLOC " drawable is not a native X11 window"); + return None; + } + + return GDK_SURFACE_IMPL_X11 (window->impl)->xid; +} + +static gint +gdk_x11_surface_get_scale_factor (GdkSurface *window) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + + if (GDK_SURFACE_DESTROYED (window)) + return 1; + + return impl->window_scale; +} + +/** + * gdk_x11_surface_set_frame_sync_enabled: + * @window: (type GdkX11Surface): a native #GdkSurface + * @frame_sync_enabled: whether frame-synchronization should be enabled + * + * This function can be used to disable frame synchronization for a window. + * Normally frame synchronziation will be enabled or disabled based on whether + * the system has a compositor that supports frame synchronization, but if + * the window is not directly managed by the window manager, then frame + * synchronziation may need to be disabled. This is the case for a window + * embedded via the XEMBED protocol. + */ +void +gdk_x11_surface_set_frame_sync_enabled (GdkSurface *window, + gboolean frame_sync_enabled) +{ + if (!GDK_SURFACE_IS_X11 (window) || + !_gdk_surface_has_impl (window)) + { + g_warning (G_STRLOC " drawable is not a native X11 window"); + return; + } + + GDK_SURFACE_IMPL_X11 (window->impl)->frame_sync_enabled = FALSE; +} + +static void +gdk_x11_surface_set_opaque_region (GdkSurface *window, + cairo_region_t *region) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + GdkDisplay *display; + int nitems; + gulong *data; + + if (GDK_SURFACE_DESTROYED (window)) + return; + + if (region != NULL) + { + int i, nrects; + + nrects = cairo_region_num_rectangles (region); + nitems = nrects * 4; + data = g_new (gulong, nitems); + + for (i = 0; i < nrects; i++) + { + cairo_rectangle_int_t rect; + cairo_region_get_rectangle (region, i, &rect); + data[i*4+0] = rect.x * impl->window_scale; + data[i*4+1] = rect.y * impl->window_scale; + data[i*4+2] = rect.width * impl->window_scale; + data[i*4+3] = rect.height * impl->window_scale; + } + } + else + { + nitems = 0; + data = NULL; + } + + display = gdk_surface_get_display (window); + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_SURFACE_XID (window), + gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_OPAQUE_REGION"), + XA_CARDINAL, 32, PropModeReplace, + (guchar *) data, nitems); + + g_free (data); +} + +static gboolean +gdk_x11_surface_show_window_menu (GdkSurface *window, + GdkEvent *event) +{ + GdkSurfaceImplX11 *impl = GDK_SURFACE_IMPL_X11 (window->impl); + GdkDisplay *display = GDK_SURFACE_DISPLAY (window); + GdkDevice *device; + int device_id; + double x_root, y_root; + XClientMessageEvent xclient = { 0 }; + + switch ((guint) event->any.type) + { + case GDK_BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + break; + default: + return FALSE; + } + + if (!gdk_x11_screen_supports_net_wm_hint (GDK_SURFACE_SCREEN (window), + g_intern_static_string ("_GTK_SHOW_WINDOW_MENU"))) + return FALSE; + + gdk_event_get_root_coords (event, &x_root, &y_root); + device = gdk_event_get_device (event); + g_object_get (G_OBJECT (device), + "device-id", &device_id, + NULL); + + /* Ungrab the implicit grab */ + gdk_seat_ungrab (gdk_device_get_seat (device)); + + xclient.type = ClientMessage; + xclient.window = GDK_SURFACE_XID (window); + xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "_GTK_SHOW_WINDOW_MENU"); + xclient.data.l[0] = device_id; + xclient.data.l[1] = x_root * impl->window_scale; + xclient.data.l[2] = y_root * impl->window_scale; + xclient.format = 32; + + XSendEvent (GDK_DISPLAY_XDISPLAY (display), GDK_SURFACE_XROOTWIN (window), False, + SubstructureRedirectMask | SubstructureNotifyMask, + (XEvent *)&xclient); + + return TRUE; +} + +static void +gdk_surface_impl_x11_class_init (GdkSurfaceImplX11Class *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdkSurfaceImplClass *impl_class = GDK_SURFACE_IMPL_CLASS (klass); + + object_class->finalize = gdk_surface_impl_x11_finalize; + + impl_class->ref_cairo_surface = gdk_x11_ref_cairo_surface; + impl_class->show = gdk_surface_x11_show; + impl_class->hide = gdk_surface_x11_hide; + impl_class->withdraw = gdk_surface_x11_withdraw; + impl_class->set_events = gdk_surface_x11_set_events; + impl_class->get_events = gdk_surface_x11_get_events; + impl_class->raise = gdk_surface_x11_raise; + impl_class->lower = gdk_surface_x11_lower; + impl_class->restack_toplevel = gdk_surface_x11_restack_toplevel; + impl_class->move_resize = gdk_surface_x11_move_resize; + impl_class->get_geometry = gdk_surface_x11_get_geometry; + impl_class->get_root_coords = gdk_surface_x11_get_root_coords; + impl_class->get_device_state = gdk_surface_x11_get_device_state; + impl_class->shape_combine_region = gdk_surface_x11_shape_combine_region; + impl_class->input_shape_combine_region = gdk_surface_x11_input_shape_combine_region; + impl_class->queue_antiexpose = _gdk_x11_surface_queue_antiexpose; + impl_class->destroy = gdk_x11_surface_destroy; + impl_class->beep = gdk_x11_surface_beep; + + impl_class->focus = gdk_x11_surface_focus; + impl_class->set_type_hint = gdk_x11_surface_set_type_hint; + impl_class->get_type_hint = gdk_x11_surface_get_type_hint; + impl_class->set_modal_hint = gdk_x11_surface_set_modal_hint; + impl_class->set_skip_taskbar_hint = gdk_x11_surface_set_skip_taskbar_hint; + impl_class->set_skip_pager_hint = gdk_x11_surface_set_skip_pager_hint; + impl_class->set_urgency_hint = gdk_x11_surface_set_urgency_hint; + impl_class->set_geometry_hints = gdk_x11_surface_set_geometry_hints; + impl_class->set_title = gdk_x11_surface_set_title; + impl_class->set_role = gdk_x11_surface_set_role; + impl_class->set_startup_id = gdk_x11_surface_set_startup_id; + impl_class->set_transient_for = gdk_x11_surface_set_transient_for; + impl_class->get_frame_extents = gdk_x11_surface_get_frame_extents; + impl_class->set_accept_focus = gdk_x11_surface_set_accept_focus; + impl_class->set_focus_on_map = gdk_x11_surface_set_focus_on_map; + impl_class->set_icon_list = gdk_x11_surface_set_icon_list; + impl_class->set_icon_name = gdk_x11_surface_set_icon_name; + impl_class->iconify = gdk_x11_surface_iconify; + impl_class->deiconify = gdk_x11_surface_deiconify; + impl_class->stick = gdk_x11_surface_stick; + impl_class->unstick = gdk_x11_surface_unstick; + impl_class->maximize = gdk_x11_surface_maximize; + impl_class->unmaximize = gdk_x11_surface_unmaximize; + impl_class->fullscreen = gdk_x11_surface_fullscreen; + impl_class->fullscreen_on_monitor = gdk_x11_surface_fullscreen_on_monitor; + impl_class->apply_fullscreen_mode = gdk_x11_surface_apply_fullscreen_mode; + impl_class->unfullscreen = gdk_x11_surface_unfullscreen; + impl_class->set_keep_above = gdk_x11_surface_set_keep_above; + impl_class->set_keep_below = gdk_x11_surface_set_keep_below; + impl_class->get_group = gdk_x11_surface_get_group; + impl_class->set_group = gdk_x11_surface_set_group; + impl_class->set_decorations = gdk_x11_surface_set_decorations; + impl_class->get_decorations = gdk_x11_surface_get_decorations; + impl_class->set_functions = gdk_x11_surface_set_functions; + impl_class->begin_resize_drag = gdk_x11_surface_begin_resize_drag; + impl_class->begin_move_drag = gdk_x11_surface_begin_move_drag; + impl_class->set_opacity = gdk_x11_surface_set_opacity; + impl_class->destroy_notify = gdk_x11_surface_destroy_notify; + impl_class->register_dnd = _gdk_x11_surface_register_dnd; + impl_class->drag_begin = _gdk_x11_surface_drag_begin; + impl_class->get_scale_factor = gdk_x11_surface_get_scale_factor; + impl_class->set_opaque_region = gdk_x11_surface_set_opaque_region; + impl_class->set_shadow_width = gdk_x11_surface_set_shadow_width; + impl_class->show_window_menu = gdk_x11_surface_show_window_menu; + impl_class->create_gl_context = gdk_x11_surface_create_gl_context; + impl_class->get_unscaled_size = gdk_x11_surface_get_unscaled_size; + impl_class->supports_edge_constraints = gdk_x11_surface_supports_edge_constraints; +} |