diff --git a/examples/bloatpad.c b/examples/bloatpad.c
index b2cff86..04d054b 100644
--- a/examples/bloatpad.c
+++ b/examples/bloatpad.c
@@ -1,4 +1,38 @@
 #include <gtk/gtk.h>
+#include <math.h>
+
+GtkAdjustment *v_adjustment;
+
+static gboolean
+view_scroll_event_cb (GtkTextView          *view,
+                             GdkEventSmoothScroll *event,
+                             gpointer              data)
+{
+  printf("NORMAL SCROLL EVENT\n");
+  // Do nothing and return TRUE (for "event has been handled") to prevent the scroll event to scroll the view
+  return TRUE;
+}
+
+static gboolean
+view_smooth_scroll_event_cb (GtkTextView          *view,
+                             GdkEventSmoothScroll *event,
+                             gpointer              data)
+{
+  gdouble scroll_distance = event->scroll_y;
+
+  //gdouble transformed_scroll_distance = (scroll_distance*20)*(scroll_distance*20) / 10 * (scroll_distance<0.0 ? (-1) : 1);
+
+  gdouble transformed_scroll_distance = transform_smooth_scroll(scroll_distance);
+
+  printf("                   received scroll distance: %f, transformed: %f\n", scroll_distance, transformed_scroll_distance);
+
+  gdouble current_v_adjustment = gtk_adjustment_get_value(v_adjustment);
+  gdouble new_v_adjustment = current_v_adjustment + transformed_scroll_distance;
+  gtk_adjustment_set_value(v_adjustment, new_v_adjustment);
+
+  /* We've handled it, stop processing */
+  return TRUE;
+}
 
 static void
 new_window (GApplication *app,
@@ -14,6 +48,8 @@ new_window (GApplication *app,
   gtk_container_add (GTK_CONTAINER (scrolled), view);
   gtk_container_add (GTK_CONTAINER (window), scrolled);
 
+  v_adjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(view));
+
   if (file != NULL)
     {
       gchar *contents;
@@ -29,6 +65,13 @@ new_window (GApplication *app,
         }
     }
 
+  // TODO(nh2) find a way / the bug to disable normal scrolling by just unsetting the flag in gtk_widget_set_events
+  g_signal_connect (view, "scroll-event",
+                    G_CALLBACK (view_scroll_event_cb), NULL);
+  g_signal_connect (view, "smooth-scroll-event",
+                    G_CALLBACK (view_smooth_scroll_event_cb), NULL);
+  gtk_widget_set_events (view, (gtk_widget_get_events (view) & (GDK_ALL_EVENTS_MASK - GDK_SCROLL_MASK)) | GDK_SMOOTH_SCROLL_MASK);
+
   gtk_widget_show_all (GTK_WIDGET (window));
 }
 
diff --git a/examples/drawing.c b/examples/drawing.c
index 28f291b..71defdc 100644
--- a/examples/drawing.c
+++ b/examples/drawing.c
@@ -3,6 +3,9 @@
 /* Surface to store current scribbles */
 static cairo_surface_t *surface = NULL;
 
+static double cur_scroll_x = 20.0;
+static double cur_scroll_y = 20.0;
+
 static void
 clear_surface (void)
 {
@@ -135,6 +138,58 @@ motion_notify_event_cb (GtkWidget      *widget,
   return TRUE;
 }
 
+static gboolean
+scroll_event_cb (GtkWidget            *widget,
+                 GdkEventScroll *event,
+                 gpointer              data)
+{
+  printf("nh2: STANDARD SCROLL EVENT\n");
+  return TRUE;
+}
+
+static gboolean
+smooth_scroll_event_cb (GtkWidget            *widget,
+                        GdkEventSmoothScroll *event,
+                        gpointer              data)
+{
+  // TODO(nh2) remove hack
+  static gdouble last_scroll_pos_x;
+  static gdouble last_scroll_pos_y;
+  int x, y;
+  GdkModifierType state;
+
+  /* paranoia check, in case we haven't gotten a configure event */
+  if (surface == NULL)
+    return FALSE;
+
+  gdk_window_get_pointer (event->window, &x, &y, &state);
+
+  gdouble new_scroll_pos_x = event->scroll_x;
+  gdouble new_scroll_pos_y = event->scroll_y;
+
+  gdouble scroll_distance_x = new_scroll_pos_x - last_scroll_pos_x;
+  gdouble scroll_distance_y = new_scroll_pos_y - last_scroll_pos_y;
+  printf("scrolling by distance ( %f  %f )\n", scroll_distance_x, scroll_distance_y);
+
+  gdouble scroll_factor = 5.0;
+
+  cur_scroll_x += scroll_distance_x * scroll_factor;
+  cur_scroll_y += scroll_distance_y * scroll_factor;
+  draw_brush (widget, (int) cur_scroll_x, (int) cur_scroll_y);
+
+  if (new_scroll_pos_x > 1.0) new_scroll_pos_x -= 1.0;
+  if (new_scroll_pos_x < 0.0) new_scroll_pos_x += 1.0;
+
+  if (new_scroll_pos_y > 1.0) new_scroll_pos_y -= 1.0;
+  if (new_scroll_pos_y < 0.0) new_scroll_pos_y += 1.0;
+
+  last_scroll_pos_x = new_scroll_pos_x;
+  last_scroll_pos_y = new_scroll_pos_y;
+
+  /* We've handled it, stop processing */
+  return TRUE;
+}
+
 static void
 close_window (void)
 {
@@ -182,6 +237,11 @@ main (int   argc,
                     G_CALLBACK (motion_notify_event_cb), NULL);
   g_signal_connect (da, "button-press-event",
                     G_CALLBACK (button_press_event_cb), NULL);
+  printf("nh2: da widget is %p\n", da);
+  g_signal_connect (da, "scroll-event",
+                    G_CALLBACK (scroll_event_cb), NULL);
+  g_signal_connect (da, "smooth-scroll-event",
+                    G_CALLBACK (smooth_scroll_event_cb), NULL);
 
   /* Ask to receive events the drawing area doesn't normally
    * subscribe to. In particular, we need to ask for the
@@ -189,6 +249,8 @@ main (int   argc,
    */
   gtk_widget_set_events (da, gtk_widget_get_events (da)
                              | GDK_BUTTON_PRESS_MASK
+                       //      | GDK_SCROLL_MASK  // It is weird that the signals come through even if not registered or explicitly unregistered
+                             | GDK_SMOOTH_SCROLL_MASK
                              | GDK_POINTER_MOTION_MASK
                              | GDK_POINTER_MOTION_HINT_MASK);
 
diff --git a/gdk/gdkdevice.c b/gdk/gdkdevice.c
index 0d3c040..0b71fc6 100644
--- a/gdk/gdkdevice.c
+++ b/gdk/gdkdevice.c
@@ -829,6 +829,8 @@ gdk_device_set_axis_use (GdkDevice   *device,
       break;
     case GDK_AXIS_XTILT:
     case GDK_AXIS_YTILT:
+    case GDK_AXIS_XSCROLL:
+    case GDK_AXIS_YSCROLL:
       info->min_axis = -1;
       info->max_axis = 1;
       break;
@@ -1110,6 +1112,7 @@ gdk_device_get_axis (GdkDevice  *device,
 {
   gint i;
 
+  printf("nh2: in gdk_device_get_axis\n");
   g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
   g_return_val_if_fail (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD, FALSE);
 
@@ -1144,7 +1147,7 @@ get_native_grab_event_mask (GdkEventMask grab_mask)
     GDK_POINTER_MOTION_MASK |
     GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
     GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
-    GDK_SCROLL_MASK |
+    GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK |
     (grab_mask &
      ~(GDK_POINTER_MOTION_HINT_MASK |
        GDK_BUTTON_MOTION_MASK |
@@ -1354,6 +1357,8 @@ _gdk_device_add_axis (GdkDevice   *device,
       break;
     case GDK_AXIS_XTILT:
     case GDK_AXIS_YTILT:
+    case GDK_AXIS_XSCROLL:
+    case GDK_AXIS_YSCROLL:
       axis_info.min_axis = -1;
       axis_info.max_axis = 1;
       break;
diff --git a/gdk/gdkdevice.h b/gdk/gdkdevice.h
index 9109d65..31777f6 100644
--- a/gdk/gdkdevice.h
+++ b/gdk/gdkdevice.h
@@ -97,6 +97,8 @@ typedef enum
  * @GDK_AXIS_IGNORE: the axis is ignored.
  * @GDK_AXIS_X: the axis is used as the x axis.
  * @GDK_AXIS_Y: the axis is used as the y axis.
+ * @GDK_AXIS_XSCROLL: the axis is used as smooth x scroll information.
+ * @GDK_AXIS_YSCROLL: the axis is used as smooth y scroll information.
  * @GDK_AXIS_PRESSURE: the axis is used for pressure information.
  * @GDK_AXIS_XTILT: the axis is used for x tilt information.
  * @GDK_AXIS_YTILT: the axis is used for y tilt information.
@@ -112,6 +114,8 @@ typedef enum
   GDK_AXIS_IGNORE,
   GDK_AXIS_X,
   GDK_AXIS_Y,
+  GDK_AXIS_XSCROLL,
+  GDK_AXIS_YSCROLL,
   GDK_AXIS_PRESSURE,
   GDK_AXIS_XTILT,
   GDK_AXIS_YTILT,
diff --git a/gdk/gdkenumtypes.c b/gdk/gdkenumtypes.c
index bd11ad8..c18dfb8 100644
--- a/gdk/gdkenumtypes.c
+++ b/gdk/gdkenumtypes.c
@@ -158,6 +158,8 @@ gdk_axis_use_get_type (void)
             { GDK_AXIS_IGNORE, "GDK_AXIS_IGNORE", "ignore" },
             { GDK_AXIS_X, "GDK_AXIS_X", "x" },
             { GDK_AXIS_Y, "GDK_AXIS_Y", "y" },
+            { GDK_AXIS_XSCROLL, "GDK_AXIS_XSCROLL", "xscroll" },
+            { GDK_AXIS_YSCROLL, "GDK_AXIS_YSCROLL", "yscroll" },
             { GDK_AXIS_PRESSURE, "GDK_AXIS_PRESSURE", "pressure" },
             { GDK_AXIS_XTILT, "GDK_AXIS_XTILT", "xtilt" },
             { GDK_AXIS_YTILT, "GDK_AXIS_YTILT", "ytilt" },
@@ -286,6 +288,7 @@ gdk_event_type_get_type (void)
             { GDK_OWNER_CHANGE, "GDK_OWNER_CHANGE", "owner-change" },
             { GDK_GRAB_BROKEN, "GDK_GRAB_BROKEN", "grab-broken" },
             { GDK_DAMAGE, "GDK_DAMAGE", "damage" },
+            { GDK_SMOOTH_SCROLL, "GDK_SMOOTH_SCROLL", "smooth-scroll" },
             { GDK_EVENT_LAST, "GDK_EVENT_LAST", "event-last" },
             { 0, NULL, NULL }
         };
@@ -589,6 +592,7 @@ gdk_event_mask_get_type (void)
             { GDK_PROXIMITY_OUT_MASK, "GDK_PROXIMITY_OUT_MASK", "proximity-out-mask" },
             { GDK_SUBSTRUCTURE_MASK, "GDK_SUBSTRUCTURE_MASK", "substructure-mask" },
             { GDK_SCROLL_MASK, "GDK_SCROLL_MASK", "scroll-mask" },
+            { GDK_SMOOTH_SCROLL_MASK, "GDK_SMOOTH_SCROLL_MASK", "smooth-scroll-mask" },
             { GDK_ALL_EVENTS_MASK, "GDK_ALL_EVENTS_MASK", "all-events-mask" },
             { 0, NULL, NULL }
         };
diff --git a/gdk/gdkevents.c b/gdk/gdkevents.c
index ae42b4f..fc6a5c3 100644
--- a/gdk/gdkevents.c
+++ b/gdk/gdkevents.c
@@ -465,6 +465,12 @@ gdk_event_new (GdkEventType type)
       new_event->scroll.x_root = 0.;
       new_event->scroll.y_root = 0.;
       break;
+    case GDK_SMOOTH_SCROLL:
+      new_event->smooth_scroll.x = 0.;
+      new_event->smooth_scroll.y = 0.;
+      new_event->smooth_scroll.x_root = 0.;
+      new_event->smooth_scroll.y_root = 0.;
+      break;
     case GDK_ENTER_NOTIFY:
     case GDK_LEAVE_NOTIFY:
       new_event->crossing.x = 0.;
@@ -704,6 +710,8 @@ gdk_event_get_time (const GdkEvent *event)
 	return event->button.time;
       case GDK_SCROLL:
         return event->scroll.time;
+      case GDK_SMOOTH_SCROLL:
+        return event->smooth_scroll.time;
       case GDK_KEY_PRESS:
       case GDK_KEY_RELEASE:
 	return event->key.time;
@@ -782,6 +790,9 @@ gdk_event_get_state (const GdkEvent        *event,
       case GDK_SCROLL:
 	*state =  event->scroll.state;
         return TRUE;
+      case GDK_SMOOTH_SCROLL:
+	*state =  event->smooth_scroll.state;
+        return TRUE;
       case GDK_KEY_PRESS:
       case GDK_KEY_RELEASE:
 	*state =  event->key.state;
@@ -861,6 +872,10 @@ gdk_event_get_coords (const GdkEvent *event,
       x = event->scroll.x;
       y = event->scroll.y;
       break;
+    case GDK_SMOOTH_SCROLL:
+      x = event->smooth_scroll.x;
+      y = event->smooth_scroll.y;
+      break;
     case GDK_BUTTON_PRESS:
     case GDK_2BUTTON_PRESS:
     case GDK_3BUTTON_PRESS:
@@ -915,6 +930,10 @@ gdk_event_get_root_coords (const GdkEvent *event,
       x = event->scroll.x_root;
       y = event->scroll.y_root;
       break;
+    case GDK_SMOOTH_SCROLL:
+      x = event->smooth_scroll.x_root;
+      y = event->smooth_scroll.y_root;
+      break;
     case GDK_BUTTON_PRESS:
     case GDK_2BUTTON_PRESS:
     case GDK_3BUTTON_PRESS:
@@ -1170,6 +1189,10 @@ gdk_event_get_axis (const GdkEvent *event,
 	  x = event->scroll.x;
 	  y = event->scroll.y;
 	  break;
+	case GDK_SMOOTH_SCROLL:
+	  x = event->smooth_scroll.x;
+	  y = event->smooth_scroll.y;
+	  break;
 	case GDK_BUTTON_PRESS:
 	case GDK_BUTTON_RELEASE:
 	  x = event->button.x;
@@ -1246,6 +1269,9 @@ gdk_event_set_device (GdkEvent  *event,
     case GDK_SCROLL:
       event->scroll.device = device;
       break;
+    case GDK_SMOOTH_SCROLL:
+      event->smooth_scroll.device = device;
+      break;
     case GDK_PROXIMITY_IN:
     case GDK_PROXIMITY_OUT:
       event->proximity.device = device;
@@ -1290,6 +1316,8 @@ gdk_event_get_device (const GdkEvent *event)
       return event->button.device;
     case GDK_SCROLL:
       return event->scroll.device;
+    case GDK_SMOOTH_SCROLL:
+      return event->smooth_scroll.device;
     case GDK_PROXIMITY_IN:
     case GDK_PROXIMITY_OUT:
       return event->proximity.device;
@@ -1317,6 +1345,7 @@ gdk_event_get_device (const GdkEvent *event)
     case GDK_DROP_START:
     case GDK_DROP_FINISHED:
     case GDK_SCROLL:
+    case GDK_SMOOTH_SCROLL:
     case GDK_GRAB_BROKEN:
     case GDK_KEY_PRESS:
     case GDK_KEY_RELEASE:
diff --git a/gdk/gdkevents.h b/gdk/gdkevents.h
index 8bf7d56..5590bab 100644
--- a/gdk/gdkevents.h
+++ b/gdk/gdkevents.h
@@ -81,6 +81,7 @@ typedef struct _GdkEventVisibility  GdkEventVisibility;
 typedef struct _GdkEventMotion	    GdkEventMotion;
 typedef struct _GdkEventButton	    GdkEventButton;
 typedef struct _GdkEventScroll      GdkEventScroll;  
+typedef struct _GdkEventSmoothScroll      GdkEventSmoothScroll;
 typedef struct _GdkEventKey	    GdkEventKey;
 typedef struct _GdkEventFocus	    GdkEventFocus;
 typedef struct _GdkEventCrossing    GdkEventCrossing;
@@ -213,6 +214,7 @@ typedef GdkFilterReturn (*GdkFilterFunc) (GdkXEvent *xevent,
  *   was added in 2.8.
  * @GDK_DAMAGE: the content of the window has been changed. This event type
  *   was added in 2.14.
+ * @GDK_SMOOTH_SCROLL: TODO(nh2) docs
  * @GDK_EVENT_LAST: marks the end of the GdkEventType enumeration. Added in 2.18
  *
  * Specifies the type of the event.
@@ -260,6 +262,7 @@ typedef enum
   GDK_OWNER_CHANGE      = 34,
   GDK_GRAB_BROKEN       = 35,
   GDK_DAMAGE            = 36,
+  GDK_SMOOTH_SCROLL     = 37,
   GDK_EVENT_LAST        /* helper variable for decls */
 } GdkEventType;
 
@@ -632,6 +635,48 @@ struct _GdkEventScroll
 };
 
 /**
+ * GdkEventSmoothScroll:
+ * @type: the type of the event (%GDK_SMOOTH_SCROLL).
+ * @window: the window which received the event.
+ * @send_event: %TRUE if the event was sent explicitly (e.g. using
+ *   <function>XSendEvent</function>).
+ * @time: the time of the event in milliseconds.
+ * @x: the x coordinate of the pointer relative to the window.
+ * @y: the y coordinate of the pointer relative to the window.
+ * @state: (type GdkModifierType): a bit-mask representing the state of
+ *   the modifier keys (e.g. Control, Shift and Alt) and the pointer
+ *   buttons. See #GdkModifierType.
+ * @scroll_x: the scroll distance in X direction (bewteen -1.0 and 1.0).
+ * @scroll_y: the scroll distance in Y direction (bewteen -1.0 and 1.0).
+ * @device: the device where the event originated.
+ * @x_root: the x coordinate of the pointer relative to the root of the
+ *   screen.
+ * @y_root: the y coordinate of the pointer relative to the root of the
+ *   screen.
+ *
+ * TODO(nh2) Better doc, like in XI2
+ * Generated from smooth scrolling devices, e.g. touchpads or track
+ * points. Some mice with floating mouse wheels might also send this
+ * event.
+ * Note that emulated GdkEventScroll events might still be sent
+ * whenever the floating scroll has exceeded a certain value.
+ */
+struct _GdkEventSmoothScroll
+{
+  GdkEventType type;
+  GdkWindow *window;
+  gint8 send_event;
+  guint32 time;
+  gdouble x;
+  gdouble y;
+  guint state;
+  gdouble scroll_x;
+  gdouble scroll_y;
+  GdkDevice *device;
+  gdouble x_root, y_root;
+};
+
+/**
  * GdkEventKey:
  * @type: the type of the event (%GDK_KEY_PRESS or %GDK_KEY_RELEASE).
  * @window: the window which received the event.
@@ -1022,6 +1067,7 @@ union _GdkEvent
   GdkEventMotion	    motion;
   GdkEventButton	    button;
   GdkEventScroll            scroll;
+  GdkEventSmoothScroll  smooth_scroll;
   GdkEventKey		    key;
   GdkEventCrossing	    crossing;
   GdkEventFocus		    focus_change;
diff --git a/gdk/gdktypes.h b/gdk/gdktypes.h
index 87ac3b2..f516a2d 100644
--- a/gdk/gdktypes.h
+++ b/gdk/gdktypes.h
@@ -351,7 +351,8 @@ typedef enum
   GDK_PROXIMITY_OUT_MASK        = 1 << 19,
   GDK_SUBSTRUCTURE_MASK         = 1 << 20,
   GDK_SCROLL_MASK               = 1 << 21,
-  GDK_ALL_EVENTS_MASK           = 0x3FFFFE
+  GDK_SMOOTH_SCROLL_MASK        = 1 << 22,
+  GDK_ALL_EVENTS_MASK           = 0x7FFFFE
 } GdkEventMask;
 
 /**
diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c
index 548330c..6dc38c9 100644
--- a/gdk/gdkwindow.c
+++ b/gdk/gdkwindow.c
@@ -1188,7 +1188,7 @@ get_native_device_event_mask (GdkWindow *private,
 	mask |=
 	  GDK_POINTER_MOTION_MASK |
 	  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
-	  GDK_SCROLL_MASK;
+	  GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK;
 
       return mask;
     }
@@ -1202,7 +1202,7 @@ get_native_grab_event_mask (GdkEventMask grab_mask)
     GDK_POINTER_MOTION_MASK |
     GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
     GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
-    GDK_SCROLL_MASK |
+    GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK |
     (grab_mask &
      ~GDK_POINTER_MOTION_HINT_MASK);
 }
@@ -6293,6 +6293,7 @@ gdk_window_move_resize (GdkWindow *window,
  * is used to scroll the window which may produce temporary visual
  * artifacts and unnecessary invalidations.
  **/
+// TODO(nh2) I think that's it for smooth scrolling! Use it
 void
 gdk_window_scroll (GdkWindow *window,
 		   gint       dx,
@@ -8083,6 +8084,7 @@ static const guint type_masks[] = {
   0, /* GDK_OWNER_CHANGE = 34 */
   0, /* GDK_GRAB_BROKEN = 35 */
   0, /* GDK_DAMAGE = 36 */
+  GDK_SMOOTH_SCROLL_MASK,/* GDK_SMOOTH_SCROLL= 37 */
 };
 G_STATIC_ASSERT (G_N_ELEMENTS (type_masks) == GDK_EVENT_LAST);
 
@@ -8114,7 +8116,10 @@ is_button_type (GdkEventType type)
 	 type == GDK_2BUTTON_PRESS ||
 	 type == GDK_3BUTTON_PRESS ||
 	 type == GDK_BUTTON_RELEASE ||
-	 type == GDK_SCROLL;
+	 type == GDK_SCROLL ||
+	 type == GDK_SMOOTH_SCROLL;
+  // TODO(nh2) check if GDK_SMOOTH_SCROLL should really be a button_type, or rather a motion_type, or an own type?
+  // this is currently NECESSARY for the smooth scrolling event to get through!
 }
 
 static gboolean
@@ -8202,6 +8207,12 @@ _gdk_make_event (GdkWindow    *window,
       event->scroll.state = the_state;
       break;
 
+    case GDK_SMOOTH_SCROLL:
+      // TODO(nh2) also check if we should set scroll_x/y for GDK_SMOOTH_SCROLL
+      event->smooth_scroll.time = the_time;
+      event->smooth_scroll.state = the_state;
+      break;
+
     case GDK_KEY_PRESS:
     case GDK_KEY_RELEASE:
       event->key.time = the_time;
@@ -9431,6 +9442,22 @@ proxy_button_event (GdkEvent *source_event,
       gdk_event_set_source_device (event, source_device);
       return TRUE;
 
+      // TODO(nh2) check if this makes sense here
+      // - it does, because currently we treat GDK_SMOOTH_SCROLL as is_button_type(), which determines whether this is called.
+      // if it is not called, the event does not get proxied.
+    case GDK_SMOOTH_SCROLL:
+      event->smooth_scroll.scroll_x = source_event->smooth_scroll.scroll_x;
+      event->smooth_scroll.scroll_y = source_event->smooth_scroll.scroll_y;
+      convert_toplevel_coords_to_window (event_win,
+					 toplevel_x, toplevel_y,
+					 &event->smooth_scroll.x, &event->smooth_scroll.y);
+      event->smooth_scroll.x_root = source_event->smooth_scroll.x_root;
+      event->smooth_scroll.y_root = source_event->smooth_scroll.y_root;
+      event->smooth_scroll.state = state;
+      event->smooth_scroll.device = source_event->smooth_scroll.device;
+      gdk_event_set_source_device (event, source_device);
+      return TRUE;
+
     default:
       return FALSE;
     }
@@ -9573,7 +9600,11 @@ _gdk_windowing_got_event (GdkDisplay *display,
       return;
     }
 
+  printf("nh2: _gdk_windowing_got_event! before return check\n");
   if (!(is_button_type (event->type) ||
+      //event->type == GDK_SMOOTH_SCROLL || // TODO(nh2) check if this should be in is_button/motion_type
+      // TODO(nh2) This might be important to get the event through
+      // It looks like it is, but this alone does not help: there is another invocation of is_button_type!
 	is_motion_type (event->type)) ||
       event_window->window_type == GDK_WINDOW_ROOT)
     return;
@@ -9658,6 +9689,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
     _gdk_display_enable_motion_hints (display, device);
 
   unlink_event = FALSE;
+  // TODO(nh2) check if smooth scrolling should be one of these types
   if (is_motion_type (event->type))
     unlink_event = proxy_pointer_event (display,
 					event,
diff --git a/gdk/x11/gdkdevice-xi2.c b/gdk/x11/gdkdevice-xi2.c
index b86d802..85cd4d7 100644
--- a/gdk/x11/gdkdevice-xi2.c
+++ b/gdk/x11/gdkdevice-xi2.c
@@ -663,6 +663,13 @@ _gdk_x11_device_xi2_translate_event_mask (GdkEventMask  event_mask,
       XISetMask (mask, XI_ButtonRelease);
     }
 
+  if (event_mask & GDK_SMOOTH_SCROLL_MASK)
+    {
+      //printf("nh2: ERROR: no X translation for GDK_SMOOTH_SCROLL_MASK (yet?)!\n");
+      printf("nh2: WARNING: trying XI_Motion for GDK_SMOOTH_SCROLL_MASK\n");
+	  XISetMask (mask, XI_Motion);
+	}
+
   if (event_mask & GDK_BUTTON_PRESS_MASK)
     XISetMask (mask, XI_ButtonPress);
 
diff --git a/gdk/x11/gdkdevicemanager-xi2.c b/gdk/x11/gdkdevicemanager-xi2.c
index c921ce0..d5bc4d0 100644
--- a/gdk/x11/gdkdevicemanager-xi2.c
+++ b/gdk/x11/gdkdevicemanager-xi2.c
@@ -38,6 +38,8 @@
 
 #include <string.h>
 
+#include <math.h>
+
 #endif /* XINPUT_2 */
 
 struct _GdkX11DeviceManagerXI2
@@ -165,6 +167,8 @@ translate_valuator_class (GdkDisplay          *display,
       label_atoms [GDK_AXIS_XTILT] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Tilt X");
       label_atoms [GDK_AXIS_YTILT] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Tilt Y");
       label_atoms [GDK_AXIS_WHEEL] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Wheel");
+      label_atoms [GDK_AXIS_XSCROLL] = gdk_x11_get_xatom_by_name_for_display (display, "Rel Horiz Scroll");
+      label_atoms [GDK_AXIS_YSCROLL] = gdk_x11_get_xatom_by_name_for_display (display, "Rel Vert Scroll");
       initialized = TRUE;
     }
 
@@ -819,6 +823,7 @@ translate_axes (GdkDevice       *device,
   g_object_get (device, "n-axes", &n_axes, NULL);
 
   axes = g_new0 (gdouble, n_axes);
+  printf("nh2: found %d axes - ", n_axes);
   vals = valuators->values;
 
   for (i = 0; i < valuators->mask_len * 8; i++)
@@ -846,7 +851,15 @@ translate_axes (GdkDevice       *device,
                 axes[i] = y;
             }
           break;
+        case GDK_AXIS_XSCROLL:
+        case GDK_AXIS_YSCROLL:
+            // TODO(nh2) It looks like the xserver does not send this (not even set the mask at i)
+          if (use == GDK_AXIS_XSCROLL) printf("nh2: detected X scroll of value %f  at axis %d\n", val, i);
+          if (use == GDK_AXIS_YSCROLL) printf("nh2: detected Y scroll of value %f  at axis %d\n", val, i);
+          axes[i] = val;
+          break;
         default:
+          // TODO(nh2) How comes we can write to axes[i]? i is < 64, what if the mask is like 11001? Segfault?
           _gdk_device_translate_axis (device, i, val, &axes[i]);
           break;
         }
@@ -855,6 +868,46 @@ translate_axes (GdkDevice       *device,
   return axes;
 }
 
+// Naively tells for which axis the device contains smooth-scrolling information right now.
+// TODO(nh2) replace loop by something sensible or get better info from xserver
+guint SMOOTH_SCROLL_AXIS_X = 1 << 0;
+guint SMOOTH_SCROLL_AXIS_Y = 1 << 1;
+static guint
+get_smooth_scroll_axes_mask (GdkDevice      *device,
+                            gdouble          x,
+                            gdouble          y,
+                            GdkWindow       *window,
+                            XIValuatorState *valuators)
+{
+  guint i;
+  guint axes_mask = 0;
+
+  for (i = 0; i < valuators->mask_len * 8; i++)
+    {
+      GdkAxisUse use;
+
+      if (!XIMaskIsSet (valuators->mask, i))
+        continue;
+
+      use = gdk_device_get_axis_use (device, i);
+
+      switch (use)
+        {
+        case GDK_AXIS_XSCROLL:
+  printf("here is an x axis\n");
+          axes_mask |= SMOOTH_SCROLL_AXIS_X;
+          break;
+        case GDK_AXIS_YSCROLL:
+          axes_mask |= SMOOTH_SCROLL_AXIS_Y;
+          break;
+        default:
+          break;
+        }
+    }
+
+  return axes_mask;
+}
+
 static gboolean
 is_parent_of (GdkWindow *parent,
               GdkWindow *child)
@@ -1119,6 +1172,13 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
           case 5:
           case 6:
           case 7:
+            if (xev->flags & XIPointerEmulated)
+              {
+                // TODO(nh2) add an option to disable this or in some way make sure that _no_ lecacy app loses scrolling.
+                printf("nh2: ignoring emulated scroll button press event\n");
+                return_val = FALSE;
+                break;
+              }
              /* Button presses of button 4-7 are scroll events */
             if (ev->evtype == XI_ButtonPress)
               {
@@ -1234,13 +1294,100 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
         /* There doesn't seem to be motion hints in XI */
         event->motion.is_hint = FALSE;
 
-        event->motion.axes = translate_axes (event->motion.device,
+        double *axes = translate_axes (event->motion.device,
                                              event->motion.x,
                                              event->motion.y,
                                              event->motion.window,
                                              &xev->valuators);
+        event->motion.axes = axes;
+
+
+        // TODO(nh2) find the good way to check for a smooth scroll
+        guint smooth_scroll_axis_mask = get_smooth_scroll_axes_mask (event->motion.device,
+                                                                     event->motion.x,
+                                                                     event->motion.y,
+                                                                     event->motion.window,
+                                                                     &xev->valuators);
+
+        guint SMOOTH_SCROLL_AXIS_ANY = SMOOTH_SCROLL_AXIS_X | SMOOTH_SCROLL_AXIS_Y;
+
+        if(smooth_scroll_axis_mask & SMOOTH_SCROLL_AXIS_ANY)
+        {
+          printf("nh2: making a smooth-scroll event\n");
+          event->smooth_scroll.type = GDK_SMOOTH_SCROLL;
+
+          event->smooth_scroll.window = window;
+          event->smooth_scroll.time = xev->time;
+          event->smooth_scroll.x = (gdouble) xev->event_x;
+          event->smooth_scroll.y = (gdouble) xev->event_y;
+          event->smooth_scroll.x_root = (gdouble) xev->root_x;
+          event->smooth_scroll.y_root = (gdouble) xev->root_y;
+          event->smooth_scroll.device = g_hash_table_lookup (device_manager->id_table,
+                                                      GINT_TO_POINTER (xev->deviceid));
+          source_device = g_hash_table_lookup (device_manager->id_table,
+                                               GUINT_TO_POINTER (xev->sourceid));
+          gdk_event_set_source_device (event, source_device);
+
+
+          // TODO(niklash) are statics shared _across all programs_ that use the gtk shared lib?
+          // yes, and this leads to huge jumping in window A when you go to window B, scroll, go to A, scroll
+          static gdouble last_scroll_pos_x = 0.0;
+          static gdouble last_scroll_pos_y = 0.0;
+
+          gdouble scroll_distance_x;
+          gdouble scroll_distance_y;
+
+          // floating point modulo, always positive
+//          double inline wrap_mod(double x, double y)
+//          {
+//            if (y == 0) return x;
+//
+//            return x - y * floor(x/y);
+//          }
+
+          if (smooth_scroll_axis_mask & SMOOTH_SCROLL_AXIS_X)
+          {
+            gdouble new_scroll_pos_x = axes[2];
+
+            scroll_distance_x = new_scroll_pos_x - last_scroll_pos_x;
+
+            //if (new_scroll_pos_x >= 1.0) new_scroll_pos_x = wrap_mod(new_scroll_pos_x, 1.0);  // TODO(nh2) check >= or >
+            //if (new_scroll_pos_x < 0.0) new_scroll_pos_x = wrap_mod(new_scroll_pos_x, 1.0);
+
+            if (scroll_distance_x > 0.6 || scroll_distance_x < -0.6)
+              printf("distance X alert: last_pos: %f, axes[2]: %f, new_pos: %f, distance: %f\n", last_scroll_pos_x, axes[2], new_scroll_pos_x, scroll_distance_x);
+
+            last_scroll_pos_x = new_scroll_pos_x;
+      printf("jo my x is %f\n", scroll_distance_x);
+          }
+          else {
+            scroll_distance_x = 0.0;
+      printf("x axis not present\n");
+          }
+
+          if (smooth_scroll_axis_mask & SMOOTH_SCROLL_AXIS_Y)
+          {
+            gdouble new_scroll_pos_y = axes[3]; // TODO(nh2) fix 3
+
+            scroll_distance_y = new_scroll_pos_y - last_scroll_pos_y;
+
+            //if (new_scroll_pos_y >= 1.0) new_scroll_pos_y = wrap_mod(new_scroll_pos_y, 1.0);  // TODO(nh2) check >= or >
+            //if (new_scroll_pos_y < 0.0) new_scroll_pos_y = wrap_mod(new_scroll_pos_y, 1.0);
+
+            if (scroll_distance_y > 0.6 || scroll_distance_y < -0.6)
+              printf("distance Y alert: last_pos: %f, axes[3]: %f, new_pos: %f, distance: %f\n", last_scroll_pos_y, axes[3], new_scroll_pos_y, scroll_distance_y);
+
+            last_scroll_pos_y = new_scroll_pos_y;
+          }
+          else
+            scroll_distance_y = 0.0;
+
+          event->smooth_scroll.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
+          event->smooth_scroll.scroll_x = scroll_distance_x;
+          event->smooth_scroll.scroll_y = scroll_distance_y;
+        }
 
-        if (gdk_device_get_mode (event->motion.device) == GDK_MODE_WINDOW)
+        else if (gdk_device_get_mode (event->motion.device) == GDK_MODE_WINDOW)
           {
             GdkDevice *device = event->motion.device;
 
@@ -1335,6 +1482,7 @@ gdk_x11_device_manager_xi2_get_handled_events (GdkEventTranslator *translator)
           GDK_BUTTON_PRESS_MASK |
           GDK_BUTTON_RELEASE_MASK |
           GDK_SCROLL_MASK |
+          GDK_SMOOTH_SCROLL_MASK |
           GDK_ENTER_NOTIFY_MASK |
           GDK_LEAVE_NOTIFY_MASK |
           GDK_POINTER_MOTION_MASK |
diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c
index e42f730..2bab5f9 100644
--- a/gtk/gtkmain.c
+++ b/gtk/gtkmain.c
@@ -1831,6 +1831,7 @@ gtk_main_do_event (GdkEvent *event)
       break;
 
     case GDK_SCROLL:
+    case GDK_SMOOTH_SCROLL:
     case GDK_BUTTON_PRESS:
     case GDK_2BUTTON_PRESS:
     case GDK_3BUTTON_PRESS:
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index 1f2d33b..b1dc217 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -180,6 +180,8 @@ static void     gtk_scrolled_window_size_allocate      (GtkWidget         *widge
                                                         GtkAllocation     *allocation);
 static gboolean gtk_scrolled_window_scroll_event       (GtkWidget         *widget,
                                                         GdkEventScroll    *event);
+static gboolean gtk_scrolled_window_smooth_scroll_event (GtkWidget            *widget,
+                                                         GdkEventSmoothScroll *event);
 static gboolean gtk_scrolled_window_focus              (GtkWidget         *widget,
                                                         GtkDirectionType   direction);
 static void     gtk_scrolled_window_add                (GtkContainer      *container,
@@ -274,6 +276,7 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
   widget_class->draw = gtk_scrolled_window_draw;
   widget_class->size_allocate = gtk_scrolled_window_size_allocate;
   widget_class->scroll_event = gtk_scrolled_window_scroll_event;
+  widget_class->smooth_scroll_event = gtk_scrolled_window_smooth_scroll_event;
   widget_class->focus = gtk_scrolled_window_focus;
   widget_class->get_preferred_width = gtk_scrolled_window_get_preferred_width;
   widget_class->get_preferred_height = gtk_scrolled_window_get_preferred_height;
@@ -1842,6 +1845,7 @@ static gboolean
 gtk_scrolled_window_scroll_event (GtkWidget      *widget,
 				  GdkEventScroll *event)
 {
+  printf("nh2: gtk_scrolled_window_scroll_event here\n");
   GtkScrolledWindowPrivate *priv;
   GtkScrolledWindow *scrolled_window;
   GtkWidget *range;
@@ -1872,6 +1876,98 @@ gtk_scrolled_window_scroll_event (GtkWidget      *widget,
   return FALSE;
 }
 
+gdouble transform_linear (gdouble scroll_distance)
+{
+  return scroll_distance * 50.0;
+}
+
+gdouble transform_quadratic_1 (gdouble scroll_distance)
+{
+  static gdouble b1, b2;
+
+  gdouble transformed_scroll_distance = ((scroll_distance < 0.0) ? -1.0 : 1.0) * pow(fabs(scroll_distance * 100.0), 1.6) / 40;
+
+  if(b1 > 10.0 && b2 > 10.0 && transformed_scroll_distance < -7) transformed_scroll_distance = b1;
+
+  if(b1 < -10.0 && b2 < -10.0 && transformed_scroll_distance > 7) transformed_scroll_distance = b1;
+
+  b2 = b1;
+  b1 = transformed_scroll_distance;
+
+  return transformed_scroll_distance;
+}
+
+gdouble transform_quadratic_2 (gdouble scroll_distance)
+{
+  double sign = (scroll_distance >= 0.0 ? 1 : -1);
+  return pow(fabs(scroll_distance) * 8.0, 1.4) * sign;
+}
+
+gdouble transform_quadratic_3 (gdouble scroll_distance)
+{
+  static gdouble last_1 = 0, last_2 = 0;
+
+  double res = (transform_quadratic_2(scroll_distance) + last_1 + last_2) / 3.0;
+
+  last_2 = last_1;
+  last_1 = scroll_distance;
+
+  return res;
+}
+
+gdouble transform_smooth_scroll (gdouble scroll_distance)
+{
+  return transform_quadratic_2(scroll_distance);
+}
+
+static gboolean
+gtk_scrolled_window_smooth_scroll_event (GtkWidget            *widget,
+                                         GdkEventSmoothScroll *event)
+{
+  printf("nh2:                        gtk_scrolled_window_smooth_scroll_event\n");
+  GtkScrolledWindowPrivate *priv;
+  GtkScrolledWindow *scrolled_window;
+  GtkWidget *range_h;
+  GtkWidget *range_v;
+
+  gboolean handled = FALSE;
+
+  g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  scrolled_window = GTK_SCROLLED_WINDOW (widget);
+  priv = scrolled_window->priv;
+
+  range_h = priv->hscrollbar;
+  range_v = priv->vscrollbar;
+
+  if (range_h && gtk_widget_get_visible (range_h))
+    {
+      GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (range_h));
+
+      gdouble delta_x = event->scroll_x;
+
+      gtk_adjustment_set_value (adjustment, gtk_adjustment_get_value (adjustment) + delta_x);
+
+      handled = TRUE;
+    }
+
+  if (range_v && gtk_widget_get_visible (range_v))
+    {
+      GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (range_v));
+
+      //gdouble delta_y = event->scroll_y * 50.0;
+
+      double delta_y = transform_smooth_scroll(event->scroll_y);
+
+      gtk_adjustment_set_value (adjustment, gtk_adjustment_get_value (adjustment) + delta_y);
+
+      handled = TRUE;
+    }
+
+  return handled;
+}
+
 static gboolean
 gtk_scrolled_window_focus (GtkWidget        *widget,
 			   GtkDirectionType  direction)
diff --git a/gtk/gtkscrolledwindow.h b/gtk/gtkscrolledwindow.h
index b05b8aa..5cc5c4d 100644
--- a/gtk/gtkscrolledwindow.h
+++ b/gtk/gtkscrolledwindow.h
@@ -120,6 +120,7 @@ void           gtk_scrolled_window_set_min_content_height (GtkScrolledWindow *sc
 
 gint _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
 
+gdouble transform_smooth_scroll (gdouble scroll_distance);
 
 G_END_DECLS
 
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 9b07334..e315b13 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -8434,6 +8434,7 @@ text_window_realize (GtkTextWindow *win,
   attributes.height = win->allocation.height;
   attributes.event_mask = (GDK_EXPOSURE_MASK            |
                            GDK_SCROLL_MASK              |
+                           GDK_SMOOTH_SCROLL_MASK       |
                            GDK_KEY_PRESS_MASK           |
                            GDK_BUTTON_PRESS_MASK        |
                            GDK_BUTTON_RELEASE_MASK      |
diff --git a/gtk/gtktooltip.c b/gtk/gtktooltip.c
index f65e08d..941a0be 100644
--- a/gtk/gtktooltip.c
+++ b/gtk/gtktooltip.c
@@ -1546,6 +1546,7 @@ _gtk_tooltip_handle_event (GdkEvent *event)
       case GDK_DRAG_ENTER:
       case GDK_GRAB_BROKEN:
       case GDK_SCROLL:
+      case GDK_SMOOTH_SCROLL:
 	gtk_tooltip_hide_tooltip (current_tooltip);
 	break;
 
diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c
index 43c3d60..cedd219 100644
--- a/gtk/gtktreeview.c
+++ b/gtk/gtktreeview.c
@@ -2220,6 +2220,7 @@ gtk_tree_view_realize (GtkWidget *widget)
   attributes.height = allocation.height;
   attributes.event_mask = (GDK_EXPOSURE_MASK |
                            GDK_SCROLL_MASK |
+                           GDK_SMOOTH_SCROLL_MASK |
                            GDK_POINTER_MOTION_MASK |
                            GDK_ENTER_NOTIFY_MASK |
                            GDK_LEAVE_NOTIFY_MASK |
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index d5c689c..c994297 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -440,6 +440,7 @@ enum {
   BUTTON_PRESS_EVENT,
   BUTTON_RELEASE_EVENT,
   SCROLL_EVENT,
+  SMOOTH_SCROLL_EVENT,
   MOTION_NOTIFY_EVENT,
   DELETE_EVENT,
   DESTROY_EVENT,
@@ -1915,6 +1916,34 @@ gtk_widget_class_init (GtkWidgetClass *klass)
 		  GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
 
   /**
+   * GtkWidget::smooth-scroll-event:
+   * @widget: the object which received the signal.
+   * @event: (type Gdk.EventSmoothScroll): the #GdkEventSmoothScroll which triggered
+   *   this signal.
+   *
+   * The ::smooth-scroll-event signal is emitted when ... TODO(nh2).
+   *
+   * To receive this signal, the #GdkWindow associated to the widget needs
+   * to enable the #GDK_SMOOTH_SCROLL_MASK mask.
+   *
+   * This signal will be sent to the grab widget if there is one.
+   * TODO(nh2) Check if this copy-paste is true.
+   *
+   * Returns: %TRUE to stop other handlers from being invoked for the event.
+   *   %FALSE to propagate the event further.
+   *   TODO(nh2) check. should be true
+   */
+  widget_signals[SMOOTH_SCROLL_EVENT] =
+    g_signal_new (I_("smooth-scroll-event"),
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          G_STRUCT_OFFSET (GtkWidgetClass, smooth_scroll_event),
+          _gtk_boolean_handled_accumulator, NULL,
+          _gtk_marshal_BOOLEAN__BOXED,
+          G_TYPE_BOOLEAN, 1,
+          GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+  /**
    * GtkWidget::motion-notify-event:
    * @widget: the object which received the signal.
    * @event: (type Gdk.EventMotion): the #GdkEventMotion which triggered
@@ -5995,7 +6024,11 @@ event_window_is_still_viewable (GdkEvent *event)
     case GDK_ENTER_NOTIFY:
     case GDK_PROXIMITY_IN:
     case GDK_SCROLL:
+    case GDK_SMOOTH_SCROLL:
       return event->any.window && gdk_window_is_viewable (event->any.window);
+      // TODO(nh2) remove hack (not even sure if it's necessary)
+  //    printf("nh2: event_window_is_still_viewable right event\n");
+  //    return TRUE;
 
 #if 0
     /* The following events are the second half of paired events;
@@ -6052,6 +6085,9 @@ gtk_widget_event_internal (GtkWidget *widget,
 	case GDK_SCROLL:
 	  signal_num = SCROLL_EVENT;
 	  break;
+	case GDK_SMOOTH_SCROLL:
+	  signal_num = SMOOTH_SCROLL_EVENT;
+	  break;
 	case GDK_BUTTON_RELEASE:
 	  signal_num = BUTTON_RELEASE_EVENT;
 	  break;
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index ead01ed..04ef8d1 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -427,6 +427,9 @@ struct _GtkWidgetClass
 
   void         (* style_updated)          (GtkWidget *widget);
 
+  gboolean (* smooth_scroll_event)      (GtkWidget            *widget,
+					 GdkEventSmoothScroll *event);
+
   /*< private >*/
 
   GtkWidgetClassPrivate *priv;
@@ -438,7 +441,6 @@ struct _GtkWidgetClass
   void (*_gtk_reserved5) (void);
   void (*_gtk_reserved6) (void);
   void (*_gtk_reserved7) (void);
-  void (*_gtk_reserved8) (void);
 };
 
 struct _GtkWidgetAuxInfo
