mirror of https://github.com/GNOME/gimp.git
1386 lines
43 KiB
C
1386 lines
43 KiB
C
/* LIBGIMP - The GIMP Library
|
|
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
|
*
|
|
* gimpwidgets.c
|
|
* Copyright (C) 2000 Michael Natterer <mitch@gimp.org>
|
|
*
|
|
* 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 3 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
|
|
* Library 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
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <lcms2.h>
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#ifdef GDK_WINDOWING_WIN32
|
|
#include <gdk/gdkwin32.h>
|
|
#endif
|
|
#ifdef GDK_WINDOWING_X11
|
|
#include <gdk/gdkx.h>
|
|
#endif
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
#include <gdk/gdkwayland.h>
|
|
#endif
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include <windows.h>
|
|
#include <icm.h>
|
|
#endif
|
|
|
|
#ifdef GDK_WINDOWING_QUARTZ
|
|
#include <Carbon/Carbon.h>
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
#include <CoreServices/CoreServices.h>
|
|
#endif
|
|
#ifdef PLATFORM_OSX
|
|
#import <Cocoa/Cocoa.h>
|
|
#endif
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
|
#include "libgimpconfig/gimpconfig.h"
|
|
|
|
#include "gimpwidgetstypes.h"
|
|
|
|
#include "gimpsizeentry.h"
|
|
#include "gimpwidgetsutils.h"
|
|
|
|
#include "libgimp/libgimp-intl.h"
|
|
|
|
|
|
/**
|
|
* SECTION: gimpwidgetsutils
|
|
* @title: GimpWidgetsUtils
|
|
* @short_description: A collection of helper functions.
|
|
*
|
|
* A collection of helper functions.
|
|
**/
|
|
|
|
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
static void gimp_widget_wayland_window_exported (GdkWindow *window,
|
|
const char *handle,
|
|
GBytes **phandle);
|
|
#endif
|
|
|
|
static void gimp_widget_set_handle_on_realize (GtkWidget *widget,
|
|
GBytes **phandle);
|
|
static gboolean gimp_widget_set_handle_on_mapped (GtkWidget *widget,
|
|
GdkEventAny *event,
|
|
GBytes **phandle);
|
|
|
|
|
|
static GtkWidget *
|
|
find_mnemonic_widget (GtkWidget *widget,
|
|
gint level)
|
|
{
|
|
gboolean can_focus;
|
|
|
|
g_object_get (widget, "can-focus", &can_focus, NULL);
|
|
|
|
if (GTK_WIDGET_GET_CLASS (widget)->activate_signal ||
|
|
can_focus ||
|
|
GTK_WIDGET_GET_CLASS (widget)->mnemonic_activate !=
|
|
GTK_WIDGET_CLASS (g_type_class_peek (GTK_TYPE_WIDGET))->mnemonic_activate)
|
|
{
|
|
return widget;
|
|
}
|
|
|
|
if (GIMP_IS_SIZE_ENTRY (widget))
|
|
{
|
|
GimpSizeEntry *entry = GIMP_SIZE_ENTRY (widget);
|
|
gint n_fields = gimp_size_entry_get_n_fields (entry);
|
|
|
|
return gimp_size_entry_get_help_widget (entry, n_fields - 1);
|
|
}
|
|
else if (GTK_IS_CONTAINER (widget))
|
|
{
|
|
GtkWidget *mnemonic_widget = NULL;
|
|
GList *children;
|
|
GList *list;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (widget));
|
|
|
|
for (list = children; list; list = g_list_next (list))
|
|
{
|
|
mnemonic_widget = find_mnemonic_widget (list->data, level + 1);
|
|
|
|
if (mnemonic_widget)
|
|
break;
|
|
}
|
|
|
|
g_list_free (children);
|
|
|
|
return mnemonic_widget;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gimp_event_triggers_context_menu:
|
|
* @event: The #GdkEvent to verify.
|
|
* @on_release: Whether a menu is triggered on a button release event
|
|
* instead of a press event.
|
|
*
|
|
* Alternative of gdk_event_triggers_context_menu() with the additional
|
|
* feature of allowing a menu triggering to happen on a button release
|
|
* event. All the other rules on whether @event should trigger a
|
|
* contextual menu are exactly the same. Only the swapping to release
|
|
* state as additional feature is different.
|
|
*
|
|
* Returns: %TRUE if the event should trigger a context menu.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
gboolean
|
|
gimp_event_triggers_context_menu (const GdkEvent *event,
|
|
gboolean on_release)
|
|
{
|
|
GdkEvent *copy_event;
|
|
gboolean trigger = FALSE;
|
|
|
|
g_return_val_if_fail (event != NULL, FALSE);
|
|
|
|
copy_event = gdk_event_copy (event);
|
|
|
|
/* It's an ugly trick because GDK only considers press events as
|
|
* menu-triggering events. We want to use the same per-platform
|
|
* conventions as set in GTK/GDK, except for this small point.
|
|
* So when we want a menu-triggering on release events, we just
|
|
* temporary simulate the event to be a PRESS event and pass it to
|
|
* the usual function.
|
|
*/
|
|
if (on_release)
|
|
{
|
|
if (event->type == GDK_BUTTON_RELEASE)
|
|
copy_event->type = GDK_BUTTON_PRESS;
|
|
else if (event->type == GDK_BUTTON_PRESS)
|
|
copy_event->type = GDK_BUTTON_RELEASE;
|
|
}
|
|
trigger = gdk_event_triggers_context_menu (copy_event);
|
|
|
|
gdk_event_free (copy_event);
|
|
|
|
return trigger;
|
|
}
|
|
|
|
/**
|
|
* gimp_grid_attach_aligned:
|
|
* @grid: The #GtkGrid the widgets will be attached to.
|
|
* @left: The column to start with.
|
|
* @top: The row to attach the widgets.
|
|
* @label_text: The text for the #GtkLabel which will be attached left of
|
|
* the widget.
|
|
* @label_xalign: The horizontal alignment of the #GtkLabel.
|
|
* @label_yalign: The vertical alignment of the #GtkLabel.
|
|
* @widget: The #GtkWidget to attach right of the label.
|
|
* @widget_columns: The number of columns the widget will use.
|
|
*
|
|
* Note that the @label_text can be %NULL and that the widget will be
|
|
* attached starting at (@column + 1) in this case, too.
|
|
*
|
|
* Returns: (transfer none): The created #GtkLabel.
|
|
**/
|
|
GtkWidget *
|
|
gimp_grid_attach_aligned (GtkGrid *grid,
|
|
gint left,
|
|
gint top,
|
|
const gchar *label_text,
|
|
gfloat label_xalign,
|
|
gfloat label_yalign,
|
|
GtkWidget *widget,
|
|
gint widget_columns)
|
|
{
|
|
GtkWidget *label = NULL;
|
|
|
|
if (label_text)
|
|
{
|
|
GtkWidget *mnemonic_widget;
|
|
|
|
label = gtk_label_new_with_mnemonic (label_text);
|
|
gtk_label_set_xalign (GTK_LABEL (label), label_xalign);
|
|
gtk_label_set_yalign (GTK_LABEL (label), label_yalign);
|
|
gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
|
|
gtk_grid_attach (grid, label, left, top, 1, 1);
|
|
gtk_widget_show (label);
|
|
|
|
mnemonic_widget = find_mnemonic_widget (widget, 0);
|
|
|
|
if (mnemonic_widget)
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (label), mnemonic_widget);
|
|
}
|
|
|
|
gtk_widget_set_hexpand (widget, TRUE);
|
|
gtk_grid_attach (grid, widget, left + 1, top, widget_columns, 1);
|
|
gtk_widget_show (widget);
|
|
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* gimp_label_set_attributes: (skip)
|
|
* @label: a #GtkLabel
|
|
* @...: a list of PangoAttrType and value pairs terminated by -1.
|
|
*
|
|
* Sets Pango attributes on a #GtkLabel in a more convenient way than
|
|
* gtk_label_set_attributes().
|
|
*
|
|
* This function is useful if you want to change the font attributes
|
|
* of a #GtkLabel. This is an alternative to using PangoMarkup which
|
|
* is slow to parse and awkward to handle in an i18n-friendly way.
|
|
*
|
|
* The attributes are set on the complete label, from start to end. If
|
|
* you need to set attributes on part of the label, you will have to
|
|
* use the PangoAttributes API directly.
|
|
*
|
|
* Since: 2.2
|
|
**/
|
|
void
|
|
gimp_label_set_attributes (GtkLabel *label,
|
|
...)
|
|
{
|
|
PangoAttribute *attr = NULL;
|
|
PangoAttrList *attrs;
|
|
va_list args;
|
|
|
|
g_return_if_fail (GTK_IS_LABEL (label));
|
|
|
|
attrs = pango_attr_list_new ();
|
|
|
|
va_start (args, label);
|
|
|
|
do
|
|
{
|
|
PangoAttrType attr_type = va_arg (args, PangoAttrType);
|
|
|
|
if (attr_type == -1)
|
|
attr_type = PANGO_ATTR_INVALID;
|
|
|
|
switch (attr_type)
|
|
{
|
|
case PANGO_ATTR_LANGUAGE:
|
|
attr = pango_attr_language_new (va_arg (args, PangoLanguage *));
|
|
break;
|
|
|
|
case PANGO_ATTR_FAMILY:
|
|
attr = pango_attr_family_new (va_arg (args, const gchar *));
|
|
break;
|
|
|
|
case PANGO_ATTR_STYLE:
|
|
attr = pango_attr_style_new (va_arg (args, PangoStyle));
|
|
break;
|
|
|
|
case PANGO_ATTR_WEIGHT:
|
|
attr = pango_attr_weight_new (va_arg (args, PangoWeight));
|
|
break;
|
|
|
|
case PANGO_ATTR_VARIANT:
|
|
attr = pango_attr_variant_new (va_arg (args, PangoVariant));
|
|
break;
|
|
|
|
case PANGO_ATTR_STRETCH:
|
|
attr = pango_attr_stretch_new (va_arg (args, PangoStretch));
|
|
break;
|
|
|
|
case PANGO_ATTR_SIZE:
|
|
attr = pango_attr_size_new (va_arg (args, gint));
|
|
break;
|
|
|
|
case PANGO_ATTR_FONT_DESC:
|
|
attr = pango_attr_font_desc_new (va_arg (args,
|
|
const PangoFontDescription *));
|
|
break;
|
|
|
|
case PANGO_ATTR_FOREGROUND:
|
|
{
|
|
const PangoColor *color = va_arg (args, const PangoColor *);
|
|
|
|
attr = pango_attr_foreground_new (color->red,
|
|
color->green,
|
|
color->blue);
|
|
}
|
|
break;
|
|
|
|
case PANGO_ATTR_BACKGROUND:
|
|
{
|
|
const PangoColor *color = va_arg (args, const PangoColor *);
|
|
|
|
attr = pango_attr_background_new (color->red,
|
|
color->green,
|
|
color->blue);
|
|
}
|
|
break;
|
|
|
|
case PANGO_ATTR_UNDERLINE:
|
|
attr = pango_attr_underline_new (va_arg (args, PangoUnderline));
|
|
break;
|
|
|
|
case PANGO_ATTR_STRIKETHROUGH:
|
|
attr = pango_attr_strikethrough_new (va_arg (args, gboolean));
|
|
break;
|
|
|
|
case PANGO_ATTR_RISE:
|
|
attr = pango_attr_rise_new (va_arg (args, gint));
|
|
break;
|
|
|
|
case PANGO_ATTR_SCALE:
|
|
attr = pango_attr_scale_new (va_arg (args, gdouble));
|
|
break;
|
|
|
|
default:
|
|
g_warning ("%s: invalid PangoAttribute type %d",
|
|
G_STRFUNC, attr_type);
|
|
case PANGO_ATTR_INVALID:
|
|
attr = NULL;
|
|
break;
|
|
}
|
|
|
|
if (attr)
|
|
{
|
|
attr->start_index = 0;
|
|
attr->end_index = -1;
|
|
pango_attr_list_insert (attrs, attr);
|
|
}
|
|
}
|
|
while (attr);
|
|
|
|
va_end (args);
|
|
|
|
gtk_label_set_attributes (label, attrs);
|
|
pango_attr_list_unref (attrs);
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_get_monitor:
|
|
* @widget: a #GtkWidget.
|
|
*
|
|
* Returns: (transfer none): the #GdkMonitor where @widget is current displayed on.
|
|
*/
|
|
GdkMonitor *
|
|
gimp_widget_get_monitor (GtkWidget *widget)
|
|
{
|
|
GdkWindow *window;
|
|
GtkAllocation allocation;
|
|
gint x, y;
|
|
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
|
|
|
|
window = gtk_widget_get_window (widget);
|
|
|
|
if (! window)
|
|
return gimp_get_monitor_at_pointer ();
|
|
|
|
gdk_window_get_origin (window, &x, &y);
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
if (! gtk_widget_get_has_window (widget))
|
|
{
|
|
x += allocation.x;
|
|
y += allocation.y;
|
|
}
|
|
|
|
x += allocation.width / 2;
|
|
y += allocation.height / 2;
|
|
|
|
return gdk_display_get_monitor_at_point (gdk_display_get_default (), x, y);
|
|
}
|
|
|
|
/**
|
|
* gimp_get_monitor_at_pointer:
|
|
*
|
|
* Returns: (transfer none): the #GdkMonitor where the pointer is.
|
|
*/
|
|
GdkMonitor *
|
|
gimp_get_monitor_at_pointer (void)
|
|
{
|
|
GdkDisplay *display;
|
|
GdkSeat *seat;
|
|
gint x, y;
|
|
|
|
display = gdk_display_get_default ();
|
|
seat = gdk_display_get_default_seat (display);
|
|
|
|
gdk_device_get_position (gdk_seat_get_pointer (seat),
|
|
NULL, &x, &y);
|
|
|
|
return gdk_display_get_monitor_at_point (display, x, y);
|
|
}
|
|
|
|
typedef void (* MonitorChangedCallback) (GtkWidget *, gpointer);
|
|
|
|
typedef struct
|
|
{
|
|
GtkWidget *widget;
|
|
GdkMonitor *monitor;
|
|
|
|
MonitorChangedCallback callback;
|
|
gpointer user_data;
|
|
} TrackMonitorData;
|
|
|
|
static gboolean
|
|
track_monitor_configure_event (GtkWidget *toplevel,
|
|
GdkEvent *event,
|
|
TrackMonitorData *track_data)
|
|
{
|
|
GdkMonitor *monitor = gimp_widget_get_monitor (toplevel);
|
|
|
|
if (monitor != track_data->monitor)
|
|
{
|
|
track_data->monitor = monitor;
|
|
|
|
track_data->callback (track_data->widget, track_data->user_data);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
track_monitor_hierarchy_changed (GtkWidget *widget,
|
|
GtkWidget *previous_toplevel,
|
|
TrackMonitorData *track_data)
|
|
{
|
|
GtkWidget *toplevel;
|
|
|
|
if (previous_toplevel)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (previous_toplevel,
|
|
track_monitor_configure_event,
|
|
track_data);
|
|
}
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
if (GTK_IS_WINDOW (toplevel))
|
|
{
|
|
GClosure *closure;
|
|
GdkMonitor *monitor;
|
|
|
|
closure = g_cclosure_new (G_CALLBACK (track_monitor_configure_event),
|
|
track_data, NULL);
|
|
g_object_watch_closure (G_OBJECT (widget), closure);
|
|
g_signal_connect_closure (toplevel, "configure-event", closure, FALSE);
|
|
|
|
monitor = gimp_widget_get_monitor (toplevel);
|
|
|
|
if (monitor != track_data->monitor)
|
|
{
|
|
track_data->monitor = monitor;
|
|
|
|
track_data->callback (track_data->widget, track_data->user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_track_monitor:
|
|
* @widget: a #GtkWidget
|
|
* @monitor_changed_callback: the callback when @widget's monitor changes
|
|
* @user_data: data passed to @monitor_changed_callback
|
|
* @user_data_destroy: destroy function for @user_data.
|
|
*
|
|
* This function behaves as if #GtkWidget had a signal
|
|
*
|
|
* GtkWidget::monitor_changed(GtkWidget *widget, gpointer user_data)
|
|
*
|
|
* That is emitted whenever @widget's toplevel window is moved from
|
|
* one monitor to another. This function automatically connects to
|
|
* the right toplevel #GtkWindow, even across moving @widget between
|
|
* toplevel windows.
|
|
*
|
|
* Note that this function tracks the toplevel, not @widget itself, so
|
|
* all a window's widgets are always considered to be on the same
|
|
* monitor. This is because this function is mainly used for fetching
|
|
* the new monitor's color profile, and it makes little sense to use
|
|
* different profiles for the widgets of one window.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
void
|
|
gimp_widget_track_monitor (GtkWidget *widget,
|
|
GCallback monitor_changed_callback,
|
|
gpointer user_data,
|
|
GDestroyNotify user_data_destroy)
|
|
{
|
|
TrackMonitorData *track_data;
|
|
GtkWidget *toplevel;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (monitor_changed_callback != NULL);
|
|
|
|
track_data = g_new0 (TrackMonitorData, 1);
|
|
|
|
track_data->widget = widget;
|
|
track_data->callback = (MonitorChangedCallback) monitor_changed_callback;
|
|
track_data->user_data = user_data;
|
|
|
|
g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) g_free,
|
|
track_data);
|
|
|
|
if (user_data_destroy)
|
|
g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) user_data_destroy,
|
|
user_data);
|
|
|
|
g_signal_connect (widget, "hierarchy-changed",
|
|
G_CALLBACK (track_monitor_hierarchy_changed),
|
|
track_data);
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
if (GTK_IS_WINDOW (toplevel))
|
|
track_monitor_hierarchy_changed (widget, NULL, track_data);
|
|
}
|
|
|
|
static gint
|
|
monitor_number (GdkMonitor *monitor)
|
|
{
|
|
GdkDisplay *display = gdk_monitor_get_display (monitor);
|
|
gint n_monitors = gdk_display_get_n_monitors (display);
|
|
gint i;
|
|
|
|
for (i = 0; i < n_monitors; i++)
|
|
if (gdk_display_get_monitor (display, i) == monitor)
|
|
return i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* gimp_monitor_get_color_profile:
|
|
* @monitor: a #GdkMonitor
|
|
*
|
|
* This function returns the #GimpColorProfile of @monitor
|
|
* or %NULL if there is no profile configured.
|
|
*
|
|
* Since: 3.0
|
|
*
|
|
* Returns: (nullable) (transfer full): the monitor's #GimpColorProfile,
|
|
* or %NULL.
|
|
**/
|
|
GimpColorProfile *
|
|
gimp_monitor_get_color_profile (GdkMonitor *monitor)
|
|
{
|
|
GimpColorProfile *profile = NULL;
|
|
|
|
g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL);
|
|
|
|
#if defined GDK_WINDOWING_X11
|
|
{
|
|
GdkDisplay *display;
|
|
GdkScreen *screen;
|
|
GdkAtom type = GDK_NONE;
|
|
gint format = 0;
|
|
gint nitems = 0;
|
|
gint number;
|
|
gchar *atom_name;
|
|
guchar *data = NULL;
|
|
|
|
display = gdk_monitor_get_display (monitor);
|
|
number = monitor_number (monitor);
|
|
|
|
if (number > 0)
|
|
atom_name = g_strdup_printf ("_ICC_PROFILE_%d", number);
|
|
else
|
|
atom_name = g_strdup ("_ICC_PROFILE");
|
|
|
|
screen = gdk_display_get_default_screen (display);
|
|
|
|
if (gdk_property_get (gdk_screen_get_root_window (screen),
|
|
gdk_atom_intern (atom_name, FALSE),
|
|
GDK_NONE,
|
|
0, 64 * 1024 * 1024, FALSE,
|
|
&type, &format, &nitems, &data) && nitems > 0)
|
|
{
|
|
profile = gimp_color_profile_new_from_icc_profile (data, nitems,
|
|
NULL);
|
|
g_free (data);
|
|
}
|
|
|
|
g_free (atom_name);
|
|
}
|
|
#elif defined GDK_WINDOWING_QUARTZ
|
|
{
|
|
CGColorSpaceRef space = NULL;
|
|
|
|
space = CGDisplayCopyColorSpace (monitor_number (monitor));
|
|
|
|
if (space)
|
|
{
|
|
CFDataRef data;
|
|
|
|
data = CGColorSpaceCopyICCData (space);
|
|
|
|
if (data)
|
|
{
|
|
UInt8 *buffer = g_malloc (CFDataGetLength (data));
|
|
|
|
/* We cannot use CFDataGetBytesPtr(), because that returns
|
|
* a const pointer where cmsOpenProfileFromMem wants a
|
|
* non-const pointer.
|
|
*/
|
|
CFDataGetBytes (data, CFRangeMake (0, CFDataGetLength (data)),
|
|
buffer);
|
|
|
|
profile = gimp_color_profile_new_from_icc_profile (buffer,
|
|
CFDataGetLength (data),
|
|
NULL);
|
|
|
|
g_free (buffer);
|
|
CFRelease (data);
|
|
}
|
|
|
|
CFRelease (space);
|
|
}
|
|
}
|
|
#elif defined G_OS_WIN32
|
|
{
|
|
GdkRectangle monitor_geometry;
|
|
gint scale_factor;
|
|
POINT point;
|
|
gint offsetx = GetSystemMetrics (SM_XVIRTUALSCREEN);
|
|
gint offsety = GetSystemMetrics (SM_YVIRTUALSCREEN);
|
|
HMONITOR monitor_handle;
|
|
MONITORINFOEXW info;
|
|
DISPLAY_DEVICEW display_device;
|
|
|
|
info.cbSize = sizeof (info);
|
|
display_device.cb = sizeof (display_device);
|
|
|
|
/* If the first monitor is not set as the main monitor,
|
|
* monitor_number(monitor) may not match the index used in
|
|
* EnumDisplayDevices(devicename, index, displaydevice, flags).
|
|
*/
|
|
gdk_monitor_get_geometry (monitor, &monitor_geometry);
|
|
scale_factor = gdk_monitor_get_scale_factor (monitor);
|
|
point.x = monitor_geometry.x * scale_factor + offsetx;
|
|
point.y = monitor_geometry.y * scale_factor + offsety;
|
|
monitor_handle = MonitorFromPoint (point, MONITOR_DEFAULTTONEAREST);
|
|
|
|
if (GetMonitorInfoW (monitor_handle, (LPMONITORINFO)&info))
|
|
{
|
|
if (EnumDisplayDevicesW (info.szDevice, 0, &display_device, 0))
|
|
{
|
|
wchar_t *device_key = display_device.DeviceKey;
|
|
wchar_t *filename_utf16 = NULL;
|
|
char *filename = NULL;
|
|
wchar_t *dir_utf16 = NULL;
|
|
char *dir = NULL;
|
|
char *fullpath = NULL;
|
|
GFile *file = NULL;
|
|
DWORD len = 0;
|
|
gboolean per_user;
|
|
WCS_PROFILE_MANAGEMENT_SCOPE scope;
|
|
|
|
WcsGetUsePerUserProfiles (device_key, CLASS_MONITOR, &per_user);
|
|
scope = per_user ? WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER :
|
|
WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
|
|
|
|
if (WcsGetDefaultColorProfileSize (scope,
|
|
device_key,
|
|
CPT_ICC,
|
|
CPST_NONE,
|
|
0,
|
|
&len))
|
|
{
|
|
filename_utf16 = (wchar_t*) g_malloc0 (len);
|
|
|
|
WcsGetDefaultColorProfile (scope,
|
|
device_key,
|
|
CPT_ICC,
|
|
CPST_NONE,
|
|
0,
|
|
len,
|
|
filename_utf16);
|
|
}
|
|
else
|
|
{
|
|
/* Due to a bug in Windows, the meanings of LCS_sRGB and
|
|
* LCS_WINDOWS_COLOR_SPACE are swapped.
|
|
*/
|
|
if (GetStandardColorSpaceProfileW (NULL, LCS_sRGB, NULL, &len) != 0 && len > 0)
|
|
{
|
|
filename_utf16 = (wchar_t*) g_malloc0 (len);
|
|
GetStandardColorSpaceProfileW (NULL, LCS_sRGB, filename_utf16, &len);
|
|
}
|
|
}
|
|
|
|
if (filename_utf16 != NULL)
|
|
{
|
|
filename = g_utf16_to_utf8 (filename_utf16, -1, NULL, NULL, NULL);
|
|
|
|
GetColorDirectoryW (NULL, NULL, &len);
|
|
dir_utf16 = g_malloc0 (len);
|
|
GetColorDirectoryW (NULL, dir_utf16, &len);
|
|
|
|
dir = g_utf16_to_utf8 (dir_utf16, -1, NULL, NULL, NULL);
|
|
|
|
fullpath = g_build_filename (dir, filename, NULL);
|
|
file = g_file_new_for_path (fullpath);
|
|
|
|
profile = gimp_color_profile_new_from_file (file, NULL);
|
|
g_object_unref (file);
|
|
|
|
g_free (fullpath);
|
|
g_free (dir);
|
|
g_free (dir_utf16);
|
|
g_free (filename);
|
|
g_free (filename_utf16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return profile;
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_get_color_profile:
|
|
* @widget: a #GtkWidget
|
|
*
|
|
* This function returns the #GimpColorProfile of the monitor @widget is
|
|
* currently displayed on, or %NULL if there is no profile configured.
|
|
*
|
|
* Since: 3.0
|
|
*
|
|
* Returns: (nullable) (transfer full): @widget's monitor's #GimpColorProfile,
|
|
* or %NULL.
|
|
**/
|
|
GimpColorProfile *
|
|
gimp_widget_get_color_profile (GtkWidget *widget)
|
|
{
|
|
GdkMonitor *monitor;
|
|
|
|
g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
|
|
|
|
if (widget)
|
|
{
|
|
monitor = gimp_widget_get_monitor (widget);
|
|
}
|
|
else
|
|
{
|
|
monitor = gdk_display_get_monitor (gdk_display_get_default (), 0);
|
|
}
|
|
|
|
return gimp_monitor_get_color_profile (monitor);
|
|
}
|
|
|
|
static GimpColorProfile *
|
|
get_display_profile (GtkWidget *widget,
|
|
GimpColorConfig *config)
|
|
{
|
|
GimpColorProfile *profile = NULL;
|
|
|
|
if (gimp_color_config_get_display_profile_from_gdk (config))
|
|
/* get the toplevel's profile so all a window's colors look the same */
|
|
profile = gimp_widget_get_color_profile (gtk_widget_get_toplevel (widget));
|
|
|
|
if (! profile)
|
|
profile = gimp_color_config_get_display_color_profile (config, NULL);
|
|
|
|
if (! profile)
|
|
profile = gimp_color_profile_new_rgb_srgb ();
|
|
|
|
return profile;
|
|
}
|
|
|
|
typedef struct _TransformCache TransformCache;
|
|
|
|
struct _TransformCache
|
|
{
|
|
GimpColorTransform *transform;
|
|
|
|
GimpColorConfig *config;
|
|
GimpColorProfile *src_profile;
|
|
const Babl *src_format;
|
|
GimpColorProfile *dest_profile;
|
|
const Babl *dest_format;
|
|
GimpColorProfile *proof_profile;
|
|
GimpColorRenderingIntent proof_intent;
|
|
gboolean proof_bpc;
|
|
|
|
gulong notify_id;
|
|
};
|
|
|
|
static GList *transform_caches = NULL;
|
|
static gboolean debug_cache = FALSE;
|
|
|
|
static gboolean
|
|
profiles_equal (GimpColorProfile *profile1,
|
|
GimpColorProfile *profile2)
|
|
{
|
|
return ((profile1 == NULL && profile2 == NULL) ||
|
|
(profile1 != NULL && profile2 != NULL &&
|
|
gimp_color_profile_is_equal (profile1, profile2)));
|
|
}
|
|
|
|
static TransformCache *
|
|
transform_cache_get (GimpColorConfig *config,
|
|
GimpColorProfile *src_profile,
|
|
const Babl *src_format,
|
|
GimpColorProfile *dest_profile,
|
|
const Babl *dest_format,
|
|
GimpColorProfile *proof_profile,
|
|
GimpColorRenderingIntent proof_intent,
|
|
gboolean proof_bpc)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = transform_caches; list; list = g_list_next (list))
|
|
{
|
|
TransformCache *cache = list->data;
|
|
|
|
if (config == cache->config &&
|
|
src_format == cache->src_format &&
|
|
dest_format == cache->dest_format &&
|
|
profiles_equal (src_profile, cache->src_profile) &&
|
|
profiles_equal (dest_profile, cache->dest_profile) &&
|
|
profiles_equal (proof_profile, cache->proof_profile) &&
|
|
proof_intent == cache->proof_intent &&
|
|
proof_bpc == cache->proof_bpc)
|
|
{
|
|
if (debug_cache)
|
|
g_printerr ("found cache %p\n", cache);
|
|
|
|
return cache;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
transform_cache_config_notify (GObject *config,
|
|
const GParamSpec *pspec,
|
|
TransformCache *cache)
|
|
{
|
|
transform_caches = g_list_remove (transform_caches, cache);
|
|
|
|
g_signal_handler_disconnect (config, cache->notify_id);
|
|
|
|
if (cache->transform)
|
|
g_object_unref (cache->transform);
|
|
|
|
g_object_unref (cache->src_profile);
|
|
g_object_unref (cache->dest_profile);
|
|
|
|
if (cache->proof_profile)
|
|
g_object_unref (cache->proof_profile);
|
|
|
|
if (debug_cache)
|
|
g_printerr ("deleted cache %p\n", cache);
|
|
|
|
g_free (cache);
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_get_color_transform:
|
|
* @widget: a #GtkWidget
|
|
* @config: a #GimpColorConfig
|
|
* @src_profile: a #GimpColorProfile
|
|
* @src_format: Babl format for the transform's source pixels
|
|
* @dest_format: Babl format for the transforms's destination pixels
|
|
*
|
|
* This function returns the #GimpColorTransform that transforms pixels
|
|
* from @src_profile to the profile of the #GdkMonitor the @widget is
|
|
* displayed on.
|
|
*
|
|
* Returns: (nullable) (transfer full): the #GimpColorTransform.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
GimpColorTransform *
|
|
gimp_widget_get_color_transform (GtkWidget *widget,
|
|
GimpColorConfig *config,
|
|
GimpColorProfile *src_profile,
|
|
const Babl *src_format,
|
|
const Babl *dest_format,
|
|
GimpColorProfile *softproof_profile,
|
|
GimpColorRenderingIntent proof_intent,
|
|
gboolean proof_bpc)
|
|
{
|
|
static gboolean initialized = FALSE;
|
|
GimpColorProfile *proof_profile = NULL;
|
|
GimpColorProfile *dest_profile = NULL;
|
|
TransformCache *cache;
|
|
|
|
g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
|
|
g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), NULL);
|
|
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (src_profile), NULL);
|
|
g_return_val_if_fail (src_format != NULL, NULL);
|
|
g_return_val_if_fail (dest_format != NULL, NULL);
|
|
|
|
if (G_UNLIKELY (! initialized))
|
|
{
|
|
initialized = TRUE;
|
|
|
|
debug_cache = g_getenv ("GIMP_DEBUG_TRANSFORM_CACHE") != NULL;
|
|
}
|
|
|
|
switch (gimp_color_config_get_mode (config))
|
|
{
|
|
case GIMP_COLOR_MANAGEMENT_OFF:
|
|
return NULL;
|
|
|
|
case GIMP_COLOR_MANAGEMENT_SOFTPROOF:
|
|
if (! softproof_profile)
|
|
proof_profile = gimp_color_config_get_simulation_color_profile (config,
|
|
NULL);
|
|
else
|
|
proof_profile = g_object_ref (softproof_profile);
|
|
/* fallthru */
|
|
|
|
case GIMP_COLOR_MANAGEMENT_DISPLAY:
|
|
dest_profile = get_display_profile (widget, config);
|
|
break;
|
|
}
|
|
|
|
cache = transform_cache_get (config,
|
|
src_profile,
|
|
src_format,
|
|
dest_profile,
|
|
dest_format,
|
|
proof_profile,
|
|
proof_intent,
|
|
proof_bpc);
|
|
|
|
if (cache)
|
|
{
|
|
g_object_unref (dest_profile);
|
|
|
|
if (proof_profile)
|
|
g_object_unref (proof_profile);
|
|
|
|
if (cache->transform)
|
|
return g_object_ref (cache->transform);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (! proof_profile &&
|
|
gimp_color_profile_is_equal (src_profile, dest_profile))
|
|
{
|
|
g_object_unref (dest_profile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
cache = g_new0 (TransformCache, 1);
|
|
|
|
if (debug_cache)
|
|
g_printerr ("creating cache %p\n", cache);
|
|
|
|
cache->config = g_object_ref (config);
|
|
cache->src_profile = g_object_ref (src_profile);
|
|
cache->src_format = src_format;
|
|
cache->dest_profile = dest_profile;
|
|
cache->dest_format = dest_format;
|
|
cache->proof_profile = proof_profile;
|
|
cache->proof_intent = proof_intent;
|
|
cache->proof_bpc = proof_bpc;
|
|
|
|
cache->notify_id =
|
|
g_signal_connect (cache->config, "notify",
|
|
G_CALLBACK (transform_cache_config_notify),
|
|
cache);
|
|
|
|
transform_caches = g_list_prepend (transform_caches, cache);
|
|
|
|
if (cache->proof_profile)
|
|
{
|
|
GimpColorTransformFlags flags = 0;
|
|
|
|
if (proof_bpc)
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
|
|
|
|
if (! gimp_color_config_get_simulation_optimize (config))
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
|
|
|
|
if (gimp_color_config_get_simulation_gamut_check (config))
|
|
{
|
|
GeglColor *color;
|
|
cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { 0, };
|
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_GAMUT_CHECK;
|
|
|
|
color = gimp_color_config_get_out_of_gamut_color (config);
|
|
/* Little-CMS documentation doesn't say which space should the
|
|
* out-of-gamut color be. Let's just give sRGB data.
|
|
*/
|
|
gegl_color_get_pixel (color, babl_format ("R'G'B' u16"), alarmCodes);
|
|
cmsSetAlarmCodes (alarmCodes);
|
|
|
|
g_object_unref (color);
|
|
}
|
|
|
|
cache->transform =
|
|
gimp_color_transform_new_proofing (cache->src_profile,
|
|
cache->src_format,
|
|
cache->dest_profile,
|
|
cache->dest_format,
|
|
cache->proof_profile,
|
|
cache->proof_intent,
|
|
gimp_color_config_get_display_intent (config),
|
|
flags);
|
|
}
|
|
else
|
|
{
|
|
GimpColorTransformFlags flags = 0;
|
|
|
|
if (gimp_color_config_get_display_bpc (config))
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
|
|
|
|
if (! gimp_color_config_get_display_optimize (config))
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
|
|
|
|
cache->transform =
|
|
gimp_color_transform_new (cache->src_profile,
|
|
cache->src_format,
|
|
cache->dest_profile,
|
|
cache->dest_format,
|
|
gimp_color_config_get_display_intent (config),
|
|
flags);
|
|
}
|
|
|
|
if (cache->transform)
|
|
return g_object_ref (cache->transform);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_get_render_format:
|
|
* @widget: (nullable): [class@Gtk.Widget] to draw the focus indicator on.
|
|
* @config: the color management settings.
|
|
* @softproof: whether the color must also be soft-proofed.
|
|
*
|
|
* Gets the Babl format to use as target format when rendering raw data on
|
|
* @widget. The format model with be "R'G'B'A" and the component types will be
|
|
* double.
|
|
*
|
|
* If @config is set, the color configuration as set by the user will be used,
|
|
* in particular using any custom monitor profile set in preferences (overriding
|
|
* system-set profile). If no such custom profile is set, it will use the
|
|
* profile of the monitor @widget is displayed on and will default to sRGB if
|
|
* @widget is %NULL.
|
|
*
|
|
* Use [func@Gimp.get_color_configuration] to retrieve the user
|
|
* [class@Gimp.ColorConfig].
|
|
*
|
|
* TODO: @softproof is currently unused.
|
|
*
|
|
* Since: 3.0
|
|
**/
|
|
const Babl *
|
|
gimp_widget_get_render_space (GtkWidget *widget,
|
|
GimpColorConfig *config)
|
|
{
|
|
GimpColorProfile *dest_profile = NULL;
|
|
const Babl *space = NULL;
|
|
|
|
g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
|
|
|
|
if (config)
|
|
_gimp_widget_get_profiles (widget, config, NULL, &dest_profile);
|
|
else
|
|
/* When no GimpColorConfig is given, we just return the monitor's color
|
|
* profile, disregarding any user preferences.
|
|
*/
|
|
dest_profile = gimp_widget_get_color_profile (gtk_widget_get_toplevel (widget));
|
|
|
|
if (dest_profile)
|
|
{
|
|
space = gimp_color_profile_get_space (dest_profile,
|
|
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
NULL);
|
|
g_object_unref (dest_profile);
|
|
}
|
|
|
|
return space;
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_set_native_handle:
|
|
* @widget: a #GtkWindow
|
|
* @handle: (out): pointer to store the native handle as a #GBytes.
|
|
*
|
|
* This function is used to store the handle representing @window into
|
|
* @handle so that it can later be reused to set other windows as
|
|
* transient to this one (even in other processes, such as plug-ins).
|
|
*
|
|
* Depending on the platform, the actual content of @handle can be
|
|
* various types. Moreover it may be filled asynchronously in a
|
|
* callback, so you should not assume that @handle is set after running
|
|
* this function.
|
|
*
|
|
* This convenience function is safe to use even before @widget is
|
|
* visible as it will set the handle once it is mapped.
|
|
*/
|
|
void
|
|
gimp_widget_set_native_handle (GtkWidget *widget,
|
|
GBytes **handle)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (handle != NULL && *handle == NULL);
|
|
|
|
/* This may seem overly complicated but there is a reason: "map-event" can
|
|
* only be received by widgets with their own windows (see description of
|
|
* gtk_widget_set_events()). Instead we could just use "realize" which is
|
|
* received by every widgets (even by the ones whose window actually belongs
|
|
* to a parent widget), but this causes process locks on Wayland (see comment
|
|
* in gimp_widget_set_handle_on_mapped()).
|
|
* This is why we do this weird case-based dance.
|
|
*/
|
|
if (gtk_widget_get_has_window (widget))
|
|
{
|
|
gtk_widget_add_events (widget, GDK_STRUCTURE_MASK);
|
|
g_signal_connect (widget, "map-event",
|
|
G_CALLBACK (gimp_widget_set_handle_on_mapped),
|
|
handle);
|
|
}
|
|
else
|
|
{
|
|
g_signal_connect (widget, "realize",
|
|
G_CALLBACK (gimp_widget_set_handle_on_realize),
|
|
handle);
|
|
}
|
|
|
|
if (gtk_widget_get_realized (widget))
|
|
gimp_widget_set_handle_on_mapped (widget, NULL, handle);
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_free_native_handle:
|
|
* @widget: a #GtkWindow
|
|
* @window_handle: (out): same pointer previously passed to set_native_handle
|
|
*
|
|
* Disposes a widget's native window handle created asynchronously after
|
|
* a previous call to gimp_widget_set_native_handle.
|
|
* This disposes what the pointer points to, a *GBytes, if any.
|
|
* Call this when the widget and the window handle it owns is being disposed.
|
|
*
|
|
* This should be called at least once, paired with set_native_handle.
|
|
* This knows how to free @window_handle, especially that on some platforms,
|
|
* an asynchronous callback must be canceled else it might call back
|
|
* with the pointer, after the widget and its private is freed.
|
|
*
|
|
* This is safe to call when deferenced @window_handle is NULL,
|
|
* when the window handle was never actually set,
|
|
* on Wayland where actual setting is asynchronous.
|
|
*
|
|
* !!! The word "handle" has two meanings.
|
|
* A "window handle" is an ID of a window.
|
|
* A "handle" also commonly means a pointer to a pointer, in this case **GBytes.
|
|
* @window_handle is both kinds of handle.
|
|
*/
|
|
void
|
|
gimp_widget_free_native_handle (GtkWidget *widget,
|
|
GBytes **window_handle)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
/* window-handle as pointer to pointer must not be NULL. */
|
|
g_return_if_fail (window_handle != NULL);
|
|
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
/* Cancel the asynch callback which has a pointer into widget's private.
|
|
* Cancel regardless whether callback has already come.
|
|
*/
|
|
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()) &&
|
|
/* The GdkWindow is likely already destroyed. */
|
|
gtk_widget_get_window (widget) != NULL)
|
|
gdk_wayland_window_unexport_handle (gtk_widget_get_window (widget));
|
|
#endif
|
|
|
|
/* On some platforms, window_handle may be NULL when an asynch callback has not come yet.
|
|
* The dereferenced pointer is the window handle.
|
|
*/
|
|
if (*window_handle != NULL)
|
|
{
|
|
/* On all platforms *window_handle is-a allocated GBytes.
|
|
* A GBytes is NOT a GObject and there is no way to ensure is-a GBytes.
|
|
*
|
|
* g_clear_pointer takes a pointer to pointer and unrefs at the pointer.
|
|
*/
|
|
g_clear_pointer (window_handle, g_bytes_unref);
|
|
}
|
|
/* Else no GBytes was ever allocated. */
|
|
}
|
|
|
|
/**
|
|
* gimp_widget_animation_enabled:
|
|
*
|
|
* This function attempts to read the user's system preference for
|
|
* showing animation. It can be used to turn off or hide unnecessary
|
|
* animations such as the scrolling credits or Easter Egg animations.
|
|
*
|
|
* Returns: %TRUE if the user has animations enabled on their system
|
|
*
|
|
* Since: 3.0
|
|
*/
|
|
gboolean
|
|
gimp_widget_animation_enabled (void)
|
|
{
|
|
gboolean animation_enabled = TRUE;
|
|
|
|
/* Per Luca Bacci, KDE syncs their animation setting with GTK. See
|
|
* https://invent.kde.org/plasma/kde-gtk-config/-/blob/v6.0.90/kded/gtkconfig.cpp?ref_type=tags#L232
|
|
*/
|
|
g_object_get (gtk_settings_get_default (),
|
|
"gtk-enable-animations", &animation_enabled,
|
|
NULL);
|
|
|
|
#ifdef PLATFORM_OSX
|
|
/* The MacOS setting is TRUE if the user wants animations turned off, so
|
|
* we invert it to match the format of the other platforms */
|
|
animation_enabled =
|
|
! ([[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]);
|
|
#endif
|
|
#ifdef G_OS_WIN32
|
|
SystemParametersInfo (SPI_GETCLIENTAREAANIMATION, 0x00, &animation_enabled,
|
|
0x00);
|
|
#endif
|
|
|
|
return animation_enabled;
|
|
}
|
|
|
|
|
|
/* Internal functions */
|
|
|
|
void
|
|
_gimp_widget_get_profiles (GtkWidget *widget,
|
|
GimpColorConfig *config,
|
|
GimpColorProfile **proof_profile,
|
|
GimpColorProfile **dest_profile)
|
|
{
|
|
g_return_if_fail (dest_profile != NULL && *dest_profile == NULL);
|
|
g_return_if_fail (proof_profile == NULL || *proof_profile == NULL);
|
|
|
|
switch (gimp_color_config_get_mode (config))
|
|
{
|
|
case GIMP_COLOR_MANAGEMENT_OFF:
|
|
return;
|
|
|
|
case GIMP_COLOR_MANAGEMENT_SOFTPROOF:
|
|
if (proof_profile)
|
|
*proof_profile = gimp_color_config_get_simulation_color_profile (config, NULL);
|
|
|
|
/* fallthru */
|
|
|
|
case GIMP_COLOR_MANAGEMENT_DISPLAY:
|
|
*dest_profile = get_display_profile (widget, config);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Private functions */
|
|
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
/* Handler from asynchronous callback from Wayland.
|
|
* You can't know when it will be called, if ever.
|
|
*
|
|
* @phandle is the address of a pointer in some widget's private data.
|
|
* Before freeing that private data, you must cancel (unexport)
|
|
* so that this callback is not subsequently invoked.
|
|
*/
|
|
static void
|
|
gimp_widget_wayland_window_exported (GdkWindow *window,
|
|
const char *handle,
|
|
GBytes **phandle)
|
|
{
|
|
GBytes *wayland_handle;
|
|
|
|
wayland_handle = g_bytes_new (handle, strlen (handle) + 1);
|
|
|
|
g_bytes_unref (*phandle);
|
|
*phandle = wayland_handle;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
gimp_widget_set_handle_on_realize (GtkWidget *widget,
|
|
GBytes **phandle)
|
|
{
|
|
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
gtk_widget_add_events (toplevel, GDK_STRUCTURE_MASK);
|
|
g_signal_connect (toplevel, "map-event",
|
|
G_CALLBACK (gimp_widget_set_handle_on_mapped),
|
|
phandle);
|
|
|
|
if (gtk_widget_get_mapped (toplevel))
|
|
gimp_widget_set_handle_on_mapped (widget, NULL, phandle);
|
|
}
|
|
|
|
static gboolean
|
|
gimp_widget_set_handle_on_mapped (GtkWidget *widget,
|
|
GdkEventAny *event,
|
|
GBytes **phandle)
|
|
{
|
|
GdkWindow *surface;
|
|
GBytes *handle = NULL;
|
|
#if defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_X11)
|
|
GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
widget = toplevel;
|
|
#endif
|
|
|
|
g_clear_pointer (phandle, g_bytes_unref);
|
|
|
|
surface = gtk_widget_get_window (GTK_WIDGET (widget));
|
|
g_return_val_if_fail (surface != NULL, FALSE);
|
|
|
|
#ifdef GDK_WINDOWING_WIN32
|
|
if (GDK_IS_WIN32_WINDOW (surface))
|
|
{
|
|
HANDLE id;
|
|
|
|
id = GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET (widget)));
|
|
handle = g_bytes_new (&id, sizeof (HANDLE));
|
|
}
|
|
#endif
|
|
|
|
#ifdef GDK_WINDOWING_X11
|
|
if (GDK_IS_X11_WINDOW (surface))
|
|
{
|
|
Window id;
|
|
|
|
id = GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (widget)));
|
|
handle = g_bytes_new (&id, sizeof (Window));
|
|
}
|
|
#endif
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
if (GDK_IS_WAYLAND_WINDOW (surface))
|
|
{
|
|
/* I don't run this on "realize" event because somehow it locks
|
|
* the whole processus in Wayland. The "map-event" happens
|
|
* slightly after the window became visible and I didn't
|
|
* experience any lock.
|
|
*/
|
|
if (! gdk_wayland_window_export_handle (surface,
|
|
(GdkWaylandWindowExported) gimp_widget_wayland_window_exported,
|
|
phandle, NULL))
|
|
g_printerr ("%s: gdk_wayland_window_export_handle() failed. "
|
|
"It will not be possible to set windows in other processes as transient to this display shell.\n",
|
|
G_STRFUNC);
|
|
}
|
|
#endif
|
|
|
|
*phandle = handle;
|
|
|
|
return FALSE;
|
|
}
|