Files
gimp/app/widgets/gimpdeviceinfo.c
Carlos Garnacho 02eea132c2 widgets: Add GimpDeviceInfo API to generate a GtkPadController
This API call will snapshot the current configuration of a device
into a GtkPadController, that is created and attached to a toplevel
(this event controller only acts on toplevels).

This controller will handle pad events, trigger actions, and update
compositor feedback (e.g. GNOME Shell pad OSD) as per the actions
mapped in the configuration dialog.
2023-12-08 12:35:34 +00:00

1501 lines
46 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpcontainer.h"
#include "core/gimpcurve.h"
#include "core/gimpcurve-map.h"
#include "core/gimpdatafactory.h"
#include "core/gimppadactions.h"
#include "core/gimpparamspecs.h"
#include "core/gimptoolinfo.h"
#include "widgets/gimpaction.h"
#include "gimpdeviceinfo.h"
#include "gimp-intl.h"
#define GIMP_DEVICE_INFO_DATA_KEY "gimp-device-info"
/**
* Some default names for axis. We will only use them when the device is
* not plugged (in which case, we would use names returned by GDK).
*/
static const gchar *const axis_use_strings[] =
{
N_("X"),
N_("Y"),
N_("Pressure"),
N_("X tilt"),
N_("Y tilt"),
/* Wheel as in mouse or input device wheel.
* Some pens would use the same axis for their rotation feature.
* See bug 791455.
* Yet GTK+ has a different axis since v. 3.22.
* TODO: this should be actually tested with a device having such
* feature.
*/
N_("Wheel"),
N_("Distance"),
N_("Rotation"),
N_("Slider")
};
enum
{
PROP_0,
PROP_DEVICE,
PROP_DISPLAY,
PROP_MODE,
PROP_SOURCE,
PROP_VENDOR_ID,
PROP_PRODUCT_ID,
PROP_TOOL_TYPE,
PROP_TOOL_SERIAL,
PROP_TOOL_HARDWARE_ID,
PROP_AXES,
PROP_KEYS,
PROP_PRESSURE_CURVE,
PROP_PAD_ACTIONS
};
struct _GimpDeviceInfoPrivate
{
GdkDevice *device;
GdkDisplay *display;
/* either "device" or the options below are set */
GdkInputMode mode;
gint n_axes;
GdkAxisUse *axes_uses;
gchar **axes_names;
gint n_keys;
GimpDeviceKey *keys;
GimpPadActions *pad_actions;
/* curves */
GimpCurve *pressure_curve;
};
/* local function prototypes */
static void gimp_device_info_constructed (GObject *object);
static void gimp_device_info_finalize (GObject *object);
static void gimp_device_info_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_device_info_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_device_info_guess_icon (GimpDeviceInfo *info);
static void gimp_device_info_tool_changed (GdkDevice *device,
GdkDeviceTool *tool,
GimpDeviceInfo *info);
static void gimp_device_info_device_changed (GdkDevice *device,
GimpDeviceInfo *info);
static void gimp_device_info_updated (GimpDeviceInfo *info);
G_DEFINE_TYPE_WITH_PRIVATE (GimpDeviceInfo, gimp_device_info, GIMP_TYPE_TOOL_PRESET)
#define parent_class gimp_device_info_parent_class
static void
gimp_device_info_class_init (GimpDeviceInfoClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
GParamSpec *param_spec;
object_class->constructed = gimp_device_info_constructed;
object_class->finalize = gimp_device_info_finalize;
object_class->set_property = gimp_device_info_set_property;
object_class->get_property = gimp_device_info_get_property;
viewable_class->default_icon_name = GIMP_ICON_INPUT_DEVICE;
g_object_class_install_property (object_class, PROP_DEVICE,
g_param_spec_object ("device",
NULL, NULL,
GDK_TYPE_DEVICE,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_EXPLICIT_NOTIFY));
g_object_class_install_property (object_class, PROP_DISPLAY,
g_param_spec_object ("display",
NULL, NULL,
GDK_TYPE_DISPLAY,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_EXPLICIT_NOTIFY));
GIMP_CONFIG_PROP_ENUM (object_class, PROP_MODE,
"mode",
_("Mode"),
NULL,
GDK_TYPE_INPUT_MODE,
GDK_MODE_DISABLED,
GIMP_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SOURCE,
g_param_spec_enum ("source",
NULL, NULL,
GDK_TYPE_INPUT_SOURCE,
GDK_SOURCE_MOUSE,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_VENDOR_ID,
g_param_spec_string ("vendor-id",
NULL, NULL,
NULL,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_PRODUCT_ID,
g_param_spec_string ("product-id",
NULL, NULL,
NULL,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_TOOL_TYPE,
g_param_spec_enum ("tool-type",
NULL, NULL,
GDK_TYPE_DEVICE_TOOL_TYPE,
GDK_DEVICE_TOOL_TYPE_UNKNOWN,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_TOOL_SERIAL,
g_param_spec_uint64 ("tool-serial",
NULL, NULL,
0, G_MAXUINT64, 0,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_TOOL_HARDWARE_ID,
g_param_spec_uint64 ("tool-hardware-id",
NULL, NULL,
0, G_MAXUINT64, 0,
GIMP_PARAM_STATIC_STRINGS |
G_PARAM_READABLE));
param_spec = g_param_spec_enum ("axis",
NULL, NULL,
GDK_TYPE_AXIS_USE,
GDK_AXIS_IGNORE,
GIMP_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_AXES,
gimp_param_spec_value_array ("axes",
NULL, NULL,
param_spec,
GIMP_PARAM_STATIC_STRINGS |
GIMP_CONFIG_PARAM_FLAGS));
param_spec = g_param_spec_string ("key",
NULL, NULL,
NULL,
GIMP_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_KEYS,
gimp_param_spec_value_array ("keys",
NULL, NULL,
param_spec,
GIMP_PARAM_STATIC_STRINGS |
GIMP_CONFIG_PARAM_FLAGS));
GIMP_CONFIG_PROP_OBJECT (object_class, PROP_PRESSURE_CURVE,
"pressure-curve",
_("Pressure curve"),
NULL,
GIMP_TYPE_CURVE,
GIMP_CONFIG_PARAM_AGGREGATE);
GIMP_CONFIG_PROP_OBJECT (object_class, PROP_PAD_ACTIONS,
"pad-actions",
_("Pad actions"),
NULL,
GIMP_TYPE_PAD_ACTIONS,
GIMP_CONFIG_PARAM_AGGREGATE);
}
static void
gimp_device_info_init (GimpDeviceInfo *info)
{
gimp_data_make_internal (GIMP_DATA (info), NULL);
info->priv = gimp_device_info_get_instance_private (info);
info->priv->mode = GDK_MODE_DISABLED;
info->priv->pressure_curve = GIMP_CURVE (gimp_curve_new ("pressure curve"));
info->priv->axes_names = NULL;
info->priv->pad_actions = gimp_pad_actions_new ();
g_signal_connect (info, "notify::name",
G_CALLBACK (gimp_device_info_guess_icon),
NULL);
}
static void
gimp_device_info_constructed (GObject *object)
{
GimpDeviceInfo *info = GIMP_DEVICE_INFO (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
gimp_device_info_updated (info);
gimp_assert ((info->priv->device == NULL && info->priv->display == NULL) ||
(GDK_IS_DEVICE (info->priv->device) && GDK_IS_DISPLAY (info->priv->display)));
}
static void
gimp_device_info_finalize (GObject *object)
{
GimpDeviceInfo *info = GIMP_DEVICE_INFO (object);
g_clear_pointer (&info->priv->axes_uses, g_free);
g_clear_pointer (&info->priv->keys, g_free);
g_strfreev (info->priv->axes_names);
g_clear_object (&info->priv->pressure_curve);
g_clear_object (&info->priv->pad_actions);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_device_info_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpDeviceInfo *info = GIMP_DEVICE_INFO (object);
GdkDevice *device = info->priv->device;
GimpCurve *src_curve = NULL;
GimpCurve *dest_curve = NULL;
switch (property_id)
{
case PROP_DEVICE:
/* Nothing to disconnect, it's G_PARAM_CONSTRUCT_ONLY. */
info->priv->device = g_value_get_object (value);
if (info->priv->device)
{
g_signal_connect_object (info->priv->device, "tool-changed",
G_CALLBACK (gimp_device_info_tool_changed),
G_OBJECT (info), 0);
g_signal_connect_object (info->priv->device, "changed",
G_CALLBACK (gimp_device_info_device_changed),
G_OBJECT (info), 0);
}
break;
case PROP_DISPLAY:
info->priv->display = g_value_get_object (value);
break;
case PROP_MODE:
gimp_device_info_set_mode (info, g_value_get_enum (value));
break;
case PROP_AXES:
{
/* Axes information as stored in devicerc. */
GimpValueArray *array = g_value_get_boxed (value);
if (array)
{
gint i;
gint n_device_values = gimp_value_array_length (array);
if (device)
{
if (info->priv->n_axes != 0 &&
gdk_device_get_device_type (device) != GDK_DEVICE_TYPE_MASTER &&
info->priv->n_axes != n_device_values)
g_printerr ("%s: stored 'num-axes' for device '%s' doesn't match "
"number of axes present in device\n",
G_STRFUNC, gdk_device_get_name (device));
n_device_values = MIN (n_device_values,
gdk_device_get_n_axes (device));
}
else if (! info->priv->n_axes && n_device_values > 0)
{
info->priv->n_axes = n_device_values;
info->priv->axes_uses = g_new0 (GdkAxisUse, info->priv->n_axes);
info->priv->axes_names = g_new0 (gchar *, info->priv->n_axes + 1);
}
for (i = 0; i < n_device_values; i++)
{
GdkAxisUse axis_use;
axis_use = g_value_get_enum (gimp_value_array_index (array, i));
gimp_device_info_set_axis_use (info, i, axis_use);
}
}
}
break;
case PROP_KEYS:
{
GimpValueArray *array = g_value_get_boxed (value);
if (array)
{
gint i;
gint n_device_values = gimp_value_array_length (array);
if (device)
n_device_values = MIN (n_device_values,
gdk_device_get_n_keys (device));
info->priv->n_keys = n_device_values;
info->priv->keys = g_renew (GimpDeviceKey, info->priv->keys, info->priv->n_keys);
if (info->priv->n_keys > 0)
memset (info->priv->keys, 0, info->priv->n_keys * sizeof (GimpDeviceKey));
for (i = 0; i < n_device_values; i++)
{
const gchar *accel;
guint keyval;
GdkModifierType modifiers;
accel = g_value_get_string (gimp_value_array_index (array, i));
gtk_accelerator_parse (accel, &keyval, &modifiers);
gimp_device_info_set_key (info, i, keyval, modifiers);
}
}
}
break;
case PROP_PRESSURE_CURVE:
src_curve = g_value_get_object (value);
dest_curve = info->priv->pressure_curve;
break;
case PROP_PAD_ACTIONS:
g_clear_object (&info->priv->pad_actions);
info->priv->pad_actions = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
if (src_curve && dest_curve)
{
gimp_config_copy (GIMP_CONFIG (src_curve),
GIMP_CONFIG (dest_curve),
GIMP_CONFIG_PARAM_SERIALIZE);
}
}
static void
gimp_device_info_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpDeviceInfo *info = GIMP_DEVICE_INFO (object);
switch (property_id)
{
case PROP_DEVICE:
g_value_set_object (value, info->priv->device);
break;
case PROP_DISPLAY:
g_value_set_object (value, info->priv->display);
break;
case PROP_MODE:
g_value_set_enum (value, gimp_device_info_get_mode (info));
break;
case PROP_SOURCE:
g_value_set_enum (value, gimp_device_info_get_source (info));
break;
case PROP_VENDOR_ID:
g_value_set_string (value, gimp_device_info_get_vendor_id (info));
break;
case PROP_PRODUCT_ID:
g_value_set_string (value, gimp_device_info_get_product_id (info));
break;
case PROP_TOOL_TYPE:
g_value_set_enum (value, gimp_device_info_get_tool_type (info));
break;
case PROP_TOOL_SERIAL:
g_value_set_uint64 (value, gimp_device_info_get_tool_serial (info));
break;
case PROP_TOOL_HARDWARE_ID:
g_value_set_uint64 (value, gimp_device_info_get_tool_hardware_id (info));
break;
case PROP_AXES:
{
GimpValueArray *array;
GValue enum_value = G_VALUE_INIT;
gint n_axes;
gint i;
array = gimp_value_array_new (6);
g_value_init (&enum_value, GDK_TYPE_AXIS_USE);
n_axes = gimp_device_info_get_n_axes (info);
for (i = 0; i < n_axes; i++)
{
g_value_set_enum (&enum_value,
gimp_device_info_get_axis_use (info, i));
gimp_value_array_append (array, &enum_value);
}
g_value_unset (&enum_value);
g_value_take_boxed (value, array);
}
break;
case PROP_KEYS:
{
GimpValueArray *array;
GValue string_value = G_VALUE_INIT;
gint n_keys;
gint i;
array = gimp_value_array_new (32);
g_value_init (&string_value, G_TYPE_STRING);
n_keys = gimp_device_info_get_n_keys (info);
for (i = 0; i < n_keys; i++)
{
guint keyval;
GdkModifierType modifiers;
gimp_device_info_get_key (info, i, &keyval, &modifiers);
if (keyval)
{
gchar *accel;
gchar *escaped;
accel = gtk_accelerator_name (keyval, modifiers);
escaped = g_strescape (accel, NULL);
g_free (accel);
g_value_set_string (&string_value, escaped);
g_free (escaped);
}
else
{
g_value_set_string (&string_value, "");
}
gimp_value_array_append (array, &string_value);
}
g_value_unset (&string_value);
g_value_take_boxed (value, array);
}
break;
case PROP_PRESSURE_CURVE:
g_value_set_object (value, info->priv->pressure_curve);
break;
case PROP_PAD_ACTIONS:
g_value_set_object (value, info->priv->pad_actions);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_device_info_guess_icon (GimpDeviceInfo *info)
{
GimpViewable *viewable = GIMP_VIEWABLE (info);
if (gimp_object_get_name (viewable) &&
! strcmp (gimp_viewable_get_icon_name (viewable),
GIMP_VIEWABLE_GET_CLASS (viewable)->default_icon_name))
{
const gchar *icon_name = NULL;
gchar *down = g_ascii_strdown (gimp_object_get_name (viewable),
-1);
if (strstr (down, "eraser"))
{
icon_name = GIMP_ICON_TOOL_ERASER;
}
else if (strstr (down, "pen"))
{
icon_name = GIMP_ICON_TOOL_PAINTBRUSH;
}
else if (strstr (down, "airbrush"))
{
icon_name = GIMP_ICON_TOOL_AIRBRUSH;
}
else if (strstr (down, "cursor") ||
strstr (down, "mouse") ||
strstr (down, "pointer") ||
strstr (down, "touchpad") ||
strstr (down, "trackpoint"))
{
icon_name = GIMP_ICON_CURSOR;
}
g_free (down);
if (icon_name)
gimp_viewable_set_icon_name (viewable, icon_name);
}
}
static void
gimp_device_info_tool_changed (GdkDevice *device,
GdkDeviceTool *tool,
GimpDeviceInfo *info)
{
g_object_freeze_notify (G_OBJECT (info));
/* GDK docs says that the number of axes can change on "changed"
* signal for virtual devices only, but here, I encounter a change of
* number of axes on a physical device, the first time when a stylus
* approached then moved away from the active surface. When the tool
* became then a GDK_DEVICE_TOOL_TYPE_UNKNOWN, I had one more axis
* (which created criticals later).
* So let's check specifically for such case.
*/
if (info->priv->n_axes != gdk_device_get_n_axes (device))
{
gint n_axes = gdk_device_get_n_axes (device);
gint old_n_axes = info->priv->n_axes;
gint i;
for (i = n_axes; i < info->priv->n_axes; i++)
g_free (info->priv->axes_names[i]);
info->priv->axes_names = g_renew (gchar *, info->priv->axes_names, n_axes + 1);
for (i = info->priv->n_axes; i < n_axes + 1; i++)
info->priv->axes_names[i] = NULL;
info->priv->axes_uses = g_renew (GdkAxisUse, info->priv->axes_uses, n_axes);
info->priv->n_axes = n_axes;
for (i = old_n_axes; i < n_axes; i++)
{
GdkAxisUse axis_use;
axis_use = gdk_device_get_axis_use (info->priv->device, i);
gimp_device_info_set_axis_use (info, i, axis_use);
}
}
g_object_notify (G_OBJECT (info), "tool-type");
g_object_notify (G_OBJECT (info), "tool-serial");
g_object_notify (G_OBJECT (info), "tool-hardware-id");
g_object_thaw_notify (G_OBJECT (info));
}
static void
gimp_device_info_device_changed (GdkDevice *device,
GimpDeviceInfo *info)
{
/* Number of axes or keys can change on virtual devices when the
* physical device changes.
*/
gimp_device_info_updated (info);
}
static void
gimp_device_info_updated (GimpDeviceInfo *info)
{
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
g_return_if_fail ((info->priv->device == NULL && info->priv->display == NULL) ||
(GDK_IS_DEVICE (info->priv->device) && GDK_IS_DISPLAY (info->priv->display)));
g_object_freeze_notify (G_OBJECT (info));
if (info->priv->device)
{
GdkAxisUse *old_uses;
GList *axes;
GList *iter;
gint old_n_axes;
gint i;
GimpDeviceKey *old_keys;
gint old_n_keys;
g_object_set_data (G_OBJECT (info->priv->device), GIMP_DEVICE_INFO_DATA_KEY, info);
gimp_object_set_name (GIMP_OBJECT (info),
gdk_device_get_name (info->priv->device));
gimp_device_info_set_mode (info, gdk_device_get_mode (info->priv->device));
old_n_axes = info->priv->n_axes;
old_uses = info->priv->axes_uses;
g_strfreev (info->priv->axes_names);
axes = gdk_device_list_axes (info->priv->device);
info->priv->n_axes = g_list_length (axes);
info->priv->axes_uses = g_new0 (GdkAxisUse, info->priv->n_axes);
info->priv->axes_names = g_new0 (gchar *, info->priv->n_axes + 1);
for (i = 0, iter = axes; i < info->priv->n_axes; i++, iter = iter->next)
{
GdkAxisUse use;
/* Virtual devices are special and just reproduce their actual
* physical device at a given moment. We should never try to
* reuse the data because we risk to pass device configuration
* from one physical device to another.
*/
if (gdk_device_get_device_type (info->priv->device) == GDK_DEVICE_TYPE_MASTER ||
i >= old_n_axes)
use = gdk_device_get_axis_use (info->priv->device, i);
else
use = old_uses[i];
gimp_device_info_set_axis_use (info, i, use);
if (iter->data != GDK_NONE)
info->priv->axes_names[i] = gdk_atom_name (iter->data);
else
info->priv->axes_names[i] = NULL;
}
g_list_free (axes);
g_clear_pointer (&old_uses, g_free);
old_n_keys = info->priv->n_keys;
old_keys = info->priv->keys;
info->priv->n_keys = gdk_device_get_n_keys (info->priv->device);
info->priv->keys = g_new0 (GimpDeviceKey, info->priv->n_keys);
for (i = 0; i < info->priv->n_keys; i++)
{
GimpDeviceKey key;
if (gdk_device_get_device_type (info->priv->device) == GDK_DEVICE_TYPE_MASTER ||
i >= old_n_keys)
gdk_device_get_key (info->priv->device, i, &key.keyval, &key.modifiers);
else
key = old_keys[i];
gimp_device_info_set_key (info, i, key.keyval, key.modifiers);
}
g_clear_pointer (&old_keys, g_free);
}
/* sort order depends on device presence */
gimp_object_name_changed (GIMP_OBJECT (info));
g_object_notify (G_OBJECT (info), "source");
g_object_notify (G_OBJECT (info), "vendor-id");
g_object_notify (G_OBJECT (info), "product-id");
g_object_notify (G_OBJECT (info), "tool-type");
g_object_notify (G_OBJECT (info), "tool-serial");
g_object_notify (G_OBJECT (info), "tool-hardware-id");
g_object_notify (G_OBJECT (info), "device");
g_object_notify (G_OBJECT (info), "display");
g_object_thaw_notify (G_OBJECT (info));
}
static void
gimp_device_info_pad_action_map_foreach (GimpPadActions *pad_actions,
GimpPadActionType action_type,
guint number,
guint mode,
const gchar *action_name,
gpointer data)
{
GtkPadController *controller = data;
GimpDeviceInfo *info;
Gimp *gimp;
GimpAction *action;
gchar *label;
gchar *accel_pos;
info = g_object_get_data (G_OBJECT (controller), "device-info");
if (!info)
return;
gimp = GIMP_TOOL_PRESET (info)->gimp;
action = GIMP_ACTION (g_action_map_lookup_action (G_ACTION_MAP (gimp->app),
action_name));
if (!action)
return;
/* Trim the accelerator from the feedback string */
label = g_strdup (gimp_action_get_label (action));
accel_pos = g_utf8_strchr (label, -1, '_');
if (accel_pos)
strcpy (accel_pos, accel_pos + 1);
gtk_pad_controller_set_action (controller,
/* Action type enums are binary compatible */
(GtkPadActionType) action_type,
number, mode,
label,
gimp_action_get_name (action));
g_free (label);
}
/* public functions */
GimpDeviceInfo *
gimp_device_info_new (Gimp *gimp,
GdkDevice *device,
GdkDisplay *display)
{
GimpContext *context;
GimpToolInfo *tool_info;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
context = gimp_get_user_context (gimp);
tool_info = gimp_context_get_tool (context);
g_return_val_if_fail (tool_info != NULL, NULL);
return g_object_new (GIMP_TYPE_DEVICE_INFO,
"gimp", gimp,
"device", device,
"display", display,
"mode", gdk_device_get_mode (device),
"tool-options", tool_info->tool_options,
NULL);
}
GdkDevice *
gimp_device_info_get_device (GimpDeviceInfo *info,
GdkDisplay **display)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL);
if (display)
*display = info->priv->display;
return info->priv->device;
}
gboolean
gimp_device_info_set_device (GimpDeviceInfo *info,
GdkDevice *device,
GdkDisplay *display)
{
GdkInputMode mode;
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), FALSE);
g_return_val_if_fail ((device == NULL && display == NULL) ||
(GDK_IS_DEVICE (device) && GDK_IS_DISPLAY (display)),
FALSE);
g_return_val_if_fail (device == NULL ||
strcmp (gdk_device_get_name (device),
gimp_object_get_name (info)) == 0, FALSE);
if (device && info->priv->device)
{
g_printerr ("%s: trying to set GdkDevice '%s' on GimpDeviceInfo "
"which already has a device\n",
G_STRFUNC, gdk_device_get_name (device));
#ifdef G_OS_WIN32
/* This is a very weird/dirty difference we make between Win32 and
* Linux. On Linux, we had breakage because of duplicate devices,
* fixed by overwriting the info's old device (assuming it to be
* dead) with the new one. Unfortunately doing this on Windows
* too broke a lot of devices (which used to work with the old
* way). See the regression bug #2495.
*
* NOTE that this only happens if something is wrong on the USB
* or udev or libinput or whatever side and the same device is
* present multiple times. Therefore there doesn't seem to be an
* absolute single "solution" to this problem (well there is, but
* probably not in GIMP, where we can only react). This is more
* of an experimenting-in-real-life kind of bug.
* Also we had no clear report on macOS or BSD (AFAIK) of broken
* tablets with any of the version of the code. So let's keep
* these similar to Linux for now.
*/
return FALSE;
#endif /* G_OS_WIN32 */
}
else if (! device && ! info->priv->device)
{
g_printerr ("%s: trying to unset GdkDevice of GimpDeviceInfo '%s'"
"which has no device\n",
G_STRFUNC, gimp_object_get_name (info));
/* bail out, unsetting twice makes no sense */
return FALSE;
}
else if (info->priv->device)
{
if (info->priv->n_axes != gdk_device_get_n_axes (info->priv->device))
g_printerr ("%s: stored 'num-axes' for device '%s' doesn't match "
"number of axes present in device\n",
G_STRFUNC, gdk_device_get_name (info->priv->device));
if (info->priv->n_keys != gdk_device_get_n_keys (info->priv->device))
g_printerr ("%s: stored 'num-keys' for device '%s' doesn't match "
"number of keys present in device\n",
G_STRFUNC, gdk_device_get_name (info->priv->device));
}
if (info->priv->device)
{
g_object_set_data (G_OBJECT (info->priv->device),
GIMP_DEVICE_INFO_DATA_KEY, NULL);
g_signal_handlers_disconnect_by_func (info->priv->device,
gimp_device_info_tool_changed,
info);
g_signal_handlers_disconnect_by_func (info->priv->device,
gimp_device_info_device_changed,
info);
}
mode = gimp_device_info_get_mode (info);
info->priv->device = device;
info->priv->display = display;
if (info->priv->device)
{
g_signal_connect_object (info->priv->device, "tool-changed",
G_CALLBACK (gimp_device_info_tool_changed),
G_OBJECT (info), 0);
g_signal_connect_object (info->priv->device, "changed",
G_CALLBACK (gimp_device_info_device_changed),
G_OBJECT (info), 0);
}
gimp_device_info_updated (info);
/* The info existed from a previous run. Restore its mode. */
gimp_device_info_set_mode (info, mode);
return TRUE;
}
void
gimp_device_info_set_default_tool (GimpDeviceInfo *info)
{
GimpContainer *tools;
GimpToolInfo *tool_info = NULL;
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
tools = GIMP_TOOL_PRESET (info)->gimp->tool_info_list;
tool_info =
GIMP_TOOL_INFO (gimp_container_get_child_by_name (tools,
"gimp-paintbrush-tool"));
if (info->priv->device)
{
switch (gdk_device_get_source (info->priv->device))
{
case GDK_SOURCE_ERASER:
tool_info =
GIMP_TOOL_INFO (gimp_container_get_child_by_name (tools,
"gimp-eraser-tool"));
break;
case GDK_SOURCE_PEN:
tool_info =
GIMP_TOOL_INFO (gimp_container_get_child_by_name (tools,
"gimp-paintbrush-tool"));
break;
case GDK_SOURCE_TOUCHSCREEN:
tool_info =
GIMP_TOOL_INFO (gimp_container_get_child_by_name (tools,
"gimp-smudge-tool"));
break;
default:
break;
}
}
if (tool_info)
{
g_object_set (info,
"tool-options", tool_info->tool_options,
NULL);
}
}
void
gimp_device_info_save_tool (GimpDeviceInfo *info)
{
GimpToolPreset *preset = GIMP_TOOL_PRESET (info);
GimpContext *user_context;
GimpToolInfo *tool_info;
GimpContextPropMask serialize_props;
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
user_context = gimp_get_user_context (GIMP_TOOL_PRESET (info)->gimp);
tool_info = gimp_context_get_tool (user_context);
g_object_set (info,
"tool-options", tool_info->tool_options,
NULL);
serialize_props =
gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options));
g_object_set (preset,
"use-fg-bg",
(serialize_props & GIMP_CONTEXT_PROP_MASK_FOREGROUND) ||
(serialize_props & GIMP_CONTEXT_PROP_MASK_BACKGROUND),
"use-brush",
(serialize_props & GIMP_CONTEXT_PROP_MASK_BRUSH) != 0,
"use-dynamics",
(serialize_props & GIMP_CONTEXT_PROP_MASK_DYNAMICS) != 0,
"use-mypaint-brush",
(serialize_props & GIMP_CONTEXT_PROP_MASK_MYBRUSH) != 0,
"use-gradient",
(serialize_props & GIMP_CONTEXT_PROP_MASK_GRADIENT) != 0,
"use-pattern",
(serialize_props & GIMP_CONTEXT_PROP_MASK_PATTERN) != 0,
"use-palette",
(serialize_props & GIMP_CONTEXT_PROP_MASK_PALETTE) != 0,
"use-font",
(serialize_props & GIMP_CONTEXT_PROP_MASK_FONT) != 0,
NULL);
}
void
gimp_device_info_restore_tool (GimpDeviceInfo *info)
{
GimpToolPreset *preset;
GimpContext *user_context;
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
preset = GIMP_TOOL_PRESET (info);
user_context = gimp_get_user_context (GIMP_TOOL_PRESET (info)->gimp);
if (preset->tool_options)
{
if (gimp_context_get_tool_preset (user_context) != preset)
{
gimp_context_set_tool_preset (user_context, preset);
}
else
{
gimp_context_tool_preset_changed (user_context);
}
}
}
GdkInputMode
gimp_device_info_get_mode (GimpDeviceInfo *info)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), GDK_MODE_DISABLED);
if (info->priv->device)
return gdk_device_get_mode (info->priv->device);
else
return info->priv->mode;
}
void
gimp_device_info_set_mode (GimpDeviceInfo *info,
GdkInputMode mode)
{
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
if (mode != gimp_device_info_get_mode (info))
{
if (info->priv->device)
gdk_device_set_mode (info->priv->device, mode);
else
info->priv->mode = mode;
g_object_notify (G_OBJECT (info), "mode");
}
}
gboolean
gimp_device_info_has_cursor (GimpDeviceInfo *info)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), FALSE);
if (info->priv->device)
{
/* this should really be
*
* "is slave, *and* the associated master is gdk_seat_get_pointer()"
*
* but who knows if future multiple masters will all have their
* own visible pointer or not, and what the API for figuring
* that will be, so for now let's simply assume that all
* devices except floating ones move the pointer on screen.
*/
return gdk_device_get_device_type (info->priv->device) !=
GDK_DEVICE_TYPE_FLOATING;
}
return FALSE;
}
GdkInputSource
gimp_device_info_get_source (GimpDeviceInfo *info)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), GDK_SOURCE_MOUSE);
if (info->priv->device)
return gdk_device_get_source (info->priv->device);
if (info->priv->pad_actions &&
gimp_pad_actions_get_n_actions (info->priv->pad_actions) > 0)
return GDK_SOURCE_TABLET_PAD;
return GDK_SOURCE_MOUSE;
}
const gchar *
gimp_device_info_get_vendor_id (GimpDeviceInfo *info)
{
const gchar *id = _("(Device not present)");
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL);
if (info->priv->device)
{
if (gdk_device_get_device_type (info->priv->device) == GDK_DEVICE_TYPE_MASTER)
{
id = _("(Virtual device)");
}
else
{
id = gdk_device_get_vendor_id (info->priv->device);
if (! (id && strlen (id)))
id = _("(none)");
}
}
return id;
}
const gchar *
gimp_device_info_get_product_id (GimpDeviceInfo *info)
{
const gchar *id = _("(Device not present)");
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL);
if (info->priv->device)
{
if (gdk_device_get_device_type (info->priv->device) == GDK_DEVICE_TYPE_MASTER)
{
return _("(Virtual device)");
}
else
{
id = gdk_device_get_product_id (info->priv->device);
if (! (id && strlen (id)))
id = _("(none)");
}
}
return id;
}
GdkDeviceToolType
gimp_device_info_get_tool_type (GimpDeviceInfo *info)
{
GdkDeviceToolType type = GDK_DEVICE_TOOL_TYPE_UNKNOWN;
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), type);
if (info->priv->device)
{
GdkDeviceTool *tool;
g_object_get (info->priv->device, "tool", &tool, NULL);
if (tool)
{
type = gdk_device_tool_get_tool_type (tool);
g_object_unref (tool);
}
}
return type;
}
guint64
gimp_device_info_get_tool_serial (GimpDeviceInfo *info)
{
guint64 serial = 0;
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), serial);
if (info->priv->device)
{
GdkDeviceTool *tool;
g_object_get (info->priv->device, "tool", &tool, NULL);
if (tool)
{
serial = gdk_device_tool_get_serial (tool);
g_object_unref (tool);
}
}
return serial;
}
guint64
gimp_device_info_get_tool_hardware_id (GimpDeviceInfo *info)
{
guint64 id = 0;
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), id);
if (info->priv->device)
{
GdkDeviceTool *tool;
g_object_get (info->priv->device, "tool", &tool, NULL);
if (tool)
{
id = gdk_device_tool_get_hardware_id (tool);
g_object_unref (tool);
}
}
return id;
}
gint
gimp_device_info_get_n_axes (GimpDeviceInfo *info)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), 0);
if (info->priv->device)
return gdk_device_get_n_axes (info->priv->device);
else
return info->priv->n_axes;
}
gboolean
gimp_device_info_ignore_axis (GimpDeviceInfo *info,
gint axis)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), TRUE);
g_return_val_if_fail (axis >= 0 && axis < info->priv->n_axes, TRUE);
return (info->priv->axes_names[axis] == NULL);
}
const gchar *
gimp_device_info_get_axis_name (GimpDeviceInfo *info,
gint axis)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL);
g_return_val_if_fail (axis >= 0 && axis < GDK_AXIS_LAST, NULL);
if (info->priv->device && axis < info->priv->n_axes &&
info->priv->axes_names[axis] != NULL)
return info->priv->axes_names[axis];
else
return axis_use_strings[axis];
}
GdkAxisUse
gimp_device_info_get_axis_use (GimpDeviceInfo *info,
gint axis)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), GDK_AXIS_IGNORE);
g_return_val_if_fail (axis >= 0 && axis < gimp_device_info_get_n_axes (info),
GDK_AXIS_IGNORE);
if (info->priv->device)
return gdk_device_get_axis_use (info->priv->device, axis);
else
return info->priv->axes_uses[axis];
}
void
gimp_device_info_set_axis_use (GimpDeviceInfo *info,
gint axis,
GdkAxisUse use)
{
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
g_return_if_fail (axis >= 0 && axis < gimp_device_info_get_n_axes (info));
if (use != gimp_device_info_get_axis_use (info, axis))
{
if (info->priv->device)
gdk_device_set_axis_use (info->priv->device, axis, use);
info->priv->axes_uses[axis] = use;
g_object_notify (G_OBJECT (info), "axes");
}
}
gint
gimp_device_info_get_n_keys (GimpDeviceInfo *info)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), 0);
if (info->priv->device)
return gdk_device_get_n_keys (info->priv->device);
else
return info->priv->n_keys;
}
void
gimp_device_info_get_key (GimpDeviceInfo *info,
gint key,
guint *keyval,
GdkModifierType *modifiers)
{
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
g_return_if_fail (key >= 0 && key < gimp_device_info_get_n_keys (info));
g_return_if_fail (keyval != NULL);
g_return_if_fail (modifiers != NULL);
if (info->priv->device)
{
*keyval = 0;
*modifiers = 0;
gdk_device_get_key (info->priv->device, key,
keyval,
modifiers);
}
else
{
*keyval = info->priv->keys[key].keyval;
*modifiers = info->priv->keys[key].modifiers;
}
}
void
gimp_device_info_set_key (GimpDeviceInfo *info,
gint key,
guint keyval,
GdkModifierType modifiers)
{
guint old_keyval;
GdkModifierType old_modifiers;
g_return_if_fail (GIMP_IS_DEVICE_INFO (info));
g_return_if_fail (key >= 0 && key < gimp_device_info_get_n_keys (info));
gimp_device_info_get_key (info, key, &old_keyval, &old_modifiers);
if (keyval != old_keyval ||
modifiers != old_modifiers)
{
if (info->priv->device)
gdk_device_set_key (info->priv->device, key, keyval, modifiers);
info->priv->keys[key].keyval = keyval;
info->priv->keys[key].modifiers = modifiers;
g_object_notify (G_OBJECT (info), "keys");
}
}
GimpCurve *
gimp_device_info_get_curve (GimpDeviceInfo *info,
GdkAxisUse use)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL);
switch (use)
{
case GDK_AXIS_PRESSURE:
return info->priv->pressure_curve;
break;
default:
return NULL;
}
}
gdouble
gimp_device_info_map_axis (GimpDeviceInfo *info,
GdkAxisUse use,
gdouble value)
{
g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), value);
/* CLAMP() the return values be safe against buggy XInput drivers */
switch (use)
{
case GDK_AXIS_PRESSURE:
return gimp_curve_map_value (info->priv->pressure_curve, value);
case GDK_AXIS_XTILT:
return CLAMP (value, GIMP_COORDS_MIN_TILT, GIMP_COORDS_MAX_TILT);
case GDK_AXIS_YTILT:
return CLAMP (value, GIMP_COORDS_MIN_TILT, GIMP_COORDS_MAX_TILT);
case GDK_AXIS_WHEEL:
return CLAMP (value, GIMP_COORDS_MIN_WHEEL, GIMP_COORDS_MAX_WHEEL);
default:
break;
}
return value;
}
GimpDeviceInfo *
gimp_device_info_get_by_device (GdkDevice *device)
{
g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
return g_object_get_data (G_OBJECT (device), GIMP_DEVICE_INFO_DATA_KEY);
}
gint
gimp_device_info_compare (GimpDeviceInfo *a,
GimpDeviceInfo *b)
{
GdkSeat *seat;
if (a->priv->device && a->priv->display &&
(seat = gdk_display_get_default_seat (a->priv->display)) &&
a->priv->device == gdk_seat_get_pointer (seat))
{
return -1;
}
else if (b->priv->device && b->priv->display &&
(seat = gdk_display_get_default_seat (b->priv->display)) &&
b->priv->device == gdk_seat_get_pointer (seat))
{
return 1;
}
else if (a->priv->device && ! b->priv->device)
{
return -1;
}
else if (! a->priv->device && b->priv->device)
{
return 1;
}
else
{
return gimp_object_name_collate ((GimpObject *) a,
(GimpObject *) b);
}
}
GimpPadActions *
gimp_device_info_get_pad_actions (GimpDeviceInfo *info)
{
return info->priv->pad_actions;
}
GtkPadController *
gimp_device_info_create_pad_controller (GimpDeviceInfo *info,
GimpWindow *window)
{
Gimp *gimp;
GimpPadActions *pad_actions;
GtkPadController *controller;
gimp = GIMP_TOOL_PRESET (info)->gimp;
pad_actions = gimp_device_info_get_pad_actions (info);
if (gimp_pad_actions_get_n_actions (pad_actions) == 0)
return NULL;
controller = gtk_pad_controller_new (GTK_WINDOW (window),
G_ACTION_GROUP (gimp->app),
info->priv->device);
g_object_set_data (G_OBJECT (controller), "device-info", info);
gimp_pad_actions_foreach (pad_actions,
gimp_device_info_pad_action_map_foreach,
controller);
return controller;
}