diff --git a/src/bundles/mouse_modes/src/mousemodes.py b/src/bundles/mouse_modes/src/mousemodes.py
old mode 100644
new mode 100755
index eea9b8496..fdd99387d
--- a/src/bundles/mouse_modes/src/mousemodes.py
+++ b/src/bundles/mouse_modes/src/mousemodes.py
@@ -64,7 +64,7 @@ class MouseMode:
 
     def enable(self):
         '''
-        Supported API. 
+        Supported API.
         Called when mouse mode is enabled.
         Override if mode wants to know that it has been bound to a mouse button.
         '''
@@ -139,7 +139,7 @@ class MouseMode:
     def uses_wheel(self):
         '''Return True if derived class implements the wheel() method.'''
         return getattr(self, 'wheel') != MouseMode.wheel
-    
+
     def pause(self, position):
         '''
         Supported API.
@@ -201,7 +201,7 @@ class MouseMode:
         cfile = inspect.getfile(cls)
         p = path.join(path.dirname(cfile), file)
         return p
-    
+
 class MouseBinding:
     '''
     Associates a mouse button ('left', 'middle', 'right', 'wheel', 'pause') and
@@ -227,6 +227,8 @@ class MouseBinding:
         '''
         return button == self.button and set(modifiers) == set(self.modifiers)
 
+
+
 class MouseModes:
     '''
     Keep the list of available mouse modes and also which mode is bound
@@ -246,9 +248,6 @@ class MouseModes:
 
         from PyQt5.QtCore import Qt
         # Qt maps control to meta on Mac...
-        self._modifier_bits = []
-        for keyfunc in ["alt", "control", "command", "shift"]:
-            self._modifier_bits.append((mod_key_info(keyfunc)[0], keyfunc))
 
         # Mouse pause parameters
         self._last_mouse_time = None
@@ -261,7 +260,7 @@ class MouseModes:
         self._last_mode = None			# Remember mode at mouse down and stay with it until mouse up
 
         from .trackpad import MultitouchTrackpad
-        self.trackpad = MultitouchTrackpad(session)
+        self.trackpad = MultitouchTrackpad(session, self)
 
     def bind_mouse_mode(self, button, modifiers, mode):
         '''
@@ -331,7 +330,7 @@ class MouseModes:
             if m.name == name:
                 return m
         return None
-    
+
     def mouse_pause_tracking(self):
         '''
         Called periodically to check for mouse pause and invoke pause mode.
@@ -382,7 +381,7 @@ class MouseModes:
     def _mouse_buttons_down(self):
         from PyQt5.QtCore import Qt
         return self.session.ui.mouseButtons() != Qt.NoButton
-        
+
     def _dispatch_mouse_event(self, event, action):
         button, modifiers = self._event_type(event)
         if button is None:
@@ -404,7 +403,7 @@ class MouseModes:
             self._last_mode = None
 
     def _event_type(self, event):
-        modifiers = self._key_modifiers(event)
+        modifiers = key_modifiers(event)
 
         # button() gives press/release buttons; buttons() gives move buttons
         from PyQt5.QtCore import Qt
@@ -449,17 +448,31 @@ class MouseModes:
 
         return button, modifiers
 
+    def _dispatch_touch_event(self, touch_event):
+        te = touch_event
+        t_string = ('Registered touch event: \n'
+            'wheel_value: {}\n'
+            'two_finger_trans: {}\n'
+            'two_finger_scale: {}\n'
+            'two_finger_twist: {}\n'
+            'three_finger_trans: {}\n'
+            'four_finger_trans: {}').format(
+                te.wheel_value,
+                te.two_finger_trans,
+                te.two_finger_scale,
+                te.two_finger_twist,
+                te.three_finger_trans,
+                te.four_finger_trans
+            )
+        print(t_string)
+
+
     def _have_mode(self, button, modifier):
         for b in self.bindings:
             if b.exact_match(button, [modifier]):
                 return True
         return False
 
-    def _key_modifiers(self, event):
-        mod = event.modifiers()
-        modifiers = [mod_name for bit, mod_name in self._modifier_bits if bit & mod]
-        return modifiers
-
     def _mouse_pause(self):
         m = self.mode('pause')
         if m:
@@ -482,7 +495,7 @@ class MouseModes:
     def _wheel_event(self, event):
         if self.trackpad.discard_trackpad_wheel_event(event):
             return	# Trackpad processing handled this event
-        f = self.mode('wheel', self._key_modifiers(event))
+        f = self.mode('wheel', key_modifiers(event))
         if f:
             f.wheel(MouseEvent(event))
 
@@ -498,7 +511,7 @@ class MouseEvent:
                                         # for mouse button emulation.
         self._position = position	# x,y in pixels, can be None
         self._wheel_value = wheel_value # wheel clicks (usually 1 click equals 15 degrees rotation).
-        
+
     def shift_down(self):
         '''
         Supported API.
@@ -556,7 +569,7 @@ class MouseEvent:
                 delta = min(deltas.x(), deltas.y())
             return delta/120.0   # Usually one wheel click is delta of 120
         return 0
-        
+
 def mod_key_info(key_function):
     """Qt swaps control/meta on Mac, so centralize that knowledge here.
     The possible "key_functions" are: alt, control, command, and shift
@@ -584,6 +597,15 @@ def mod_key_info(key_function):
             return Qt.ControlModifier, "control"
         return Qt.MetaModifier, command_name
 
+_function_keys = ["alt", "control", "command", "shift"]
+_modifier_bits = [(mod_key_info(fkey)[0], fkey) for fkey in _function_keys]
+
+def key_modifiers(event):
+    mod = event.modifiers()
+    modifiers = [mod_name for bit, mod_name in _modifier_bits if bit & mod]
+    return modifiers
+
+
 def keyboard_modifier_names(qt_keyboard_modifiers):
     from PyQt5.QtCore import Qt
     import sys
@@ -601,9 +623,13 @@ def keyboard_modifier_names(qt_keyboard_modifiers):
     mnames = [mname for mflag, mname in modifiers if mflag & qt_keyboard_modifiers]
     return mnames
 
+
+
+
+
 def unpickable(drawing):
     return not getattr(drawing, 'pickable', True)
-    
+
 def picked_object(window_x, window_y, view, max_transparent_layers = 3, exclude = unpickable):
     xyz1, xyz2 = view.clip_plane_points(window_x, window_y)
     if xyz1 is None or xyz2 is None:
@@ -621,4 +647,3 @@ def picked_object_on_segment(xyz1, xyz2, view, max_transparent_layers = 3, exclu
         else:
             break
     return p2 if p2 else p
-
diff --git a/src/bundles/mouse_modes/src/trackpad.py b/src/bundles/mouse_modes/src/trackpad.py
old mode 100644
new mode 100755
index d0e2109d3..f18fe8dea
--- a/src/bundles/mouse_modes/src/trackpad.py
+++ b/src/bundles/mouse_modes/src/trackpad.py
@@ -17,10 +17,12 @@ class MultitouchTrackpad:
     and three finger drag translate scene,
     and two finger pinch zoom scene.
     '''
-    def __init__(self, session):
+    def __init__(self, session, mouse_mode_mgr):
         self._session = session
+        self._mouse_mode_mgr = mouse_mode_mgr
         self._view = session.main_view
         self._recent_touches = []	# List of Touch instances
+        self._modifier_keys = []
         self._last_touch_locations = {}	# Map touch id -> (x,y)
         from .settings import settings
         self.trackpad_speed = settings.trackpad_sensitivity   	# Trackpad position sensitivity
@@ -50,7 +52,7 @@ class MultitouchTrackpad:
             t.remove_handler(h)
             h = None
         self._touch_handler = h
-    
+
     def _enable_touch_events(self, graphics_window):
         from sys import platform
         if platform == 'darwin':
@@ -69,7 +71,7 @@ class MultitouchTrackpad:
         w.setAttribute(Qt.WA_AcceptTouchEvents)
         print('graphics widget touch enabled', w.testAttribute(Qt.WA_AcceptTouchEvents))
         '''
-        
+
     # Appears that Qt has disabled touch events on Mac due to unresolved scrolling lag problems.
     # Searching for qt setAcceptsTouchEvents shows they were disabled Oct 17, 2012.
     # A patch that allows an environment variable QT_MAC_ENABLE_TOUCH_EVENTS to allow touch
@@ -84,8 +86,10 @@ class MultitouchTrackpad:
 
         from PyQt5.QtCore import QEvent
         t = event.type()
+        from .mousemodes import key_modifiers
+        self._modifier_keys = key_modifiers(event)
         if t == QEvent.TouchUpdate:
-            # On Mac touch events get backlogged in queue when the events cause 
+            # On Mac touch events get backlogged in queue when the events cause
             # time consuming computatation.  It appears Qt does not collapse the events.
             # So event processing can get tens of seconds behind.  To reduce this problem
             # we only handle the most recent touch update per redraw.
@@ -100,61 +104,53 @@ class MultitouchTrackpad:
     def _collapse_touch_events(self):
         touches = self._recent_touches
         if touches:
-            self._process_touches(touches)
+            event = self._process_touches(touches)
             self._recent_touches = []
+            self._mouse_mode_mgr._dispatch_touch_event(event)
 
     def _process_touches(self, touches):
+        pinch = twist = scroll = None
+        two_swipe = None
+        three_swipe = None
+        four_swipe = None
         n = len(touches)
         speed = self.trackpad_speed
         moves = [t.move(self._last_touch_locations) for t in touches]
+        dx = sum(x for x,y in moves)/n
+        dy = sum(y for x,y in moves)/n
+
         if n == 2:
             (dx0,dy0),(dx1,dy1) = moves[0], moves[1]
             from math import sqrt, exp, atan2, pi
             l0,l1 = sqrt(dx0*dx0 + dy0*dy0),sqrt(dx1*dx1 + dy1*dy1)
             d12 = dx0*dx1+dy0*dy1
             if d12 < 0:
-                # Finger moving in opposite directions: pinch or twist
+                # Finger moving in opposite directions: pinch/twist
                 (x0,y0),(x1,y1) = [(t.x,t.y) for t in touches[:2]]
                 sx,sy = x1-x0,y1-y0
                 sn = sqrt(sx*sx + sy*sy)
                 sd0,sd1 = sx*dx0 + sy*dy0, sx*dx1 + sy*dy1
-                if abs(sd0) > 0.5*sn*l0 and abs(sd1) > 0.5*sn*l1:
-                    # Fingers move along line between them: pinch to zoom
-                    zf = 1 + speed * self._zoom_scaling * (l0+l1) / self._full_width_translation_distance
-                    if sd1 < 0:
-                        zf = 1/zf
-                    self._zoom(zf)
-                else:
-                    # Fingers move perpendicular to line between them: twist
-                    rot = atan2(-sy*dx1+sx*dy1,sn*sn) + atan2(sy*dx0-sx*dy0,sn*sn)
-                    a = -speed * self._twist_scaling * rot * 180 / pi
-                    zaxis = (0,0,1)
-                    self._rotate(zaxis, a)
-                return
-            # Fingers moving in same direction: rotation
-            dx = sum(x for x,y in moves)/n
-            dy = sum(y for x,y in moves)/n
-            from math import sqrt
-            turns = sqrt(dx*dx + dy*dy)/self._full_rotation_distance
-            angle = speed*360*turns
-            self._rotate((dy, dx, 0), angle)
+                zf = 1 + speed * self._zoom_scaling * (l0+l1) / self._full_width_translation_distance
+                if sd1 < 0:
+                    zf = 1/zf
+                pinch = zf
+                rot = atan2(-sy*dx1+sx*dy1,sn*sn) + atan2(sy*dx0-sx*dy0,sn*sn)
+                a = -speed * self._twist_scaling * rot * 180 / pi
+                twist = a
+            else:
+                two_swipe = (dx, dy)
+                scroll = speed * dy / self._wheel_click_pixels
         elif n == 3:
-            dx = sum(x for x,y in moves)/n
-            dy = sum(y for x,y in moves)/n
-            ww = self._view.window_size[0]	# Window width in pixels
-            s = speed * ww / self._full_width_translation_distance
-            self._translate((s*dx, -s*dy, 0))
+            three_swipe = (dx, dy)
         elif n == 4:
-            # Use scrollwheel mouse mode
-            ses = self._session
-            from .mousemodes import keyboard_modifier_names, MouseEvent
-            modifiers = keyboard_modifier_names(ses.ui.queryKeyboardModifiers())
-            scrollwheel_mode = ses.ui.mouse_modes.mode(button = 'wheel', modifiers = modifiers)
-            if scrollwheel_mode:
-                xy = (sum(t.x for t in touches)/n, sum(t.y for t in touches)/n)
-                dy = sum(y for x,y in moves)/n			# pixels
-                delta = speed * dy / self._wheel_click_pixels	# wheel clicks
-                scrollwheel_mode.wheel(MouseEvent(position = xy, wheel_value = delta, modifiers = modifiers))
+            four_swipe = (dx, dy)
+
+        return MultitouchEvent(modifiers=self._modifier_keys,
+            wheel_value=scroll, two_finger_trans=two_swipe, two_finger_scale=pinch,
+            two_finger_twist=twist, three_finger_trans=three_swipe,
+            four_finger_trans=four_swipe)
+
+        return pinch, twist, scroll, two_swipe, three_swipe, four_swipe
 
     def _rotate(self, screen_axis, angle):
         if angle == 0:
@@ -230,3 +226,81 @@ class Touch:
         x,y = self.x, self.y
         last_touch_locations[id] = (x,y)
         return (x-lx, y-ly)
+
+
+from .mousemodes import MouseEvent
+class MultitouchEvent(MouseEvent):
+    '''
+    Provides an interface to events fired by multi-touch trackpads and modifier
+    keys so that mouse modes do not directly depend on details of the window
+    toolkit or trackpad implementation.
+    '''
+    def __init__(self, modifiers = None,  wheel_value = None,
+            two_finger_trans=None, two_finger_scale=None, two_finger_twist=None,
+            three_finger_trans=None, four_finger_trans=None):
+        super().__init__(event=None, modifiers=modifiers, position=None, wheel_value=wheel_value)
+        self._two_finger_trans = two_finger_trans
+        self._two_finger_scale = two_finger_scale
+        self._two_finger_twist = two_finger_twist
+        self._three_finger_trans = three_finger_trans
+        self._four_finger_trans = four_finger_trans
+
+    @property
+    def event(self):
+        '''
+        The core QTouchEvent object
+        '''
+        return self._event
+
+    @property
+    def wheel_value(self):
+        '''
+        Supported API.
+        Effective mouse wheel value if two-finger vertical swipe is to be
+        interpreted as a scrolling action.
+        '''
+        return self._wheel_value
+
+    @property
+    def two_finger_trans(self):
+        '''
+        Supported API.
+        Returns a tuple (delta_x, delta_y) in screen coordinates representing
+        the movement when a two-finger swipe is interpreted as a translation
+        action.
+        '''
+        return self._two_finger_trans
+
+    @property
+    def two_finger_scale(self):
+        '''
+        Supported API
+        Returns a float representing the change in a two-finger pinching action.
+        '''
+        return self._two_finger_scale
+
+    @property
+    def two_finger_twist(self):
+        '''
+        Supported API
+        Returns the rotation in degrees defined by a two-finger twisting action.
+        '''
+        return self._two_finger_twist
+
+    @property
+    def three_finger_trans(self):
+        '''
+        Supported API
+        Returns a tuple (delta_x, delta_y) in screen coordinates representing
+        the translation in a 3-fingered swipe.
+        '''
+        return self._three_finger_trans
+
+    @property
+    def four_finger_trans(self):
+        '''
+        Supported API
+        Returns a tuple (delta_x, delta_y) in screen coordinates representing
+        the translation in a 3-fingered swipe.
+        '''
+        return self._four_finger_trans
