/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2009 Intel Corp. * * 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 . * * Author: Emmanuele Bassi */ /** * SECTION:clutter-input-device * @short_description: An input device managed by Clutter * * #ClutterInputDevice represents an input device known to Clutter. * * The #ClutterInputDevice class holds the state of the device, but * its contents are usually defined by the Clutter backend in use. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-input-device.h" #include "clutter-actor-private.h" #include "clutter-debug.h" #include "clutter-device-manager-private.h" #include "clutter-enum-types.h" #include "clutter-marshal.h" #include "clutter-private.h" #include "clutter-stage-private.h" enum { PROP_0, PROP_BACKEND, PROP_ID, PROP_NAME, PROP_DEVICE_TYPE, PROP_DEVICE_MANAGER, PROP_DEVICE_MODE, PROP_HAS_CURSOR, PROP_N_AXES, PROP_LAST }; enum { SELECT_STAGE_EVENTS, LAST_SIGNAL }; static GParamSpec *obj_props[PROP_LAST] = { NULL, }; static guint device_signals[LAST_SIGNAL] = { 0, }; G_DEFINE_TYPE (ClutterInputDevice, clutter_input_device, G_TYPE_OBJECT); static void clutter_input_device_dispose (GObject *gobject) { ClutterInputDevice *device = CLUTTER_INPUT_DEVICE (gobject); g_free (device->device_name); if (device->device_mode == CLUTTER_INPUT_MODE_SLAVE) _clutter_input_device_remove_slave (device->associated, device); if (device->associated != NULL) { _clutter_input_device_set_associated_device (device->associated, NULL); g_object_unref (device->associated); device->associated = NULL; } if (device->axes != NULL) { g_array_free (device->axes, TRUE); device->axes = NULL; } if (device->keys != NULL) { g_array_free (device->keys, TRUE); device->keys = NULL; } G_OBJECT_CLASS (clutter_input_device_parent_class)->dispose (gobject); } static void clutter_input_device_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject); switch (prop_id) { case PROP_ID: self->id = g_value_get_int (value); break; case PROP_DEVICE_TYPE: self->device_type = g_value_get_enum (value); break; case PROP_DEVICE_MANAGER: self->device_manager = g_value_get_object (value); break; case PROP_DEVICE_MODE: self->device_mode = g_value_get_enum (value); break; case PROP_BACKEND: self->backend = g_value_get_object (value); break; case PROP_NAME: self->device_name = g_value_dup_string (value); break; case PROP_HAS_CURSOR: self->has_cursor = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_input_device_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject); switch (prop_id) { case PROP_ID: g_value_set_int (value, self->id); break; case PROP_DEVICE_TYPE: g_value_set_enum (value, self->device_type); break; case PROP_DEVICE_MANAGER: g_value_set_object (value, self->device_manager); break; case PROP_DEVICE_MODE: g_value_set_enum (value, self->device_mode); break; case PROP_BACKEND: g_value_set_object (value, self->backend); break; case PROP_NAME: g_value_set_string (value, self->device_name); break; case PROP_HAS_CURSOR: g_value_set_boolean (value, self->has_cursor); break; case PROP_N_AXES: if (self->axes != NULL) g_value_set_uint (value, self->axes->len); else g_value_set_uint (value, 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_input_device_class_init (ClutterInputDeviceClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /** * ClutterInputDevice:id: * * The unique identifier of the device * * Since: 1.2 */ obj_props[PROP_ID] = g_param_spec_int ("id", P_("Id"), P_("Unique identifier of the device"), -1, G_MAXINT, 0, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * ClutterInputDevice:name: * * The name of the device * * Since: 1.2 */ obj_props[PROP_NAME] = g_param_spec_string ("name", P_("Name"), P_("The name of the device"), NULL, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * ClutterInputDevice:device-type: * * The type of the device * * Since: 1.2 */ obj_props[PROP_DEVICE_TYPE] = g_param_spec_enum ("device-type", P_("Device Type"), P_("The type of the device"), CLUTTER_TYPE_INPUT_DEVICE_TYPE, CLUTTER_POINTER_DEVICE, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); obj_props[PROP_DEVICE_MANAGER] = g_param_spec_object ("device-manager", P_("Device Manager"), P_("The device manager instance"), CLUTTER_TYPE_DEVICE_MANAGER, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); obj_props[PROP_DEVICE_MODE] = g_param_spec_enum ("device-mode", P_("Device Mode"), P_("The mode of the device"), CLUTTER_TYPE_INPUT_MODE, CLUTTER_INPUT_MODE_FLOATING, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); obj_props[PROP_HAS_CURSOR] = g_param_spec_boolean ("has-cursor", P_("Has Cursor"), P_("Whether the device has a cursor"), FALSE, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); obj_props[PROP_N_AXES] = g_param_spec_uint ("n-axes", P_("Number of Axes"), P_("The number of axes on the device"), 0, G_MAXUINT, 0, CLUTTER_PARAM_READABLE); obj_props[PROP_BACKEND] = g_param_spec_object ("backend", P_("Backend"), P_("The backend instance"), CLUTTER_TYPE_BACKEND, CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); gobject_class->dispose = clutter_input_device_dispose; gobject_class->set_property = clutter_input_device_set_property; gobject_class->get_property = clutter_input_device_get_property; g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); device_signals[SELECT_STAGE_EVENTS] = g_signal_new (I_("select-stage-events"), G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, _clutter_marshal_VOID__OBJECT_INT, G_TYPE_NONE, 2, CLUTTER_TYPE_STAGE, G_TYPE_INT); } static void clutter_input_device_init (ClutterInputDevice *self) { self->id = -1; self->device_type = CLUTTER_POINTER_DEVICE; self->click_count = 0; self->current_time = self->previous_time = CLUTTER_CURRENT_TIME; self->current_x = self->previous_x = -1; self->current_y = self->previous_y = -1; self->current_button_number = self->previous_button_number = -1; self->current_state = self->previous_state = 0; self->min_keycode = 0; self->max_keycode = G_MAXUINT; } /* * _clutter_input_device_set_coords: * @device: a #ClutterInputDevice * @x: X coordinate of the device * @y: Y coordinate of the device * * Stores the last known coordinates of the device */ void _clutter_input_device_set_coords (ClutterInputDevice *device, gint x, gint y) { g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); if (device->current_x != x) device->current_x = x; if (device->current_y != y) device->current_y = y; } /* * _clutter_input_device_set_state: * @device: a #ClutterInputDevice * @state: a bitmask of modifiers * * Stores the last known modifiers state of the device */ void _clutter_input_device_set_state (ClutterInputDevice *device, ClutterModifierType state) { g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); device->current_state = state; } /* * _clutter_input_device_set_time: * @device: a #ClutterInputDevice * @time_: the time * * Stores the last known event time of the device */ void _clutter_input_device_set_time (ClutterInputDevice *device, guint32 time_) { g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); if (device->current_time != time_) device->current_time = time_; } /* * cursor_weak_unref: * * #ClutterInputDevice keeps a weak reference on the actor * under its pointer; this function unsets the reference on * the actor to avoid keeping around stale pointers */ static void cursor_weak_unref (gpointer user_data, GObject *object_pointer) { ClutterInputDevice *device = user_data; device->cursor_actor = NULL; } /* * _clutter_input_device_set_stage: * @device: a #ClutterInputDevice * @stage: a #ClutterStage or %NULL * * Stores the stage under the device */ void _clutter_input_device_set_stage (ClutterInputDevice *device, ClutterStage *stage) { ClutterStage *old_stage; g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); old_stage = device->stage; device->stage = stage; /* if we left the stage then we also need to unset the * cursor actor (and update its :has-pointer property) */ if (device->stage == NULL && device->cursor_actor != NULL && device->cursor_actor != CLUTTER_ACTOR (old_stage)) { ClutterEvent cev; cev.crossing.type = CLUTTER_LEAVE; cev.crossing.time = device->current_time; cev.crossing.flags = 0; cev.crossing.stage = old_stage; cev.crossing.source = device->cursor_actor; cev.crossing.x = device->current_x; cev.crossing.y = device->current_y; cev.crossing.device = device; cev.crossing.related = device->stage != NULL ? CLUTTER_ACTOR (device->stage) : CLUTTER_ACTOR (old_stage); _clutter_stage_queue_event (old_stage, &cev); _clutter_actor_set_has_pointer (device->cursor_actor, FALSE); g_object_weak_unref (G_OBJECT (device->cursor_actor), cursor_weak_unref, device); } device->cursor_actor = NULL; } /* * _clutter_input_device_set_actor: * @device: a #ClutterInputDevice * @actor: a #ClutterActor * * Sets the actor under the pointer coordinates of @device * * This function is called by _clutter_input_device_update() * and it will: * * - queue a %CLUTTER_LEAVE event on the previous pointer actor * of @device, if any * - set to %FALSE the :has-pointer property of the previous * pointer actor of @device, if any * - queue a %CLUTTER_ENTER event on the new pointer actor * - set to %TRUE the :has-pointer property of the new pointer * actor */ void _clutter_input_device_set_actor (ClutterInputDevice *device, ClutterActor *actor) { ClutterActor *old_actor; ClutterEvent cev; g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); if (actor == device->cursor_actor) return; old_actor = device->cursor_actor; if (old_actor != NULL) { cev.crossing.type = CLUTTER_LEAVE; cev.crossing.time = device->current_time; cev.crossing.flags = 0; cev.crossing.stage = device->stage; cev.crossing.source = device->cursor_actor; cev.crossing.x = device->current_x; cev.crossing.y = device->current_y; cev.crossing.device = device; cev.crossing.related = actor; /* we need to make sure that this event is processed before * any other event we might have queued up until now, so we * go on and synthesize the event emission */ _clutter_process_event (&cev); /* processing the event might have destroyed the actor */ if (device->cursor_actor != NULL) { _clutter_actor_set_has_pointer (device->cursor_actor, FALSE); g_object_weak_unref (G_OBJECT (device->cursor_actor), cursor_weak_unref, device); device->cursor_actor = NULL; } } if (actor != NULL) { cev.crossing.type = CLUTTER_ENTER; cev.crossing.time = device->current_time; cev.crossing.flags = 0; cev.crossing.stage = device->stage; cev.crossing.x = device->current_x; cev.crossing.y = device->current_y; cev.crossing.device = device; CLUTTER_NOTE (EVENT, "Device '%s' entering '%s' at %d, %d", device->device_name, clutter_actor_get_name (actor) != NULL ? clutter_actor_get_name (actor) : G_OBJECT_TYPE_NAME (actor), device->current_x, device->current_y); /* if there is an actor overlapping the Stage boundary and we * don't do this check then we'll emit an ENTER event only on * the actor instead of emitting it on the Stage *and* the * actor */ if (old_actor == NULL && actor != CLUTTER_ACTOR (device->stage)) { cev.crossing.source = CLUTTER_ACTOR (device->stage); cev.crossing.related = NULL; CLUTTER_NOTE (EVENT, "Adding Crossing[Enter] event for Stage"); _clutter_process_event (&cev); cev.crossing.source = actor; cev.crossing.related = CLUTTER_ACTOR (device->stage); } else { cev.crossing.source = actor; cev.crossing.related = old_actor; } /* as above: we need to make sure that this event is processed * before any other event we might have queued up until now, so * we go on and synthesize the event emission */ _clutter_process_event (&cev); } device->cursor_actor = actor; if (device->cursor_actor != NULL) { g_object_weak_ref (G_OBJECT (device->cursor_actor), cursor_weak_unref, device); _clutter_actor_set_has_pointer (device->cursor_actor, TRUE); } } /** * clutter_input_device_get_device_type: * @device: a #ClutterInputDevice * * Retrieves the type of @device * * Return value: the type of the device * * Since: 1.0 */ ClutterInputDeviceType clutter_input_device_get_device_type (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), CLUTTER_POINTER_DEVICE); return device->device_type; } /** * clutter_input_device_get_device_id: * @device: a #ClutterInputDevice * * Retrieves the unique identifier of @device * * Return value: the identifier of the device * * Since: 1.0 */ gint clutter_input_device_get_device_id (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), -1); return device->id; } /** * clutter_input_device_get_device_coords: * @device: a #ClutterInputDevice of type %CLUTTER_POINTER_DEVICE * @x: (out): return location for the X coordinate * @y: (out): return location for the Y coordinate * * Retrieves the latest coordinates of the pointer of @device * * Since: 1.2 */ void clutter_input_device_get_device_coords (ClutterInputDevice *device, gint *x, gint *y) { g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); if (x) *x = device->current_x; if (y) *y = device->current_y; } /* * _clutter_input_device_update: * @device: a #ClutterInputDevice * * Updates the input @device by determining the #ClutterActor underneath the * pointer's cursor * * This function calls _clutter_input_device_set_actor() if needed. * * This function only works for #ClutterInputDevice of type * %CLUTTER_POINTER_DEVICE. * * Since: 1.2 */ ClutterActor * _clutter_input_device_update (ClutterInputDevice *device) { ClutterStage *stage; ClutterActor *new_cursor_actor; ClutterActor *old_cursor_actor; gint x, y; if (device->device_type == CLUTTER_KEYBOARD_DEVICE) return NULL; stage = device->stage; if (G_UNLIKELY (stage == NULL)) { CLUTTER_NOTE (EVENT, "No stage defined for device '%s'", clutter_input_device_get_device_name (device)); return NULL; } clutter_input_device_get_device_coords (device, &x, &y); old_cursor_actor = device->cursor_actor; new_cursor_actor = _clutter_do_pick (stage, x, y, CLUTTER_PICK_REACTIVE); /* if the pick could not find an actor then we do not update the * input device, to avoid ghost enter/leave events; the pick should * never fail, except for bugs in the glReadPixels() implementation * in which case this is the safest course of action anyway */ if (new_cursor_actor == NULL) return NULL; CLUTTER_NOTE (EVENT, "Actor under cursor (device %d, at %d, %d): %s", clutter_input_device_get_device_id (device), x, y, clutter_actor_get_name (new_cursor_actor) != NULL ? clutter_actor_get_name (new_cursor_actor) : G_OBJECT_TYPE_NAME (new_cursor_actor)); /* short-circuit here */ if (new_cursor_actor == old_cursor_actor) return old_cursor_actor; _clutter_input_device_set_actor (device, new_cursor_actor); return device->cursor_actor; } /** * clutter_input_device_get_pointer_actor: * @device: a #ClutterInputDevice of type %CLUTTER_POINTER_DEVICE * * Retrieves the #ClutterActor underneath the pointer of @device * * Return value: (transfer none): a pointer to the #ClutterActor or %NULL * * Since: 1.2 */ ClutterActor * clutter_input_device_get_pointer_actor (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); g_return_val_if_fail (device->device_type == CLUTTER_POINTER_DEVICE, NULL); return device->cursor_actor; } /** * clutter_input_device_get_pointer_stage: * @device: a #ClutterInputDevice of type %CLUTTER_POINTER_DEVICE * * Retrieves the #ClutterStage underneath the pointer of @device * * Return value: (transfer none): a pointer to the #ClutterStage or %NULL * * Since: 1.2 */ ClutterStage * clutter_input_device_get_pointer_stage (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); g_return_val_if_fail (device->device_type == CLUTTER_POINTER_DEVICE, NULL); return device->stage; } /** * clutter_input_device_get_device_name: * @device: a #ClutterInputDevice * * Retrieves the name of the @device * * Return value: the name of the device, or %NULL. The returned string * is owned by the #ClutterInputDevice and should never be modified * or freed * * Since: 1.2 */ G_CONST_RETURN gchar * clutter_input_device_get_device_name (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); return device->device_name; } /** * clutter_input_device_update_from_event: * @device: a #ClutterInputDevice * @event: a #ClutterEvent * @update_stage: whether to update the #ClutterStage of the @device * using the stage of the event * * Forcibly updates the state of the @device using a #ClutterEvent * * This function should never be used by applications: it is meant * for integration with embedding toolkits, like clutter-gtk * * Embedding toolkits that disable the event collection inside Clutter * need to use this function to update the state of input devices depending * on a #ClutterEvent that they are going to submit to the event handling code * in Clutter though clutter_do_event(). Since the input devices hold the state * that is going to be used to fill in fields like the #ClutterButtonEvent * click count, or to emit synthesized events like %CLUTTER_ENTER and * %CLUTTER_LEAVE, it is necessary for embedding toolkits to also be * responsible of updating the input device state. * * For instance, this might be the code to translate an embedding toolkit * native motion notification into a Clutter #ClutterMotionEvent and ask * Clutter to process it: * * |[ * ClutterEvent c_event; * * translate_native_event_to_clutter (native_event, &c_event); * * clutter_do_event (&c_event); * ]| * * Before letting clutter_do_event() process the event, it is necessary to call * clutter_input_device_update_from_event(): * * |[ * ClutterEvent c_event; * ClutterDeviceManager *manager; * ClutterInputDevice *device; * * translate_native_event_to_clutter (native_event, &c_event); * * /* get the device manager */ * manager = clutter_device_manager_get_default (); * * /* use the default Core Pointer that Clutter * * backends register by default * */ * device = clutter_device_manager_get_core_device (manager, %CLUTTER_POINTER_DEVICE); * * /* update the state of the input device */ * clutter_input_device_update_from_event (device, &c_event, FALSE); * * clutter_do_event (&c_event); * ]| * * The @update_stage boolean argument should be used when the input device * enters and leaves a #ClutterStage; it will use the #ClutterStage field * of the passed @event to update the stage associated to the input device. * * Since: 1.2 */ void clutter_input_device_update_from_event (ClutterInputDevice *device, ClutterEvent *event, gboolean update_stage) { ClutterModifierType event_state; ClutterStage *event_stage; gfloat event_x, event_y; guint32 event_time; g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); g_return_if_fail (event != NULL); event_state = clutter_event_get_state (event); event_time = clutter_event_get_time (event); event_stage = clutter_event_get_stage (event); clutter_event_get_coords (event, &event_x, &event_y); _clutter_input_device_set_coords (device, event_x, event_y); _clutter_input_device_set_state (device, event_state); _clutter_input_device_set_time (device, event_time); if (update_stage) _clutter_input_device_set_stage (device, event_stage); } void _clutter_input_device_reset_axes (ClutterInputDevice *device) { if (device->axes != NULL) { g_array_free (device->axes, TRUE); g_object_notify_by_pspec (G_OBJECT (device), obj_props[PROP_N_AXES]); } } guint _clutter_input_device_add_axis (ClutterInputDevice *device, ClutterInputAxis axis, gdouble minimum, gdouble maximum, gdouble resolution) { ClutterAxisInfo info; guint pos; if (device->axes == NULL) device->axes = g_array_new (FALSE, TRUE, sizeof (ClutterAxisInfo)); info.axis = axis; info.min_value = minimum; info.max_value = maximum; info.resolution = resolution; switch (axis) { case CLUTTER_INPUT_AXIS_X: case CLUTTER_INPUT_AXIS_Y: info.min_axis = 0; info.max_axis = 0; break; case CLUTTER_INPUT_AXIS_XTILT: case CLUTTER_INPUT_AXIS_YTILT: info.min_axis = -1; info.max_axis = 1; break; default: info.min_axis = 0; info.max_axis = 1; } device->axes = g_array_append_val (device->axes, info); pos = device->axes->len - 1; g_object_notify_by_pspec (G_OBJECT (device), obj_props[PROP_N_AXES]); return pos; } gboolean _clutter_input_device_translate_axis (ClutterInputDevice *device, guint index_, gint value, gdouble *axis_value) { ClutterAxisInfo *info; gdouble width; gdouble real_value; if (device->axes == NULL || index_ >= device->axes->len) return FALSE; info = &g_array_index (device->axes, ClutterAxisInfo, index_); if (info->axis == CLUTTER_INPUT_AXIS_X || info->axis == CLUTTER_INPUT_AXIS_Y) return FALSE; width = info->max_value - info->min_value; real_value = (info->max_axis * (value - info->min_value) + info->min_axis * (info->max_value - value)) / width; if (axis_value) *axis_value = real_value; return TRUE; } ClutterInputAxis _clutter_input_device_get_axis (ClutterInputDevice *device, guint index_) { ClutterAxisInfo *info; if (device->axes == NULL) return CLUTTER_INPUT_AXIS_IGNORE; if (index_ >= device->axes->len) return CLUTTER_INPUT_AXIS_IGNORE; info = &g_array_index (device->axes, ClutterAxisInfo, index_); return info->axis; } guint clutter_input_device_get_n_axes (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), 0); if (device->axes != NULL) return device->axes->len; return 0; } void _clutter_input_device_set_keys (ClutterInputDevice *device, guint n_keys, gint min_keycode, gint max_keycode) { if (device->keys != NULL) g_array_free (device->keys, TRUE); device->n_keys = n_keys; device->keys = g_array_sized_new (FALSE, TRUE, sizeof (ClutterKeyInfo), n_keys); device->min_keycode = min_keycode; device->max_keycode = max_keycode; } guint clutter_input_device_get_n_keys (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), 0); if (device->keys != NULL) return device->keys->len; return 0; } void clutter_input_device_set_key (ClutterInputDevice *device, guint index_, guint keyval, ClutterModifierType modifiers) { ClutterKeyInfo *key_info; g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); g_return_if_fail (index_ < device->n_keys); g_return_if_fail (keyval >= device->min_keycode && keyval <= device->max_keycode); key_info = &g_array_index (device->keys, ClutterKeyInfo, index_); key_info->keyval = keyval; key_info->modifiers = modifiers; } gboolean clutter_input_device_get_key (ClutterInputDevice *device, guint index_, guint *keyval, ClutterModifierType *modifiers) { ClutterKeyInfo *key_info; g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), FALSE); if (device->keys == NULL) return FALSE; if (index_ > device->keys->len) return FALSE; key_info = &g_array_index (device->keys, ClutterKeyInfo, index_); if (!key_info->keyval && !key_info->modifiers) return FALSE; if (keyval) *keyval = key_info->keyval; if (modifiers) *modifiers = key_info->modifiers; return TRUE; } void _clutter_input_device_add_slave (ClutterInputDevice *master, ClutterInputDevice *slave) { if (g_list_find (master->slaves, slave) == NULL) master->slaves = g_list_prepend (master->slaves, slave); } void _clutter_input_device_remove_slave (ClutterInputDevice *master, ClutterInputDevice *slave) { if (g_list_find (master->slaves, slave) != NULL) master->slaves = g_list_remove (master->slaves, slave); } GList * clutter_input_device_get_slave_devices (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); return g_list_copy (device->slaves); } void _clutter_input_device_set_associated_device (ClutterInputDevice *device, ClutterInputDevice *associated) { if (device->associated == associated) return; if (device->associated != NULL) g_object_unref (device->associated); device->associated = associated; if (device->associated != NULL) g_object_ref (device->associated); CLUTTER_NOTE (MISC, "Associating device '%s' to device '%s'", clutter_input_device_get_device_name (device), device->associated != NULL ? clutter_input_device_get_device_name (device->associated) : "(none)"); if (device->device_mode != CLUTTER_INPUT_MODE_MASTER) { if (device->associated != NULL) device->device_mode = CLUTTER_INPUT_MODE_SLAVE; else device->device_mode = CLUTTER_INPUT_MODE_FLOATING; g_object_notify_by_pspec (G_OBJECT (device), obj_props[PROP_DEVICE_MODE]); } } ClutterInputDevice * clutter_input_device_get_associated_device (ClutterInputDevice *device) { g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); return device->associated; } void _clutter_input_device_select_stage_events (ClutterInputDevice *device, ClutterStage *stage, gint event_mask) { g_signal_emit (device, device_signals[SELECT_STAGE_EVENTS], 0, stage, event_mask); }