From 75aeaa73faaafb3b6a0aa227019d0fcd0010b475 Mon Sep 17 00:00:00 2001 From: Arne Bachmann Date: Thu, 31 May 2018 08:28:03 +0000 Subject: [PATCH 1/5] Update README.md Corrected header Markdown formatting --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49096f8..dd203d1 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ wyager's Proof-of-concept code for a Leap Motion-based mouse controller. It now The most recent version is on Github at github.com/openleap/pyleapmouse. -###Configuration: +### Configuration ### 1. Launch the Leap app (if not launched already) and plug in your Leap 2. If you have not done so already, Configure your Leap screen from the Leap menu. -3. WINDOWS USERS: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. -4. LINUX USERS: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. +3. 'WINDOWS USERS': You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. +4. 'LINUX USERS': You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. 5. `cd` to the directory all this stuff is in and run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files. 6. Launch with the --palm argument to run in palm mode (with much more accurate two-handed control). -###Usage with Finger Mode (python PyLeapMouse.py --finger) (default): +### Usage with Finger Mode (python PyLeapMouse.py --finger) (default) ### 1. Insert your hand into frame. 2. The forwardmost finger that the program detects is the mouse finger. Where it points, the cursor goes. 3. Stick your thumb out (see note) to click down, and fold your thumb in to click up. 4. Using two pointer fingers (e.g. index and middle) goes into scroll mode, which is not very intuitive but shows how it might work. The fingertips must be within a short distance of each other to activate scroll mode. -###Usage with Palm Mode (python PyLeapMouse.py --palm): +### Usage with Palm Mode (python PyLeapMouse.py --palm) ### Operation is as follows: One hand in frame: The tilt of this hand moves the mouse. Two hands in frame: Left hand controls action. @@ -28,7 +28,7 @@ Two hands in frame: Left hand controls action. Two fingers open: Scrolling. Scrolling with right hand movement. This is a somewhat unintuitive method of operation, but I find that it gives exceptionally better control than the most obvious "point-at-screen" method of mouse control. With this two-handed tilt based mode, it is easy to hit and properly engage small buttons, scroll through webpages, etc. -###Usage with Motion Mode (python PyLeapMouse.py --motion): +### Usage with Motion Mode (python PyLeapMouse.py --motion) ### Movements are associated with commands listed in a file `commands.ini` placed at the root folder. Here is an example of what the file should look like : [screentap] @@ -63,9 +63,9 @@ Movements are associated with commands listed in a file `commands.ini` placed at 4finger: rhythmbox-client --pause 5finger: rhythmbox-client --pause -Every commands could have a different behaviour if 1, 2, 3 ... 10 fingers are recognized but It's recommanded to use the same command for each number of fingers due to a lack of precision with Leap Motion. +Every commands could have a different behaviour if 1, 2, 3 ... 10 fingers are recognized but it's recommended to use the same command for each number of fingers due to a lack of precision with *Leap Motion*. -###Notes: +### Notes ### This is a spare-time project, so it's not perfect quality. However, I tried to keep the code clean and readable. Let me know if you find any bugs (which there are certainly at least a few of). The contents of the files are as follows: PyLeapMouse.py: The actual program @@ -78,14 +78,14 @@ Geometry.py: Geometric functions MiscFunctions.py: Things that aren't strictly geometry and aren't specific to any interface style README.md: You are here -###Advanced Options: +### Advanced Options ### `--smooth-aggressiveness [value]` sets the number of samples to use for pointer finger mouse smoothing. `--smooth-falloff [value]` sets the rate at which previous samples lose importance. For every sample back in time, the previous location of the mouse is weighted with weight smooth_falloff^(-#sample). So if smooth_falloff = 1.2, the current frame has weight 1/(1.2^0)=1, but the frame from 5 frames ago has weight 1/(1.2^5) = .4 By default, the smooth aggressiveness is 8 frames with a falloff of 1.3. -###TODO: +### TODO ### Add proper relative mouse movement. Should be pretty easy on Windows, not sure how to do so on OS X. Add multiple monitor support for absolute mouse mode (and OS X's pseudo-relative mode). Use PyUserInput for all mouse input? Or use Xlib directly for Linux? From 266ee86f9bc5e2d2d1c2b73dae7a0855cfdb4fbe Mon Sep 17 00:00:00 2001 From: Arne Bachmann Date: Thu, 31 May 2018 08:36:47 +0000 Subject: [PATCH 2/5] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd203d1..92dd871 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ The most recent version is on Github at github.com/openleap/pyleapmouse. ### Configuration ### 1. Launch the Leap app (if not launched already) and plug in your Leap 2. If you have not done so already, Configure your Leap screen from the Leap menu. -3. 'WINDOWS USERS': You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. -4. 'LINUX USERS': You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. +3. `WINDOWS USERS`: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. +4. `LINUX USERS`: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. 5. `cd` to the directory all this stuff is in and run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files. 6. Launch with the --palm argument to run in palm mode (with much more accurate two-handed control). From 8d4223fbce617150984096b48c8a00bea2eee690 Mon Sep 17 00:00:00 2001 From: Arne Bachmann Date: Thu, 31 May 2018 09:17:28 +0000 Subject: [PATCH 3/5] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92dd871..565d032 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ The most recent version is on Github at github.com/openleap/pyleapmouse. ### Configuration ### 1. Launch the Leap app (if not launched already) and plug in your Leap -2. If you have not done so already, Configure your Leap screen from the Leap menu. -3. `WINDOWS USERS`: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. -4. `LINUX USERS`: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. -5. `cd` to the directory all this stuff is in and run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files. -6. Launch with the --palm argument to run in palm mode (with much more accurate two-handed control). +1. If you have not done so already, Configure your Leap screen from the Leap menu. +1. See [here](https://developer.leapmotion.com/documentation/python/devguide/Project_Setup.html) for setup instructions. + 1. `WINDOWS USERS`: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. + 1. `LINUX USERS`: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. +1. `cd` to the directory all this stuff is in and run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files. +1. Launch with the --palm argument to run in palm mode (with much more accurate two-handed control). ### Usage with Finger Mode (python PyLeapMouse.py --finger) (default) ### 1. Insert your hand into frame. From fd52e8919016691a8716f96e1f46f852a99bd699 Mon Sep 17 00:00:00 2001 From: ArneBachmann Date: Thu, 31 May 2018 11:32:48 +0200 Subject: [PATCH 4/5] Updated docs --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 565d032..83813fa 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,26 @@ wyager's Proof-of-concept code for a Leap Motion-based mouse controller. It now The most recent version is on Github at github.com/openleap/pyleapmouse. +### Installation ### +- Download the [Leap controller](https://developer.leapmotion.com/get-started/) via the link in the text "`by itself`" +- Download the [SDK](https://developer.leapmotion.com/get-started/) via the green button (requires registration) +- Extract the `LeapSDK` folder from the archive + ### Configuration ### -1. Launch the Leap app (if not launched already) and plug in your Leap -1. If you have not done so already, Configure your Leap screen from the Leap menu. -1. See [here](https://developer.leapmotion.com/documentation/python/devguide/Project_Setup.html) for setup instructions. - 1. `WINDOWS USERS`: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to the "Windows" folder. These files are already included for OS X users, because OS X is 64-bit only. - 1. `LINUX USERS`: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed. -1. `cd` to the directory all this stuff is in and run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files. -1. Launch with the --palm argument to run in palm mode (with much more accurate two-handed control). +1. Launch the Leap app and plug in your Leap, if not already done +1. Configure your Leap screen from the Leap menu, if not already done +1. See [here](https://developer.leapmotion.com/documentation/python/devguide/Project_Setup.html) for setup instructions, in detail: + 1. `WINDOWS USERS`: You must copy the Leap.py file and all required library files (.libs and .dlls) from your Leap SDK folder to this project's `.\Windows\` folder + 1. `LINUX USERS`: You must copy the Leap.py file and all required library files (.sos) from your Leap SDK folder to the "Linux" folder (same reason as for Windows); alternatively, add the directory (or directories) containing them to your PYTHONPATH. Additionally, you must have the PyUserInput and Xlib Python modules installed + 1. `MACOS USERS`: These files are already included for OS X users, because OS X is 64-bit only +1. Run `python PyLeapMouse.py` (minus quotes) or just double-click PyLeapMouse.py if you have your computer configured to launch .py files +1. Launch with the `--palm` argument to run in palm mode (with much more accurate two-handed control). ### Usage with Finger Mode (python PyLeapMouse.py --finger) (default) ### -1. Insert your hand into frame. -2. The forwardmost finger that the program detects is the mouse finger. Where it points, the cursor goes. -3. Stick your thumb out (see note) to click down, and fold your thumb in to click up. -4. Using two pointer fingers (e.g. index and middle) goes into scroll mode, which is not very intuitive but shows how it might work. The fingertips must be within a short distance of each other to activate scroll mode. +1. Insert your hand into frame +1. The forwardmost finger that the program detects is the mouse finger. Where it points, the cursor goes +1. Stick your thumb out (see note) to click down, and fold your thumb in to click up +1. Using two pointer fingers (e.g. index and middle) goes into scroll mode, which is not very intuitive but shows how it might work. The fingertips must be within a short distance of each other to activate scroll mode. ### Usage with Palm Mode (python PyLeapMouse.py --palm) ### Operation is as follows: From eeb761793aa3ffb650ad5f3ddecc956ebfb57e81 Mon Sep 17 00:00:00 2001 From: ArneBachmann Date: Thu, 31 May 2018 11:42:55 +0200 Subject: [PATCH 5/5] Code comment and formatting cleanups --- FingerControl.py | 109 +++++++++++++++++++++++------------------------ Geometry.py | 18 ++++---- MiscFunctions.py | 100 +++++++++++++++++++++---------------------- MotionControl.py | 78 ++++++++++++++++----------------- PalmControl.py | 85 ++++++++++++++++++------------------ PyLeapMouse.py | 40 +++++++++-------- 6 files changed, 214 insertions(+), 216 deletions(-) diff --git a/FingerControl.py b/FingerControl.py index 1eb0656..4be51d8 100755 --- a/FingerControl.py +++ b/FingerControl.py @@ -1,7 +1,6 @@ -#William Yager -#Leap Python mouse controller POC -#This file is for pointer-finger-based control (--finger and default) - +# William Yager +# Leap Python mouse controller POC +# This file is for pointer-finger-based control (--finger and default) import math import sys @@ -9,16 +8,16 @@ from MiscFunctions import * -class Finger_Control_Listener(Leap.Listener): #The Listener that we attach to the controller. This listener is for pointer finger movement +class Finger_Control_Listener(Leap.Listener): # The Listener that we attach to the controller. This listener is for pointer finger movement def __init__(self, mouse, smooth_aggressiveness=8, smooth_falloff=1.3): - super(Finger_Control_Listener, self).__init__() #Initialize like a normal listener - #Initialize a bunch of stuff specific to this implementation + super(Finger_Control_Listener, self).__init__() # Initialize like a normal listener + # Initialize a bunch of stuff specific to this implementation self.screen = None - self.screen_resolution = (1920,1080) - self.cursor = mouse.absolute_cursor() #The cursor object that lets us control mice cross-platform - self.mouse_position_smoother = mouse_position_smoother(smooth_aggressiveness, smooth_falloff) #Keeps the cursor from fidgeting - self.mouse_button_debouncer = debouncer(5) #A signal debouncer that ensures a reliable, non-jumpy click - self.most_recent_pointer_finger_id = None #This holds the ID of the most recently used pointing finger, to prevent annoying switching + self.screen_resolution = (1920, 1080) + self.cursor = mouse.absolute_cursor() # The cursor object that lets us control mice cross-platform + self.mouse_position_smoother = mouse_position_smoother(smooth_aggressiveness, smooth_falloff) # Keeps the cursor from fidgeting + self.mouse_button_debouncer = debouncer(5) # A signal debouncer that ensures a reliable, non-jumpy click + self.most_recent_pointer_finger_id = None # This holds the ID of the most recently used pointing finger, to prevent annoying switching def on_init(self, controller): print "Initialized" @@ -33,7 +32,7 @@ def on_exit(self, controller): print "Exited" def on_frame(self, controller): - frame = controller.frame() #Grab the latest 3D data + frame = controller.frame() # Grab the latest 3D data finger = frame.fingers.frontmost stabilizedPosition = finger.stabilized_tip_position interactionBox = frame.interaction_box @@ -57,61 +56,61 @@ def on_frame(self, controller): elif finger_count == 2: self.cursor.set_left_button_pressed(True) self.cursor.move(normalizedPosition.x * self.screen_resolution[0], self.screen_resolution[1] - normalizedPosition.y * self.screen_resolution[1]) - #if(finger.touch_distance > -0.3 and finger.touch_zone != Leap.Pointable.ZONE_NONE): - #self.cursor.set_left_button_pressed(False) - #self.cursor.move(normalizedPosition.x * self.screen_resolution[0], self.screen_resolution[1] - normalizedPosition.y * self.screen_resolution[1]) - #elif(finger.touch_distance <= -0.4): - #self.cursor.set_left_button_pressed(True) - # print finger.touch_distance + # if(finger.touch_distance > -0.3 and finger.touch_zone != Leap.Pointable.ZONE_NONE): + # self.cursor.set_left_button_pressed(False) + # self.cursor.move(normalizedPosition.x * self.screen_resolution[0], self.screen_resolution[1] - normalizedPosition.y * self.screen_resolution[1]) + # elif(finger.touch_distance <= -0.4): + # self.cursor.set_left_button_pressed(True) + # print finger.touch_distance - def do_scroll_stuff(self, hand): #Take a hand and use it as a scroller - fingers = hand.fingers #The list of fingers on said hand - if not fingers.is_empty: #Make sure we have some fingers to work with - sorted_fingers = sort_fingers_by_distance_from_screen(fingers) #Prioritize fingers by distance from screen - finger_velocity = sorted_fingers[0].tip_velocity #Get the velocity of the forwardmost finger + def do_scroll_stuff(self, hand): # Take a hand and use it as a scroller + fingers = hand.fingers # The list of fingers on said hand + if not fingers.is_empty: # Make sure we have some fingers to work with + sorted_fingers = sort_fingers_by_distance_from_screen(fingers) # Prioritize fingers by distance from screen + finger_velocity = sorted_fingers[0].tip_velocity # Get the velocity of the forwardmost finger x_scroll = self.velocity_to_scroll_amount(finger_velocity.x) y_scroll = self.velocity_to_scroll_amount(finger_velocity.y) self.cursor.scroll(x_scroll, y_scroll) - def velocity_to_scroll_amount(self, velocity): #Converts a finger velocity to a scroll velocity - #The following algorithm was designed to reflect what I think is a comfortable - #Scrolling behavior. - vel = velocity #Save to a shorter variable - vel = vel + math.copysign(300, vel) #Add/subtract 300 to velocity + def velocity_to_scroll_amount(self, velocity): # Converts a finger velocity to a scroll velocity + # The following algorithm was designed to reflect what I think is a comfortable + # Scrolling behavior. + vel = velocity # Save to a shorter variable + vel = vel + math.copysign(300, vel) # Add/subtract 300 to velocity vel = vel / 150 - vel = vel ** 3 #Cube vel + vel = vel ** 3 # Cube vel vel = vel / 8 - vel = vel * -1 #Negate direction, depending on how you like to scroll + vel = vel * -1 # Negate direction, depending on how you like to scroll return vel - def do_mouse_stuff(self, hand): #Take a hand and use it as a mouse - fingers = hand.fingers #The list of fingers on said hand - if not fingers.is_empty: #Make sure we have some fingers to work with - pointer_finger = self.select_pointer_finger(fingers) #Determine which finger to use - + def do_mouse_stuff(self, hand): # Take a hand and use it as a mouse + fingers = hand.fingers # The list of fingers on said hand + if not fingers.is_empty: # Make sure we have some fingers to work with + pointer_finger = self.select_pointer_finger(fingers) # Determine which finger to use + try: - intersection = self.screen.intersect(pointer_finger, True) #Where the finger projection intersects with the screen - if not math.isnan(intersection.x) and not math.isnan(intersection.y): #If the finger intersects with the screen - x_coord = intersection.x * self.screen_resolution[0] #x pixel of intersection - y_coord = (1.0 - intersection.y) * self.screen_resolution[1] #y pixel of intersection - x_coord,y_coord = self.mouse_position_smoother.update((x_coord,y_coord)) #Smooth movement - self.cursor.move(x_coord,y_coord) #Move the cursor - if has_thumb(hand): #We've found a thumb! - self.mouse_button_debouncer.signal(True) #We have detected a possible click. The debouncer ensures that we don't have click jitter + intersection = self.screen.intersect(pointer_finger, True) # Where the finger projection intersects with the screen + if not math.isnan(intersection.x) and not math.isnan(intersection.y): # If the finger intersects with the screen + x_coord = intersection.x * self.screen_resolution[0] # x pixel of intersection + y_coord = (1.0 - intersection.y) * self.screen_resolution[1] # y pixel of intersection + x_coord, y_coord = self.mouse_position_smoother.update((x_coord, y_coord)) # Smooth movement + self.cursor.move(x_coord, y_coord) # Move the cursor + if has_thumb(hand): # We've found a thumb! + self.mouse_button_debouncer.signal(True) # We have detected a possible click. The debouncer ensures that we don't have click jitter else: - self.mouse_button_debouncer.signal(False) #Same idea as above (but opposite) + self.mouse_button_debouncer.signal(False) # Same idea as above (but opposite) - if self.cursor.left_button_pressed != self.mouse_button_debouncer.state: #We need to push/unpush the cursor's button - self.cursor.set_left_button_pressed(self.mouse_button_debouncer.state) #Set the cursor to click/not click + if self.cursor.left_button_pressed != self.mouse_button_debouncer.state: # We need to push/unpush the cursor's button + self.cursor.set_left_button_pressed(self.mouse_button_debouncer.state) # Set the cursor to click/not click except Exception as e: print e - def select_pointer_finger(self, possible_fingers): #Choose the best pointer finger - sorted_fingers = sort_fingers_by_distance_from_screen(possible_fingers) #Prioritize fingers by distance from screen - if self.most_recent_pointer_finger_id != None: #If we have a previous pointer finger in memory - for finger in sorted_fingers: #Look at all the fingers - if finger.id == self.most_recent_pointer_finger_id: #The previously used pointer finger is still in frame - return finger #Keep using it - #If we got this far, it means we don't have any previous pointer fingers OR we didn't find the most recently used pointer finger in the frame - self.most_recent_pointer_finger_id = sorted_fingers[0].id #This is the new pointer finger + def select_pointer_finger(self, possible_fingers): # Choose the best pointer finger + sorted_fingers = sort_fingers_by_distance_from_screen(possible_fingers) # Prioritize fingers by distance from screen + if self.most_recent_pointer_finger_id: # If we have a previous pointer finger in memory + for finger in sorted_fingers: # Look at all the fingers + if finger.id == self.most_recent_pointer_finger_id: # The previously used pointer finger is still in frame + return finger # Keep using it + # If we got this far, it means we don't have any previous pointer fingers OR we didn't find the most recently used pointer finger in the frame + self.most_recent_pointer_finger_id = sorted_fingers[0].id # This is the new pointer finger return sorted_fingers[0] diff --git a/Geometry.py b/Geometry.py index d8ab8c6..f49c4db 100755 --- a/Geometry.py +++ b/Geometry.py @@ -1,5 +1,5 @@ -#William Yager -#Leap Python mouse controller POC +# William Yager +# Leap Python mouse controller POC import math @@ -56,6 +56,7 @@ def __init__(self, point1, point2): self.point1 = point1 self.point2 = point2 #Shortest distance code based off of http://geomalgorithms.com/a07-_distance.html + def min_distance_infinite(self, other): #Return shortest distance between two lines u = self.point2 - self.point1 v = other.point2 - other.point1 @@ -80,11 +81,12 @@ def min_distance_infinite(self, other): #Return shortest distance between two l tc = (a * e - b * d) / D dP = w + u**sc - v**tc return dP.norm() - def min_distance_finite(self, other): #Return shortest distance between two segments + + def min_distance_finite(self, other): # Return shortest distance between two segments u = self.point2 - self.point1 v = other.point2 - other.point1 w = self.point1 - other.point1 - a = u * u #* here is cross product + a = u * u # * here is cross product b = u * v c = v * v d = u * w @@ -139,7 +141,7 @@ def min_distance_finite(self, other): #Return shortest distance between two seg tc = 0.0 else: tc = tN / tD - dP = w + u**sc - v**tc #I'm pretty sure dP is the actual vector linking the lines + dP = w + u**sc - v**tc # I'm pretty sure dP is the actual vector linking the lines return dP.norm() @@ -151,8 +153,8 @@ def __init__(self, point1, direction_vector): def angle_between_vectors(vector1, vector2): - #cos(theta)=dot product / (|a|*|b|) - top = vector1 * vector2 #* is dot product + # cos(theta)=dot product / (|a|*|b|) + top = vector1 * vector2 # * is dot product bottom = vector1.norm() * vector2.norm() angle = math.acos(top/bottom) - return angle #In radians + return angle # In radians diff --git a/MiscFunctions.py b/MiscFunctions.py index ee8148a..4a53857 100644 --- a/MiscFunctions.py +++ b/MiscFunctions.py @@ -1,16 +1,15 @@ -#William Yager -#Leap Python mouse controller POC -#This file contains miscellaneous functions that are not interface-specific - +# William Yager +# Leap Python mouse controller POC +# This file contains miscellaneous functions that are not interface-specific import math from leap import Leap import Geometry -#Smooths the mouse's position +# Smooths the mouse's position class mouse_position_smoother(object): def __init__(self, smooth_aggressiveness, smooth_falloff): - #Input validation + # Input validation if smooth_aggressiveness < 1: raise Exception("Smooth aggressiveness must be greater than 1.") if smooth_falloff < 1: @@ -18,11 +17,13 @@ def __init__(self, smooth_aggressiveness, smooth_falloff): self.previous_positions = [] self.smooth_falloff = smooth_falloff self.smooth_aggressiveness = int(smooth_aggressiveness) - def update(self, (x,y)): - self.previous_positions.append((x,y)) + + def update(self, (x, y)): + self.previous_positions.append((x, y)) if len(self.previous_positions) > self.smooth_aggressiveness: del self.previous_positions[0] return self.get_current_smooth_value() + def get_current_smooth_value(self): smooth_x = 0 smooth_y = 0 @@ -37,100 +38,99 @@ def get_current_smooth_value(self): smooth_y /= total_weight return smooth_x, smooth_y -class debouncer(object): #Takes a binary "signal" and debounces it. - def __init__(self, debounce_time): #Takes as an argument the number of opposite samples it needs to debounce. - self.opposite_counter = 0 #Number of contrary samples vs agreeing samples. - self.state = False #Default state. - self.debounce_time = debounce_time #Number of samples to change states (debouncing threshold). +class debouncer(object): # Takes a binary "signal" and debounces it. + def __init__(self, debounce_time): # Takes as an argument the number of opposite samples it needs to debounce. + self.opposite_counter = 0 # Number of contrary samples vs agreeing samples. + self.state = False # Default state. + self.debounce_time = debounce_time # Number of samples to change states (debouncing threshold). - def signal(self, value): #Update the signal. - if value != self.state: #We are receiving a different signal than what we have been. + def signal(self, value): # Update the signal. + if value != self.state: # We are receiving a different signal than what we have been. self.opposite_counter = self.opposite_counter + 1 - else: #We are recieving the same signal that we have been + else: # We are recieving the same signal that we have been self.opposite_counter = self.opposite_counter - 1 if self.opposite_counter < 0: self.opposite_counter = 0 if self.opposite_counter > self.debounce_time: self.opposite_counter = self.debounce_time - #No sense building up negative or huge numbers of agreeing/contrary samples + # No sense building up negative or huge numbers of agreeing/contrary samples - if self.opposite_counter >= self.debounce_time: #We have seen a lot of evidence that our internal state is wrong - self.state = not self.state #Change internal state - self.opposite_counter = 0 #We reset the number of contrary samples - return self.state #Return the debounced signal (may help keep code cleaner) + if self.opposite_counter >= self.debounce_time: # We have seen a lot of evidence that our internal state is wrong + self.state = not self.state # Change internal state + self.opposite_counter = 0 # We reset the number of contrary samples + return self.state # Return the debounced signal (may help keep code cleaner) -class n_state_debouncer(object): #A signal debouncer that has `number_of_states` states +class n_state_debouncer(object): # A signal debouncer that has `number_of_states` states def __init__(self, debounce_time, number_of_states): - self.state_counters = [0]*number_of_states #One counter for every state - self.state = 0 #Default state + self.state_counters = [0]*number_of_states # One counter for every state + self.state = 0 # Default state self.debounce_time = debounce_time def signal(self, signal_value): - if signal_value < 0 or signal_value >= len(self.state_counters): #Check for invalid state + if signal_value < 0 or signal_value >= len(self.state_counters): # Check for invalid state raise Exception("Invalid state. Out of bounds.") return - self.state_counters[signal_value] = self.state_counters[signal_value] + 1 #Increment signalled state - for i in range(0,len(self.state_counters)): - if i is not signal_value: self.state_counters[i] = self.state_counters[i] - 1 #Decrement all others - for i in range(0,len(self.state_counters)): #Fix bounds and check for a confirmed state change + self.state_counters[signal_value] = self.state_counters[signal_value] + 1 # Increment signalled state + for i in range(0, len(self.state_counters)): + if i is not signal_value: self.state_counters[i] = self.state_counters[i] - 1 # Decrement all others + for i in range(0, len(self.state_counters)): # Fix bounds and check for a confirmed state change if self.state_counters[i] < 0: self.state_counters[i] = 0 - if self.state_counters[i] >= self.debounce_time: #Confirmed new state at index i + if self.state_counters[i] >= self.debounce_time: # Confirmed new state at index i self.state_counters[i] = self.debounce_time - for x in range(0,len(self.state_counters)): - if x is not i: self.state_counters[x] = 0 #Zero out all other state counters - self.state = i #Save the new state + for x in range(0, len(self.state_counters)): + if x is not i: self.state_counters[x] = 0 # Zero out all other state counters + self.state = i # Save the new state return self.state def sort_fingers_by_distance_from_screen(fingers): - new_finger_list = [finger for finger in fingers] #Copy the list of fingers - new_finger_list.sort(key=lambda x: x.tip_position.z) #Sort by increasing z - return new_finger_list #Lower indices = closer to screen + new_finger_list = [finger for finger in fingers] # Copy the list of fingers + new_finger_list.sort(key=lambda x: x.tip_position.z) # Sort by increasing z + return new_finger_list # Lower indices = closer to screen -def has_thumb(hand): #The level of accuracy with this function is surprisingly high - if hand.fingers.empty: #We assume no thumbs +def has_thumb(hand): # The level of accuracy with this function is surprisingly high + if hand.fingers.empty: # We assume no thumbs return False distances = [] palm_position = Geometry.to_vector(hand.palm_position) - for finger in hand.fingers: #Make a list of all distances from the center of the palm + for finger in hand.fingers: # Make a list of all distances from the center of the palm finger_position = Geometry.to_vector(finger.tip_position) difference = finger_position - palm_position - distances.append(difference.norm()) #Record the distance from the palm to the fingertip + distances.append(difference.norm()) # Record the distance from the palm to the fingertip average = sum(distances)/len(distances) minimum = min(distances) - if average - minimum > 20: #Check if the finger closest to the palm is more than 20mm closer than the average distance - #Note: I have recieved feedback that a smaller value may work better. I do have big hands, however + if average - minimum > 20: # Check if the finger closest to the palm is more than 20mm closer than the average distance + # Note: I have recieved feedback that a smaller value may work better. I do have big hands, however return True else: return False -def has_two_pointer_fingers(hand): #Checks if we are using two pointer fingers - if len(hand.fingers) < 2: #Obviously not +def has_two_pointer_fingers(hand): # Checks if we are using two pointer fingers + if len(hand.fingers) < 2: # Obviously not return False sorted_fingers = sort_fingers_by_distance_from_screen(hand.fingers) finger1_pos = Geometry.to_vector(sorted_fingers[0].tip_position) finger2_pos = Geometry.to_vector(sorted_fingers[1].tip_position) difference = finger1_pos - finger2_pos - if difference.norm() < 40: #Check if the fingertips are close together + if difference.norm() < 40: # Check if the fingertips are close together return True else: return False - -#Check if the vectors of length 'vector_length' shooting out of a pair of fingers intersect within tolerance 'tolerance' +# Check if the vectors of length 'vector_length' shooting out of a pair of fingers intersect within tolerance 'tolerance' def finger_vectors_intersect(finger1, finger2, vector_length, tolerance): - #Take Leap Finger objects and produce two line segment objects + # Take Leap Finger objects and produce two line segment objects finger_1_location = Geometry.to_vector(finger1.tip_position) finger_1_direction = Geometry.to_vector(finger1.direction) - finger_1_vector = finger_1_direction.unit_vector() ** vector_length; #** is scalar mult + finger_1_vector = finger_1_direction.unit_vector() ** vector_length # ** is scalar mult finger_1_endpoint = finger_1_vector + finger_1_location finger_1_segment = Geometry.segment(finger_1_location, finger_1_endpoint) finger_2_location = Geometry.to_vector(finger2.tip_position) finger_2_direction = Geometry.to_vector(finger2.direction) - finger_2_vector = finger_2_direction.unit_vector() ** vector_length; #** is scalar mult + finger_2_vector = finger_2_direction.unit_vector() ** vector_length # ** is scalar mult finger_2_endpoint = finger_2_vector + finger_2_location finger_2_segment = Geometry.segment(finger_2_location, finger_2_endpoint) diff --git a/MotionControl.py b/MotionControl.py index 61f44cd..7965a7d 100644 --- a/MotionControl.py +++ b/MotionControl.py @@ -1,13 +1,13 @@ import sys, os, ConfigParser from leap import Leap, CircleGesture, KeyTapGesture, ScreenTapGesture, SwipeGesture -class Motion_Control_Listener(Leap.Listener): #The Listener that we attach to the controller. This listener is for motion control +class Motion_Control_Listener(Leap.Listener): # The Listener that we attach to the controller. This listener is for motion control def __init__(self, mouse): - super(Motion_Control_Listener, self).__init__() #Initialize like a normal listener + super(Motion_Control_Listener, self).__init__() # Initialize like a normal listener def on_init(self, controller): - self.read_config() #Read the config file - self.init_list_of_commands() #Initialize the list of recognized commands + self.read_config() # Read the config file + self.init_list_of_commands() # Initialize the list of recognized commands print "Initialized" @@ -16,7 +16,7 @@ def read_config(self): self.config.read("./commands.ini") def init_list_of_commands(self): - #Initialize all commands an put it in an array + # Initialize all commands an put it in an array self.commands = [ ScreentapCommand(), SwiperightCommand(), @@ -27,11 +27,11 @@ def init_list_of_commands(self): ] def on_connect(self, controller): - #Enable all gestures - controller.enable_gesture(Leap.Gesture.TYPE_CIRCLE); - controller.enable_gesture(Leap.Gesture.TYPE_KEY_TAP); - controller.enable_gesture(Leap.Gesture.TYPE_SCREEN_TAP); - controller.enable_gesture(Leap.Gesture.TYPE_SWIPE); + # Enable all gestures + controller.enable_gesture(Leap.Gesture.TYPE_CIRCLE) + controller.enable_gesture(Leap.Gesture.TYPE_KEY_TAP) + controller.enable_gesture(Leap.Gesture.TYPE_SCREEN_TAP) + controller.enable_gesture(Leap.Gesture.TYPE_SWIPE) print "Connected" @@ -42,18 +42,18 @@ def on_exit(self, controller): print "Exited" def on_frame(self, controller): - frame = controller.frame() #Grab the latest 3D data - if not frame.hands.is_empty: #Make sure we have some hands to work with - for command in self.commands: #Loop all enabled commands - if(command.applicable(frame)): #If the motion associated to the command is triggered - self.execute(frame, command.name) #Execute the command + frame = controller.frame() # Grab the latest 3D data + if not frame.hands.is_empty: # Make sure we have some hands to work with + for command in self.commands: # Loop all enabled commands + if(command.applicable(frame)): # If the motion associated to the command is triggered + self.execute(frame, command.name) # Execute the command def execute(self, frame, command_name): - number_for_fingers = self.get_fingers_code(frame) #Get a text correspond to the number of fingers - if(self.config.has_option(command_name, number_for_fingers)): #If the command if finded in the config file - syscommand = self.config.get(command_name, number_for_fingers) #Prepare the command + number_for_fingers = self.get_fingers_code(frame) # Get a text correspond to the number of fingers + if(self.config.has_option(command_name, number_for_fingers)): # If the command if finded in the config file + syscommand = self.config.get(command_name, number_for_fingers) # Prepare the command print(syscommand) - os.system(syscommand) #Execute the command + os.system(syscommand) # Execute the command def get_fingers_code(self, frame): return "%dfinger" % len(frame.fingers) @@ -62,60 +62,60 @@ def get_fingers_code(self, frame): class ScreentapCommand(): def __init__(self): self.name = "screentap" - #The name of the command in the config file + # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): return(frame.gestures()[0].type == Leap.Gesture.TYPE_SCREEN_TAP) class KeytapCommand(): def __init__(self): - self.name = "keytap" #The name of the command in the config file + self.name = "keytap" # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): return(frame.gestures()[0].type == Leap.Gesture.TYPE_KEY_TAP) class SwiperightCommand(): def __init__(self): - self.name = "swiperight" #The name of the command in the config file + self.name = "swiperight" # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): swipe = SwipeGesture(frame.gestures()[0]) return(swipe.state == Leap.Gesture.STATE_STOP - and swipe.type == Leap.Gesture.TYPE_SWIPE - and swipe.direction[0] < 0) + and swipe.type == Leap.Gesture.TYPE_SWIPE + and swipe.direction[0] < 0) class SwipeleftCommand(): def __init__(self): - self.name = "swipeleft" #The name of the command in the config file + self.name = "swipeleft" # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): swipe = SwipeGesture(frame.gestures()[0]) return(swipe.state == Leap.Gesture.STATE_STOP - and swipe.type == Leap.Gesture.TYPE_SWIPE - and swipe.direction[0] > 0) + and swipe.type == Leap.Gesture.TYPE_SWIPE + and swipe.direction[0] > 0) class ClockwiseCommand(): def __init__(self): - self.name = "clockwise" #The name of the command in the config file + self.name = "clockwise" # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): circle = CircleGesture(frame.gestures()[0]) return(circle.state == Leap.Gesture.STATE_STOP and - circle.type == Leap.Gesture.TYPE_CIRCLE and - circle.pointable.direction.angle_to(circle.normal) <= Leap.PI/4) + circle.type == Leap.Gesture.TYPE_CIRCLE and + circle.pointable.direction.angle_to(circle.normal) <= Leap.PI/4) class CounterclockwiseCommand(): def __init__(self): - self.name = "counterclockwise" #The name of the command in the config file + self.name = "counterclockwise" # The name of the command in the config file - #Return true if the command is applicable + # Return true if the command is applicable def applicable(self, frame): circle = CircleGesture(frame.gestures()[0]) return(circle.state == Leap.Gesture.STATE_STOP and - circle.type == Leap.Gesture.TYPE_CIRCLE and - circle.pointable.direction.angle_to(circle.normal) > Leap.PI/4) + circle.type == Leap.Gesture.TYPE_CIRCLE and + circle.pointable.direction.angle_to(circle.normal) > Leap.PI/4) diff --git a/PalmControl.py b/PalmControl.py index 2f67156..423c760 100755 --- a/PalmControl.py +++ b/PalmControl.py @@ -1,7 +1,6 @@ -#William Yager -#Leap Python mouse controller POC -#This file is for palm-tilt and gesture-based control (--palm) - +# William Yager +# Leap Python mouse controller POC +# This file is for palm-tilt and gesture-based control (--palm) import math from leap import Leap, Mouse @@ -9,12 +8,12 @@ from MiscFunctions import * -class Palm_Control_Listener(Leap.Listener): #The Listener that we attach to the controller. This listener is for palm tilt movement +class Palm_Control_Listener(Leap.Listener): # The Listener that we attach to the controller. This listener is for palm tilt movement def __init__(self, mouse): - super(Palm_Control_Listener, self).__init__() #Initialize like a normal listener - #Initialize a bunch of stuff specific to this implementation - self.cursor = mouse.relative_cursor() #The cursor object that lets us control mice cross-platform - self.gesture_debouncer = n_state_debouncer(5,3) #A signal debouncer that ensures a reliable, non-jumpy gesture detection + super(Palm_Control_Listener, self).__init__() # Initialize like a normal listener + # Initialize a bunch of stuff specific to this implementation + self.cursor = mouse.relative_cursor() # The cursor object that lets us control mice cross-platform + self.gesture_debouncer = n_state_debouncer(5, 3) # A signal debouncer that ensures a reliable, non-jumpy gesture detection def on_init(self, controller): print "Initialized" @@ -29,17 +28,17 @@ def on_exit(self, controller): print "Exited" def on_frame(self, controller): - frame = controller.frame() #Grab the latest 3D data - if not frame.hands.is_empty: #Make sure we have some hands to work with - rightmost_hand = None #We always have at least one "right hand" - if len(frame.hands) < 2: #Just one hand - self.do_mouse_stuff(frame.hands[0]) #If there's only one hand, we assume it's to be used for mouse control - else: #Multiple hands. We have a right AND a left - rightmost_hand = max(frame.hands, key=lambda hand: hand.palm_position.x) #Get rightmost hand - leftmost_hand = min(frame.hands, key=lambda hand: hand.palm_position.x) #Get leftmost hand - self.do_gesture_recognition(leftmost_hand, rightmost_hand) #This will run with >1 hands in frame + frame = controller.frame() # Grab the latest 3D data + if not frame.hands.is_empty: # Make sure we have some hands to work with + rightmost_hand = None # We always have at least one "right hand" + if len(frame.hands) < 2: # Just one hand + self.do_mouse_stuff(frame.hands[0]) # If there's only one hand, we assume it's to be used for mouse control + else: # Multiple hands. We have a right AND a left + rightmost_hand = max(frame.hands, key=lambda hand: hand.palm_position.x) # Get rightmost hand + leftmost_hand = min(frame.hands, key=lambda hand: hand.palm_position.x) # Get leftmost hand + self.do_gesture_recognition(leftmost_hand, rightmost_hand) # This will run with >1 hands in frame - def do_mouse_stuff(self, hand): #Take a hand and use it as a mouse + def do_mouse_stuff(self, hand): # Take a hand and use it as a mouse hand_normal_direction = Geometry.to_vector(hand.palm_normal) hand_direction = Geometry.to_vector(hand.direction) roll = hand_normal_direction.roll() @@ -47,41 +46,41 @@ def do_mouse_stuff(self, hand): #Take a hand and use it as a mouse mouse_velocity = self.convert_angles_to_mouse_velocity(roll, pitch) self.cursor.move(mouse_velocity[0], mouse_velocity[1]) - #The gesture hand signals what action to do, - #The mouse hand gives extra data (if applicable) - #Like scroll speed/direction + # The gesture hand signals what action to do, + # The mouse hand gives extra data (if applicable) + # Like scroll speed/direction def do_gesture_recognition(self, gesture_hand, mouse_hand): - if len(gesture_hand.fingers) == 2: #Two open fingers on gesture hand (scroll mode) - self.gesture_debouncer.signal(2) #Tell the debouncer we've seen this gesture - elif len(gesture_hand.fingers) == 1: #One open finger on gesture hand (click down) + if len(gesture_hand.fingers) == 2: # Two open fingers on gesture hand (scroll mode) + self.gesture_debouncer.signal(2) # Tell the debouncer we've seen this gesture + elif len(gesture_hand.fingers) == 1: # One open finger on gesture hand (click down) self.gesture_debouncer.signal(1) - else: #No open fingers or 3+ open fingers (click up/no action) + else: # No open fingers or 3+ open fingers (click up/no action) self.gesture_debouncer.signal(0) - #Now that we've told the debouncer what we *think* the current gesture is, we must act - #On what the debouncer thinks the gesture is - if self.gesture_debouncer.state == 2: #Scroll mode - y_scroll_amount = self.velocity_to_scroll_amount(mouse_hand.palm_velocity.y) #Mouse hand controls scroll amount + # Now that we've told the debouncer what we *think* the current gesture is, we must act + # On what the debouncer thinks the gesture is + if self.gesture_debouncer.state == 2: # Scroll mode + y_scroll_amount = self.velocity_to_scroll_amount(mouse_hand.palm_velocity.y) # Mouse hand controls scroll amount x_scroll_amount = self.velocity_to_scroll_amount(mouse_hand.palm_velocity.x) self.cursor.scroll(x_scroll_amount, y_scroll_amount) - elif self.gesture_debouncer.state == 1: #Click/drag mode - if not self.cursor.left_button_pressed: self.cursor.click_down() #Click down (if needed) - self.do_mouse_stuff(mouse_hand) #We may want to click and drag - elif self.gesture_debouncer.state == 0: #Move cursor mode - if self.cursor.left_button_pressed: self.cursor.click_up() #Click up (if needed) + elif self.gesture_debouncer.state == 1: # Click/drag mode + if not self.cursor.left_button_pressed: self.cursor.click_down() # Click down (if needed) + self.do_mouse_stuff(mouse_hand) # We may want to click and drag + elif self.gesture_debouncer.state == 0: # Move cursor mode + if self.cursor.left_button_pressed: self.cursor.click_up() # Click up (if needed) self.do_mouse_stuff(mouse_hand) - def velocity_to_scroll_amount(self, velocity): #Converts a finger velocity to a scroll velocity - #The following algorithm was designed to reflect what I think is a comfortable - #Scrolling behavior. - vel = velocity #Save to a shorter variable - vel = vel + math.copysign(300, vel) #Add/subtract 300 to velocity + def velocity_to_scroll_amount(self, velocity): # Converts a finger velocity to a scroll velocity + # The following algorithm was designed to reflect what I think is a comfortable + # Scrolling behavior. + vel = velocity # Save to a shorter variable + vel = vel + math.copysign(300, vel) # Add/subtract 300 to velocity vel = vel / 150 - vel = vel ** 3 #Cube vel + vel = vel ** 3 # Cube vel vel = vel / 8 - vel = vel * -1 #Negate direction, depending on how you like to scroll + vel = vel * -1 # Negate direction, depending on how you like to scroll return vel - def convert_angles_to_mouse_velocity(self, roll, pitch): #Angles are in radians + def convert_angles_to_mouse_velocity(self, roll, pitch): # Angles are in radians x_movement = 5.0*math.copysign((4.0*math.sin(roll) + 2.0*roll)*math.sin(roll), roll) y_movement = 5.0*math.copysign((4.0*math.sin(pitch) + 2.0*pitch)*math.sin(pitch), pitch) return (x_movement, y_movement) diff --git a/PyLeapMouse.py b/PyLeapMouse.py index 86e69ce..806663f 100755 --- a/PyLeapMouse.py +++ b/PyLeapMouse.py @@ -1,10 +1,13 @@ -#William Yager -#Leap Python mouse controller POC +# William Yager +# Leap Python mouse controller POC + import sys from leap import Leap, Mouse -from PalmControl import Palm_Control_Listener #For palm-tilt based control -from FingerControl import Finger_Control_Listener #For finger-pointing control -from MotionControl import Motion_Control_Listener #For motion control +from PalmControl import Palm_Control_Listener # For palm-tilt based control +from FingerControl import Finger_Control_Listener # For finger-pointing control +from MotionControl import Motion_Control_Listener # For motion control + +if sys.version_info.major > 2: raise Exception("Requires Python 2.7") def show_help(): print "----------------------------------PyLeapMouse----------------------------------" @@ -18,18 +21,14 @@ def main(): show_help() return - print "----------------------------------PyLeapMouse----------------------------------" - print "Use --finger (or blank) for pointer finger control, and --palm for palm control." - print "Use -h or --help for more info.\n" - - #Default + # Default finger_mode = True palm_mode = False motion_mode = False smooth_aggressiveness = 8 smooth_falloff = 1.3 - for i in range(0,len(sys.argv)): + for i in range(0, len(sys.argv)): arg = sys.argv[i].lower() if "--palm" in arg: finger_mode = False @@ -44,29 +43,28 @@ def main(): if "--smooth-aggressiveness" in arg: smooth_aggressiveness = int(sys.argv[i+1]) - listener = None; #I'm tired and can't think of a way to organize this segment nicely + listener = None # I'm tired and can't think of a way to organize this segment nicely - #Create a custom listener object which controls the mouse - if finger_mode: #Finger pointer mode + # Create a custom listener object which controls the mouse + if finger_mode: # Finger pointer mode listener = Finger_Control_Listener(Mouse, smooth_aggressiveness=smooth_aggressiveness, smooth_falloff=smooth_falloff) print "Using finger mode..." - elif palm_mode: #Palm control mode + elif palm_mode: # Palm control mode listener = Palm_Control_Listener(Mouse) print "Using palm mode..." - elif motion_mode: #Motion control mode + elif motion_mode: # Motion control mode listener = Motion_Control_Listener(Mouse) print "Using motion mode..." - - controller = Leap.Controller() #Get a Leap controller + controller = Leap.Controller() # Get a Leap controller controller.set_policy_flags(Leap.Controller.POLICY_BACKGROUND_FRAMES) print "Adding Listener." - controller.add_listener(listener) #Attach the listener + controller.add_listener(listener) # Attach the listener - #Keep this process running until Enter is pressed + # Keep this process running until Enter is pressed print "Press Enter to quit..." sys.stdin.readline() - #Remove the sample listener when done + # Remove the sample listener when done controller.remove_listener(listener) main()