[Xfce4-commits] <midori:master> Implement direction-based mouse gesture configuration
Christian Dywan
noreply at xfce.org
Tue Mar 26 08:30:01 CET 2013
Updating branch refs/heads/master
to ea2ba1945057936372ed2ea5eaebc3c5ae90e1c7 (commit)
from 15f43889607f1261fc031bcaf2aa844787bb2bd6 (commit)
commit ea2ba1945057936372ed2ea5eaebc3c5ae90e1c7
Author: Christian Dywan <christian at twotoasts.de>
Date: Tue Mar 26 08:26:21 2013 +0100
Implement direction-based mouse gesture configuration
Fixes: https://bugs.launchpad.net/midori/+bug/968677
extensions/mouse-gestures.c | 399 +++++++++++++++++++++++++++++++++----------
1 files changed, 308 insertions(+), 91 deletions(-)
diff --git a/extensions/mouse-gestures.c b/extensions/mouse-gestures.c
index 3941594..6339797 100644
--- a/extensions/mouse-gestures.c
+++ b/extensions/mouse-gestures.c
@@ -11,49 +11,207 @@
*/
#include <midori/midori.h>
+#include <math.h>
typedef struct _MouseGesture MouseGesture;
typedef enum _MouseButton MouseButton;
-enum _MouseButton {
+enum _MouseButton
+{
MOUSE_BUTTON_LEFT = 1,
MOUSE_BUTTON_RIGHT = 3,
MOUSE_BUTTON_MIDDLE = 2,
MOUSE_BUTTON_UNSET = 0
};
-struct MouseGestureNode {
+/* equivalent to the angle measured anticlockwise from east, divided by 45 or pi/4 */
+typedef enum
+{
+ STROKE_EAST = 0,
+ STROKE_NORTHEAST,
+ STROKE_NORTH,
+ STROKE_NORTHWEST,
+ STROKE_WEST,
+ STROKE_SOUTHWEST,
+ STROKE_SOUTH,
+ STROKE_SOUTHEAST,
+ STROKE_NONE,
+} MouseGestureDirection;
+
+static const gchar* direction_names[]=
+{
+ "E",
+ "NE",
+ "N",
+ "NW",
+ "W",
+ "SW",
+ "S",
+ "SE",
+ "NONE",
+};
+
+#define N_DIRECTIONS 8
+
+#define DEVIANCE (15 * M_PI / 180)
+#define MINLENGTH 30
+
+char** config_actions = NULL;
+MouseGestureDirection** config_gestures = NULL;
+
+const char* default_actions[]=
+{
+ "TabClose",
+ "Reload",
+ "TabNew",
+ "Stop",
+ "Forward",
+ "Back",
+ NULL
+};
+
+const MouseGestureDirection default_gesture_strokes[] =
+{
+ STROKE_SOUTH, STROKE_EAST, STROKE_NONE,
+ STROKE_SOUTH, STROKE_WEST, STROKE_NONE,
+ STROKE_SOUTH, STROKE_NONE,
+ STROKE_NORTH, STROKE_NONE,
+ STROKE_EAST, STROKE_NONE,
+ STROKE_WEST, STROKE_NONE,
+ STROKE_NONE,
+};
+
+const MouseGestureDirection* default_gestures[] =
+{
+ &default_gesture_strokes[0],
+ &default_gesture_strokes[3],
+ &default_gesture_strokes[6],
+ &default_gesture_strokes[8],
+ &default_gesture_strokes[10],
+ &default_gesture_strokes[12],
+ &default_gesture_strokes[14],
+};
+
+static gboolean
+parse_direction (const char* str, MouseGestureDirection* dir)
+{
+ int i;
+ for (i = 0; i < N_DIRECTIONS; i++)
+ {
+ if(!strcmp(str, direction_names[i]))
+ {
+ *dir = i;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+strokes_equal (const MouseGestureDirection* a, const MouseGestureDirection* b)
+{
+ int i;
+ for (i = 0; a[i] != STROKE_NONE && b[i] != STROKE_NONE; i++)
+ {
+ if(a[i] != b[i])
+ return FALSE;
+ }
+ return a[i] == b[i];
+}
+
+struct MouseGestureNode
+{
double x;
double y;
};
-struct _MouseGesture {
+static guint
+dist_sqr (guint x1, guint y1, guint x2, guint y2)
+{
+ guint xdiff = abs(x1 - x2);
+ guint ydiff = abs(y1 - y2);
+ return xdiff * xdiff + ydiff * ydiff;
+}
+
+static float
+get_angle_for_direction (MouseGestureDirection direction)
+{
+ return direction * 2 * M_PI / N_DIRECTIONS;
+}
+
+static MouseGestureDirection
+nearest_direction_for_angle (float angle)
+{
+ /* move halfway to the next direction so we can floor to round */
+ angle += M_PI / N_DIRECTIONS;
+
+ /* ensure we stay within [0, 2pi) */
+ if (angle >= 2 * M_PI)
+ angle -= 2 * M_PI;
+
+ return (MouseGestureDirection)((angle * N_DIRECTIONS) / (2* M_PI));
+}
+
+static gboolean
+vector_follows_direction (float angle, float distance, MouseGestureDirection direction)
+{
+ if (direction == STROKE_NONE)
+ return distance < MINLENGTH / 2;
+
+ float dir_angle = get_angle_for_direction (direction);
+ if (fabsf(angle - dir_angle) < DEVIANCE || fabsf(angle - dir_angle + 2 * M_PI) < DEVIANCE)
+ return TRUE;
+
+ if(distance < MINLENGTH / 2)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* returns the angle in the range [0, 2pi) (anticlockwise from east) from point 1 to 2 */
+static float
+get_angle_between_points (guint x1, guint y1, guint x2, guint y2)
+{
+ float distance = sqrtf (dist_sqr (x1, y1, x2, y2));
+
+ /* compute the angle of the vector from a to b */
+ float cval=((signed int)x2 - (signed int)x1) / distance;
+ float angle = acosf (cval);
+ if(y2 > y1)
+ angle = 2 * M_PI - angle;
+
+ return angle;
+}
+
+#define N_NODES 8
+
+struct _MouseGesture
+{
MouseButton button;
- struct MouseGestureNode start;
- struct MouseGestureNode middle;
- struct MouseGestureNode end;
+ MouseGestureDirection strokes[N_NODES + 1];
+ struct MouseGestureNode locations[N_NODES];
+ struct MouseGestureNode last_pos;
+ float last_distance;
+ /* the index of the location to be filled next */
+ guint count;
MouseButton last;
};
-#define DEVIANCE 20
-#define MINLENGTH 50
-
MouseGesture *gesture = NULL;
-void mouse_gesture_clear (MouseGesture *g)
+static void
+mouse_gesture_clear (MouseGesture *g)
{
- g->start.x = 0;
- g->start.y = 0;
- g->middle.x = 0;
- g->middle.y = 0;
- g->end.x = 0;
- g->end.y = 0;
+ memset(g->locations, 0, sizeof(g->locations));
+ g->strokes[0] = STROKE_NONE;
+ g->count = 0;
+ g->last_distance = 0;
g->last = MOUSE_BUTTON_UNSET;
}
MouseGesture* mouse_gesture_new (void)
{
- MouseGesture* g = g_new (MouseGesture, 1);
+ MouseGesture* g = g_slice_new (MouseGesture);
mouse_gesture_clear (g);
return g;
@@ -68,10 +226,11 @@ mouse_gestures_button_press_event_cb (GtkWidget* web_view,
{
/* If the gesture was previously cleaned,
start a new gesture and coordinates. */
- if (gesture->last == MOUSE_BUTTON_UNSET)
+ if (gesture->count == MOUSE_BUTTON_UNSET)
{
- gesture->start.x = event->button.x;
- gesture->start.y = event->button.y;
+ gesture->locations[gesture->count].x = event->button.x;
+ gesture->locations[gesture->count].y = event->button.y;
+ gesture->last_pos = gesture->locations[gesture->count];
gesture->last = event->button.button;
}
return TRUE;
@@ -85,27 +244,58 @@ mouse_gestures_motion_notify_event_cb (GtkWidget* web_view,
GdkEvent* event,
MidoriBrowser* browser)
{
+ /* wait until a button has been pressed */
if (gesture->last != MOUSE_BUTTON_UNSET)
{
- guint x, y;
+ guint x, y, oldx, oldy;
+ float angle, distance;
+ MouseGestureDirection old_direction, new_direction;
x = event->motion.x;
y = event->motion.y;
+ oldx = gesture->locations[gesture->count].x;
+ oldy = gesture->locations[gesture->count].y;
+
+ old_direction = gesture->strokes[gesture->count];
+
+ angle = get_angle_between_points (oldx, oldy, x, y);
+ distance = sqrtf (dist_sqr (oldx, oldy, x, y));
- if ((gesture->start.x - x < DEVIANCE && gesture->start.x - x > -DEVIANCE) ||
- (gesture->start.y - y < DEVIANCE && gesture->start.y - y > -DEVIANCE))
+ /* wait until minimum distance has been reached to set an initial direction. */
+ if (old_direction == STROKE_NONE)
{
- gesture->middle.x = x;
- gesture->middle.y = y;
- return TRUE;
+ if (distance >= MINLENGTH)
+ {
+ gesture->strokes[gesture->count] = nearest_direction_for_angle (angle);
+ if(midori_debug ("adblock:match"))
+ g_debug ("detected %s\n", direction_names[gesture->strokes[gesture->count]]);
+ }
}
- else if ((gesture->middle.x - x < DEVIANCE && gesture->middle.x - x > -DEVIANCE) ||
- (gesture->middle.y - y < DEVIANCE && gesture->middle.y - y > -DEVIANCE))
+ else if (!vector_follows_direction (angle, distance, old_direction)
+ || distance < gesture->last_distance)
{
- gesture->end.x = x;
- gesture->end.y = y;
- return TRUE;
+ /* if path curves or we've reversed our movement, try to detect a new direction */
+ angle = get_angle_between_points (gesture->last_pos.x, gesture->last_pos.y, x, y);
+ new_direction = nearest_direction_for_angle (angle);
+
+ if (new_direction != old_direction && gesture->count + 1 < N_NODES)
+ {
+ /* record this node and return to an indeterminate direction */
+ gesture->count++;
+ gesture->strokes[gesture->count] = STROKE_NONE;
+ gesture->locations[gesture->count].x = x;
+ gesture->locations[gesture->count].y = y;
+ gesture->last_distance = 0;
+ }
}
+ else if(distance > gesture->last_distance)
+ {
+ /* if following the same direction, store the progress along it for later divergence checks */
+ gesture->last_pos.x = x;
+ gesture->last_pos.y = y;
+ gesture->last_distance = distance;
+ }
+ return TRUE;
}
return FALSE;
@@ -125,69 +315,31 @@ mouse_gestures_button_release_event_cb (GtkWidget* web_view,
GdkEventButton* event,
MidoriView* view)
{
- /* All mouse gestures will use this mouse button */
- if (gesture->last == gesture->button)
+ int i;
+
+ if (gesture->strokes[gesture->count] != STROKE_NONE)
{
- /* The initial horizontal move is between the bounds */
- if ((gesture->middle.x - gesture->start.x < DEVIANCE) &&
- (gesture->middle.x - gesture->start.x > -DEVIANCE))
- {
- /* We initially moved down more than MINLENGTH pixels */
- if (gesture->middle.y > gesture->start.y + MINLENGTH)
- {
- /* Then we the final vertical move is between the bounds and
- we moved right more than MINLENGTH pixels */
- if ((gesture->middle.y - gesture->end.y < DEVIANCE) &&
- (gesture->middle.y - gesture->end.y > -DEVIANCE) &&
- (gesture->end.x > gesture->middle.x + MINLENGTH))
- /* We moved down then right: close the tab */
- return mouse_gestures_activate_action (view, "TabClose");
- /* Then we the final vertical move is between the bounds and
- we moved left more than MINLENGTH pixels */
- else if ((gesture->middle.y - gesture->end.y < DEVIANCE) &&
- (gesture->middle.y - gesture->end.y > -DEVIANCE) &&
- (gesture->end.x + MINLENGTH < gesture->middle.x))
- /* We moved down then left: reload */
- return mouse_gestures_activate_action (view, "Reload");
- /* The end node was never updated, we only did a vertical move */
- else if(gesture->end.y == 0 && gesture->end.x == 0)
- /* We moved down then: create a new tab */
- return mouse_gestures_activate_action (view, "TabNew");
- }
- /* We initially moved up more than MINLENGTH pixels */
- else if (gesture->middle.y + MINLENGTH < gesture->start.y)
- {
- /* The end node was never updated, we only did a vertical move */
- if (gesture->end.y == 0 && gesture->end.x == 0)
- /* We moved up: stop */
- return mouse_gestures_activate_action (view, "Stop");
- }
- }
- /* The initial horizontal move is between the bounds */
- else if ((gesture->middle.y - gesture->start.y < DEVIANCE) &&
- (gesture->middle.y - gesture->start.y > -DEVIANCE))
+ gesture->count++;
+ gesture->strokes[gesture->count] = STROKE_NONE;
+ }
+
+ const MouseGestureDirection** gestures = config_gestures ?
+ (const MouseGestureDirection**)config_gestures :
+ default_gestures;
+ const gchar** actions = config_actions ? (const char**)config_actions : default_actions;
+
+ for(i = 0; gestures[i][0] != STROKE_NONE; i++)
+ {
+ if(strokes_equal (gesture->strokes, gestures[i]))
{
- /* We initially moved right more than MINLENGTH pixels */
- if (gesture->middle.x > gesture->start.x + MINLENGTH)
- {
- /* The end node was never updated, we only did an horizontal move */
- if (gesture->end.x == 0 && gesture->end.y == 0)
- /* We moved right: forward */
- return mouse_gestures_activate_action (view, "Forward");
- }
- /* We initially moved left more than MINLENGTH pixels */
- else if (gesture->middle.x + MINLENGTH < gesture->start.x)
- {
- /* The end node was never updated, we only did an horizontal move */
- if (gesture->end.x == 0 && gesture->end.y == 0)
- /* We moved left: back */
- return mouse_gestures_activate_action (view, "Back");
- }
+ mouse_gesture_clear (gesture);
+ return mouse_gestures_activate_action (view, actions[i]);
}
- mouse_gesture_clear (gesture);
}
- if (MIDORI_EVENT_CONTEXT_MENU (event))
+ mouse_gesture_clear (gesture);
+
+ if (MIDORI_EVENT_CONTEXT_MENU (event) && 0)
{
GtkWidget* menu = gtk_menu_new ();
midori_view_populate_popup (view, menu, TRUE);
@@ -200,6 +352,62 @@ mouse_gestures_button_release_event_cb (GtkWidget* web_view,
}
static void
+mouse_gestures_load_config (MidoriExtension* extension)
+{
+ int i;
+ gchar* config_file;
+ gsize n_keys;
+ gchar** keys;
+ GKeyFile* keyfile;
+
+ config_file = g_build_filename (midori_extension_get_config_dir (extension),
+ "gestures", NULL);
+ keyfile = g_key_file_new ();
+ g_key_file_load_from_file (keyfile, config_file, G_KEY_FILE_NONE, NULL);
+ g_free (config_file);
+
+ if (!keyfile)
+ return;
+
+ keys = g_key_file_get_keys (keyfile, "gestures", &n_keys, NULL);
+ if (!keys)
+ return;
+
+ if(config_gestures)
+ {
+ g_strfreev ((gchar**)config_gestures);
+ g_strfreev (config_actions);
+ }
+ config_gestures = g_malloc ((n_keys + 1) * sizeof (MouseGestureDirection*));
+ config_actions = g_malloc (n_keys * sizeof (gchar*));
+
+ for(i = 0; keys[i]; i++)
+ {
+ gsize n_strokes;
+ int j;
+ gchar** stroke_strings = g_key_file_get_string_list (keyfile, "gestures", keys[i], &n_strokes,
+ NULL);
+
+ config_gestures[i] = g_malloc ((n_strokes + 1) * sizeof (MouseGestureDirection));
+
+ for (j = 0; j < n_strokes; j++)
+ {
+ if (!parse_direction (stroke_strings[j], &config_gestures[i][j]))
+ g_warning ("mouse-gestures: failed to parse direction \"%s\"\n", stroke_strings[j]);
+ }
+ config_gestures[i][j] = STROKE_NONE;
+
+ config_actions[i] = keys[i];
+ g_strfreev (stroke_strings);
+ }
+ config_gestures[i] = g_malloc (sizeof (MouseGestureDirection));
+ config_gestures[i][0] = STROKE_NONE;
+
+ g_free (keys);
+ g_key_file_free (keyfile);
+}
+
+static void
mouse_gestures_add_tab_cb (MidoriBrowser* browser,
MidoriView* view,
MidoriExtension* extension)
@@ -268,7 +476,14 @@ mouse_gestures_deactivate_cb (MidoriExtension* extension,
for (; tabs; tabs = g_list_next (tabs))
mouse_gestures_deactivate_tabs (tabs->data, browser);
g_list_free (tabs);
- g_free (gesture);
+ g_slice_free (MouseGesture, gesture);
+ if(config_gestures)
+ {
+ g_strfreev ((gchar**)config_gestures);
+ config_gestures = NULL;
+ g_strfreev (config_actions);
+ config_actions = NULL;
+ }
}
static void
@@ -280,6 +495,7 @@ mouse_gestures_activate_cb (MidoriExtension* extension,
gesture = mouse_gesture_new ();
gesture->button = midori_extension_get_integer (extension, "button");
+ mouse_gestures_load_config (extension);
browsers = katze_object_get_object (app, "browsers");
KATZE_ARRAY_FOREACH_ITEM (browser, browsers)
@@ -296,9 +512,10 @@ extension_init (void)
MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION,
"name", _("Mouse Gestures"),
"description", _("Control Midori by moving the mouse"),
- "version", "0.1" MIDORI_VERSION_SUFFIX,
+ "version", "0.2" MIDORI_VERSION_SUFFIX,
"authors", "Matthias Kruk <mkruk at matthiaskruk.de>", NULL);
midori_extension_install_integer (extension, "button", MOUSE_BUTTON_RIGHT);
+ midori_extension_install_integer (extension, "actions", MOUSE_BUTTON_RIGHT);
g_signal_connect (extension, "activate",
G_CALLBACK (mouse_gestures_activate_cb), NULL);
More information about the Xfce4-commits
mailing list