diff --git a/openscad.pro b/openscad.pro
index 6b32afc..1bd5b95 100644
--- a/openscad.pro
+++ b/openscad.pro
@@ -577,3 +577,10 @@ INSTALLS += icons
 man.path = $$PREFIX/share/man/man1
 man.extra = cp -f doc/openscad.1 \"\$(INSTALL_ROOT)$${man.path}/$${FULLNAME}.1\"
 INSTALLS += man
+
+# This is ugly, but what would be a better way to do this?
+exists("/usr/include/spnav.h") | exists("/usr/local/include/spnav.h") {
+  message("spacenav found")
+  DEFINES += SPACENAV
+  LIBS += -lspnav
+}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 5e1e023..c6b6d87 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -101,6 +101,19 @@ private slots:
 	void openCSGSettingsChanged();
 	void consoleOutput(const QString &msg);
 
+#ifdef SPACENAV
+private:
+    QTimer *spnav_timer;
+private slots:
+    void spnav_init();
+    void spnav_deinit();
+    void spnav_poll();
+#endif
+
+protected:
+    bool spnav_dominant=false;
+    int spnav_sensitivity=128;  // divide values from driver by this value; lower values mean higher sensitivity
+
 private:
         void initActionIcon(QAction *action, const char *darkResource, const char *lightResource);
 	void openFile(const QString &filename);
diff --git a/src/QGLView.cc b/src/QGLView.cc
index bef5744..8953e8d 100644
--- a/src/QGLView.cc
+++ b/src/QGLView.cc
@@ -225,15 +225,11 @@ void QGLView::mouseMoveEvent(QMouseEvent *event)
       ) {
       // Left button rotates in xz, Shift-left rotates in xy
       // On Mac, Ctrl-Left is handled as right button on other platforms
-      cam.object_rot.x() += dy;
       if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0)
-        cam.object_rot.y() += dx;
+          rotate(dy, dx, 0);
       else
-        cam.object_rot.z() += dx;
+          rotate(dy, 0, dx);
 
-      normalizeAngle(cam.object_rot.x());
-      normalizeAngle(cam.object_rot.y());
-      normalizeAngle(cam.object_rot.z());
     } else {
       // Right button pans in the xz plane
       // Middle button pans in the xy plane
@@ -242,44 +238,23 @@ void QGLView::mouseMoveEvent(QMouseEvent *event)
 	      cam.zoom(-12.0 * dy);
       } else {
 
-      double mx = +(dx) * 3.0 * cam.zoomValue() / QWidget::width();
-      double mz = -(dy) * 3.0 * cam.zoomValue() / QWidget::height();
+          double mx = +(dx) * 3.0 * cam.zoomValue() / QWidget::width();
+          double mz = -(dy) * 3.0 * cam.zoomValue() / QWidget::height();
 
-      double my = 0;
+          double my = 0;
 #if (QT_VERSION < QT_VERSION_CHECK(4, 7, 0))
-      if (event->buttons() & Qt::MidButton) {
+          if (event->buttons() & Qt::MidButton) {
 #else
-      if (event->buttons() & Qt::MiddleButton) {
+          if (event->buttons() & Qt::MiddleButton) {
 #endif
-        my = mz;
-        mz = 0;
-        // actually lock the x-position
-        // (turns out to be easier to use than xy panning)
-        mx = 0;
-      }
-
-      Matrix3d aax, aay, aaz, tm3;
-      aax = Eigen::AngleAxisd(-(cam.object_rot.x()/180) * M_PI, Vector3d::UnitX());
-      aay = Eigen::AngleAxisd(-(cam.object_rot.y()/180) * M_PI, Vector3d::UnitY());
-      aaz = Eigen::AngleAxisd(-(cam.object_rot.z()/180) * M_PI, Vector3d::UnitZ());
-      tm3 = Matrix3d::Identity();
-      tm3 = aaz * (aay * (aax * tm3));
-
-      Matrix4d tm;
-      tm = Matrix4d::Identity();
-      for (int i=0;i<3;i++) for (int j=0;j<3;j++) tm(j,i)=tm3(j,i);
-
-      Matrix4d vec;
-      vec <<
-        0,  0,  0,  mx,
-        0,  0,  0,  my,
-        0,  0,  0,  mz,
-        0,  0,  0,  1
-      ;
-      tm = tm * vec;
-      cam.object_trans.x() += tm(0,3);
-      cam.object_trans.y() += tm(1,3);
-      cam.object_trans.z() += tm(2,3);
+            my = mz;
+            mz = 0;
+            // actually lock the x-position
+            // (turns out to be easier to use than xy panning)
+            mx = 0;
+          }
+
+          translate(mx,my,mz);
       }
     }
     updateGL();
@@ -317,16 +292,71 @@ void QGLView::wheelEvent(QWheelEvent *event)
   updateGL();
 }
 
-void QGLView::ZoomIn(void)
+void QGLView::rotate(double x, double y, double z) {
+    cam.object_rot.x() += x;
+    cam.object_rot.y() += y;
+    cam.object_rot.z() += z;
+
+    normalizeAngle(cam.object_rot.x());
+    normalizeAngle(cam.object_rot.y());
+    normalizeAngle(cam.object_rot.z());
+}
+
+void QGLView::rotate2(double x, double y, double z) {
+
+    // FIXME: This is not quite right.
+    double rx = x;
+    double ry = -cos(cam.object_rot.x()/180.*M_PI)*y + sin(cam.object_rot.x()/180.*M_PI)*z;
+    double rz =  sin(cam.object_rot.x()/180.*M_PI)*y + cos(cam.object_rot.x()/180.*M_PI)*z;
+
+    cam.object_rot.x() += rx;
+    cam.object_rot.y() += ry;
+    cam.object_rot.z() += rz;
+
+    normalizeAngle(cam.object_rot.x());
+    normalizeAngle(cam.object_rot.y());
+    normalizeAngle(cam.object_rot.z());
+}
+
+void QGLView::translate(double x, double y, double z) {
+    Matrix3d aax, aay, aaz, tm3;
+    aax = Eigen::AngleAxisd(-(cam.object_rot.x()/180) * M_PI, Vector3d::UnitX());
+    aay = Eigen::AngleAxisd(-(cam.object_rot.y()/180) * M_PI, Vector3d::UnitY());
+    aaz = Eigen::AngleAxisd(-(cam.object_rot.z()/180) * M_PI, Vector3d::UnitZ());
+    tm3 = Matrix3d::Identity();
+    tm3 = aaz * (aay * (aax * tm3));
+
+    Matrix4d tm;
+    tm = Matrix4d::Identity();
+    for (int i=0;i<3;i++) for (int j=0;j<3;j++) tm(j,i)=tm3(j,i);
+
+    Matrix4d vec;
+    vec <<
+      0,  0,  0,  x,
+      0,  0,  0,  y,
+      0,  0,  0,  z,
+      0,  0,  0,  1
+    ;
+    tm = tm * vec;
+    cam.object_trans.x() += tm(0,3);
+    cam.object_trans.y() += tm(1,3);
+    cam.object_trans.z() += tm(2,3);
+}
+
+void QGLView::zoom(int delta) {
+    this->cam.zoom(delta);
+}
+
+void QGLView::ZoomIn()
 {
-  this->cam.zoom(120);
-  updateGL();
+    zoom(120);
+    updateGL();
 }
 
-void QGLView::ZoomOut(void)
+void QGLView::ZoomOut()
 {
-  this->cam.zoom(-120);
-  updateGL();
+    zoom(-120);
+    updateGL();
 }
 
 void QGLView::setOrthoMode(bool enabled) {
diff --git a/src/QGLView.h b/src/QGLView.h
index 697d3b5..c729d97 100644
--- a/src/QGLView.h
+++ b/src/QGLView.h
@@ -49,8 +49,12 @@ public:
 	void viewAll();
 
 public slots:
-	void ZoomIn(void);
-	void ZoomOut(void);
+    void rotate(double x, double y, double z);
+    void rotate2(double x, double y, double z);
+    void translate(double x, double y, double z);
+    void zoom(int delta);
+    void ZoomIn();
+    void ZoomOut();
 
 public:
 	QLabel *statusLabel;
diff --git a/src/mainwin.cc b/src/mainwin.cc
index 5735622..78f1819 100644
--- a/src/mainwin.cc
+++ b/src/mainwin.cc
@@ -125,6 +125,10 @@
 #include "boosty.h"
 #include "FontCache.h"
 
+#ifdef SPACENAV
+#include <spnav.h>
+#endif
+
 // Keeps track of open window
 QSet<MainWindow*> *MainWindow::windows = NULL;
 
@@ -582,6 +586,9 @@ MainWindow::MainWindow(const QString &filename)
 
 	setAcceptDrops(true);
 	clearCurrentOutput();
+#ifdef SPACENAV
+    spnav_init();
+#endif
 }
 
 void MainWindow::initActionIcon(QAction *action, const char *darkResource, const char *lightResource)
@@ -689,6 +696,9 @@ void MainWindow::updateReorderMode(bool reorderMode)
 
 MainWindow::~MainWindow()
 {
+#ifdef SPACENAV
+    spnav_deinit();
+#endif
 	if (root_module) delete root_module;
 	if (root_node) delete root_node;
 	if (root_chain) delete root_chain;
@@ -1552,6 +1562,8 @@ void MainWindow::findBufferChanged() {
 
 bool MainWindow::eventFilter(QObject* obj, QEvent *event)
 {
+    // FIXME: Compilation error when spnav.h is included
+#ifndef SPACENAV
     if (obj == find_panel)
     {
         if (event->type() == QEvent::KeyPress)
@@ -1565,6 +1577,7 @@ bool MainWindow::eventFilter(QObject* obj, QEvent *event)
         }
         return false;
     }
+#endif
     return QMainWindow::eventFilter(obj, event);
 }
 
@@ -2746,3 +2759,95 @@ void MainWindow::hideFontCacheDialog()
 	assert(MainWindow::fontCacheDialog);
 	MainWindow::fontCacheDialog->reset();
 }
+
+#ifdef SPACENAV
+void MainWindow::spnav_init() {
+    int r = spnav_open();
+    if(r==0) {
+        spnav_timer = new QTimer(this);
+        connect(spnav_timer, SIGNAL(timeout()), this, SLOT(spnav_poll()));
+        spnav_timer->start(10);
+    } else {
+        spnav_timer=0;
+    }
+}
+
+void MainWindow::spnav_deinit() {
+    if(spnav_timer) {
+        spnav_timer->stop();
+        delete spnav_timer;
+    }
+    spnav_close();
+}
+
+void MainWindow::spnav_poll() {
+    spnav_event sev;
+    bool update=false;
+
+    while(spnav_poll_event(&sev) != 0) {
+        if(!isActiveWindow()) continue;
+
+        if(sev.type == SPNAV_EVENT_MOTION) {
+            if(spnav_dominant) {
+                // dominant axis only
+                int m=sev.motion.x;
+                if(abs(m)<abs(sev.motion.y)) m=sev.motion.y;
+                if(abs(m)<abs(sev.motion.z)) m=sev.motion.z;
+                if(abs(m)<abs(sev.motion.rx)) m=sev.motion.rx;
+                if(abs(m)<abs(sev.motion.ry)) m=sev.motion.ry;
+                if(abs(m)<abs(sev.motion.rz)) m=sev.motion.rz;
+
+                if(sev.motion.x ==m) {                 sev.motion.y=0; sev.motion.z=0; sev.motion.rx=0; sev.motion.ry=0; sev.motion.rz=0; }
+                if(sev.motion.y ==m) { sev.motion.x=0;                 sev.motion.z=0; sev.motion.rx=0; sev.motion.ry=0; sev.motion.rz=0; }
+                if(sev.motion.z ==m) { sev.motion.x=0; sev.motion.y=0;                 sev.motion.rx=0; sev.motion.ry=0; sev.motion.rz=0; }
+                if(sev.motion.rx==m) { sev.motion.x=0; sev.motion.y=0; sev.motion.z=0;                  sev.motion.ry=0; sev.motion.rz=0; }
+                if(sev.motion.ry==m) { sev.motion.x=0; sev.motion.y=0; sev.motion.z=0; sev.motion.rx=0;                  sev.motion.rz=0; }
+                if(sev.motion.rz==m) { sev.motion.x=0; sev.motion.y=0; sev.motion.z=0; sev.motion.rx=0; sev.motion.ry=0;                  }
+            }
+
+            // rotation
+            qglview->rotate2((double)sev.motion.rx / spnav_sensitivity,
+                             (double)sev.motion.ry / spnav_sensitivity,
+                             (double)sev.motion.rz / spnav_sensitivity);
+
+            // translation
+            qglview->translate(sev.motion.x / (double)spnav_sensitivity,
+                               0,
+                               sev.motion.z / (double)spnav_sensitivity);
+
+            // zoom
+            qglview->zoom(10 * sev.motion.y / (double)spnav_sensitivity);
+
+            update=true;
+
+        } else if(sev.type == SPNAV_EVENT_BUTTON) {
+            if(sev.button.press==1) {
+                switch(sev.button.bnum) {
+                    case 0: viewOrthogonal();                       break; // 1: orthogonal
+                    case 1: viewPerspective();                      break; // 2: perspective
+                    case 2: qglview->cam.object_rot << 0,90,0;      break; // 3: sideways 1 (for testing)
+                    case 3: qglview->cam.object_rot << 30,90,330;   break; // 4: sideways 2 (for testing)
+                    case 4: actionRenderPreview();                  break; // 5: render preview
+                    case 5: actionRender();                         break; // 6: render
+
+                    case 6: qglview->cam.object_rot << 90,0,0;      break; // T:   top
+                    case 7: qglview->cam.object_rot << 0,0,90;      break; // L:   left
+                    case 8: qglview->cam.object_rot << 0,0,270;     break; // R:   right
+                    case 9: qglview->cam.object_rot << 0,0,0;       break; // F:   front
+                    case 19: qglview->cam.object_rot << 35,0,-25 ;  break; // 3D:  diagonal
+                    case 14: qglview->viewAll();                    break; // Fit: view all
+
+                    case 16: if(spnav_sensitivity>32  ) spnav_sensitivity /=2;  break; // +:   increase sensitivity
+                    case 17: if(spnav_sensitivity<2048) spnav_sensitivity *=2;  break; // -:   decrease sensitivity
+                    case 18: spnav_dominant = !spnav_dominant;                  break; // Dom: toggle dominant axis mode
+                }
+                update=true;
+            }
+        }
+    }
+    // First, process all events from the queue, then updateGL() only once (if necessary at all).
+    // If the queue unexpectedly fills so fast that this behaviour could cause noticeable delays,
+    // then rendering more often would only make matters worse anyway.
+    if(update) qglview->updateGL();
+}
+#endif //SPACENAV
