From ba812e52bd581d2dd17bc095dc9b4f8d79c78f42 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 16:57:00 -0400
Subject: initial travis test
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e4f9ff7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+language: cpp
+compiler:
+  - gcc
+  - clang
+before_install:
+ - sudu apt-get update -qq
+ - sudu apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
+
+branches:
+  only:
+    - travis
+
+script: echo "Success"
-- 
cgit v0.10.1
From 1f6c61d9ce6baa31f86e0151519acd7df5f39ee8 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 17:00:13 -0400
Subject: typo
diff --git a/.travis.yml b/.travis.yml
index e4f9ff7..04d4728 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,8 @@ compiler:
   - gcc
   - clang
 before_install:
- - sudu apt-get update -qq
- - sudu apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
 
 branches:
   only:
-- 
cgit v0.10.1
From cc1688cab89f1be54ff7cbd7f749033d4f26d44b Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 17:04:54 -0400
Subject: Script for kicking off Travis CI
diff --git a/.travis.yml b/.travis.yml
index 04d4728..786e1a8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,4 +10,4 @@ branches:
   only:
     - travis
 
-script: echo "Success"
+script: ./scripts/travis-ci.sh
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
new file mode 100755
index 0000000..a5ea97f
--- /dev/null
+++ b/scripts/travis-ci.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+qmake
+make
+cd tests
+cmake .
+make
+ctest
-- 
cgit v0.10.1
From f8abd39095b0f75aa1f94db3c6e2ecd29bcd631f Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 17:16:21 -0400
Subject: Get OpenCSG from PPA
diff --git a/.travis.yml b/.travis.yml
index 786e1a8..2d526e9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,9 @@ compiler:
   - clang
 before_install:
  - sudo apt-get update -qq
- - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
+ - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
+ - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
+ - sudo apt-get install -qq libopencsg-dev 
 
 branches:
   only:
-- 
cgit v0.10.1
From e06dbbe7421ac2f7bd2f59710cae344665cd1bd7 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 17:22:22 -0400
Subject: Get OpenCSG from PPA - attempt 2
diff --git a/.travis.yml b/.travis.yml
index 2d526e9..040c5dc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ compiler:
 before_install:
  - sudo apt-get update -qq
  - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
- - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
+ - echo 'yes' | sudo add-apt-repository ppa:chrysn/opencsg
  - sudo apt-get install -qq libopencsg-dev 
 
 branches:
-- 
cgit v0.10.1
From acdf95002f919186f823effc75932814bc37eb64 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 17:32:53 -0400
Subject: Get OpenCSG from PPA - attempt 3
diff --git a/.travis.yml b/.travis.yml
index 040c5dc..f8c3fa7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,8 @@ compiler:
 before_install:
  - sudo apt-get update -qq
  - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
- - echo 'yes' | sudo add-apt-repository ppa:chrysn/opencsg
- - sudo apt-get install -qq libopencsg-dev 
+ - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
+ - sudo apt-get update -qq libopencsg-dev 
 
 branches:
   only:
-- 
cgit v0.10.1
From 2b966f92f247d50f2b3201d492ebc167302fc1bf Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 18:22:06 -0400
Subject: Get OpenCSG from PPA - attempt 4
diff --git a/.travis.yml b/.travis.yml
index f8c3fa7..7c9bdd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,10 +3,10 @@ compiler:
   - gcc
   - clang
 before_install:
+ - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
  - sudo apt-get update -qq
  - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
- - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
- - sudo apt-get update -qq libopencsg-dev 
+ - sudo apt-get install -qq libopencsg-dev
 
 branches:
   only:
-- 
cgit v0.10.1
From d37cb95e2ede85193e3f3f5004095964cedc0d35 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 19:32:54 -0400
Subject: Detect errors mid-way into test run
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
index a5ea97f..8aa5d1e 100755
--- a/scripts/travis-ci.sh
+++ b/scripts/travis-ci.sh
@@ -1,8 +1,23 @@
 #!/bin/bash
 
-qmake
-make
+qmake && make
+if [[ $? != 0 ]]; then
+  echo "Error building OpenSCAD executable"
+  exit 1
+fi
 cd tests
-cmake .
+cmake . 
+if [[ $? != 0 ]]; then
+  echo "Error configuring test suite"
+  exit 1
+fi
 make
+if [[ $? != 0 ]]; then
+  echo "Error building test suite"
+  exit 1
+fi
 ctest
+if [[ $? != 0 ]]; then
+  echo "Test failure"
+  exit 1
+fi
-- 
cgit v0.10.1
From 7d667b1025da27902f17dea7a85c55c830050c21 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 21:53:20 -0300
Subject: Update README.md
diff --git a/README.md b/README.md
index c22871a..293efbb 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
 # What is OpenSCAD?
 [](https://flattr.com/submit/auto?user_id=openscad&url=http://openscad.org&title=OpenSCAD&language=&tags=github&category=software)
 
-- 
cgit v0.10.1
From 9e55b312651f9ef94e91ea5b0fa58fff44fe17fe Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 21:14:24 -0400
Subject: clang not yet supported by the travis script
diff --git a/.travis.yml b/.travis.yml
index 7c9bdd2..9442ca4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 language: cpp
 compiler:
   - gcc
-  - clang
 before_install:
  - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
  - sudo apt-get update -qq
-- 
cgit v0.10.1
From 83dc80ff5f1dbf30fff8d7f263d47d7f70832bd4 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 13 May 2013 21:14:34 -0400
Subject: Parallel build
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
index 8aa5d1e..6f9868d 100755
--- a/scripts/travis-ci.sh
+++ b/scripts/travis-ci.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-qmake && make
+qmake && make -j4
 if [[ $? != 0 ]]; then
   echo "Error building OpenSCAD executable"
   exit 1
@@ -11,12 +11,12 @@ if [[ $? != 0 ]]; then
   echo "Error configuring test suite"
   exit 1
 fi
-make
+make -j4
 if [[ $? != 0 ]]; then
   echo "Error building test suite"
   exit 1
 fi
-ctest
+ctest -j8
 if [[ $? != 0 ]]; then
   echo "Test failure"
   exit 1
-- 
cgit v0.10.1
From 435e0c021c5018ee5de69d3218c3e31c8ab75be5 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Wed, 30 Oct 2013 22:39:18 -0400
Subject: Limit parallel builds as Travis apparently runs out of memory
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
index 6f9868d..9d4258a 100755
--- a/scripts/travis-ci.sh
+++ b/scripts/travis-ci.sh
@@ -11,7 +11,7 @@ if [[ $? != 0 ]]; then
   echo "Error configuring test suite"
   exit 1
 fi
-make -j4
+make -j2
 if [[ $? != 0 ]]; then
   echo "Error building test suite"
   exit 1
-- 
cgit v0.10.1
From cf9f19818ca5886275019f8e93c7fb8ec0e4bde6 Mon Sep 17 00:00:00 2001
From: Don Bright 
Date: Tue, 26 Nov 2013 20:04:57 -0600
Subject: prevent crash in CGAL nef3. fix #issue 410 . also deal w qmake bug re
 .h files
diff --git a/openscad.pro b/openscad.pro
index fd9f494..b38419e 100644
--- a/openscad.pro
+++ b/openscad.pro
@@ -39,6 +39,7 @@ debug: DEFINES += DEBUG
 TEMPLATE = app
 
 INCLUDEPATH += src
+DEPENDPATH += src
 
 # Handle custom library location.
 # Used when manually installing 3rd party libraries
@@ -368,6 +369,7 @@ HEADERS += src/cgal.h \
            src/PolySetCGALEvaluator.h \
            src/CGALRenderer.h \
            src/CGAL_Nef_polyhedron.h \
+           src/CGAL_Nef3_workaround.h \
            src/cgalworker.h
 
 SOURCES += src/cgalutils.cc \
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc
index ec01315..242fe0f 100644
--- a/src/CGALEvaluator.cc
+++ b/src/CGALEvaluator.cc
@@ -159,9 +159,19 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node)
 				PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export");
 			}
 			else {
-				chN.p3->convert_to_Polyhedron(P);
-				std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), 
-											 boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1));
+				bool err = false;
+				try{
+					err = nefworkaround::convert_to_Polyhedron( *(chN.p3), P );
+					//chN.p3->convert_to_Polyhedron(P);
+				} catch (...) {
+					err = true;
+				}
+				if (err) {
+					PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed");
+				} else {
+					std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), 
+										 boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1));
+				}
 			}
 		}
 		chnode->progress_report();
diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc
index 440f4ed..4761d26 100644
--- a/src/CGAL_Nef_polyhedron.cc
+++ b/src/CGAL_Nef_polyhedron.cc
@@ -98,11 +98,22 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset()
 		CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
 		try {
 			CGAL_Polyhedron P;
-			this->p3->convert_to_Polyhedron(P);
-			ps = createPolySetFromPolyhedron(P);
+			bool err = nefworkaround::convert_to_Polyhedron( *(this->p3), P );
+			//this->p3->convert_to_Polyhedron(P);
+			if (err) {
+				PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed");
+			} else {
+				ps = createPolySetFromPolyhedron(P);
+			}
 		}
 		catch (const CGAL::Precondition_exception &e) {
-			PRINTB("CGAL error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what());
+			PRINTB("CGAL Precondition error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what());
+		}
+		catch (const CGAL::Assertion_exception &e) {
+			PRINTB("CGAL Assertion error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what());
+		}
+		catch (...) {
+			PRINT("CGAL unknown error in CGAL_Nef_polyhedron::convertToPolyset()");
 		}
 		CGAL::set_error_behaviour(old_behaviour);
 	}
diff --git a/src/cgal.h b/src/cgal.h
index 45228be..69c8c27 100644
--- a/src/cgal.h
+++ b/src/cgal.h
@@ -27,6 +27,7 @@ using boost::uintmax_t;
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -48,9 +49,10 @@ typedef CGAL::Exact_predicates_exact_constructions_kernel CGAL_ExactKernel2;
 typedef CGAL::Polygon_2 CGAL_Poly2;
 typedef CGAL::Polygon_with_holes_2 CGAL_Poly2h;
 
- //typedef CGAL::Cartesian CGAL_Kernel3;
-typedef CGAL::Exact_predicates_exact_constructions_kernel CGAL_Kernel3;
-typedef CGAL::Exact_predicates_exact_constructions_kernel::FT NT3;
+typedef CGAL::Gmpq NT3;
+typedef CGAL::Cartesian CGAL_Kernel3;
+//typedef CGAL::Exact_predicates_exact_constructions_kernel::FT NT3;
+//typedef CGAL::Exact_predicates_exact_constructions_kernel CGAL_Kernel3;
 typedef CGAL::Nef_polyhedron_3 CGAL_Nef_polyhedron3;
 typedef CGAL_Nef_polyhedron3::Aff_transformation_3 CGAL_Aff_transformation;
 
diff --git a/src/export.cc b/src/export.cc
index ec6e576..cef323e 100644
--- a/src/export.cc
+++ b/src/export.cc
@@ -42,7 +42,12 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output)
 	CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
 	try {
 	CGAL_Polyhedron P;
-	root_N->p3->convert_to_Polyhedron(P);
+	//root_N->p3->convert_to_Polyhedron(P);
+	bool err = nefworkaround::convert_to_Polyhedron( *(root_N->p3), P );
+	if (err) {
+	        PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed");
+		return;
+	}
 
 	typedef CGAL_Polyhedron::Vertex                                 Vertex;
 	typedef CGAL_Polyhedron::Vertex_const_iterator                  VCI;
@@ -114,6 +119,9 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output)
 	catch (const CGAL::Assertion_exception &e) {
 		PRINTB("CGAL error in CGAL_Nef_polyhedron3::convert_to_Polyhedron(): %s", e.what());
 	}
+	catch (...) {
+		PRINT("CGAL unknown error in CGAL_Nef_polyhedron3::convert_to_Polyhedron()");
+	}
 	CGAL::set_error_behaviour(old_behaviour);
 }
 
-- 
cgit v0.10.1
From faf008ce24e5169dcfe75d90bfbc988abdfd7f93 Mon Sep 17 00:00:00 2001
From: Don Bright 
Date: Tue, 26 Nov 2013 20:29:29 -0600
Subject: simplify nef polyhedron code. attempt to add test for bug
diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc
index 4761d26..49b9a53 100644
--- a/src/CGAL_Nef_polyhedron.cc
+++ b/src/CGAL_Nef_polyhedron.cc
@@ -96,24 +96,22 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset()
 	}
 	else if (this->dim == 3) {
 		CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
+		bool err = true;
+		std::string errmsg("");
+		CGAL_Polyhedron P;
 		try {
-			CGAL_Polyhedron P;
-			bool err = nefworkaround::convert_to_Polyhedron( *(this->p3), P );
+			err = nefworkaround::convert_to_Polyhedron( *(this->p3), P );
 			//this->p3->convert_to_Polyhedron(P);
-			if (err) {
-				PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed");
-			} else {
-				ps = createPolySetFromPolyhedron(P);
-			}
 		}
-		catch (const CGAL::Precondition_exception &e) {
-			PRINTB("CGAL Precondition error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what());
+		catch (const CGAL::Failure_exception &e) {
+			err = true;
+			errmsg = std::string(e.what());
 		}
-		catch (const CGAL::Assertion_exception &e) {
-			PRINTB("CGAL Assertion error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what());
-		}
-		catch (...) {
-			PRINT("CGAL unknown error in CGAL_Nef_polyhedron::convertToPolyset()");
+		if (err) {
+			PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed.");
+			if (errmsg!="") PRINTB("ERROR: %s",errmsg);
+		} else {
+			ps = createPolySetFromPolyhedron(P);
 		}
 		CGAL::set_error_behaviour(old_behaviour);
 	}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3d3aad1..731a418 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -800,6 +800,7 @@ list(APPEND CGALPNGTEST_FILES ${FEATURES_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_FILE
 list(APPEND CGALPNGTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/transform-nan-inf-tests.scad
+                           ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/stl-cgal-convert_to_Polyhedron-crash.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles-test.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles_dir/localfiles-compatibility-test.scad)
 
-- 
cgit v0.10.1
From fd715c6526e961cb7f3d6ba6a0563788d7d1674d Mon Sep 17 00:00:00 2001
From: Don Bright 
Date: Tue, 26 Nov 2013 20:55:26 -0600
Subject: finish adding new test, add png for new test
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 731a418..0477a45 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -794,6 +794,7 @@ list(APPEND DUMPTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/escape-test
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles_dir/localfiles-compatibility-test.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allexpressions.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allfunctions.scad
+                           ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/stl-cgal-convert_to_Polyhedron-crash.scad
                            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allmodules.scad)
 
 list(APPEND CGALPNGTEST_FILES ${FEATURES_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_FILES})
@@ -823,6 +824,10 @@ disable_tests(openscad-csgpng_child-background)
 disable_tests(opencsgtest_example006 cgalpngtest_example006)
 disable_tests(openscad-csgpng_example006 openscad-cgalpng_example006)
 
+# NefPolyhedron->Polyhedron conversion failures. No images for OpenCSG/Thrown
+disable_tests(opencsgtest_stl-cgal-convert_to_Polyhedron-crash)
+disable_tests(throwntogethertest_stl-cgal-convert_to_Polyhedron-crash)
+
 # These tests only makes sense in OpenCSG mode
 disable_tests(cgalpngtest_child-background
               cgalpngtest_highlight-and-background-modifier
diff --git a/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png b/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png
new file mode 100644
index 0000000..318cbaa
Binary files /dev/null and b/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png differ
diff --git a/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg b/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg
new file mode 100644
index 0000000..acad52f
--- /dev/null
+++ b/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg
@@ -0,0 +1,5 @@
+group() {
+	render(convexity = 1) {
+		import(file = "stl-cgal-convert_to_Polyhedron-crash.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
+	}
+}
-- 
cgit v0.10.1
From 9ea7713335122eabdd243cfcf1e5ae87a8bd23d1 Mon Sep 17 00:00:00 2001
From: Don Bright 
Date: Sat, 30 Nov 2013 15:43:00 -0600
Subject: print errmsg for applyHull. add quotes around err msg in version
 check.
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc
index 242fe0f..4a05274 100644
--- a/src/CGALEvaluator.cc
+++ b/src/CGALEvaluator.cc
@@ -160,14 +160,15 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node)
 			}
 			else {
 				bool err = false;
+				std::string errmsg("");
 				try{
 					err = nefworkaround::convert_to_Polyhedron( *(chN.p3), P );
 					//chN.p3->convert_to_Polyhedron(P);
-				} catch (...) {
+				catch (const CGAL::Failure_exception &e) {
 					err = true;
 				}
 				if (err) {
-					PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed");
+					PRINTB("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed. %s", e.what());
 				} else {
 					std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), 
 										 boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1));
diff --git a/src/version_check.h b/src/version_check.h
index fbea077..be52e61 100644
--- a/src/version_check.h
+++ b/src/version_check.h
@@ -113,7 +113,7 @@ a time, to avoid confusion.
                    + __GNUC_MINOR__ * 100 \
                    + __GNUC_PATCHLEVEL__)
 #if GCC_VERSION == 40802
-#error OpenSCAD isn't compatible with gcc 4.8.2. Please try a different version
+#error "OpenSCAD isnt compatible with gcc 4.8.2. Please try a different version"
 #endif
 
 #endif // OPENSCAD_SKIP_VERSION_CHECK
-- 
cgit v0.10.1
From 791a49b9e8489818e41deae2b1d4ba2b6ff50e5f Mon Sep 17 00:00:00 2001
From: Don Bright 
Date: Sat, 30 Nov 2013 17:26:50 -0600
Subject: build bug fix
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc
index 4a05274..86118d7 100644
--- a/src/CGALEvaluator.cc
+++ b/src/CGALEvaluator.cc
@@ -161,14 +161,15 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node)
 			else {
 				bool err = false;
 				std::string errmsg("");
-				try{
+				try {
 					err = nefworkaround::convert_to_Polyhedron( *(chN.p3), P );
 					//chN.p3->convert_to_Polyhedron(P);
-				catch (const CGAL::Failure_exception &e) {
+				} catch (const CGAL::Failure_exception &e) {
 					err = true;
+					errmsg = std::string(e.what());
 				}
 				if (err) {
-					PRINTB("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed. %s", e.what());
+					PRINTB("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed. %s", errmsg);
 				} else {
 					std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), 
 										 boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1));
diff --git a/src/CGAL_Nef3_workaround.h b/src/CGAL_Nef3_workaround.h
new file mode 100644
index 0000000..c2482ac
--- /dev/null
+++ b/src/CGAL_Nef3_workaround.h
@@ -0,0 +1,352 @@
+// Copyright (c) 1997-2002,2005 Max-Planck-Institute Saarbruecken (Germany).
+// All rights reserved.
+//
+// This file is part of CGAL (www.cgal.org).
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation,
+// either version 3 of the License, or (at your option) any later version.
+//
+// Licensees holding a valid commercial license may use this file in
+// accordance with the commercial license agreement provided with the software.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// $URL: svn+ssh://scm.gforge.inria.fr/svn/cgal/branches/releases/CGAL-4.0-branch/Nef_3/include/CGAL/Nef_helper_3.h $
+// $Id: Nef_helper_3.h 67117 2012-01-13 18:14:48Z lrineau $
+// 
+//
+// Author(s)     : Michael Seel    
+//                 Miguel Granados 
+//                 Susan Hert      
+//                 Lutz Kettner    
+//                 Ralf Osbild     
+//                 Peter Hachenberger 
+
+/*
+ modified by don bright for OpenSCAD, 2013.
+
+This works around issue #410, where CGAL's Nef_Polyhedron3.convert_to_Polyhedron
+throws an uncatchable exception, due to an CGAL_Assertion being thrown in
+Polyhedron Incremental Builder's destructor while a Triangulation exception
+is still active, resulting in program termination (crashing).
+
+The purpose here is not to improve/change the way CGAL's Nef code works,
+but instead to tweak it slightly to prevent OpenSCAD from crashing. The
+behavior of the code should otherwise be exactly the same as CGAL's standard
+code.
+
+This file was created by copying three sections
+from CGAL's Nef_polyhedron3.h that were protected/private:
+
+ Triangulation_handler2
+ Build_Polyhedron
+ convert_to_polyhedron
+
+Very small code changes have been made. First, there are many template
+type specifiers added to enable the movement of the code to the outside
+of the Nef Polyhedron class. Second, there is a try{}catch(...){} block
+added in the Builder around the Triangulation code. Third, there is an error
+variable added for non-Exception communication with the caller.
+
+Eventually, if CGAL itself is updated and the update is widely
+distributed, this file may become obsolete and can be deleted from OpenSCAD
+
+*/
+
+
+#ifndef _CGAL_NEF3_WORKAROUND_H
+#define _CGAL_NEF3_WORKAROUND_H
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "printutils.h"
+
+namespace nefworkaround {
+
+template
+class Triangulation_handler2 {
+
+    typedef typename CGAL::Triangulation_vertex_base_2               Vb;
+    typedef typename CGAL::Constrained_triangulation_face_base_2     Fb;
+    typedef typename CGAL::Triangulation_data_structure_2             TDS;
+    typedef typename CGAL::Constrained_triangulation_2           CT;
+
+    typedef typename CT::Face_handle           Face_handle;
+    typedef typename CT::Vertex_handle         CTVertex_handle;
+    typedef typename CT::Finite_faces_iterator Finite_face_iterator;
+    typedef typename CT::Edge                  Edge;
+    CT ct;
+    CGAL::Unique_hash_map visited;
+    CGAL::Unique_hash_map ctv2v;
+    Finite_face_iterator fi;
+    typename Nef::Plane_3 supporting_plane;
+
+  public:
+    Triangulation_handler2(typename Nef::Halffacet_const_handle f) :
+      visited(false), supporting_plane(f->plane()) {
+
+      typename Nef::Halffacet_cycle_const_iterator fci;
+      for(fci=f->facet_cycles_begin(); fci!=f->facet_cycles_end(); ++fci) {
+	if(fci.is_shalfedge()) {
+          typename Nef::SHalfedge_around_facet_const_circulator sfc(fci), send(sfc);
+	  CGAL_For_all(sfc,send) {
+            CGAL_NEF_TRACEN("  insert point" << sfc->source()->source()->point());
+	    CTVertex_handle ctv = ct.insert(sfc->source()->source()->point());
+	    ctv2v[ctv] = sfc->source()->source();
+          }
+        }
+      }
+
+      for(fci=f->facet_cycles_begin(); fci!=f->facet_cycles_end(); ++fci) {
+	if(fci.is_shalfedge()) {
+          typename Nef::SHalfedge_around_facet_const_circulator sfc(fci), send(sfc);
+	  CGAL_For_all(sfc,send) {
+            CGAL_NEF_TRACEN("  insert constraint" << sfc->source()->source()->point()
+	                     << "->" << sfc->source()->twin()->source()->point());
+	    ct.insert_constraint(sfc->source()->source()->point(),
+	                         sfc->source()->twin()->source()->point());
+          }
+        }
+      }
+      CGAL_assertion(ct.is_valid());
+
+      CGAL_NEF_TRACEN("number of finite triangles " << ct.number_of_faces());
+
+      typename CT::Face_handle infinite = ct.infinite_face();
+      typename CT::Vertex_handle ctv = infinite->vertex(1);
+      if(ct.is_infinite(ctv)) ctv = infinite->vertex(2);
+      CGAL_assertion(!ct.is_infinite(ctv));
+
+      typename CT::Face_handle opposite;
+      typename CT::Face_circulator vc(ctv,infinite);
+      do { opposite = vc++;
+      } while(!ct.is_constrained(typename CT::Edge(vc,vc->index(opposite))));
+      typename CT::Face_handle first = vc;
+
+      CGAL_assertion(!ct.is_infinite(first));
+      traverse_triangulation(first, first->index(opposite));
+
+      fi = ct.finite_faces_begin();
+    }
+
+    void traverse_triangulation(Face_handle f, int parent) {
+      visited[f] = true;
+      if(!ct.is_constrained(Edge(f,ct.cw(parent))) && !visited[f->neighbor(ct.cw(parent))]) {
+	Face_handle child(f->neighbor(ct.cw(parent)));
+	traverse_triangulation(child, child->index(f));
+      } 
+      if(!ct.is_constrained(Edge(f,ct.ccw(parent))) && !visited[f->neighbor(ct.ccw(parent))]) {
+	Face_handle child(f->neighbor(ct.ccw(parent)));
+	traverse_triangulation(child, child->index(f));
+      } 
+    } 
+ 
+    template
+    bool get_next_triangle(Triangle_3& tr) {
+      while(fi != ct.finite_faces_end() && visited[fi] == false) ++fi;
+      if(fi == ct.finite_faces_end()) return false;
+      tr = Triangle_3(fi->vertex(0)->point(), fi->vertex(1)->point(), fi->vertex(2)->point());
+      ++fi;
+      return true;
+    }
+
+    bool same_orientation(typename Nef::Plane_3 p1) const {
+      if(p1.a() != 0)
+	return CGAL::sign(p1.a()) == CGAL::sign(supporting_plane.a());
+      if(p1.b() != 0)
+	return CGAL::sign(p1.b()) == CGAL::sign(supporting_plane.b());
+      return CGAL::sign(p1.c()) == CGAL::sign(supporting_plane.c());
+    }
+
+    template
+    void handle_triangles(PIB& pib, Index& VI) {
+      while(fi != ct.finite_faces_end() && visited[fi] == false) ++fi;
+      while(fi != ct.finite_faces_end()) {
+	typename Nef::Plane_3 plane(fi->vertex(0)->point(),
+		      fi->vertex(1)->point(),
+		      fi->vertex(2)->point());
+	pib.begin_facet();
+	if(same_orientation(plane)) {
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(0)]]);
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(1)]]);
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(2)]]);
+	} else {
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(0)]]);
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(2)]]);
+	  pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(1)]]);
+	}
+	pib.end_facet();
+	do {
+	  ++fi;
+	} while(fi != ct.finite_faces_end() && visited[fi] == false);
+      }
+    }
+};
+
+
+
+
+
+
+
+
+template 
+class Build_polyhedron : public CGAL::Modifier_base {
+public:
+    bool error; // added for OpenSCAD
+    class Visitor {
+      typedef typename CGAL::Projection_traits_xy_3       XY;
+      typedef typename CGAL::Projection_traits_yz_3       YZ;
+      typedef typename CGAL::Projection_traits_xz_3       XZ;
+
+      const CGAL::Object_index& VI;
+      CGAL::Polyhedron_incremental_builder_3& B;
+      const typename Nef::SNC_const_decorator& D;
+      
+    public:
+      bool error;//added for OpenSCAD
+      Visitor(CGAL::Polyhedron_incremental_builder_3& BB,
+	      const typename Nef::SNC_const_decorator& sd,
+	      CGAL::Object_index& vi) : VI(vi), B(BB), D(sd), error(false) {}
+
+      void visit(typename Nef::Halffacet_const_handle opposite_facet) {
+
+	CGAL_NEF_TRACEN("Build_polyhedron: visit facet " << opposite_facet->plane());
+ 
+	CGAL_assertion(Nef::Infi_box::is_standard(opposite_facet->plane()));
+	
+	typename Nef::SHalfedge_const_handle se;
+	typename Nef::Halffacet_cycle_const_iterator fc;
+     	
+	typename Nef::Halffacet_const_handle f = opposite_facet->twin();
+
+	typename Nef::SHalfedge_around_facet_const_circulator 
+	  sfc1(f->facet_cycles_begin()), sfc2(sfc1);
+	
+	if(++f->facet_cycles_begin() != f->facet_cycles_end() ||
+	   ++(++(++sfc1)) != sfc2) {
+	  typename Nef::Vector_3 orth = f->plane().orthogonal_vector();
+	  int c = CGAL::abs(orth[0]) > CGAL::abs(orth[1]) ? 0 : 1;
+	  c = CGAL::abs(orth[2]) > CGAL::abs(orth[c]) ? 2 : c;
+
+	try{	  // added for OpenSCAD
+	  if(c == 0) {
+	    Triangulation_handler2 th(f);
+	    th.handle_triangles(B, VI);
+	  } else if(c == 1) {
+	    Triangulation_handler2 th(f);
+	    th.handle_triangles(B, VI);
+	  } else if(c == 2) {
+	    Triangulation_handler2 th(f);
+	    th.handle_triangles(B, VI);
+	  } else
+	    CGAL_error_msg( "wrong value");
+	 } catch(...) { // added for OpenSCAD
+	  PRINT("ERROR: CGAL NefPolyhedron Triangulation failed"); // added for OpenSCAD
+	  this->error=true; //added for OpenSCAD
+	 } // added for OpenSCAD
+	} else {
+
+	  B.begin_facet();
+	  fc = f->facet_cycles_begin();
+	  se = typename Nef::SHalfedge_const_handle(fc);
+	  CGAL_assertion(se!=0);
+	  typename Nef::SHalfedge_around_facet_const_circulator hc_start(se);
+	  typename Nef::SHalfedge_around_facet_const_circulator hc_end(hc_start);
+	  CGAL_For_all(hc_start,hc_end) {
+	    CGAL_NEF_TRACEN("   add vertex " << hc_start->source()->center_vertex()->point());
+	    B.add_vertex_to_facet(VI[hc_start->source()->center_vertex()]);
+	  }
+	  B.end_facet();
+	}
+      }
+
+      void visit(typename Nef::SFace_const_handle) {}
+      void visit(typename Nef::Halfedge_const_handle) {}
+      void visit(typename Nef::Vertex_const_handle) {}
+      void visit(typename Nef::SHalfedge_const_handle) {}
+      void visit(typename Nef::SHalfloop_const_handle) {}
+    };
+
+  public:
+
+    const typename Nef::SNC_const_decorator& scd;
+    CGAL::Object_index VI;
+
+    Build_polyhedron(const typename Nef::SNC_const_decorator& s) : error(false),
+      scd(s), VI(s.vertices_begin(),s.vertices_end(),'V') {}
+    
+    void operator()( HDS& hds) {
+      CGAL::Polyhedron_incremental_builder_3 B(hds, true);
+
+      int skip_volumes;
+      if(Nef::Infi_box::extended_kernel()) {
+	B.begin_surface(scd.number_of_vertices()-8, 
+			scd.number_of_facets()-6,
+			scd.number_of_edges()-12);
+	skip_volumes = 2;
+      }
+      else {
+	B.begin_surface(scd.number_of_vertices(), 
+			2*scd.number_of_vertices()-4,
+			3*scd.number_of_vertices()-6);
+	skip_volumes = 1;
+      }
+      
+      int vertex_index = 0;
+      typename Nef::Vertex_const_iterator v;
+      CGAL_forall_vertices(v,scd) {
+	if(Nef::Infi_box::is_standard(v->point())) {
+	  VI[v]=vertex_index++;
+	  B.add_vertex(v->point());
+	}
+      }     
+      
+      Visitor V(B,scd,VI);
+      typename Nef::Volume_const_handle c;
+      CGAL_forall_volumes(c,scd)
+	if(skip_volumes-- <= 0) {
+	  scd.visit_shell_objects(typename Nef:: SFace_const_handle(c->shells_begin()),V);
+	}
+     B.end_surface();
+     this->error=B.error()||V.error; // added for OpenSCAD
+     if (B.error()) B.rollback(); // added for OpenSCAD
+    }
+
+};
+
+template 
+bool convert_to_Polyhedron( const CGAL::Nef_polyhedron_3 &N, CGAL::Polyhedron_3 &P )
+{
+	// several lines here added for OpenSCAD
+	typedef typename CGAL::Nef_polyhedron_3 Nef3;
+	typedef typename CGAL::Polyhedron_3 Polyhedron;
+	typedef typename Polyhedron::HalfedgeDS HalfedgeDS;
+	CGAL_precondition(N.is_simple());
+	P.clear();
+	Build_polyhedron bp(N);
+	P.delegate(bp);
+	return bp.error;
+}
+
+
+
+
+
+} //namespace nefworkaround
+
+
+
+
+#endif
+
-- 
cgit v0.10.1
From 69cbb17497ab52620e39874a2323db1aadf6dcbe Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Wed, 4 Dec 2013 01:15:19 -0500
Subject: Killed warnings
diff --git a/src/CocoaUtils.mm b/src/CocoaUtils.mm
index 92640fd..9856b3d 100644
--- a/src/CocoaUtils.mm
+++ b/src/CocoaUtils.mm
@@ -8,7 +8,7 @@ void CocoaUtils::endApplication()
                   object:nil];
 }
 
-void CocoaUtils::nslog(const std::string &str, void *userdata)
+void CocoaUtils::nslog(const std::string &str, void * /* userdata */)
 {       
-  NSLog([NSString stringWithUTF8String: str.c_str()]);
+  NSLog(@"%s", str.c_str());
 }
diff --git a/src/modcontext.cc b/src/modcontext.cc
index 5b48009..7941cf5 100644
--- a/src/modcontext.cc
+++ b/src/modcontext.cc
@@ -162,7 +162,7 @@ void ModuleContext::dump(const AbstractModule *mod, const ModuleInstantiation *i
 #endif
 
 FileContext::FileContext(const class FileModule &module, const Context *parent)
-	: usedlibs(module.usedlibs), ModuleContext(parent)
+	: ModuleContext(parent), usedlibs(module.usedlibs)
 {
 	if (!module.modulePath().empty()) this->document_path = module.modulePath();
 }
-- 
cgit v0.10.1
From f2fe074e1d947f74e34833453cc613e46e5450a6 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Wed, 4 Dec 2013 01:15:34 -0500
Subject: clang fix: Clang claims to be gcc
diff --git a/src/stl-utils.cc b/src/stl-utils.cc
index 790fd17..027339c 100644
--- a/src/stl-utils.cc
+++ b/src/stl-utils.cc
@@ -1,4 +1,4 @@
-#if defined(__APPLE__) && defined(__GNUC__)
+#if defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__)
 
 #include 
 
-- 
cgit v0.10.1
From d3b82dcac0cbd6bb46c3236d1183f84b76b44748 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 5 Dec 2013 15:56:50 +1100
Subject: Fix for bad boost libraries
Get this error because of a search for a non-existent library on linux64
-----------
[ 69%] Built target tests-cgal
Scanning dependencies of target cgalcachetest
[ 70%] Building CXX object
CMakeFiles/cgalcachetest.dir/cgalcachetest.cc.o
make[2]: *** No rule to make target `/usr/lib/libboost_thread.so',
needed by `cgalcachetest'.  Stop.
make[1]: *** [CMakeFiles/cgalcachetest.dir/all] Error 2
make: *** [all] Error 2
[2]+  Done                    gedit openscad.pro  (wd:
~/git/openscad_unicode)
----------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0477a45..4cc86f5 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -377,11 +377,45 @@ if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6)
 endif()
 inclusion(CGAL_DIR CGAL_INCLUDE_DIRS)
 
+#Get rid of bad libraries suggested for BOOST dependencies (they don't exist on some machines and cause build failures).
+#/usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_system.so" FIND_POSITION  )
+if(NOT "-1" STREQUAL ${FIND_POSITION} )
+if(NOT EXISTS "/usr/lib/libboost_system.so")
+  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_system.so -- stripping" )
+  string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+endif()
+endif() 
+string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_thread.so" FIND_POSITION  )
+if(NOT "-1" STREQUAL ${FIND_POSITION} )
+if(NOT EXISTS "/usr/lib/libboost_thread.so")
+  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_thread.so -- stripping" )
+  string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+endif()
+endif() 
 if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" )
 	string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 	string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 endif()
 
+if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
+  # Force pkg-config to look _only_ in the local library folder
+  # in case OPENSCAD_LIBRARIES is set.
+  set(ENV{PKG_CONFIG_PATH} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig")
+  set(ENV{PKG_CONFIG_LIBDIR} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig")
+endif()
+
+# Find libraries (system installed or dependency built) using pkg-config
+find_package(PkgConfig REQUIRED)
+
+#GLib-2
+pkg_search_module(GLIB2 REQUIRED glib-2.0>=2.2.0)
+#Can't use the CXXFlags directly as they are ;-separated
+string(REPLACE ";" " " GLIB2_CFLAGS "${GLIB2_CFLAGS}")
+message(STATUS "glib-2.0 found: ${GLIB2_VERSION}")
+
+add_definitions(${GLIB2_CFLAGS})
+
 # Imagemagick
 
 if (SKIP_IMAGEMAGICK)
-- 
cgit v0.10.1
From 0717c67c9fa894ecb08dc5de281753a00922d1ee Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 5 Dec 2013 17:56:54 +1100
Subject: Unicode support for strings
Add suport for using unicode strings in .scad files. Support iterating
across them/accessing them via [] and searching.
--------
Add GLIB (to build for test and normal build -- both with installed and
built locally development files).
Add support for unicode chars to length and search builtin functions and
[] for strings.
Added unicode testing functions.
Ad GLIB to library info page.
diff --git a/.gitignore b/.gitignore
index 50dace1..59bac49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 /*.scad
 *.dmg
+*~
 *.tar*
 Makefile
 objects
diff --git a/README.md b/README.md
index 27f12ce..1e97e0f 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ Follow the instructions for the platform you're compiling on below.
 * [OpenCSG (1.3.2)](http://www.opencsg.org/)
 * [GLEW (1.5.4 ->)](http://glew.sourceforge.net/)
 * [Eigen (3.0 - 3.2)](http://eigen.tuxfamily.org/)
+* [glib2 (2.2.0)](https://developer.gnome.org/glib/)
 * [GCC C++ Compiler (4.2 ->)](http://gcc.gnu.org/)
 * [Bison (2.4)](http://www.gnu.org/software/bison/)
 * [Flex (2.5.35)](http://flex.sourceforge.net/)
diff --git a/common.pri b/common.pri
index 7153ded..696c8b1 100644
--- a/common.pri
+++ b/common.pri
@@ -11,3 +11,4 @@ include(opencsg.pri)
 include(glew.pri)
 include(eigen.pri)
 include(boost.pri)
+include(glib-2.0.pri)
\ No newline at end of file
diff --git a/glib-2.0.pri b/glib-2.0.pri
new file mode 100644
index 0000000..0fbc4e2
--- /dev/null
+++ b/glib-2.0.pri
@@ -0,0 +1,38 @@
+# Detect glib-2.0, then use this priority list to determine
+# which library to use:
+#
+# Priority
+# 1. GLIB2_INCLUDEPATH / GLIB2_LIBPATH (qmake parameter, not checked it given on commandline)
+# 2. OPENSCAD_LIBRARIES (environment variable)
+# 3. system's standard include paths from pkg-config
+
+glib-2.0 {
+
+# read environment variables
+OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
+GLIB2_DIR = $$(GLIB2DIR)
+
+!isEmpty(OPENSCAD_LIBRARIES_DIR) {
+  isEmpty(GLIB2_INCLUDEPATH) {
+    GLIB2_INCLUDEPATH_1 = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
+    GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include
+    GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
+  }
+}
+
+isEmpty(GLIB2_INCLUDEPATH) {
+  GLIB2_CFLAGS = $$system("pkg-config --cflags glib-2.0")
+} else {
+  GLIB2_CFLAGS = -I$$GLIB2_INCLUDEPATH_1
+  GLIB2_CFLAGS += -I$$GLIB2_INCLUDEPATH_2
+}
+
+isEmpty(GLIB2_LIBPATH) {
+  GLIB2_LIBS = $$system("pkg-config --libs glib-2.0")
+} else {
+  GLIB2_LIBS = -L$$GLIB2_LIBPATH -lglib-2.0
+}
+
+QMAKE_CXXFLAGS += $$GLIB2_CFLAGS
+LIBS += $$GLIB2_LIBS
+}
diff --git a/openscad.pro b/openscad.pro
index b38419e..ec5af20 100644
--- a/openscad.pro
+++ b/openscad.pro
@@ -8,7 +8,7 @@
 #   OPENCSGDIR
 #   OPENSCAD_LIBRARIES
 #
-# Please see the 'Buildling' sections of the OpenSCAD user manual 
+# Please see the 'Building' sections of the OpenSCAD user manual 
 # for updated tips & workarounds.
 #
 # http://en.wikibooks.org/wiki/OpenSCAD_User_Manual
@@ -156,6 +156,7 @@ CONFIG += cgal
 CONFIG += opencsg
 CONFIG += boost
 CONFIG += eigen
+CONFIG += glib-2.0
 
 #Uncomment the following line to enable QCodeEdit
 #CONFIG += qcodeedit
diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh
index b63c677..e587198 100755
--- a/scripts/check-dependencies.sh
+++ b/scripts/check-dependencies.sh
@@ -66,6 +66,21 @@ cgal_sysver()
   cgal_sysver_result=`grep "define  *CGAL_VERSION  *[0-9.]*" $cgalpath | awk '{print $3}'`
 }
 
+glib2_sysver()
+{
+  #Get architecture triplet - e.g. x86_64-linux-gnu
+  glib2archtriplet=`gcc -dumpmachine 2>/dev/null`
+  if [ -z "$VAR" ]; then
+    glib2archtriplet=`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`
+  fi
+  glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h
+  if [ ! -e $glib2path ]; then return; fi
+  glib2major=`grep "define  *GLIB_MAJOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2minor=`grep "define  *GLIB_MINOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2micro=`grep "define  *GLIB_MICRO_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2_sysver_result="${glib2major}.${glib2minor}.${glib2micro}"
+}
+
 boost_sysver()
 {
   boostpath=$1/include/boost/version.hpp
@@ -530,7 +545,7 @@ checkargs()
 
 main()
 {
-  deps="qt4 cgal gmp mpfr boost opencsg glew eigen gcc bison flex make"
+  deps="qt4 cgal gmp mpfr boost opencsg glew eigen glib2 gcc bison flex make"
   #deps="$deps curl git" # not technically necessary for build
   #deps="$deps python cmake imagemagick" # only needed for tests
   #deps="cgal"
diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh
index e652c47..8d912c3 100755
--- a/scripts/uni-build-dependencies.sh
+++ b/scripts/uni-build-dependencies.sh
@@ -603,5 +603,6 @@ build_boost 1.53.0
 build_cgal 4.0.2
 build_glew 1.9.0
 build_opencsg 1.3.2
+build_glib2 2.38.2
 
 echo "OpenSCAD dependencies built and installed to " $BASEDIR
diff --git a/scripts/uni-get-dependencies.sh b/scripts/uni-get-dependencies.sh
index a0306ef..d2408c0 100755
--- a/scripts/uni-get-dependencies.sh
+++ b/scripts/uni-get-dependencies.sh
@@ -8,7 +8,7 @@ get_fedora_deps()
 {
  sudo yum install qt-devel bison flex eigen3-devel python-paramiko \
   boost-devel mpfr-devel gmp-devel glew-devel CGAL-devel gcc gcc-c++ pkgconfig \
-  opencsg-devel git libXmu-devel curl imagemagick ImageMagick make \
+  opencsg-devel git libXmu-devel curl imagemagick ImageMagick glib2-devel make \
   xorg-x11-server-Xvfb
 }
 
@@ -21,7 +21,7 @@ get_altlinux_deps()
 {
  for i in boost-devel boost-filesystem-devel gcc4.5 gcc4.5-c++ boost-program_options-devel \
   boost-thread-devel boost-system-devel boost-regex-devel eigen3 libmpfr libgmp libgmp_cxx-devel qt4-devel libcgal-devel git-core \
-  libglew-devel flex bison curl imagemagick; do sudo apt-get install $i; done
+  libglew-devel flex bison curl imagemagick glib2-devel; do sudo apt-get install $i; done
 }
 
 get_freebsd_deps()
@@ -29,20 +29,21 @@ get_freebsd_deps()
  pkg_add -r bison boost-libs cmake git bash eigen3 flex gmake gmp mpfr \
   xorg libGLU libXmu libXi xorg-vfbserver glew \
   qt4-corelib qt4-gui qt4-moc qt4-opengl qt4-qmake qt4-rcc qt4-uic \
-  opencsg cgal curl imagemagick
+  opencsg cgal curl imagemagick glib2-devel
 }
 
 get_netbsd_deps()
 {
  sudo pkgin install bison boost cmake git bash eigen flex gmake gmp mpfr \
   qt4 glew cgal opencsg modular-xorg python27 py27-paramiko curl \
-  imagemagick ImageMagick
+  imagemagick ImageMagick glib2-devel
 }
 
 get_opensuse_deps()
 {
  sudo zypper install libeigen3-devel mpfr-devel gmp-devel boost-devel \
-  libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl
+  libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl \
+  glib2-devel
 }
 
 get_mageia_deps()
@@ -50,7 +51,7 @@ get_mageia_deps()
  sudo urpmi ctags
  sudo urpmi task-c-devel task-c++-devel libqt4-devel libgmp-devel \
   libmpfr-devel libboost-devel eigen3-devel libglew-devel bison flex \
-  cmake imagemagick python curl git x11-server-xvfb
+  cmake imagemagick glib2-devel python curl git x11-server-xvfb
 }
 
 get_debian_deps()
@@ -59,7 +60,7 @@ get_debian_deps()
   libxmu-dev cmake bison flex git-core libboost-all-dev \
   libXi-dev libmpfr-dev libboost-dev libglew-dev \
   libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev \
-  python-paramiko curl imagemagick; do
+  python-paramiko curl imagemagick libglib2.0-dev; do
    sudo apt-get -y install $pkg;
  done
 }
diff --git a/src/AboutDialog.html b/src/AboutDialog.html
index 99e7c3b..65a54d7 100644
--- a/src/AboutDialog.html
+++ b/src/AboutDialog.html
@@ -64,6 +64,7 @@ Please visit this link for a copy of the license: C++, GCC, clang
 python
 Nullsoft installer
+GLib
 
 
 
diff --git a/src/PlatformUtils.cc b/src/PlatformUtils.cc
index b02b822..8b39f6d 100644
--- a/src/PlatformUtils.cc
+++ b/src/PlatformUtils.cc
@@ -1,6 +1,8 @@
 #include "PlatformUtils.h"
 #include "boosty.h"
 
+#include 
+
 bool PlatformUtils::createLibraryPath()
 {
 	std::string path = PlatformUtils::libraryPath();
@@ -114,6 +116,7 @@ std::string PlatformUtils::info()
 	  << "\nOpenCSG version: " << OPENCSG_VERSION_STRING
 	  << "\nQt version: " << qtVersion
 	  << "\nMingW build: " << mingwstatus
+	  << "\nGLib version: "       << GLIB_MAJOR_VERSION << "." << GLIB_MINOR_VERSION << "." << GLIB_MICRO_VERSION
 	  << "\nOPENSCADPATH: " << getenv("OPENSCADPATH") << "\n"
 	;
 	return s.str();
diff --git a/src/func.cc b/src/func.cc
index 865a2b4..4587f72 100644
--- a/src/func.cc
+++ b/src/func.cc
@@ -45,6 +45,8 @@
 
 #include 
 #include 
+/*Unicode support for string lengths and array accesses*/
+#include 
 
 #ifdef __WIN32__
 #include 
@@ -306,7 +308,11 @@ Value builtin_length(const Context *, const EvalContext *evalctx)
 {
 	if (evalctx->numArgs() == 1) {
 		if (evalctx->getArgValue(0).type() == Value::VECTOR) return Value(int(evalctx->getArgValue(0).toVector().size()));
-		if (evalctx->getArgValue(0).type() == Value::STRING) return Value(int(evalctx->getArgValue(0).toString().size()));
+		if (evalctx->getArgValue(0).type() == Value::STRING) {
+			//Unicode glyph count for the length -- rather than the string (num. of bytes) length.
+			std::string text = evalctx->getArgValue(0).toString();
+			return Value(int( g_utf8_strlen( text.c_str(), text.size() ) ));
+		}
 	}
 	return Value();
 }
@@ -380,10 +386,17 @@ Value builtin_lookup(const Context *, const EvalContext *evalctx)
   num_returns_per_match : int;
   index_col_num : int;
 
+ The search string and searched strings can be unicode strings.
  Examples:
   Index values return as list:
     search("a","abcdabcd");
-        - returns [0,4]
+        - returns [0]
+    search("Π","Π");  //A unicode string
+        - returns [0]
+    search("π‘aΠ","aπ‘Ππ‘aπ‘Ππ‘a",0);
+        - returns [[1,3,5,7],[0,4,8],[2,6]]
+    search("a","abcdabcd",0); //Search up to all matches
+        - returns [[0,4]]
     search("a","abcdabcd",1);
         - returns [0]
     search("e","abcdabcd",1);
@@ -433,16 +446,25 @@ Value builtin_search(const Context *, const EvalContext *evalctx)
 		}
 	} else if (findThis.type() == Value::STRING) {
 		unsigned int searchTableSize;
-		if (searchTable.type() == Value::STRING) searchTableSize = searchTable.toString().size();
-		else searchTableSize = searchTable.toVector().size();
-		for (size_t i = 0; i < findThis.toString().size(); i++) {
+		//Unicode glyph count for the length
+		unsigned int findThisSize =  g_utf8_strlen( findThis.toString().c_str(), findThis.toString().size() );
+		if (searchTable.type() == Value::STRING) {
+			searchTableSize = g_utf8_strlen( searchTable.toString().c_str(), searchTable.toString().size() );
+		} else {
+		    searchTableSize = searchTable.toVector().size();
+		}
+		for (size_t i = 0; i < findThisSize; i++) {
 		  unsigned int matchCount = 0;
 			Value::VectorType resultvec;
 		  for (size_t j = 0; j < searchTableSize; j++) {
-		    if ((searchTable.type() == Value::VECTOR && 
-						 findThis.toString()[i] == searchTable.toVector()[j].toVector()[index_col_num].toString()[0]) ||
-						(searchTable.type() == Value::STRING && 
-						 findThis.toString()[i] == searchTable.toString()[j])) {
+		    gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i);
+		    gchar* ptr_st = NULL;
+		    if(searchTable.type() == Value::VECTOR) {
+		        ptr_st = g_utf8_offset_to_pointer(searchTable.toVector()[j].toVector()[index_col_num].toString().c_str(), 0);
+		    } else if(searchTable.type() == Value::STRING){
+		    	ptr_st = g_utf8_offset_to_pointer(searchTable.toString().c_str(), j);
+		    }
+		    if( (ptr_ft) && (ptr_st) && (g_utf8_get_char(ptr_ft) == g_utf8_get_char(ptr_st)) ) {
 		      Value resultValue((double(j)));
 		      matchCount++;
 		      if (num_returns_per_match == 1) {
@@ -454,7 +476,14 @@ Value builtin_search(const Context *, const EvalContext *evalctx)
 		      if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break;
 		    }
 		  }
-		  if (matchCount == 0) PRINTB("  WARNING: search term not found: \"%s\"", findThis.toString()[i]);
+		  if (matchCount == 0) {
+			  gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i);
+			  gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
+			  if(ptr_ft) {
+			      g_utf8_strncpy( utf8_of_cp, ptr_ft, 1 );
+		      }
+			  PRINTB("  WARNING: search term not found: \"%s\"", utf8_of_cp );
+		  }
 		  if (num_returns_per_match == 0 || num_returns_per_match > 1) {
 				returnvec.push_back(Value(resultvec));
 			}
diff --git a/src/value.cc b/src/value.cc
index 5afb650..c8a88c6 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -36,6 +36,8 @@
 #include 
 #include "boost-utils.h"
 #include "boosty.h"
+/*Unicode support for string lengths and array accesses*/
+#include 
 
 std::ostream &operator<<(std::ostream &stream, const Filename &filename)
 {
@@ -579,14 +581,28 @@ Value Value::operator-() const
   }
 */
 
+/*
+ * bracket operation [] detecting multi-byte unicode.
+ * If the string is multi-byte unicode then the index will offset to the character (2 or 4 byte) and not to the byte.
+ * A 'normal' string with byte chars are a subset of unicode and still work.
+ */
 class bracket_visitor : public boost::static_visitor
 {
 public:
   Value operator()(const std::string &str, const double &idx) const {
     int i = int(idx);
     Value v;
+    //Check that the index is positive and less than the size in bytes
     if ((i >= 0) && (i < (int)str.size())) {
-      v = Value(str[int(idx)]);
+	  //Ensure character (not byte) index is inside the character/glyph array
+	  if( (unsigned) i < g_utf8_strlen( str.c_str(), str.size() ) )	{
+		  gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
+		  gchar* ptr = g_utf8_offset_to_pointer(str.c_str(), i);
+		  if(ptr) {
+		    g_utf8_strncpy(utf8_of_cp, ptr, 1);
+		  }
+		  v = std::string(utf8_of_cp);
+	  }
       //      std::cout << "bracket_visitor: " <<  v << "\n";
     }
     return v;
diff --git a/testdata/scad/misc/search-tests-unicode.scad b/testdata/scad/misc/search-tests-unicode.scad
new file mode 100644
index 0000000..d863eff
--- /dev/null
+++ b/testdata/scad/misc/search-tests-unicode.scad
@@ -0,0 +1,116 @@
+//Test search with unicode strings
+
+//Helper function that pretty prints our search test
+//Expected result is checked against execution of a search() invocation and OK/FAIL is indicated
+module test_search_and_echo( exp_res, search_to_find, search_to_search, search_up_to_num_matches = undef)
+{
+   if(undef != search_up_to_num_matches)
+   {
+      assign( test_res = search(search_to_find, search_to_search, search_up_to_num_matches) )
+      echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ", ", search_up_to_num_matches, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL"  ));
+   }
+   else
+   {
+      assign( test_res = search(search_to_find, search_to_search) )
+      echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL"  ));
+   }
+}
+
+
+//"Normal" text for comparison
+echo ("----- Lookup of 1 byte into 1 byte");
+//Hits - up_to_count 1
+test_search_and_echo( [0],   "a","aaaa" );
+test_search_and_echo( [0],   "a","aaaa",1 );
+test_search_and_echo( [0,0], "aa","aaaa" );
+test_search_and_echo( [0,0], "aa","aaaa",1 );
+
+
+//Hits - up to count 1+ (incl 0 == all)
+test_search_and_echo( [[0,1,2,3]] , 	"a","aaaa",0 );
+test_search_and_echo( [[0,1]], 			"a","aaaa",2 );
+test_search_and_echo( [[0,1,2]], 		"a","aaaa",3 );
+test_search_and_echo( [[0,1,2,3]] , 	"a","aaaa",4 );
+test_search_and_echo( [[0,1,2,3],[0,1,2,3]] , "aa","aaaa",0 );
+//Misses
+test_search_and_echo( [],		"b","aaaa" );
+test_search_and_echo( [],		"b","aaaa",1 );
+test_search_and_echo( [[]],		"b","aaaa",0 );
+test_search_and_echo( [[]],		"b","aaaa",2 );
+
+test_search_and_echo( [],			"bb","aaaa" );
+test_search_and_echo( [],			"bb","aaaa",1 );
+test_search_and_echo( [[],[]],		"bb","aaaa",0 );
+test_search_and_echo( [[],[]],		"bb","aaaa",2 );
+//Miss - empties
+test_search_and_echo( [], "","aaaa" );
+test_search_and_echo( [], "","" );
+test_search_and_echo( [], "a","" );
+
+
+//Unicode tests
+echo ("----- Lookup of multi-byte into 1 byte");
+test_search_and_echo( [],		"Π","aaaa" );
+test_search_and_echo( [],		"π‘","aaaa" );
+test_search_and_echo( [[]],		"Π","aaaa",0 );
+test_search_and_echo( [[]],		"π‘","aaaa",0 );
+
+test_search_and_echo( [],		"ΠΠ","aaaa" );
+test_search_and_echo( [],		"π‘π‘","aaaa" );
+test_search_and_echo( [[],[]],		"ΠΠ","aaaa",0 );
+test_search_and_echo( [[],[]],		"π‘π‘","aaaa",0 );
+
+echo ("----- Lookup of 1-byte into multi-byte");
+test_search_and_echo( [] , "a","ΠΠΠΠ" );
+test_search_and_echo( [] , "a","π‘π‘π‘π‘" );
+test_search_and_echo( [] , "a","ΠΠΠΠ",1 );
+
+test_search_and_echo( [[]] , "a","π‘π‘π‘π‘",0 );
+test_search_and_echo( [[]] , "a","π‘π‘π‘π‘",2 );
+
+echo ("----- Lookup of 1-byte into mixed multi-byte");
+test_search_and_echo( [0], "a","aΠaΠaΠaΠa" );
+test_search_and_echo( [0], "a","aπ‘aπ‘aπ‘aπ‘a" );
+test_search_and_echo( [0], "a","aπ‘Ππ‘aπ‘Ππ‘a" );
+
+test_search_and_echo( [[0,2,4,6,8]], "a","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[0,2,4,6,8]], "a","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[0,4,8]]    , "a","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of 2-byte into 2-byte");
+test_search_and_echo( [0]       , "Π","ΠΠΠΠ" );
+test_search_and_echo( [[0,1,2,3]] , "Π","ΠΠΠΠ",0 );
+
+echo ("----- Lookup of 2-byte into 4-byte");
+test_search_and_echo( [] , "Π","π‘π‘π‘π‘" );
+
+echo ("----- Lookup of 4-byte into 4-byte");
+test_search_and_echo( [0] , 		  "π‘","π‘π‘π‘π‘" );
+test_search_and_echo( [[0,1,2,3]], "π‘","π‘π‘π‘π‘",0 );
+
+echo ("----- Lookup of 4-byte into 2-byte");
+test_search_and_echo( [] , "π‘","ΠΠΠΠ" );
+
+echo ("----- Lookup of 2-byte into mixed multi-byte");
+test_search_and_echo( [1] , 	"Π","aΠaΠaΠaΠa",1 );
+test_search_and_echo( [] , 	"Π","aπ‘aπ‘aπ‘aπ‘a", 1 );
+test_search_and_echo( [2] , 	"Π","aπ‘Ππ‘aπ‘Ππ‘a", 1 );
+
+test_search_and_echo( [[1,3,5,7]] , 	"Π","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[]] , 				"Π","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[2,6]] , 			"Π","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of 4-byte into mixed multi-byte");
+test_search_and_echo( [] , 			"π‘","aΠaΠaΠaΠa",1 );
+test_search_and_echo( [1] , "π‘","aπ‘aπ‘aπ‘aπ‘a", 1 );
+
+test_search_and_echo( [[]] , 			"π‘","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[1,3,5,7]] , "π‘","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[1,3,5,7]] , "π‘","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of mixed multi-byte into mixed multi-byte");
+test_search_and_echo( [[0,2,4,6,8],[1,3,5,7],[]], "aΠπ‘","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[0,2,4,6,8],[],[1,3,5,7]], "aΠπ‘","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[0,4,8],[2,6],[1,3,5,7]]    , "aΠπ‘","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+test_search_and_echo( [[1,3,5,7],[0,4,8],[2,6]]    , "π‘aΠ","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
diff --git a/testdata/scad/misc/string-unicode.scad b/testdata/scad/misc/string-unicode.scad
new file mode 100644
index 0000000..d8e3e5c
--- /dev/null
+++ b/testdata/scad/misc/string-unicode.scad
@@ -0,0 +1,36 @@
+//Test how well arrays of unicode string are accessed.
+
+texts_array = [
+"DEADBEEF",
+"ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ",
+"ΩΨ³ΩΩ Ψ§ΩΨ²ΩΨ¬Ψ¨ΩΩ Ψ§ΩΩΨ·",
+"ζΆζ°ηε§θ²",
+"Àâü ΓΓΓ Γ",
+"πππππ
πππππππππππ",
+"β β β β β 
β β β β β β β β β β ",
+"π‘π±ππ",
+];
+
+text_2bytes = "ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ";
+text_4bytes = "π‘π±ππ";
+
+
+//Test all the normal accesses
+for (text_array_idx = [0:(len(texts_array)-1)])
+{
+	echo( "[", text_array_idx, "] = ", texts_array[text_array_idx], " of len=", len(texts_array[text_array_idx]), ":"  );
+    for (text_idx = [0:(len(texts_array[text_array_idx])-1)])
+    {
+	    echo( "    [", text_idx, ,"]=", texts_array[text_array_idx][text_idx]  );
+    }
+}
+
+//Test one past the last element of (x-byte unicode). This will be one past the length but inside the char length of the string
+echo( "Past end of unicode only 2-byte ", text_2bytes[len(text_2bytes)]  );
+echo( "Past end of unicode only 4-byte ", text_4bytes[len(text_4bytes)]  );
+
+//Test past the last element of (x-byte unicode). Outside both lengths.
+echo( "Past end of both 2-byte ", text_2bytes[ len(text_2bytes) * 2 ]   );
+echo( "Past end of both 4-byte ", text_4bytes[ len(text_4bytes) * 4 ]   );
+
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 4cc86f5..fd5097a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -587,8 +587,8 @@ set(OFFSCREEN_SOURCES
   ../src/OpenCSGRenderer.cc)
 
 add_library(tests-core STATIC ${CORE_SOURCES})
-target_link_libraries(tests-core ${OPENGL_LIBRARIES})
-set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${Boost_LIBRARIES})
+target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} )
+set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${Boost_LIBRARIES} )
 
 add_library(tests-common STATIC ${COMMON_SOURCES})
 target_link_libraries(tests-common tests-core)
@@ -808,8 +808,10 @@ list(APPEND ECHO_FILES ${FUNCTION_FILES}
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/dim-all.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-test.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-indexing.scad
+            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-unicode.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/vector-values.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests.scad
+            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests-unicode.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/recursion-tests.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests2.scad
diff --git a/tests/regression/echotest/search-tests-unicode-expected.echo b/tests/regression/echotest/search-tests-unicode-expected.echo
new file mode 100644
index 0000000..801bc8c
--- /dev/null
+++ b/tests/regression/echotest/search-tests-unicode-expected.echo
@@ -0,0 +1,109 @@
+ECHO: "----- Lookup of 1 byte into 1 byte"
+ECHO: "Expect [0] for search(a, aaaa)=[0]. OK"
+ECHO: "Expect [0] for search(a, aaaa, 1)=[0]. OK"
+ECHO: "Expect [0, 0] for search(aa, aaaa)=[0, 0]. OK"
+ECHO: "Expect [0, 0] for search(aa, aaaa, 1)=[0, 0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "Expect [[0, 1]] for search(a, aaaa, 2)=[[0, 1]]. OK"
+ECHO: "Expect [[0, 1, 2]] for search(a, aaaa, 3)=[[0, 1, 2]]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 4)=[[0, 1, 2, 3]]. OK"
+ECHO: "Expect [[0, 1, 2, 3], [0, 1, 2, 3]] for search(aa, aaaa, 0)=[[0, 1, 2, 3], [0, 1, 2, 3]]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(b, aaaa)=[]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(b, aaaa, 1)=[]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[]] for search(b, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[]] for search(b, aaaa, 2)=[[]]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(bb, aaaa)=[]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(bb, aaaa, 1)=[]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[], []] for search(bb, aaaa, 0)=[[], []]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[], []] for search(bb, aaaa, 2)=[[], []]. OK"
+ECHO: "Expect [] for search(, aaaa)=[]. OK"
+ECHO: "Expect [] for search(, )=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, )=[]. OK"
+ECHO: "----- Lookup of multi-byte into 1 byte"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, aaaa)=[]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, aaaa)=[]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[]] for search(Π, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[]] for search(π‘, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "Π"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(ΠΠ, aaaa)=[]. OK"
+  WARNING: search term not found: "π‘"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘π‘, aaaa)=[]. OK"
+  WARNING: search term not found: "Π"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[], []] for search(ΠΠ, aaaa, 0)=[[], []]. OK"
+  WARNING: search term not found: "π‘"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[], []] for search(π‘π‘, aaaa, 0)=[[], []]. OK"
+ECHO: "----- Lookup of 1-byte into multi-byte"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, ΠΠΠΠ)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, π‘π‘π‘π‘)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, ΠΠΠΠ, 1)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [[]] for search(a, π‘π‘π‘π‘, 0)=[[]]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [[]] for search(a, π‘π‘π‘π‘, 2)=[[]]. OK"
+ECHO: "----- Lookup of 1-byte into mixed multi-byte"
+ECHO: "Expect [0] for search(a, aΠaΠaΠaΠa)=[0]. OK"
+ECHO: "Expect [0] for search(a, aπ‘aπ‘aπ‘aπ‘a)=[0]. OK"
+ECHO: "Expect [0] for search(a, aπ‘Ππ‘aπ‘Ππ‘a)=[0]. OK"
+ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aΠaΠaΠaΠa, 0)=[[0, 2, 4, 6, 8]]. OK"
+ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aπ‘aπ‘aπ‘aπ‘a, 0)=[[0, 2, 4, 6, 8]]. OK"
+ECHO: "Expect [[0, 4, 8]] for search(a, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[0, 4, 8]]. OK"
+ECHO: "----- Lookup of 2-byte into 2-byte"
+ECHO: "Expect [0] for search(Π, ΠΠΠΠ)=[0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(Π, ΠΠΠΠ, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "----- Lookup of 2-byte into 4-byte"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, π‘π‘π‘π‘)=[]. OK"
+ECHO: "----- Lookup of 4-byte into 4-byte"
+ECHO: "Expect [0] for search(π‘, π‘π‘π‘π‘)=[0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(π‘, π‘π‘π‘π‘, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "----- Lookup of 4-byte into 2-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, ΠΠΠΠ)=[]. OK"
+ECHO: "----- Lookup of 2-byte into mixed multi-byte"
+ECHO: "Expect [1] for search(Π, aΠaΠaΠaΠa, 1)=[1]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, aπ‘aπ‘aπ‘aπ‘a, 1)=[]. OK"
+ECHO: "Expect [2] for search(Π, aπ‘Ππ‘aπ‘Ππ‘a, 1)=[2]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(Π, aΠaΠaΠaΠa, 0)=[[1, 3, 5, 7]]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[]] for search(Π, aπ‘aπ‘aπ‘aπ‘a, 0)=[[]]. OK"
+ECHO: "Expect [[2, 6]] for search(Π, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[2, 6]]. OK"
+ECHO: "----- Lookup of 4-byte into mixed multi-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, aΠaΠaΠaΠa, 1)=[]. OK"
+ECHO: "Expect [1] for search(π‘, aπ‘aπ‘aπ‘aπ‘a, 1)=[1]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[]] for search(π‘, aΠaΠaΠaΠa, 0)=[[]]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(π‘, aπ‘aπ‘aπ‘aπ‘a, 0)=[[1, 3, 5, 7]]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(π‘, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[1, 3, 5, 7]]. OK"
+ECHO: "----- Lookup of mixed multi-byte into mixed multi-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[0, 2, 4, 6, 8], [1, 3, 5, 7], []] for search(aΠπ‘, aΠaΠaΠaΠa, 0)=[[0, 2, 4, 6, 8], [1, 3, 5, 7], []]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[0, 2, 4, 6, 8], [], [1, 3, 5, 7]] for search(aΠπ‘, aπ‘aπ‘aπ‘aπ‘a, 0)=[[0, 2, 4, 6, 8], [], [1, 3, 5, 7]]. OK"
+ECHO: "Expect [[0, 4, 8], [2, 6], [1, 3, 5, 7]] for search(aΠπ‘, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[0, 4, 8], [2, 6], [1, 3, 5, 7]]. OK"
+ECHO: "Expect [[1, 3, 5, 7], [0, 4, 8], [2, 6]] for search(π‘aΠ, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[1, 3, 5, 7], [0, 4, 8], [2, 6]]. OK"
diff --git a/tests/regression/echotest/string-unicode-expected.echo b/tests/regression/echotest/string-unicode-expected.echo
new file mode 100644
index 0000000..b4b848f
--- /dev/null
+++ b/tests/regression/echotest/string-unicode-expected.echo
@@ -0,0 +1,104 @@
+ECHO: "[", 0, "] = ", "DEADBEEF", " of len=", 8, ":"
+ECHO: "    [", 0, "]=", "D"
+ECHO: "    [", 1, "]=", "E"
+ECHO: "    [", 2, "]=", "A"
+ECHO: "    [", 3, "]=", "D"
+ECHO: "    [", 4, "]=", "B"
+ECHO: "    [", 5, "]=", "E"
+ECHO: "    [", 6, "]=", "E"
+ECHO: "    [", 7, "]=", "F"
+ECHO: "[", 1, "] = ", "ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ", " of len=", 17, ":"
+ECHO: "    [", 0, "]=", "Π"
+ECHO: "    [", 1, "]=", "Π΅"
+ECHO: "    [", 2, "]=", "Π½"
+ECHO: "    [", 3, "]=", "ΠΈ"
+ECHO: "    [", 4, "]=", "Π²"
+ECHO: "    [", 5, "]=", "Ρ"
+ECHO: "    [", 6, "]=", "ΠΉ"
+ECHO: "    [", 7, "]=", " "
+ECHO: "    [", 8, "]=", "Ρ"
+ECHO: "    [", 9, "]=", "Ρ"
+ECHO: "    [", 10, "]=", "ΠΆ"
+ECHO: "    [", 11, "]=", "ΠΈ"
+ECHO: "    [", 12, "]=", "ΠΉ"
+ECHO: "    [", 13, "]=", " "
+ECHO: "    [", 14, "]=", "ΠΊ"
+ECHO: "    [", 15, "]=", "ΠΎ"
+ECHO: "    [", 16, "]=", "Ρ"
+ECHO: "[", 2, "] = ", "ΩΨ³ΩΩ Ψ§ΩΨ²ΩΨ¬Ψ¨ΩΩ Ψ§ΩΩΨ·", " of len=", 18, ":"
+ECHO: "    [", 0, "]=", "Ω"
+ECHO: "    [", 1, "]=", "Ψ³"
+ECHO: "    [", 2, "]=", "Ω"
+ECHO: "    [", 3, "]=", "Ω"
+ECHO: "    [", 4, "]=", " "
+ECHO: "    [", 5, "]=", "Ψ§"
+ECHO: "    [", 6, "]=", "Ω"
+ECHO: "    [", 7, "]=", "Ψ²"
+ECHO: "    [", 8, "]=", "Ω"
+ECHO: "    [", 9, "]=", "Ψ¬"
+ECHO: "    [", 10, "]=", "Ψ¨"
+ECHO: "    [", 11, "]=", "Ω"
+ECHO: "    [", 12, "]=", "Ω"
+ECHO: "    [", 13, "]=", " "
+ECHO: "    [", 14, "]=", "Ψ§"
+ECHO: "    [", 15, "]=", "Ω"
+ECHO: "    [", 16, "]=", "Ω"
+ECHO: "    [", 17, "]=", "Ψ·"
+ECHO: "[", 3, "] = ", "ζΆζ°ηε§θ²", " of len=", 5, ":"
+ECHO: "    [", 0, "]=", "ζΆ"
+ECHO: "    [", 1, "]=", "ζ°"
+ECHO: "    [", 2, "]=", "η"
+ECHO: "    [", 3, "]=", "ε§"
+ECHO: "    [", 4, "]=", "θ²"
+ECHO: "[", 4, "] = ", "Àâü ΓΓΓ Γ", " of len=", 9, ":"
+ECHO: "    [", 0, "]=", "Γ€"
+ECHO: "    [", 1, "]=", "ΓΆ"
+ECHO: "    [", 2, "]=", "ΓΌ"
+ECHO: "    [", 3, "]=", " "
+ECHO: "    [", 4, "]=", "Γ"
+ECHO: "    [", 5, "]=", "Γ"
+ECHO: "    [", 6, "]=", "Γ"
+ECHO: "    [", 7, "]=", " "
+ECHO: "    [", 8, "]=", "Γ"
+ECHO: "[", 5, "] = ", "πππππ
πππππππππππ", " of len=", 16, ":"
+ECHO: "    [", 0, "]=", "π"
+ECHO: "    [", 1, "]=", "π"
+ECHO: "    [", 2, "]=", "π"
+ECHO: "    [", 3, "]=", "π"
+ECHO: "    [", 4, "]=", "π
"
+ECHO: "    [", 5, "]=", "π"
+ECHO: "    [", 6, "]=", "π"
+ECHO: "    [", 7, "]=", "π"
+ECHO: "    [", 8, "]=", "π"
+ECHO: "    [", 9, "]=", "π"
+ECHO: "    [", 10, "]=", "π"
+ECHO: "    [", 11, "]=", "π"
+ECHO: "    [", 12, "]=", "π"
+ECHO: "    [", 13, "]=", "π"
+ECHO: "    [", 14, "]=", "π"
+ECHO: "    [", 15, "]=", "π"
+ECHO: "[", 6, "] = ", "β β β β β 
β β β β β β β β β β ", " of len=", 15, ":"
+ECHO: "    [", 0, "]=", "β "
+ECHO: "    [", 1, "]=", "β "
+ECHO: "    [", 2, "]=", "β "
+ECHO: "    [", 3, "]=", "β "
+ECHO: "    [", 4, "]=", "β 
"
+ECHO: "    [", 5, "]=", "β "
+ECHO: "    [", 6, "]=", "β "
+ECHO: "    [", 7, "]=", "β "
+ECHO: "    [", 8, "]=", "β "
+ECHO: "    [", 9, "]=", "β "
+ECHO: "    [", 10, "]=", "β "
+ECHO: "    [", 11, "]=", "β "
+ECHO: "    [", 12, "]=", "β "
+ECHO: "    [", 13, "]=", "β "
+ECHO: "    [", 14, "]=", "β "
+ECHO: "[", 7, "] = ", "π‘π±ππ", " of len=", 4, ":"
+ECHO: "    [", 0, "]=", "π‘"
+ECHO: "    [", 1, "]=", "π±"
+ECHO: "    [", 2, "]=", "π"
+ECHO: "    [", 3, "]=", "π"
+ECHO: "Past end of unicode only 2-byte ", undef
+ECHO: "Past end of unicode only 4-byte ", undef
+ECHO: "Past end of both 2-byte ", undef
+ECHO: "Past end of both 4-byte ", undef
-- 
cgit v0.10.1
From 301ef946f05a320768d4b8f974a95e2a04a5fc0d Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Thu, 5 Dec 2013 12:20:40 -0500
Subject: Qt4 patches to make it build on 10.9
diff --git a/patches/qt4/patch-libtiff.diff b/patches/qt4/patch-libtiff.diff
new file mode 100644
index 0000000..5b7f9ec
--- /dev/null
+++ b/patches/qt4/patch-libtiff.diff
@@ -0,0 +1,18 @@
+--- src/3rdparty/libtiff/libtiff/tif_config.h
++++ src/3rdparty/libtiff/libtiff/tif_config.h
+@@ -317,15 +317,6 @@
+ /* Define to empty if `const' does not conform to ANSI C. */
+ /* #undef const */
+ 
+-/* Define to `__inline__' or `__inline' if that's what the C compiler
+-   calls it, or to nothing if 'inline' is not supported under any name.  */
+-#ifndef Q_OS_SYMBIAN
+-#ifndef __cplusplus
+-#undef inline
+-#define inline
+-#endif
+-#endif
+-
+ /* Define to `long int' if  does not define. */
+ /* #undef off_t */
+ 
diff --git a/patches/qt4/patch-src_corelib_global_qglobal.h.diff b/patches/qt4/patch-src_corelib_global_qglobal.h.diff
new file mode 100644
index 0000000..8c55c5a
--- /dev/null
+++ b/patches/qt4/patch-src_corelib_global_qglobal.h.diff
@@ -0,0 +1,14 @@
+--- src/corelib/global/qglobal.h.orig	2013-06-07 07:16:52.000000000 +0200
++++ src/corelib/global/qglobal.h	2013-10-27 14:05:22.000000000 +0100
+@@ -327,7 +327,10 @@
+ #  if !defined(MAC_OS_X_VERSION_10_8)
+ #       define MAC_OS_X_VERSION_10_8 MAC_OS_X_VERSION_10_7 + 1
+ #  endif
+-#  if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_8)
++#  if !defined(MAC_OS_X_VERSION_10_9)
++#       define MAC_OS_X_VERSION_10_9 MAC_OS_X_VERSION_10_8 + 1
++#  endif
++#  if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_9)
+ #    warning "This version of Mac OS X is unsupported"
+ #  endif
+ #endif
diff --git a/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff b/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff
new file mode 100644
index 0000000..61b2eef
--- /dev/null
+++ b/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff
@@ -0,0 +1,1382 @@
+--- src/plugins/bearer/corewlan/qcorewlanengine.mm
++++ src/plugins/bearer/corewlan/qcorewlanengine.mm
+@@ -52,29 +52,17 @@
+ #include 
+ 
+ #include 
+-#include 
+-#include 
+-#include 
+-#include 
+-#include 
+-
+-#include 
+-#include 
+-#include 
+-#include 
+-
+-#include 
++
++extern "C" { // Otherwise it won't find CWKeychain* symbols at link time
++#import 
++}
++
+ #include "private/qcore_mac_p.h"
+ 
+ #include 
+ #include 
+ 
+-inline QString qt_NSStringToQString(const NSString *nsstr)
+-{ return QCFString::toQString(reinterpret_cast(nsstr)); }
+-
+-inline NSString *qt_QStringToNSString(const QString &qstr)
+-{ return [const_cast(reinterpret_cast(QCFString::toCFStringRef(qstr))) autorelease]; }
+-
++#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ 
+ @interface QT_MANGLE_NAMESPACE(QNSListener) : NSObject
+ {
+@@ -86,6 +74,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr)
+ - (void)notificationHandler;//:(NSNotification *)notification;
+ - (void)remove;
+ - (void)setEngine:(QCoreWlanEngine *)coreEngine;
++- (QCoreWlanEngine *)engine;
+ - (void)dealloc;
+ 
+ @property (assign) QCoreWlanEngine* engine;
+@@ -93,7 +82,6 @@ inline NSString *qt_QStringToNSString(const QString &qstr)
+ @end
+ 
+ @implementation QT_MANGLE_NAMESPACE(QNSListener)
+-@synthesize engine;
+ 
+ - (id) init
+ {
+@@ -101,7 +89,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr)
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+     notificationCenter = [NSNotificationCenter defaultCenter];
+     currentInterface = [CWInterface interfaceWithName:nil];
+-    [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:kCWPowerDidChangeNotification object:nil];
++    [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:CWPowerDidChangeNotification object:nil];
+     [locker unlock];
+     [autoreleasepool release];
+     return self;
+@@ -120,6 +108,11 @@ inline NSString *qt_QStringToNSString(const QString &qstr)
+     [locker unlock];
+ }
+ 
++-(QCoreWlanEngine *)engine
++{
++    return engine;
++}
++
+ -(void)remove
+ {
+     [locker lock];
+@@ -133,7 +126,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr)
+ }
+ @end
+ 
+-QT_MANGLE_NAMESPACE(QNSListener) *listener = 0;
++static QT_MANGLE_NAMESPACE(QNSListener) *listener = 0;
+ 
+ QT_BEGIN_NAMESPACE
+ 
+@@ -170,36 +163,28 @@ void QScanThread::run()
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+     QStringList found;
+     mutex.lock();
+-    CWInterface *currentInterface = [CWInterface interfaceWithName:qt_QStringToNSString(interfaceName)];
++    CWInterface *currentInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceName)];
+     mutex.unlock();
+ 
+-    if([currentInterface power]) {
++    if (currentInterface.powerOn) {
+         NSError *err = nil;
+-        NSDictionary *parametersDict =  [NSDictionary dictionaryWithObjectsAndKeys:
+-                                   [NSNumber numberWithBool:YES], kCWScanKeyMerge,
+-                                   [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType,
+-                                   [NSNumber numberWithInteger:100], kCWScanKeyRestTime, nil];
+ 
+-        NSArray* apArray = [currentInterface scanForNetworksWithParameters:parametersDict error:&err];
+-        CWNetwork *apNetwork;
++        NSSet* apSet = [currentInterface scanForNetworksWithName:nil error:&err];
+ 
+         if (!err) {
+-
+-            for(uint row=0; row < [apArray count]; row++ ) {
+-                apNetwork = [apArray objectAtIndex:row];
+-
+-                const QString networkSsid = qt_NSStringToQString([apNetwork ssid]);
++            for (CWNetwork *apNetwork in apSet) {
++                const QString networkSsid = QCFString::toQString(CFStringRef([apNetwork ssid]));
+                 const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkSsid));
+                 found.append(id);
+ 
+                 QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined;
+                 bool known = isKnownSsid(networkSsid);
+-                if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) {
+-                    if( networkSsid == qt_NSStringToQString( [currentInterface ssid])) {
++                if (currentInterface.serviceActive) {
++                    if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) {
+                         state = QNetworkConfiguration::Active;
+                     }
+                 }
+-                if(state == QNetworkConfiguration::Undefined) {
++                if (state == QNetworkConfiguration::Undefined) {
+                     if(known) {
+                         state = QNetworkConfiguration::Discovered;
+                     } else {
+@@ -207,7 +192,7 @@ void QScanThread::run()
+                     }
+                 }
+                 QNetworkConfiguration::Purpose purpose = QNetworkConfiguration::UnknownPurpose;
+-                if([[apNetwork securityMode] intValue] == kCWSecurityModeOpen) {
++                if ([apNetwork supportsSecurity:kCWSecurityNone]) {
+                     purpose = QNetworkConfiguration::PublicPurpose;
+                 } else {
+                     purpose = QNetworkConfiguration::PrivatePurpose;
+@@ -237,8 +222,8 @@ void QScanThread::run()
+                 interfaceName = ij.value();
+             }
+ 
+-            if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) {
+-                if( networkSsid == qt_NSStringToQString([currentInterface ssid])) {
++            if (currentInterface.serviceActive) {
++                if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) {
+                     state = QNetworkConfiguration::Active;
+                 }
+             }
+@@ -300,14 +285,14 @@ void QScanThread::getUserConfigurations()
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+     userProfiles.clear();
+ 
+-    NSArray *wifiInterfaces = [CWInterface supportedInterfaces];
+-    for(uint row=0; row < [wifiInterfaces count]; row++ ) {
++    NSSet *wifiInterfaces = [CWInterface interfaceNames];
++    for (NSString *ifName in wifiInterfaces) {
+ 
+-        CWInterface *wifiInterface = [CWInterface interfaceWithName: [wifiInterfaces objectAtIndex:row]];
+-        if ( ![wifiInterface power] )
++        CWInterface *wifiInterface = [CWInterface interfaceWithName: ifName];
++        if (!wifiInterface.powerOn)
+             continue;
+ 
+-        NSString *nsInterfaceName = [wifiInterface name];
++        NSString *nsInterfaceName = wifiInterface.ssid;
+ // add user configured system networks
+         SCDynamicStoreRef dynRef = SCDynamicStoreCreate(kCFAllocatorSystemDefault, (CFStringRef)@"Qt corewlan", nil, nil);
+         NSDictionary * airportPlist = (NSDictionary *)SCDynamicStoreCopyValue(dynRef, (CFStringRef)[NSString stringWithFormat:@"Setup:/Network/Interface/%@/AirPort", nsInterfaceName]);
+@@ -316,11 +301,11 @@ void QScanThread::getUserConfigurations()
+             NSDictionary *prefNetDict = [airportPlist objectForKey:@"PreferredNetworks"];
+ 
+             NSArray *thisSsidarray = [prefNetDict valueForKey:@"SSID_STR"];
+-            for(NSString *ssidkey in thisSsidarray) {
+-                QString thisSsid = qt_NSStringToQString(ssidkey);
++            for (NSString *ssidkey in thisSsidarray) {
++                QString thisSsid = QCFString::toQString(CFStringRef(ssidkey));
+                 if(!userProfiles.contains(thisSsid)) {
+                     QMap  map;
+-                    map.insert(thisSsid, qt_NSStringToQString(nsInterfaceName));
++                    map.insert(thisSsid, QCFString::toQString(CFStringRef(nsInterfaceName)));
+                     userProfiles.insert(thisSsid, map);
+                 }
+             }
+@@ -329,7 +314,7 @@ void QScanThread::getUserConfigurations()
+ 
+         // 802.1X user profiles
+         QString userProfilePath = QDir::homePath() + "/Library/Preferences/com.apple.eap.profiles.plist";
+-        NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile:qt_QStringToNSString(userProfilePath)] autorelease];
++        NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile: (NSString *)QCFString::toCFStringRef(userProfilePath)] autorelease];
+         if(eapDict != nil) {
+             NSString *profileStr= @"Profiles";
+             NSString *nameStr = @"UserDefinedName";
+@@ -348,15 +333,15 @@ void QScanThread::getUserConfigurations()
+                         QString ssid;
+                         for(int i = 0; i < dictSize; i++) {
+                             if([nameStr isEqualToString:keys[i]]) {
+-                                networkName = qt_NSStringToQString(objects[i]);
++                                networkName = QCFString::toQString(CFStringRef(objects[i]));
+                             }
+                             if([networkSsidStr isEqualToString:keys[i]]) {
+-                                ssid = qt_NSStringToQString(objects[i]);
++                                ssid = QCFString::toQString(CFStringRef(objects[i]));
+                             }
+                             if(!userProfiles.contains(networkName)
+                                 && !ssid.isEmpty()) {
+                                 QMap map;
+-                                map.insert(ssid, qt_NSStringToQString(nsInterfaceName));
++                                map.insert(ssid, QCFString::toQString(CFStringRef(nsInterfaceName)));
+                                 userProfiles.insert(networkName, map);
+                             }
+                         }
+@@ -444,7 +429,7 @@ void QCoreWlanEngine::initialize()
+     QMutexLocker locker(&mutex);
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+ 
+-    if([[CWInterface supportedInterfaces] count] > 0 && !listener) {
++    if ([[CWInterface interfaceNames] count] > 0 && !listener) {
+         listener = [[QT_MANGLE_NAMESPACE(QNSListener) alloc] init];
+         listener.engine = this;
+         hasWifi = true;
+@@ -479,141 +464,68 @@ void QCoreWlanEngine::connectToId(const QString &id)
+     QString interfaceString = getInterfaceFromId(id);
+ 
+     CWInterface *wifiInterface =
+-        [CWInterface interfaceWithName: qt_QStringToNSString(interfaceString)];
++        [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)];
+ 
+-    if ([wifiInterface power]) {
++    if (wifiInterface.powerOn) {
+         NSError *err = nil;
+-        NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
+-
+         QString wantedSsid;
+-
+         QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id);
+ 
+         const QString idHash = QString::number(qHash(QLatin1String("corewlan:") + ptr->name));
+         const QString idHash2 = QString::number(qHash(QLatin1String("corewlan:") + scanThread->getNetworkNameFromSsid(ptr->name)));
+ 
+-        bool using8021X = false;
+-        if (idHash2 != id) {
+-            NSArray *array = [CW8021XProfile allUser8021XProfiles];
+-
+-            for (NSUInteger i = 0; i < [array count]; ++i) {
+-                const QString networkNameHashCheck = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString([[array objectAtIndex:i] userDefinedName])));
+-
+-                const QString ssidHash = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString([[array objectAtIndex:i] ssid])));
+-
+-                if (id == networkNameHashCheck || id == ssidHash) {
+-                    const QString thisName = scanThread->getSsidFromNetworkName(id);
+-                    if (thisName.isEmpty())
+-                        wantedSsid = id;
+-                    else
+-                        wantedSsid = thisName;
+-
+-                    [params setValue: [array objectAtIndex:i] forKey:kCWAssocKey8021XProfile];
+-                    using8021X = true;
+-                    break;
+-                }
++        QString wantedNetwork;
++        QMapIterator > i(scanThread->userProfiles);
++        while (i.hasNext()) {
++            i.next();
++            wantedNetwork = i.key();
++            const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork));
++            if (id == networkNameHash) {
++                wantedSsid = scanThread->getSsidFromNetworkName(wantedNetwork);
++                break;
+             }
+         }
+ 
+-        if (!using8021X) {
+-            QString wantedNetwork;
+-            QMapIterator > i(scanThread->userProfiles);
+-            while (i.hasNext()) {
+-                i.next();
+-                wantedNetwork = i.key();
+-                const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork));
+-                if (id == networkNameHash) {
+-                    wantedSsid =  scanThread->getSsidFromNetworkName(wantedNetwork);
+-                    break;
+-                }
+-            }
+-        }
+-        NSDictionary *scanParameters = [NSDictionary dictionaryWithObjectsAndKeys:
+-                                        [NSNumber numberWithBool:YES], kCWScanKeyMerge,
+-                                        [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType,
+-                                        [NSNumber numberWithInteger:100], kCWScanKeyRestTime,
+-                                        qt_QStringToNSString(wantedSsid), kCWScanKeySSID,
+-                                        nil];
+-
+-        NSArray *scanArray = [wifiInterface scanForNetworksWithParameters:scanParameters error:&err];
++        NSSet *scanSet = [wifiInterface scanForNetworksWithName:(NSString *)QCFString::toCFStringRef(wantedSsid) error:&err];
+ 
+         if(!err) {
+-            for(uint row=0; row < [scanArray count]; row++ ) {
+-                CWNetwork *apNetwork = [scanArray objectAtIndex:row];
+-
+-                if(wantedSsid == qt_NSStringToQString([apNetwork ssid])) {
+-
+-                    if(!using8021X) {
+-                        SecKeychainAttribute attributes[3];
+-
+-                        NSString *account = [apNetwork ssid];
+-                        NSString *keyKind = @"AirPort network password";
+-                        NSString *keyName = account;
+-
+-                        attributes[0].tag = kSecAccountItemAttr;
+-                        attributes[0].data = (void *)[account UTF8String];
+-                        attributes[0].length = [account length];
+-
+-                        attributes[1].tag = kSecDescriptionItemAttr;
+-                        attributes[1].data = (void *)[keyKind UTF8String];
+-                        attributes[1].length = [keyKind length];
+-
+-                        attributes[2].tag = kSecLabelItemAttr;
+-                        attributes[2].data = (void *)[keyName UTF8String];
+-                        attributes[2].length = [keyName length];
+-
+-                        SecKeychainAttributeList attributeList = {3,attributes};
+-
+-                        SecKeychainSearchRef searchRef;
+-                        SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributeList, &searchRef);
+-
+-                        NSString *password = @"";
+-                        SecKeychainItemRef searchItem;
+-
+-                        if (SecKeychainSearchCopyNext(searchRef, &searchItem) == noErr) {
+-                            UInt32 realPasswordLength;
+-                            SecKeychainAttribute attributesW[8];
+-                            attributesW[0].tag = kSecAccountItemAttr;
+-                            SecKeychainAttributeList listW = {1,attributesW};
+-                            char *realPassword;
+-                            OSStatus status = SecKeychainItemCopyContent(searchItem, NULL, &listW, &realPasswordLength,(void **)&realPassword);
+-
+-                            if (status == noErr) {
+-                                if (realPassword != NULL) {
+-
+-                                    QByteArray pBuf;
+-                                    pBuf.resize(realPasswordLength);
+-                                    pBuf.prepend(realPassword);
+-                                    pBuf.insert(realPasswordLength,'\0');
+-
+-                                    password = [NSString stringWithUTF8String:pBuf];
+-                                }
+-                                SecKeychainItemFreeContent(&listW, realPassword);
+-                            }
+-
+-                            CFRelease(searchItem);
+-                        } else {
+-                            qDebug() << "SecKeychainSearchCopyNext error";
+-                        }
+-                        [params setValue: password forKey: kCWAssocKeyPassphrase];
+-                    } // end using8021X
+-
+-
+-                    bool result = [wifiInterface associateToNetwork: apNetwork parameters:[NSDictionary dictionaryWithDictionary:params] error:&err];
++            for (CWNetwork *apNetwork in scanSet) {
++                CFDataRef ssidData = (CFDataRef)[apNetwork ssidData];
++                bool result = false;
++
++                SecIdentityRef identity = 0;
++                // Check first whether we require IEEE 802.1X authentication for the wanted SSID
++                if (CWKeychainCopyEAPIdentity(ssidData, &identity) == errSecSuccess) {
++                    CFStringRef username = 0;
++                    CFStringRef password = 0;
++                    if (CWKeychainCopyEAPUsernameAndPassword(ssidData, &username, &password) == errSecSuccess) {
++                        result = [wifiInterface associateToEnterpriseNetwork:apNetwork
++                                    identity:identity username:(NSString *)username password:(NSString *)password
++                                    error:&err];
++                        CFRelease(username);
++                        CFRelease(password);
++                    }
++                    CFRelease(identity);
++                } else {
++                    CFStringRef password = 0;
++                    if (CWKeychainCopyPassword(ssidData, &password) == errSecSuccess) {
++                        result = [wifiInterface associateToNetwork:apNetwork password:(NSString *)password error:&err];
++                        CFRelease(password);
++                    }
++                }
+ 
+-                    if(!err) {
+-                        if(!result) {
+-                            emit connectionError(id, ConnectError);
+-                        } else {
+-                            return;
+-                        }
++                if (!err) {
++                    if (!result) {
++                        emit connectionError(id, ConnectError);
+                     } else {
+-                        qDebug() <<"associate ERROR"<<  qt_NSStringToQString([err localizedDescription ]);
++                        return;
+                     }
++                } else {
++                    qDebug() <<"associate ERROR"<<  QCFString::toQString(CFStringRef([err localizedDescription ]));
+                 }
+             } //end scan network
+         } else {
+-            qDebug() <<"scan ERROR"<<  qt_NSStringToQString([err localizedDescription ]);
++            qDebug() <<"scan ERROR"<<  QCFString::toQString(CFStringRef([err localizedDescription ]));
+         }
+         emit connectionError(id, InterfaceLookupError);
+     }
+@@ -631,10 +543,10 @@ void QCoreWlanEngine::disconnectFromId(const QString &id)
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+ 
+     CWInterface *wifiInterface =
+-        [CWInterface interfaceWithName: qt_QStringToNSString(interfaceString)];
++        [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)];
+ 
+     [wifiInterface disassociate];
+-    if ([[wifiInterface interfaceState]intValue] != kCWInterfaceStateInactive) {
++    if (wifiInterface.serviceActive) {
+         locker.unlock();
+         emit connectionError(id, DisconnectionError);
+         locker.relock();
+@@ -654,9 +566,9 @@ void QCoreWlanEngine::doRequestUpdate()
+ 
+     NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+ 
+-    NSArray *wifiInterfaces = [CWInterface supportedInterfaces];
+-    for (uint row = 0; row < [wifiInterfaces count]; ++row) {
+-            scanThread->interfaceName = qt_NSStringToQString([wifiInterfaces objectAtIndex:row]);
++    NSSet *wifiInterfaces = [CWInterface interfaceNames];
++    for (NSString *ifName in wifiInterfaces) {
++            scanThread->interfaceName = QCFString::toQString(CFStringRef(ifName));
+             scanThread->start();
+     }
+     locker.unlock();
+@@ -669,8 +581,8 @@ bool QCoreWlanEngine::isWifiReady(const QString &wifiDeviceName)
+     bool haswifi = false;
+     if(hasWifi) {
+         NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
+-        CWInterface *defaultInterface = [CWInterface interfaceWithName: qt_QStringToNSString(wifiDeviceName)];
+-        if([defaultInterface power]) {
++        CWInterface *defaultInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(wifiDeviceName)];
++        if (defaultInterface.powerOn) {
+             haswifi = true;
+         }
+         [autoreleasepool release];
+@@ -898,7 +810,7 @@ quint64 QCoreWlanEngine::startTime(const QString &identifier)
+                 bool ok = false;
+                 for(int i = 0; i < dictSize; i++) {
+                     if([ssidStr isEqualToString:keys[i]]) {
+-                        const QString ident = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString(objects[i])));
++                        const QString ident = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef(objects[i]))));
+                         if(ident == identifier) {
+                             ok = true;
+                         }
+@@ -944,3 +856,7 @@ quint64 QCoreWlanEngine::getBytes(const QString &interfaceName, bool b)
+ }
+ 
+ QT_END_NAMESPACE
++
++#else // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE
++#include "qcorewlanengine_10_6.mm"
++#endif
+diff --git a/src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm b/src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm
+new file mode 100644
+index 0000000..a3bf615
+--- /dev/null
++++ src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm
+@@ -0,0 +1,916 @@
++/****************************************************************************
++**
++** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
++** Contact: http://www.qt-project.org/legal
++**
++** This file is part of the plugins of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:LGPL$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and Digia.  For licensing terms and
++** conditions see http://qt.digia.com/licensing.  For further information
++** use the contact form at http://qt.digia.com/contact-us.
++**
++** GNU Lesser General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU Lesser
++** General Public License version 2.1 as published by the Free Software
++** Foundation and appearing in the file LICENSE.LGPL included in the
++** packaging of this file.  Please review the following information to
++** ensure the GNU Lesser General Public License version 2.1 requirements
++** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
++**
++** In addition, as a special exception, Digia gives you certain additional
++** rights.  These rights are described in the Digia Qt LGPL Exception
++** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3.0 as published by the Free Software
++** Foundation and appearing in the file LICENSE.GPL included in the
++** packaging of this file.  Please review the following information to
++** ensure the GNU General Public License version 3.0 requirements will be
++** met: http://www.gnu.org/copyleft/gpl.html.
++**
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include 
++
++@interface QT_MANGLE_NAMESPACE(QNSListener) : NSObject
++{
++    NSNotificationCenter *notificationCenter;
++    CWInterface *currentInterface;
++    QCoreWlanEngine *engine;
++    NSLock *locker;
++}
++- (void)notificationHandler;//:(NSNotification *)notification;
++- (void)remove;
++- (void)setEngine:(QCoreWlanEngine *)coreEngine;
++- (QCoreWlanEngine *)engine;
++- (void)dealloc;
++
++@property (assign) QCoreWlanEngine* engine;
++
++@end
++
++@implementation QT_MANGLE_NAMESPACE(QNSListener)
++
++- (id) init
++{
++    [locker lock];
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++    notificationCenter = [NSNotificationCenter defaultCenter];
++    currentInterface = [CWInterface interfaceWithName:nil];
++    [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:kCWPowerDidChangeNotification object:nil];
++    [locker unlock];
++    [autoreleasepool release];
++    return self;
++}
++
++-(void)dealloc
++{
++    [super dealloc];
++}
++
++-(void)setEngine:(QCoreWlanEngine *)coreEngine
++{
++    [locker lock];
++    if(!engine)
++        engine = coreEngine;
++    [locker unlock];
++}
++
++-(QCoreWlanEngine *)engine
++{
++    return engine;
++}
++
++-(void)remove
++{
++    [locker lock];
++    [notificationCenter removeObserver:self];
++    [locker unlock];
++}
++
++- (void)notificationHandler//:(NSNotification *)notification
++{
++    engine->requestUpdate();
++}
++@end
++
++static QT_MANGLE_NAMESPACE(QNSListener) *listener = 0;
++
++QT_BEGIN_NAMESPACE
++
++void networkChangeCallback(SCDynamicStoreRef/* store*/, CFArrayRef changedKeys, void *info)
++{
++    for ( long i = 0; i < CFArrayGetCount(changedKeys); i++) {
++
++        QString changed =  QCFString::toQString(CFStringRef((CFStringRef)CFArrayGetValueAtIndex(changedKeys, i)));
++        if( changed.contains("/Network/Global/IPv4")) {
++            QCoreWlanEngine* wlanEngine = static_cast(info);
++            wlanEngine->requestUpdate();
++        }
++    }
++    return;
++}
++
++
++QScanThread::QScanThread(QObject *parent)
++    :QThread(parent)
++{
++}
++
++QScanThread::~QScanThread()
++{
++}
++
++void QScanThread::quit()
++{
++    wait();
++}
++
++void QScanThread::run()
++{
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++    QStringList found;
++    mutex.lock();
++    CWInterface *currentInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceName)];
++    mutex.unlock();
++
++    if([currentInterface power]) {
++        NSError *err = nil;
++        NSDictionary *parametersDict =  [NSDictionary dictionaryWithObjectsAndKeys:
++                                   [NSNumber numberWithBool:YES], kCWScanKeyMerge,
++                                   [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType,
++                                   [NSNumber numberWithInteger:100], kCWScanKeyRestTime, nil];
++
++        NSArray* apArray = [currentInterface scanForNetworksWithParameters:parametersDict error:&err];
++        CWNetwork *apNetwork;
++
++        if (!err) {
++
++            for(uint row=0; row < [apArray count]; row++ ) {
++                apNetwork = [apArray objectAtIndex:row];
++
++                const QString networkSsid = QCFString::toQString(CFStringRef([apNetwork ssid]));
++                const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkSsid));
++                found.append(id);
++
++                QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined;
++                bool known = isKnownSsid(networkSsid);
++                if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) {
++                    if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) {
++                        state = QNetworkConfiguration::Active;
++                    }
++                }
++                if(state == QNetworkConfiguration::Undefined) {
++                    if(known) {
++                        state = QNetworkConfiguration::Discovered;
++                    } else {
++                        state = QNetworkConfiguration::Undefined;
++                    }
++                }
++                QNetworkConfiguration::Purpose purpose = QNetworkConfiguration::UnknownPurpose;
++                if([[apNetwork securityMode] intValue] == kCWSecurityModeOpen) {
++                    purpose = QNetworkConfiguration::PublicPurpose;
++                } else {
++                    purpose = QNetworkConfiguration::PrivatePurpose;
++                }
++
++                found.append(foundNetwork(id, networkSsid, state, interfaceName, purpose));
++
++            }
++        }
++    }
++    // add known configurations that are not around.
++    QMapIterator > i(userProfiles);
++    while (i.hasNext()) {
++        i.next();
++
++        QString networkName = i.key();
++        const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkName));
++
++        if(!found.contains(id)) {
++            QString networkSsid = getSsidFromNetworkName(networkName);
++            const QString ssidId = QString::number(qHash(QLatin1String("corewlan:") + networkSsid));
++            QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined;
++            QString interfaceName;
++            QMapIterator ij(i.value());
++            while (ij.hasNext()) {
++                ij.next();
++                interfaceName = ij.value();
++            }
++
++            if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) {
++                if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) {
++                    state = QNetworkConfiguration::Active;
++                }
++            }
++            if(state == QNetworkConfiguration::Undefined) {
++                if( userProfiles.contains(networkName)
++                    && found.contains(ssidId)) {
++                    state = QNetworkConfiguration::Discovered;
++                }
++            }
++
++            if(state == QNetworkConfiguration::Undefined) {
++                state = QNetworkConfiguration::Defined;
++            }
++
++            found.append(foundNetwork(id, networkName, state, interfaceName, QNetworkConfiguration::UnknownPurpose));
++        }
++    }
++    emit networksChanged();
++    [autoreleasepool release];
++}
++
++QStringList QScanThread::foundNetwork(const QString &id, const QString &name, const QNetworkConfiguration::StateFlags state, const QString &interfaceName, const QNetworkConfiguration::Purpose purpose)
++{
++    QStringList found;
++    QMutexLocker locker(&mutex);
++        QNetworkConfigurationPrivate *ptr = new QNetworkConfigurationPrivate;
++
++        ptr->name = name;
++        ptr->isValid = true;
++        ptr->id = id;
++        ptr->state = state;
++        ptr->type = QNetworkConfiguration::InternetAccessPoint;
++        ptr->bearerType = QNetworkConfiguration::BearerWLAN;
++        ptr->purpose = purpose;
++
++        fetchedConfigurations.append( ptr);
++        configurationInterface.insert(ptr->id, interfaceName);
++
++        locker.unlock();
++        locker.relock();
++       found.append(id);
++    return found;
++}
++
++QList QScanThread::getConfigurations()
++{
++    QMutexLocker locker(&mutex);
++
++    QList foundConfigurations = fetchedConfigurations;
++    fetchedConfigurations.clear();
++
++    return foundConfigurations;
++}
++
++void QScanThread::getUserConfigurations()
++{
++    QMutexLocker locker(&mutex);
++
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++    userProfiles.clear();
++
++    NSArray *wifiInterfaces = [CWInterface supportedInterfaces];
++    for(uint row=0; row < [wifiInterfaces count]; row++ ) {
++
++        CWInterface *wifiInterface = [CWInterface interfaceWithName: [wifiInterfaces objectAtIndex:row]];
++        if ( ![wifiInterface power] )
++            continue;
++
++        NSString *nsInterfaceName = [wifiInterface name];
++// add user configured system networks
++        SCDynamicStoreRef dynRef = SCDynamicStoreCreate(kCFAllocatorSystemDefault, (CFStringRef)@"Qt corewlan", nil, nil);
++        NSDictionary * airportPlist = (NSDictionary *)SCDynamicStoreCopyValue(dynRef, (CFStringRef)[NSString stringWithFormat:@"Setup:/Network/Interface/%@/AirPort", nsInterfaceName]);
++        CFRelease(dynRef);
++        if(airportPlist != nil) {
++            NSDictionary *prefNetDict = [airportPlist objectForKey:@"PreferredNetworks"];
++
++            NSArray *thisSsidarray = [prefNetDict valueForKey:@"SSID_STR"];
++            for(NSString *ssidkey in thisSsidarray) {
++                QString thisSsid = QCFString::toQString(CFStringRef(ssidkey));
++                if(!userProfiles.contains(thisSsid)) {
++                    QMap  map;
++                    map.insert(thisSsid, QCFString::toQString(CFStringRef(nsInterfaceName)));
++                    userProfiles.insert(thisSsid, map);
++                }
++            }
++            CFRelease(airportPlist);
++        }
++
++        // 802.1X user profiles
++        QString userProfilePath = QDir::homePath() + "/Library/Preferences/com.apple.eap.profiles.plist";
++        NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile: (NSString *)QCFString::toCFStringRef(userProfilePath)] autorelease];
++        if(eapDict != nil) {
++            NSString *profileStr= @"Profiles";
++            NSString *nameStr = @"UserDefinedName";
++            NSString *networkSsidStr = @"Wireless Network";
++            for (id profileKey in eapDict) {
++                if ([profileStr isEqualToString:profileKey]) {
++                    NSDictionary *itemDict = [eapDict objectForKey:profileKey];
++                    for (id itemKey in itemDict) {
++
++                        NSInteger dictSize = [itemKey count];
++                        id objects[dictSize];
++                        id keys[dictSize];
++
++                        [itemKey getObjects:objects andKeys:keys];
++                        QString networkName;
++                        QString ssid;
++                        for(int i = 0; i < dictSize; i++) {
++                            if([nameStr isEqualToString:keys[i]]) {
++                                networkName = QCFString::toQString(CFStringRef(objects[i]));
++                            }
++                            if([networkSsidStr isEqualToString:keys[i]]) {
++                                ssid = QCFString::toQString(CFStringRef(objects[i]));
++                            }
++                            if(!userProfiles.contains(networkName)
++                                && !ssid.isEmpty()) {
++                                QMap map;
++                                map.insert(ssid, QCFString::toQString(CFStringRef(nsInterfaceName)));
++                                userProfiles.insert(networkName, map);
++                            }
++                        }
++                    }
++                }
++            }
++        }
++    }
++    [autoreleasepool release];
++}
++
++QString QScanThread::getSsidFromNetworkName(const QString &name)
++{
++    QMutexLocker locker(&mutex);
++
++    QMapIterator > i(userProfiles);
++    while (i.hasNext()) {
++        i.next();
++        QMap map = i.value();
++        QMapIterator ij(i.value());
++         while (ij.hasNext()) {
++             ij.next();
++             const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") +i.key()));
++             if(name == i.key() || name == networkNameHash) {
++                 return ij.key();
++             }
++        }
++    }
++    return QString();
++}
++
++QString QScanThread::getNetworkNameFromSsid(const QString &ssid)
++{
++    QMutexLocker locker(&mutex);
++
++    QMapIterator > i(userProfiles);
++    while (i.hasNext()) {
++        i.next();
++        QMap map = i.value();
++        QMapIterator ij(i.value());
++         while (ij.hasNext()) {
++             ij.next();
++             if(ij.key() == ssid) {
++                 return i.key();
++             }
++         }
++    }
++    return QString();
++}
++
++bool QScanThread::isKnownSsid(const QString &ssid)
++{
++    QMutexLocker locker(&mutex);
++
++    QMapIterator > i(userProfiles);
++    while (i.hasNext()) {
++        i.next();
++        QMap map = i.value();
++        if(map.keys().contains(ssid)) {
++            return true;
++        }
++    }
++    return false;
++}
++
++
++QCoreWlanEngine::QCoreWlanEngine(QObject *parent)
++:   QBearerEngineImpl(parent), scanThread(0)
++{
++    scanThread = new QScanThread(this);
++    connect(scanThread, SIGNAL(networksChanged()),
++            this, SLOT(networksChanged()));
++}
++
++QCoreWlanEngine::~QCoreWlanEngine()
++{
++    while (!foundConfigurations.isEmpty())
++        delete foundConfigurations.takeFirst();
++    [listener remove];
++    [listener release];
++}
++
++void QCoreWlanEngine::initialize()
++{
++    QMutexLocker locker(&mutex);
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++
++    if([[CWInterface supportedInterfaces] count] > 0 && !listener) {
++        listener = [[QT_MANGLE_NAMESPACE(QNSListener) alloc] init];
++        listener.engine = this;
++        hasWifi = true;
++    } else {
++        hasWifi = false;
++    }
++    storeSession = NULL;
++
++    startNetworkChangeLoop();
++    [autoreleasepool release];
++}
++
++
++QString QCoreWlanEngine::getInterfaceFromId(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++
++    return scanThread->configurationInterface.value(id);
++}
++
++bool QCoreWlanEngine::hasIdentifier(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++
++    return scanThread->configurationInterface.contains(id);
++}
++
++void QCoreWlanEngine::connectToId(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++    QString interfaceString = getInterfaceFromId(id);
++
++    CWInterface *wifiInterface =
++        [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)];
++
++    if ([wifiInterface power]) {
++        NSError *err = nil;
++        NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
++
++        QString wantedSsid;
++
++        QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id);
++
++        const QString idHash = QString::number(qHash(QLatin1String("corewlan:") + ptr->name));
++        const QString idHash2 = QString::number(qHash(QLatin1String("corewlan:") + scanThread->getNetworkNameFromSsid(ptr->name)));
++
++        bool using8021X = false;
++        if (idHash2 != id) {
++            NSArray *array = [CW8021XProfile allUser8021XProfiles];
++
++            for (NSUInteger i = 0; i < [array count]; ++i) {
++                const QString networkNameHashCheck = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef([[array objectAtIndex:i] userDefinedName]))));
++
++                const QString ssidHash = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef([[array objectAtIndex:i] ssid]))));
++
++                if (id == networkNameHashCheck || id == ssidHash) {
++                    const QString thisName = scanThread->getSsidFromNetworkName(id);
++                    if (thisName.isEmpty())
++                        wantedSsid = id;
++                    else
++                        wantedSsid = thisName;
++
++                    [params setValue: [array objectAtIndex:i] forKey:kCWAssocKey8021XProfile];
++                    using8021X = true;
++                    break;
++                }
++            }
++        }
++
++        if (!using8021X) {
++            QString wantedNetwork;
++            QMapIterator > i(scanThread->userProfiles);
++            while (i.hasNext()) {
++                i.next();
++                wantedNetwork = i.key();
++                const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork));
++                if (id == networkNameHash) {
++                    wantedSsid =  scanThread->getSsidFromNetworkName(wantedNetwork);
++                    break;
++                }
++            }
++        }
++        NSDictionary *scanParameters = [NSDictionary dictionaryWithObjectsAndKeys:
++                                        [NSNumber numberWithBool:YES], kCWScanKeyMerge,
++                                        [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType,
++                                        [NSNumber numberWithInteger:100], kCWScanKeyRestTime,
++                                        (NSString *)QCFString::toCFStringRef(wantedSsid), kCWScanKeySSID,
++                                        nil];
++
++        NSArray *scanArray = [wifiInterface scanForNetworksWithParameters:scanParameters error:&err];
++
++        if(!err) {
++            for(uint row=0; row < [scanArray count]; row++ ) {
++                CWNetwork *apNetwork = [scanArray objectAtIndex:row];
++
++                if(wantedSsid == QCFString::toQString(CFStringRef([apNetwork ssid]))) {
++
++                    if(!using8021X) {
++                        SecKeychainAttribute attributes[3];
++
++                        NSString *account = [apNetwork ssid];
++                        NSString *keyKind = @"AirPort network password";
++                        NSString *keyName = account;
++
++                        attributes[0].tag = kSecAccountItemAttr;
++                        attributes[0].data = (void *)[account UTF8String];
++                        attributes[0].length = [account length];
++
++                        attributes[1].tag = kSecDescriptionItemAttr;
++                        attributes[1].data = (void *)[keyKind UTF8String];
++                        attributes[1].length = [keyKind length];
++
++                        attributes[2].tag = kSecLabelItemAttr;
++                        attributes[2].data = (void *)[keyName UTF8String];
++                        attributes[2].length = [keyName length];
++
++                        SecKeychainAttributeList attributeList = {3,attributes};
++
++                        SecKeychainSearchRef searchRef;
++                        SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributeList, &searchRef);
++
++                        NSString *password = @"";
++                        SecKeychainItemRef searchItem;
++
++                        if (SecKeychainSearchCopyNext(searchRef, &searchItem) == noErr) {
++                            UInt32 realPasswordLength;
++                            SecKeychainAttribute attributesW[8];
++                            attributesW[0].tag = kSecAccountItemAttr;
++                            SecKeychainAttributeList listW = {1,attributesW};
++                            char *realPassword;
++                            OSStatus status = SecKeychainItemCopyContent(searchItem, NULL, &listW, &realPasswordLength,(void **)&realPassword);
++
++                            if (status == noErr) {
++                                if (realPassword != NULL) {
++
++                                    QByteArray pBuf;
++                                    pBuf.resize(realPasswordLength);
++                                    pBuf.prepend(realPassword);
++                                    pBuf.insert(realPasswordLength,'\0');
++
++                                    password = [NSString stringWithUTF8String:pBuf];
++                                }
++                                SecKeychainItemFreeContent(&listW, realPassword);
++                            }
++
++                            CFRelease(searchItem);
++                        } else {
++                            qDebug() << "SecKeychainSearchCopyNext error";
++                        }
++                        [params setValue: password forKey: kCWAssocKeyPassphrase];
++                    } // end using8021X
++
++
++                    bool result = [wifiInterface associateToNetwork: apNetwork parameters:[NSDictionary dictionaryWithDictionary:params] error:&err];
++
++                    if(!err) {
++                        if(!result) {
++                            emit connectionError(id, ConnectError);
++                        } else {
++                            return;
++                        }
++                    } else {
++                        qDebug() <<"associate ERROR"<<  QCFString::toQString(CFStringRef([err localizedDescription ]));
++                    }
++                }
++            } //end scan network
++        } else {
++            qDebug() <<"scan ERROR"<<  QCFString::toQString(CFStringRef([err localizedDescription ]));
++        }
++        emit connectionError(id, InterfaceLookupError);
++    }
++
++    locker.unlock();
++    emit connectionError(id, InterfaceLookupError);
++    [autoreleasepool release];
++}
++
++void QCoreWlanEngine::disconnectFromId(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++
++    QString interfaceString = getInterfaceFromId(id);
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++
++    CWInterface *wifiInterface =
++        [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)];
++
++    [wifiInterface disassociate];
++    if ([[wifiInterface interfaceState]intValue] != kCWInterfaceStateInactive) {
++        locker.unlock();
++        emit connectionError(id, DisconnectionError);
++        locker.relock();
++    }
++    [autoreleasepool release];
++}
++
++void QCoreWlanEngine::requestUpdate()
++{
++    scanThread->getUserConfigurations();
++    doRequestUpdate();
++}
++
++void QCoreWlanEngine::doRequestUpdate()
++{
++    QMutexLocker locker(&mutex);
++
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++
++    NSArray *wifiInterfaces = [CWInterface supportedInterfaces];
++    for (uint row = 0; row < [wifiInterfaces count]; ++row) {
++            scanThread->interfaceName = QCFString::toQString(CFStringRef([wifiInterfaces objectAtIndex:row]));
++            scanThread->start();
++    }
++    locker.unlock();
++    [autoreleasepool release];
++}
++
++bool QCoreWlanEngine::isWifiReady(const QString &wifiDeviceName)
++{
++    QMutexLocker locker(&mutex);
++    bool haswifi = false;
++    if(hasWifi) {
++        NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++        CWInterface *defaultInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(wifiDeviceName)];
++        if([defaultInterface power]) {
++            haswifi = true;
++        }
++        [autoreleasepool release];
++    }
++    return haswifi;
++}
++
++
++QNetworkSession::State QCoreWlanEngine::sessionStateForId(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++    QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id);
++
++    if (!ptr)
++        return QNetworkSession::Invalid;
++
++    if (!ptr->isValid) {
++        return QNetworkSession::Invalid;
++    } else if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) {
++        return QNetworkSession::Connected;
++    } else if ((ptr->state & QNetworkConfiguration::Discovered) ==
++                QNetworkConfiguration::Discovered) {
++        return QNetworkSession::Disconnected;
++    } else if ((ptr->state & QNetworkConfiguration::Defined) == QNetworkConfiguration::Defined) {
++        return QNetworkSession::NotAvailable;
++    } else if ((ptr->state & QNetworkConfiguration::Undefined) ==
++                QNetworkConfiguration::Undefined) {
++        return QNetworkSession::NotAvailable;
++    }
++
++    return QNetworkSession::Invalid;
++}
++
++QNetworkConfigurationManager::Capabilities QCoreWlanEngine::capabilities() const
++{
++    return QNetworkConfigurationManager::ForcedRoaming;
++}
++
++void QCoreWlanEngine::startNetworkChangeLoop()
++{
++
++    SCDynamicStoreContext dynStoreContext = { 0, this/*(void *)storeSession*/, NULL, NULL, NULL };
++    storeSession = SCDynamicStoreCreate(NULL,
++                                 CFSTR("networkChangeCallback"),
++                                 networkChangeCallback,
++                                 &dynStoreContext);
++    if (!storeSession ) {
++        qWarning() << "could not open dynamic store: error:" << SCErrorString(SCError());
++        return;
++    }
++
++    CFMutableArrayRef notificationKeys;
++    notificationKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
++    CFMutableArrayRef patternsArray;
++    patternsArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
++
++    CFStringRef storeKey;
++    storeKey = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
++                                                     kSCDynamicStoreDomainState,
++                                                     kSCEntNetIPv4);
++    CFArrayAppendValue(notificationKeys, storeKey);
++    CFRelease(storeKey);
++
++    storeKey = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
++                                                      kSCDynamicStoreDomainState,
++                                                      kSCCompAnyRegex,
++                                                      kSCEntNetIPv4);
++    CFArrayAppendValue(patternsArray, storeKey);
++    CFRelease(storeKey);
++
++    if (!SCDynamicStoreSetNotificationKeys(storeSession , notificationKeys, patternsArray)) {
++        qWarning() << "register notification error:"<< SCErrorString(SCError());
++        CFRelease(storeSession );
++        CFRelease(notificationKeys);
++        CFRelease(patternsArray);
++        return;
++    }
++    CFRelease(notificationKeys);
++    CFRelease(patternsArray);
++
++    runloopSource = SCDynamicStoreCreateRunLoopSource(NULL, storeSession , 0);
++    if (!runloopSource) {
++        qWarning() << "runloop source error:"<< SCErrorString(SCError());
++        CFRelease(storeSession );
++        return;
++    }
++
++    CFRunLoopAddSource(CFRunLoopGetCurrent(), runloopSource, kCFRunLoopDefaultMode);
++    return;
++}
++
++QNetworkSessionPrivate *QCoreWlanEngine::createSessionBackend()
++{
++    return new QNetworkSessionPrivateImpl;
++}
++
++QNetworkConfigurationPrivatePointer QCoreWlanEngine::defaultConfiguration()
++{
++    return QNetworkConfigurationPrivatePointer();
++}
++
++bool QCoreWlanEngine::requiresPolling() const
++{
++    return true;
++}
++
++void QCoreWlanEngine::networksChanged()
++{
++    QMutexLocker locker(&mutex);
++
++    QStringList previous = accessPointConfigurations.keys();
++
++    QList foundConfigurations = scanThread->getConfigurations();
++    while (!foundConfigurations.isEmpty()) {
++        QNetworkConfigurationPrivate *cpPriv = foundConfigurations.takeFirst();
++
++        previous.removeAll(cpPriv->id);
++
++        if (accessPointConfigurations.contains(cpPriv->id)) {
++            QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(cpPriv->id);
++
++            bool changed = false;
++
++            ptr->mutex.lock();
++
++            if (ptr->isValid != cpPriv->isValid) {
++                ptr->isValid = cpPriv->isValid;
++                changed = true;
++            }
++
++            if (ptr->name != cpPriv->name) {
++                ptr->name = cpPriv->name;
++                changed = true;
++            }
++
++            if (ptr->bearerType != cpPriv->bearerType) {
++                ptr->bearerType = cpPriv->bearerType;
++                changed = true;
++            }
++
++            if (ptr->state != cpPriv->state) {
++                ptr->state = cpPriv->state;
++                changed = true;
++            }
++
++            ptr->mutex.unlock();
++
++            if (changed) {
++                locker.unlock();
++                emit configurationChanged(ptr);
++                locker.relock();
++            }
++
++            delete cpPriv;
++        } else {
++            QNetworkConfigurationPrivatePointer ptr(cpPriv);
++
++            accessPointConfigurations.insert(ptr->id, ptr);
++
++            locker.unlock();
++            emit configurationAdded(ptr);
++            locker.relock();
++        }
++    }
++
++    while (!previous.isEmpty()) {
++        QNetworkConfigurationPrivatePointer ptr =
++            accessPointConfigurations.take(previous.takeFirst());
++
++        locker.unlock();
++        emit configurationRemoved(ptr);
++        locker.relock();
++    }
++
++    locker.unlock();
++    emit updateCompleted();
++
++}
++
++quint64 QCoreWlanEngine::bytesWritten(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++    const QString interfaceStr = getInterfaceFromId(id);
++    return getBytes(interfaceStr,false);
++}
++
++quint64 QCoreWlanEngine::bytesReceived(const QString &id)
++{
++    QMutexLocker locker(&mutex);
++    const QString interfaceStr = getInterfaceFromId(id);
++    return getBytes(interfaceStr,true);
++}
++
++quint64 QCoreWlanEngine::startTime(const QString &identifier)
++{
++    QMutexLocker locker(&mutex);
++    NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
++    quint64 timestamp = 0;
++
++    NSString *filePath = @"/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist";
++    NSDictionary* plistDict = [[[NSDictionary alloc] initWithContentsOfFile:filePath] autorelease];
++    if(plistDict == nil)
++        return timestamp;
++    NSString *input = @"KnownNetworks";
++    NSString *timeStampStr = @"_timeStamp";
++
++    NSString *ssidStr = @"SSID_STR";
++
++    for (id key in plistDict) {
++        if ([input isEqualToString:key]) {
++
++            NSDictionary *knownNetworksDict = [plistDict objectForKey:key];
++            if(knownNetworksDict == nil)
++                return timestamp;
++            for (id networkKey in knownNetworksDict) {
++                bool isFound = false;
++                NSDictionary *itemDict = [knownNetworksDict objectForKey:networkKey];
++                if(itemDict == nil)
++                    return timestamp;
++                NSInteger dictSize = [itemDict count];
++                id objects[dictSize];
++                id keys[dictSize];
++
++                [itemDict getObjects:objects andKeys:keys];
++                bool ok = false;
++                for(int i = 0; i < dictSize; i++) {
++                    if([ssidStr isEqualToString:keys[i]]) {
++                        const QString ident = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef(objects[i]))));
++                        if(ident == identifier) {
++                            ok = true;
++                        }
++                    }
++                    if(ok && [timeStampStr isEqualToString:keys[i]]) {
++                        timestamp = (quint64)[objects[i] timeIntervalSince1970];
++                        isFound = true;
++                        break;
++                    }
++                }
++                if(isFound)
++                    break;
++            }
++        }
++    }
++    [autoreleasepool release];
++    return timestamp;
++}
++
++quint64 QCoreWlanEngine::getBytes(const QString &interfaceName, bool b)
++{
++    struct ifaddrs *ifAddressList, *ifAddress;
++    struct if_data *if_data;
++
++    quint64 bytes = 0;
++    ifAddressList = nil;
++    if(getifaddrs(&ifAddressList) == 0) {
++        for(ifAddress = ifAddressList; ifAddress; ifAddress = ifAddress->ifa_next) {
++            if(interfaceName == ifAddress->ifa_name) {
++                if_data = (struct if_data*)ifAddress->ifa_data;
++                if(b) {
++                    bytes = if_data->ifi_ibytes;
++                    break;
++                } else {
++                    bytes = if_data->ifi_obytes;
++                    break;
++                }
++            }
++        }
++        freeifaddrs(ifAddressList);
++    }
++    return bytes;
++}
++
++QT_END_NAMESPACE
-- 
cgit v0.10.1
From 8c060691d732c95ca5868a26334a1c0381e0f586 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Thu, 5 Dec 2013 12:21:10 -0500
Subject: Update Mac deployment target to make it build on 10.9, apply Qt4
 patches
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index 19c9709..d4ca1f7 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -24,7 +24,7 @@ BASEDIR=$PWD/../libraries
 OPENSCADDIR=$PWD
 SRCDIR=$BASEDIR/src
 DEPLOYDIR=$BASEDIR/install
-MAC_OSX_VERSION_MIN=10.6
+MAC_OSX_VERSION_MIN=10.7
 OPTION_32BIT=false
 OPTION_LLVM=false
 OPTION_CLANG=false
@@ -54,6 +54,9 @@ build_qt()
   fi
   tar xzf qt-everywhere-opensource-src-$version.tar.gz
   cd qt-everywhere-opensource-src-$version
+  patch -p0 < $OPENSCADDIR/patches/qt4/patch-src_corelib_global_qglobal.h.diff
+  patch -p0 < $OPENSCADDIR/patches/qt4/patch-libtiff.diff
+  patch -p0 < $OPENSCADDIR/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff
   if $USING_CLANG; then
     # FIX for clang
     sed -i "" -e "s/::TabletProximityRec/TabletProximityRec/g"  src/gui/kernel/qt_cocoa_helpers_mac_p.h
@@ -220,7 +223,7 @@ build_boost()
     BOOST_TOOLSET="toolset=clang"
     echo "using clang ;" >> tools/build/v2/user-config.jam 
   fi
-  ./b2 -d+2 $BOOST_TOOLSET cflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS" linkflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS -headerpad_max_install_names" install
+  ./b2 -j6 -d+2 $BOOST_TOOLSET cflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS" linkflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS -headerpad_max_install_names" install
   install_name_tool -id $DEPLOYDIR/lib/libboost_thread.dylib $DEPLOYDIR/lib/libboost_thread.dylib 
   install_name_tool -change libboost_system.dylib $DEPLOYDIR/lib/libboost_system.dylib $DEPLOYDIR/lib/libboost_thread.dylib 
   install_name_tool -change libboost_chrono.dylib $DEPLOYDIR/lib/libboost_chrono.dylib $DEPLOYDIR/lib/libboost_thread.dylib 
-- 
cgit v0.10.1
From ac7b37a3d67662b99adb2648945f7c972624344c Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 09:56:22 +1100
Subject: Update comments/messages for CGAL source of bad boost libraries
Add comments and change to a status instead of a warning (as we recover
nicely and it is a known issue in CGAL @
https://bugs.launchpad.net/ubuntu/+source/cgal/+bug/1242111)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fd5097a..65eefcd 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -376,23 +376,24 @@ if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6)
   message(FATAL_ERROR "CGAL >= 3.6 required")
 endif()
 inclusion(CGAL_DIR CGAL_INCLUDE_DIRS)
-
-#Get rid of bad libraries suggested for BOOST dependencies (they don't exist on some machines and cause build failures).
-#/usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+#Remove bad BOOST libraries from CGAL 3rd party dependencies when they don't exist (such as on 64-bit Ubuntu 13.10).
+#Libs of concern are /usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+#Confirmed bug in CGAL @ https://bugs.launchpad.net/ubuntu/+source/cgal/+bug/1242111
 string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_system.so" FIND_POSITION  )
 if(NOT "-1" STREQUAL ${FIND_POSITION} )
-if(NOT EXISTS "/usr/lib/libboost_system.so")
-  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_system.so -- stripping" )
-  string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
-endif()
+  if(NOT EXISTS "/usr/lib/libboost_system.so")
+    MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_system.so" )
+    string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+  endif()
 endif() 
 string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_thread.so" FIND_POSITION  )
 if(NOT "-1" STREQUAL ${FIND_POSITION} )
-if(NOT EXISTS "/usr/lib/libboost_thread.so")
-  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_thread.so -- stripping" )
-  string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
-endif()
+  if(NOT EXISTS "/usr/lib/libboost_thread.so")
+    MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_thread.so" )
+    string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+  endif()
 endif() 
+
 if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" )
 	string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 	string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
-- 
cgit v0.10.1
From 38a342215970396a5590281cd66d0ca9c9ab7e98 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Fri, 6 Dec 2013 00:39:21 -0500
Subject: #559 fixes for 10.9
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0477a45..b84775b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,6 +1,6 @@
 # instructions - see ../doc/testing.txt
 
-# set(DEBUG_OSCD 1) # print debug info during cmake
+#set(DEBUG_OSCD 1) # print debug info during cmake
 
 cmake_minimum_required(VERSION 2.8)
 if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_GREATER 2.8.3)
@@ -14,9 +14,16 @@ include(CMakeParseArguments.cmake)
 
 # Detect Lion and force gcc
 IF (APPLE)
+   # Somehow, since we build dependencies for 10.7, we need to also build executables
+   # for 10.7. This used to not be necessary, but since 10.9 it apparently is..
+   SET(CMAKE_OSX_DEPLOYMENT_TARGET 10.7)
    EXECUTE_PROCESS(COMMAND sw_vers -productVersion OUTPUT_VARIABLE MACOSX_VERSION)
-   IF (NOT ${MACOSX_VERSION} VERSION_LESS "10.8.0")
-     message("Detected Mountain Lion (10.8) or later")
+   IF (NOT ${MACOSX_VERSION} VERSION_LESS "10.9.0")
+     message("Detected Maverick (10.9) or later")
+     set(CMAKE_C_COMPILER "clang")
+     set(CMAKE_CXX_COMPILER "clang++")
+   ELSEIF (NOT ${MACOSX_VERSION} VERSION_LESS "10.8.0")
+     message("Detected Mountain Lion (10.8)")
      set(CMAKE_C_COMPILER "clang")
      set(CMAKE_CXX_COMPILER "clang++")
    ELSEIF (NOT ${MACOSX_VERSION} VERSION_LESS "10.7.0")
-- 
cgit v0.10.1
From e3317ecc64659ae8be2a7b02d4df17dad6d875db Mon Sep 17 00:00:00 2001
From: "David Eccles (gringer)" 
Date: Fri, 6 Dec 2013 13:14:09 +1300
Subject: Fail if any polygon points for rotate_extrude are less than 0
diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc
index bc9206f..e5eba2b 100644
--- a/src/PolySetCGALEvaluator.cc
+++ b/src/PolySetCGALEvaluator.cc
@@ -457,7 +457,13 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD
 	{
 		double max_x = 0;
 		for (size_t j = 0; j < dxf.paths[i].indices.size(); j++) {
-			max_x = fmax(max_x, dxf.points[dxf.paths[i].indices[j]][0]);
+			double point_x = dxf.points[dxf.paths[i].indices[j]][0];
+			if(point_x < 0){
+				PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates");
+				PRINT((boost::format("[Point %d on path %d has X coordinate %f]") % j % i % point_x).str());
+				return NULL;
+			}
+			max_x = fmax(max_x, point_x);
 		}
 
 		int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa);
-- 
cgit v0.10.1
From ede5c4b6882692dc28fc7017c4aeb87ec9222f8e Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Fri, 6 Dec 2013 00:52:51 -0500
Subject: delete ps when short-circuiting return, no need for explicit
 boost::format
diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc
index e5eba2b..599fd7f 100644
--- a/src/PolySetCGALEvaluator.cc
+++ b/src/PolySetCGALEvaluator.cc
@@ -458,9 +458,10 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD
 		double max_x = 0;
 		for (size_t j = 0; j < dxf.paths[i].indices.size(); j++) {
 			double point_x = dxf.points[dxf.paths[i].indices[j]][0];
-			if(point_x < 0){
+			if (point_x < 0) {
 				PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates");
-				PRINT((boost::format("[Point %d on path %d has X coordinate %f]") % j % i % point_x).str());
+				PRINTB("[Point %d on path %d has X coordinate %f]", j % i % point_x);
+				delete ps;
 				return NULL;
 			}
 			max_x = fmax(max_x, point_x);
-- 
cgit v0.10.1
From a0d8cbe692a5a474a7bc284cded4a01894b25c2a Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 17:46:52 +1100
Subject: Add in missed glib build dependencies for OS X and unix
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index 19c9709..81985bc 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -282,6 +282,30 @@ build_glew()
   make GLEW_DEST=$DEPLOYDIR CC=$CC CFLAGS.EXTRA="-no-cpp-precomp -dynamic -fno-common -mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" LDFLAGS.EXTRA="-mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" STRIP= install
 }
 
+build_glib2()
+{
+  version="$1"
+  maj_min_version="${version%.*}" #Drop micro
+
+  if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then
+    echo "glib2 already installed. not building"
+    return
+  fi
+
+   echo "Building glib2 $version..."
+  cd "$BASEDIR"/src
+  rm -rf "glib-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+    curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz"
+  fi
+  tar xJf "glib-$version.tar.xz"
+  cd "glib-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j$NUMCPU
+  make install
+}
+
 build_opencsg()
 {
   version=$1
@@ -446,6 +470,7 @@ build_boost 1.54.0
 # NB! For CGAL, also update the actual download URL in the function
 build_cgal 4.3
 build_glew 1.10.0
+build_glib2 2.38.1
 build_opencsg 1.3.2
 if $OPTION_DEPLOY; then
 #  build_sparkle andymatuschak 0ed83cf9f2eeb425d4fdd141c01a29d843970c20
diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh
index 8d912c3..ba328b7 100755
--- a/scripts/uni-build-dependencies.sh
+++ b/scripts/uni-build-dependencies.sh
@@ -409,6 +409,31 @@ build_glew()
   GLEW_DEST=$DEPLOYDIR $MAKER install
 }
 
+build_glib2()
+{
+  version="$1"
+  maj_min_version="${version%.*}" #Drop micro
+
+  if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then
+echo "glib2 already installed. not building"
+    return
+fi
+
+echo "Building glib2 $version..."
+  cd "$BASEDIR"/src
+  rm -rf "glib-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz"
+  fi
+tar xJf "glib-$version.tar.xz"
+  cd "glib-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j$NUMCPU
+  make install
+   
+}
+
 build_opencsg()
 {
   if [ -e $DEPLOYDIR/lib/libopencsg.so ]; then
-- 
cgit v0.10.1
From d7d5bea7363703c76b9787598304bfc838e893ee Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 18:33:42 +1100
Subject: Add specific tests for unicode len()
diff --git a/testdata/scad/misc/string-unicode.scad b/testdata/scad/misc/string-unicode.scad
index d8e3e5c..1386d63 100644
--- a/testdata/scad/misc/string-unicode.scad
+++ b/testdata/scad/misc/string-unicode.scad
@@ -1,3 +1,12 @@
+//Test length reporting
+text_1bytes_len = "1234";
+text_2bytes_len = "ΠΠΠΠ";
+text_4bytes_len = "π‘π±ππ";
+
+echo( "text_1bytes_len = ", text_1bytes_len, " len = ", len(text_1bytes_len)  );
+echo( "text_2bytes_len = ", text_2bytes_len, " len = ", len(text_2bytes_len)  );
+echo( "text_4bytes_len = ", text_4bytes_len, " len = ", len(text_4bytes_len)  );
+
 //Test how well arrays of unicode string are accessed.
 
 texts_array = [
@@ -33,4 +42,3 @@ echo( "Past end of unicode only 4-byte ", text_4bytes[len(text_4bytes)]  );
 echo( "Past end of both 2-byte ", text_2bytes[ len(text_2bytes) * 2 ]   );
 echo( "Past end of both 4-byte ", text_4bytes[ len(text_4bytes) * 4 ]   );
 
-
diff --git a/tests/regression/echotest/string-unicode-expected.echo b/tests/regression/echotest/string-unicode-expected.echo
index b4b848f..a1cd3be 100644
--- a/tests/regression/echotest/string-unicode-expected.echo
+++ b/tests/regression/echotest/string-unicode-expected.echo
@@ -1,3 +1,6 @@
+ECHO: "text_1bytes_len = ", "1234", " len = ", 4
+ECHO: "text_2bytes_len = ", "ΠΠΠΠ", " len = ", 4
+ECHO: "text_4bytes_len = ", "π‘π±ππ", " len = ", 4
 ECHO: "[", 0, "] = ", "DEADBEEF", " of len=", 8, ":"
 ECHO: "    [", 0, "]=", "D"
 ECHO: "    [", 1, "]=", "E"
-- 
cgit v0.10.1
From 40ae8c7b343fc5dafc8c9bdc7d8a63f6e8032fca Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sat, 7 Dec 2013 14:38:22 -0800
Subject: Add html report upload to test_pretty_print.py, removed wiki support
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index a31b1a8..c26b919 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -25,544 +25,428 @@
 
 # todo
 #
-# 1. Note: Wiki code is deprecated. All future development should move to 
-# create html output (or even xml output). Wiki design was based on the 
-# wrong assumption of easily accessible public wiki servers with 
-# auto-upload scripts available. wiki code should be removed and code 
-# simplified.
-#
-# to still use wiki, use args '--wiki' and/or '--wiki-upload' 
-#
-# 2. why is hash differing
+# 1. why is hash differing
 
-import string,sys,re,os,hashlib,subprocess,textwrap,time,platform
+import string
+import sys
+import re
+import os
+import hashlib
+import subprocess
+import time
+import platform
 
 def tryread(filename):
-	data = None
-	try:
-		f = open(filename,'rb')
-		data = f.read()
-		f.close()
-	except Exception, e:
-		print 'couldn\'t open ',filename
-		print type(e), e
-	return data
-
-def trysave(filename,data):
-	dir = os.path.dirname(filename)
-	try:
-		if not os.path.isdir(dir):
-			if not dir == '':
-				debug( 'creating' + dir)
-				os.mkdir(dir)
-		f=open(filename,'wb')
-		f.write(data)
-		f.close()
-	except Exception, e:
-		print 'problem writing to',filename
-		print type(e), e
-		return None
-	return True
-
-def ezsearch(pattern,str):
-	x = re.search(pattern,str,re.DOTALL|re.MULTILINE)
-	if x and len(x.groups())>0: return x.group(1).strip()
-	return ''
-	
+    data = None
+    try:
+        f = open(filename,'rb')
+        data = f.read()
+        f.close()
+    except Exception as e:
+        print 'couldn\'t open ',filename
+        print type(e), e
+    return data
+
+def trysave(filename, data):
+    dir = os.path.dirname(filename)
+    try:
+        if not os.path.isdir(dir):
+            if not dir == '':
+                debug( 'creating' + dir)
+                os.mkdir(dir)
+        f=open(filename,'wb')
+        f.write(data)
+        f.close()
+    except Exception as e:
+        print 'problem writing to',filename
+        print type(e), e
+        return None
+    return True
+
+def ezsearch(pattern, str):
+    x = re.search(pattern,str,re.DOTALL|re.MULTILINE)
+    if x and len(x.groups())>0: return x.group(1).strip()
+    return ''
+    
 def read_gitinfo():
-	# won't work if run from outside of branch. 
-	try:
-		data = subprocess.Popen(['git','remote','-v'],stdout=subprocess.PIPE).stdout.read()
-		origin = ezsearch('^origin *?(.*?)\(fetch.*?$',data)
-		upstream = ezsearch('^upstream *?(.*?)\(fetch.*?$',data)
-		data = subprocess.Popen(['git','branch'],stdout=subprocess.PIPE).stdout.read()
-		branch = ezsearch('^\*(.*?)$',data)
-		out  = 'Git branch: ' + branch + ' from origin ' + origin + '\n'
-		out += 'Git upstream: ' + upstream + '\n'
-	except:
-		out = 'Problem running git'
-	return out
+    # won't work if run from outside of branch. 
+    try:
+        data = subprocess.Popen(['git', 'remote', '-v'], stdout=subprocess.PIPE).stdout.read()
+        origin = ezsearch('^origin *?(.*?)\(fetch.*?$', data)
+        upstream = ezsearch('^upstream *?(.*?)\(fetch.*?$', data)
+        data = subprocess.Popen(['git', 'branch'], stdout=subprocess.PIPE).stdout.read()
+        branch = ezsearch('^\*(.*?)$', data)
+        out = 'Git branch: ' + branch + ' from origin ' + origin + '\n'
+        out += 'Git upstream: ' + upstream + '\n'
+    except:
+        out = 'Problem running git'
+    return out
 
 def read_sysinfo(filename):
-	data = tryread(filename)
-	if not data: 
-		sinfo = platform.sys.platform
-		sinfo += '\nsystem cannot create offscreen GL framebuffer object'
-		sinfo += '\nsystem cannot create GL based images'
-		sysid = platform.sys.platform+'_no_GL_renderer'
-		return sinfo, sysid
+    data = tryread(filename)
+    if not data: 
+        sinfo = platform.sys.platform
+        sinfo += '\nsystem cannot create offscreen GL framebuffer object'
+        sinfo += '\nsystem cannot create GL based images'
+        sysid = platform.sys.platform+'_no_GL_renderer'
+        return sinfo, sysid
 
-	machine = ezsearch('Machine:(.*?)\n',data)
-	machine = machine.replace(' ','-').replace('/','-')
+    machine = ezsearch('Machine:(.*?)\n',data)
+    machine = machine.replace(' ','-').replace('/','-')
 
-	osinfo = ezsearch('OS info:(.*?)\n',data)
-	osplain = osinfo.split(' ')[0].strip().replace('/','-')
-	if 'windows' in osinfo.lower(): osplain = 'win'
+    osinfo = ezsearch('OS info:(.*?)\n',data)
+    osplain = osinfo.split(' ')[0].strip().replace('/','-')
+    if 'windows' in osinfo.lower():
+        osplain = 'win'
 
-	renderer = ezsearch('GL Renderer:(.*?)\n',data)
-	tmp = renderer.split(' ')
-	tmp = string.join(tmp[0:3],'-')
-	tmp = tmp.split('/')[0]
-	renderer = tmp
+    renderer = ezsearch('GL Renderer:(.*?)\n',data)
+    tmp = renderer.split(' ')
+    tmp = string.join(tmp[0:3],'-')
+    tmp = tmp.split('/')[0]
+    renderer = tmp
 
-	data += read_gitinfo()
+    data += read_gitinfo()
 
-	data += 'Image comparison: ImageMagick'
+    data += 'Image comparison: ImageMagick'
 
-	data = data.strip()
+    data = data.strip()
 
-	# create 4 letter hash and stick on end of sysid
-	nondate_data = re.sub("\n.*?ompile date.*?\n","\n",data).strip()
-	hexhash = hashlib.md5()
-	hexhash.update(nondate_data)
-	hexhash = hexhash.hexdigest()[-4:].upper()
-	hash = ''
-	for c in hexhash: hash += chr(ord(c)+97-48) 
+    # create 4 letter hash and stick on end of sysid
+    nondate_data = re.sub("\n.*?ompile date.*?\n", "\n", data).strip()
+    hexhash = hashlib.md5(nondate_data).hexdigest()[-4:].upper()
+    hash_ = ''.join(chr(ord(c) + 97 - 48) for c in hexhash)
 
-	sysid = osplain + '_' + machine + '_' + renderer + '_' + hash
-	sysid = sysid.replace('(','_').replace(')','_')
-	sysid = sysid.lower()
+    sysid = '_'.join([osplain, machine, renderer, hash_])
+    sysid = sysid.replace('(', '_').replace(')', '_')
+    sysid = sysid.lower()
 
-	return data, sysid
+    return data, sysid
 
 class Test:
-	def __init__(self,fullname,time,passed,output,type,actualfile,expectedfile,scadfile,log):
-		self.fullname,self.time,self.passed,self.output = \
-			fullname, time, passed, output
-		self.type, self.actualfile, self.expectedfile, self.scadfile = \
-			type, actualfile, expectedfile, scadfile
-		self.fulltestlog = log
-		
-	def __str__(self):
-		x = 'fullname: ' + self.fullname
-		x+= '\nactualfile: ' + self.actualfile
-		x+= '\nexpectedfile: ' + self.expectedfile
-		x+= '\ntesttime: ' + self.time
-		x+= '\ntesttype: ' + self.type
-		x+= '\npassed: ' + str(self.passed)
-		x+= '\nscadfile: ' + self.scadfile
-		x+= '\noutput bytes: ' + str(len(self.output))
-		x+= '\ntestlog bytes: ' + str(len(self.fulltestlog))
-		x+= '\n'
-		return x
+    def __init__(self, fullname, subpr, passed, output, type, actualfile,
+                 expectedfile, scadfile, log):
+        self.fullname, self.time = fullname, time
+        self.passed, self.output = passed, output
+        self.type, self.actualfile = type, actualfile
+        self.expectedfile, self.scadfile = expectedfile, scadfile
+        self.fulltestlog = log
+        
+    def __str__(self):
+        x = 'fullname: ' + self.fullname
+        x+= '\nactualfile: ' + self.actualfile
+        x+= '\nexpectedfile: ' + self.expectedfile
+        x+= '\ntesttime: ' + self.time
+        x+= '\ntesttype: ' + self.type
+        x+= '\npassed: ' + str(self.passed)
+        x+= '\nscadfile: ' + self.scadfile
+        x+= '\noutput bytes: ' + str(len(self.output))
+        x+= '\ntestlog bytes: ' + str(len(self.fulltestlog))
+        x+= '\n'
+        return x
 
 def parsetest(teststring):
-	patterns = ["Test:(.*?)\n", # fullname
-		"Test time =(.*?) sec\n",
-		"Test time.*?Test (Passed)", # pass/fail
-		"Output:(.*?)",
-		'Command:.*?-s" "(.*?)"', # type
-		"^ actual .*?:(.*?)\n",
-		"^ expected .*?:(.*?)\n",
-		'Command:.*?(testdata.*?)"' # scadfile 
-		]
-	hits = map( lambda pattern: ezsearch(pattern,teststring), patterns )
-	test = Test(hits[0],hits[1],hits[2]=='Passed',hits[3],hits[4],hits[5],hits[6],hits[7],teststring)
-	if len(test.actualfile) > 0: test.actualfile_data = tryread(test.actualfile)
-	if len(test.expectedfile) > 0: test.expectedfile_data = tryread(test.expectedfile)
-	return test
+    patterns = ["Test:(.*?)\n", # fullname
+        "Test time =(.*?) sec\n",
+        "Test time.*?Test (Passed)", # pass/fail
+        "Output:(.*?)",
+        'Command:.*?-s" "(.*?)"', # type
+        "^ actual .*?:(.*?)\n",
+        "^ expected .*?:(.*?)\n",
+        'Command:.*?(testdata.*?)"' # scadfile 
+        ]
+    hits = map( lambda pattern: ezsearch(pattern, teststring), patterns)
+    test = Test(hits[0], hits[1], hits[2]=='Passed', hits[3], hits[4], hits[5], 
+                hits[6], hits[7], teststring)
+    if len(test.actualfile) > 0:
+        test.actualfile_data = tryread(test.actualfile)
+    if len(test.expectedfile) > 0:
+        test.expectedfile_data = tryread(test.expectedfile)
+    return test
 
 def parselog(data):
-	startdate = ezsearch('Start testing: (.*?)\n',data)
-	enddate = ezsearch('End testing: (.*?)\n',data)
-	pattern = '([0-9]*/[0-9]* Testing:.*?time elapsed.*?\n)'
-	test_chunks = re.findall(pattern,data,re.S)
-	tests = map( parsetest, test_chunks )
-	tests = sorted(tests, key = lambda t:t.passed)
-	return startdate, tests, enddate
+    startdate = ezsearch('Start testing: (.*?)\n', data)
+    enddate = ezsearch('End testing: (.*?)\n', data)
+    pattern = '([0-9]*/[0-9]* Testing:.*?time elapsed.*?\n)'
+    test_chunks = re.findall(pattern,data, re.S)
+    tests = map( parsetest, test_chunks )
+    tests = sorted(tests, key = lambda t: t.passed)
+    return startdate, tests, enddate
 
 def load_makefiles(builddir):
-	filelist = []
-	for root, dirs, files in os.walk(builddir):
-		for fname in files: filelist += [ os.path.join(root, fname) ]
-	files  = filter(lambda x: 'build.make' in os.path.basename(x), filelist)
-	files += filter(lambda x: 'flags.make' in os.path.basename(x), filelist)
-	files = filter(lambda x: 'esting' not in x and 'emporary' not in x, files)
-	result = {}
-	for fname in files:
-		result[fname.replace(builddir,'')] = open(fname,'rb').read()
-	return result
-
-def wikify_filename(fname, wiki_rootpath, sysid):
-	wikifname = fname.replace('/','_').replace('\\','_').strip('.')
-	return wiki_rootpath + '_' + sysid + '_' + wikifname
-
-def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles):
-
-	wiki_template = """
-[[WIKI_ROOTPATH]] test run report
-
-'''Sysid''': SYSID
-
-'''Result summary''': NUMPASSED / NUMTESTS tests passed ( PERCENTPASSED % ) 
-
-'''System info''':
-
-SYSINFO
-
-
-start time: STARTDATE 
-end time  : ENDDATE 
-
-'''Image tests'''
-
-
-{| border=1 cellspacing=0 cellpadding=1
-|-
-| colspan=2 | FTESTNAME  
-|-
-| Expected image || Actual image
-|-
-| [[File:EXPECTEDFILE|250px]] || ACTUALFILE_WIKI
-|}
-
-
-TESTLOG
-
-
-
-
-
-
-
-'''Text tests'''
-
-
-{|border=1 cellspacing=0 cellpadding=1
-|-
-| FTESTNAME
-|}
-
-
-TESTLOG
-
-
-
-
-
-'''build.make and flags.make'''
-
-*[[MAKEFILE_NAME]]
-
-"""
-	txtpages = {}
-	imgs = {}
-	passed_tests = filter(lambda x: x.passed, tests)
-	failed_tests = filter(lambda x: not x.passed, tests)
-
-	tests_to_report = failed_tests
-	if include_passed: tests_to_report = tests
-
-	try: percent = str(int(100.0*len(passed_tests) / len(tests)))
-	except ZeroDivisionError: percent = 'n/a'
-	s = wiki_template
-	repeat1 = ezsearch('(.*?)',s)
-	repeat2 = ezsearch('(.*?)',s)
-	repeat3 = ezsearch('(.*?)',s)
-	dic = { 'STARTDATE': startdate, 'ENDDATE': enddate, 'WIKI_ROOTPATH': wiki_rootpath,
-		'SYSINFO': sysinfo, 'SYSID':sysid, 
-		'NUMTESTS':len(tests), 'NUMPASSED':len(passed_tests), 'PERCENTPASSED':percent }
-	for key in dic.keys():
-		s = s.replace(key,str(dic[key]))
-
-	for t in tests_to_report:
-		if t.type in ('txt', 'ast', 'csg', 'term', 'echo'):
-			newchunk = re.sub('FTESTNAME',t.fullname,repeat2)
-			newchunk = newchunk.replace('TESTLOG',t.fulltestlog)
-			s = s.replace(repeat2, newchunk+repeat2)
-		elif t.type=='png':
-			tmp = t.actualfile.replace(builddir,'')
-			wikiname_a = wikify_filename(tmp,wiki_rootpath,sysid)
-			tmp = t.expectedfile.replace(os.path.dirname(builddir),'')
-			wikiname_e = wikify_filename(tmp,wiki_rootpath,sysid)
-			if hasattr(t, 'expectedfile_data'): 
-                                imgs[wikiname_e] = t.expectedfile_data
-			if t.actualfile: 
-				actualfile_wiki = '[[File:'+wikiname_a+'|250px]]'
-                                if hasattr(t, 'actualfile_data'): 
-                                        imgs[wikiname_a] = t.actualfile_data
-			else:
-				actualfile_wiki = 'No image generated.'
-			newchunk = re.sub('FTESTNAME',t.fullname,repeat1)
-			newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki)
-			newchunk = newchunk.replace('EXPECTEDFILE',wikiname_e)
-			newchunk = newchunk.replace('TESTLOG',t.fulltestlog)
-			s = s.replace(repeat1, newchunk+repeat1)
-		else:
-			raise Exception("Unknown test type %r"%t.type)
-
-	makefiles_wikinames = {}
-	for mf in sorted(makefiles.keys()):
-		tmp = mf.replace('CMakeFiles','').replace('.dir','')
-		wikiname = wikify_filename(tmp,wiki_rootpath,sysid)
-		newchunk = re.sub('MAKEFILE_NAME',wikiname,repeat3)
-		s = s.replace(repeat3, newchunk+repeat3)
-		makefiles_wikinames[mf] = wikiname
-
-	s = s.replace(repeat1,'')
-	s = s.replace(repeat2,'')
-	s = s.replace(repeat3,'')
-	s = re.sub('\n','',s)
-	s = re.sub('','',s)
-
-	mainpage_wikiname = wiki_rootpath + '_' + sysid + '_test_report'
-	txtpages[ mainpage_wikiname ] = s
-	for mf in sorted(makefiles.keys()):
-		txtpages[ makefiles_wikinames[ mf ] ] = '\n*Subreport from [['+mainpage_wikiname+']]\n\n\n\n'+makefiles[mf]+'\n
'
-
-	return imgs, txtpages
-
-def png_encode64( fname, width=250 ):
-	# en.wikipedia.org/wiki/Data_URI_scheme
-	try:
-		f = open( fname, "rb" )
-		data = f.read()
-	except:
-		data = ''
-	data_uri = data.encode("base64").replace("\n","")
-	tag  = ' \n'
-	return tag
-
-def tohtml(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles):
-	# kludge. assume wiki stuff has alreayd run and dumped files properly
-	head = ''+wiki_rootpath+' test run for '+sysid +''
-	tail = ''
-
-	passed_tests = filter(lambda x: x.passed, tests)
-	failed_tests = filter(lambda x: not x.passed, tests)
-	try: percent = str(int(100.0*len(passed_tests) / len(tests)))
-	except ZeroDivisionError: percent = 'n/a'
-
-	tests_to_report = failed_tests
-	if include_passed: tests_to_report = tests
-
-	s=''
-
-	s+= '\n
\n'
-	return tag
-
-def tohtml(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles):
-	# kludge. assume wiki stuff has alreayd run and dumped files properly
-	head = ''+wiki_rootpath+' test run for '+sysid +''
-	tail = ''
-
-	passed_tests = filter(lambda x: x.passed, tests)
-	failed_tests = filter(lambda x: not x.passed, tests)
-	try: percent = str(int(100.0*len(passed_tests) / len(tests)))
-	except ZeroDivisionError: percent = 'n/a'
-
-	tests_to_report = failed_tests
-	if include_passed: tests_to_report = tests
-
-	s=''
-
-	s+= '\n'
-	s+= '\nSystem info\n'
-	s+= '\n
'
-	s+= '
'+sysinfo+'
\n'
-
-	s+= '\n'
-	s+= '\nSTARTDATE: '+ startdate
-	s+= '\nENDDATE: '+ enddate
-	s+= '\nWIKI_ROOTPATH: '+ wiki_rootpath
-	s+= '\nSYSID: '+sysid
-	s+= '\nNUMTESTS: '+str(len(tests))
-	s+= '\nNUMPASSED: '+str(len(passed_tests))
-	s+= '\nPERCENTPASSED: '+ percent
-	s+= '\n
'
-
-	if not include_passed:
-		s+= 'Failed tests:
\n'
-
-	if len(tests_to_report)==0:
-		s+= 'none
'
-
-	for t in tests_to_report:
-		if t.type in ('txt', 'ast', 'csg', 'term', 'echo'):
-			s+='\n'+t.fullname+'
\n'
-			s+=''+t.fulltestlog+'
\n\n'
-		elif t.type=='png':
-			tmp = t.actualfile.replace(builddir,'')
-			wikiname_a = wikify_filename(tmp,wiki_rootpath,sysid)
-			tmp = t.expectedfile.replace(os.path.dirname(builddir),'')
-			wikiname_e = wikify_filename(tmp,wiki_rootpath,sysid)
-			# imgtag_e =  '
-			# imatag_a =
'
-			# imatag_a =  '
-			imgtag_e = png_encode64( t.expectedfile, 250 )
-			imgtag_a = png_encode64( t.actualfile, 250 )
-			s+='
'
-			imgtag_e = png_encode64( t.expectedfile, 250 )
-			imgtag_a = png_encode64( t.actualfile, 250 )
-			s+=''
-			s+='\n| '+t.fullname
-			s+='\n | 
| Expected | Actual'
-			s+='\n | 
| ' + imgtag_e + ''
-			s+='\n | ' + imgtag_a + ''
-			s+='\n | 
'
-			s+='\n'
-			s+=t.fulltestlog
-			s+='\n
'
-		else:
-			raise Exception("Unknown test type %r"%t.type)
-
-	s+='\n\n\n\n'
-
-	s+= '
 CMake .build files 
\n'
-	s+= '\n'
-	makefiles_wikinames = {}
-	for mf in sorted(makefiles.keys()):
-		mfname = mf.strip().lstrip(os.path.sep)
-		text = open(os.path.join(builddir,mfname)).read()
-		s+= ''+mfname+'
'
-		s+= text
-		tmp = mf.replace('CMakeFiles','').replace('.dir','')
-		wikiname = wikify_filename(tmp,wiki_rootpath,sysid)
-		# s += '\n'+wikiname+'
'
-	s+= '\n'
-	s+='\n'
-
-	return head + s + tail
-
-def wiki_login(wikiurl,api_php_path,botname,botpass):
-	site = mwclient.Site(wikiurl,api_php_path)
-	site.login(botname,botpass)
-	return site
-
-def wiki_upload(wikiurl,api_php_path,botname,botpass,filedata,wikipgname):
-	counter = 0
-	done = False
-	descrip = 'test'
-	time.sleep(1)
-	while not done:
-		try:
-			print 'login',botname,'to',wikiurl
-			site = wiki_login(wikiurl,api_php_path,botname,botpass)
-			print 'uploading...',
-			if wikipgname.endswith('png'):
-				site.upload(filedata,wikipgname,descrip,ignore=True)
-			else:
-				page = site.Pages[wikipgname]
-				text = page.edit()
-				page.save(filedata)
-			done = True
-			print 'transfer ok'
-		except Exception, e:
-			print 'Error:', type(e),e
-			counter += 1
-			if counter>maxretry: 
-				print 'giving up. please try a different wiki site'
-				done = True
-			else:
-				print 'wiki',wikiurl,'down. retrying in 15 seconds'
-				time.sleep(15)	
-
-def upload(wikiurl,api_php_path='/',wiki_rootpath='test', sysid='null', botname='cakebaby',botpass='anniew',wikidir='.',dryrun=True):
-	wetrun = not dryrun
-	if dryrun: print 'dry run'
-	try:
-		global mwclient
-		import mwclient
-	except:
-		print 'please download mwclient 0.6.5 and unpack here:', os.getcwd()
-		sys.exit()
-
-	if wetrun: site = wiki_login(wikiurl,api_php_path,botname,botpass)
-
-	wikifiles = os.listdir(wikidir)
-	testreport_page = filter( lambda x: 'test_report' in x, wikifiles )
-	if (len(testreport_page)>1): 
-		print 'multiple test reports found, please clean dir',wikidir
-		sys.exit()
-
-	rootpage = testreport_page[0]
-	print 'add',rootpage,' to main report page ',wiki_rootpath
-	if wetrun:
-		page = site.Pages[wiki_rootpath]
-		text = page.edit()
-		if not '[['+rootpage+']]' in text:
-			page.save(text +'\n*[['+rootpage+']]\n')
-
-	wikifiles = os.listdir(wikidir)
-	wikifiles = filter(lambda x: not x.endswith('html'), wikifiles)
-
-	print 'upload wiki pages:'
-	for wikiname in wikifiles:
-		filename = os.path.join(wikidir,wikiname)
-		filedata = tryread(filename)
-		print 'upload',len(filedata),'bytes from',wikiname
-		if wetrun and len(filedata)>0: 
-			wiki_upload(wikiurl,api_php_path,botname,botpass,filedata,wikiname)
-		if len(filedata)==0: 
-			print 'cancelling empty upload'
+    filelist = []
+    for root, dirs, files in os.walk(builddir):
+        for fname in files: filelist += [ os.path.join(root, fname) ]
+    files = [file for file in filelist if 'build.make' in os.path.basename(file) 
+             or 'flags.make' in os.path.basename(file)]
+    files = [file for file in files if 'esting' not in file and 'emporary' not in file]
+    result = {}
+    for fname in files:
+        result[fname.replace(builddir, '')] = tryread(fname)
+    return result
+
+
+def png_encode64(fname, width=250, data=None):
+    # en.wikipedia.org/wiki/Data_URI_scheme
+    data = data or tryread(fname) or ''
+    data_uri = data.encode('base64').replace('\n', '')
+    tag = ''' '''
+    if data == '':
+        alt = 'alt="error: no image generated" '
+    else:
+        alt = 'alt="openscad_test_image" '
+    tag %= (data_uri, width, alt)
+    return tag
+
 
 def findlogfile(builddir):
-	logpath = os.path.join(builddir,'Testing','Temporary')
-	logfilename = os.path.join(logpath,'LastTest.log.tmp')
-	if not os.path.isfile(logfilename):
-		logfilename = os.path.join(logpath,'LastTest.log')
-	if not os.path.isfile(logfilename):
-		print 'cant find and/or open logfile',logfilename
-		sys.exit()
-	return logpath, logfilename
+    logpath = os.path.join(builddir, 'Testing', 'Temporary')
+    logfilename = os.path.join(logpath, 'LastTest.log.tmp')
+    if not os.path.isfile(logfilename):
+        logfilename = os.path.join(logpath, 'LastTest.log')
+    if not os.path.isfile(logfilename):
+        print 'can\'t find and/or open logfile', logfilename
+        sys.exit()
+    return logfilename
+
+# --- Templating ---
+    
+class Templates(object):
+    html_template = '''
+    Test run for {sysid}
+    {style}
+    
+    
+
'''
+    if data == '':
+        alt = 'alt="error: no image generated" '
+    else:
+        alt = 'alt="openscad_test_image" '
+    tag %= (data_uri, width, alt)
+    return tag
+
 
 def findlogfile(builddir):
-	logpath = os.path.join(builddir,'Testing','Temporary')
-	logfilename = os.path.join(logpath,'LastTest.log.tmp')
-	if not os.path.isfile(logfilename):
-		logfilename = os.path.join(logpath,'LastTest.log')
-	if not os.path.isfile(logfilename):
-		print 'cant find and/or open logfile',logfilename
-		sys.exit()
-	return logpath, logfilename
+    logpath = os.path.join(builddir, 'Testing', 'Temporary')
+    logfilename = os.path.join(logpath, 'LastTest.log.tmp')
+    if not os.path.isfile(logfilename):
+        logfilename = os.path.join(logpath, 'LastTest.log')
+    if not os.path.isfile(logfilename):
+        print 'can\'t find and/or open logfile', logfilename
+        sys.exit()
+    return logfilename
+
+# --- Templating ---
+    
+class Templates(object):
+    html_template = '''
+    Test run for {sysid}
+    {style}
+    
+    
+        {project_name} test run report
+        
+            Sysid:  {sysid}
+        
+        
+            Result summary:  {numpassed} / {numtests} tests passed ({percent}%)
+        
+        
+        System info
+        {sysinfo}
+            
+        start time: {startdate}
+        end time: {enddate}
+
+        Image tests
+        {image_tests}
+
+        Text tests
+        {text_tests}
+
+        build.make and flags.make
+        {makefiles}
+    '''
+
+    style = '''
+    '''
+
+    image_template = '''
+    
+    | {test_name} | 
+    | Expected image | Actual image | 
+    | {expected} | {actual} | 
+    
+    
+
+    
+    {test_log}
+    
+    '''
+
+    text_template = '''
+    {test_name}
+
+    
+    {test_log}
+    
+    '''
+
+    makefile_template = '''
+    {name}
+    
+        {text}
+    
+    '''
+
+    def __init__(self, **defaults):
+        self.filled = {}
+        self.defaults = defaults
+
+    def fill(self, template, *args, **kwargs):
+        kwds = self.defaults.copy()
+        kwds.update(kwargs)
+        return getattr(self, template).format(*args, **kwds)
+
+    def add(self, template, var, *args, **kwargs):
+        self.filled[var] = self.filled.get(var, '') + self.fill(template, *args, **kwargs)
+        return self.filled[var]
+
+    def get(self, var):
+        return self.filled[var]
+
+
+def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles):
+    passed_tests = [test for test in tests if test.passed]
+    failed_tests = [test for test in tests if not test.passed]
+
+    report_tests = failed_tests
+    if include_passed:
+        report_tests = tests
+
+    percent = '%.0f' % (100.0 * len(passed_tests) / len(tests)) if tests else 'n/a'
+
+    templates = Templates()
+    for test in report_tests:
+        if test.type in ('txt', 'ast', 'csg', 'term', 'echo'):
+            templates.add('text_template', 'text_tests',
+                          test_name=test.fullname,
+                          test_log=test.fulltestlog)
+        elif test.type == 'png':
+            actual_img = png_encode64(test.actualfile,
+                                  data=vars(test).get('actualfile_data'))
+            expected_img = png_encode64(test.expectedfile,
+                                    data=vars(test).get('expectedfile_data'))
+            templates.add('image_template', 'image_tests',
+                          test_name=test.fullname,
+                          test_log=test.fulltestlog,
+                          actual=actual_img,
+                          expected=expected_img)
+        else:
+            raise TypeError('Unknown test type %r' % test.type)
+    
+    for mf in sorted(makefiles.keys()):
+        mfname = mf.strip().lstrip(os.path.sep)
+        text = open(os.path.join(builddir, mfname)).read()
+        templates.add('makefile_template', 'makefiles', name=mfname, text=text)
+
+    text_tests = templates.get('text_tests')
+    image_tests = templates.get('image_tests')
+    makefiles_str = templates.get('makefiles')
+
+    return templates.fill('html_template', style=Templates.style,
+                          sysid=sysid, sysinfo=sysinfo,
+                          startdate=startdate, enddate=enddate,
+                          project_name=project_name,
+                          numtests=len(tests),
+                          numpassed=len(passed_tests),
+                          percent=percent, image_tests=image_tests,
+                          text_tests=text_tests, makefiles=makefiles_str)
+
+# --- End Templating ---
+
+# --- Web Upload ---
+
+def postify(data):
+    return urlencode(data).encode()
+
+def create_page():
+    data = {
+        'action': 'create',
+        'type': 'html'
+    }
+    try:
+        response = urlopen('http://www.dinkypage.com', data=postify(data))
+    except:
+        return None
+    return response.geturl()
+
+def upload_html(page_url, title, html):
+    data = {
+        'mode': 'editor',
+        'title': title,
+        'html': html,
+        'ajax': '1'
+    }
+    try:
+        response = urlopen(page_url, data=postify(data))
+    except:
+        return False
+    return 'success' in response.read().decode()
+
+# --- End Web Upload ---
 
 def debug(x):
-	if debug_test_pp: print 'test_pretty_print: '+x
+    if debug_test_pp:
+        print 'test_pretty_print: ' + x
 
-debug_test_pp=False
-builddir=os.getcwd()
+debug_test_pp = False
+builddir = os.getcwd()
 
 def main():
-	#wikisite = 'cakebaby.referata.com'
-	#wiki_api_path = ''
-	global wikisite, wiki_api_path, wiki_rootpath, builddir, debug_test_pp
-	global maxretry, dry, include_passed
-
-	wikisite = 'cakebaby.wikia.com'
-	wiki_api_path = '/'
-	wiki_rootpath = 'OpenSCAD'
-	if '--debug' in string.join(sys.argv): debug_test_pp=True
-	maxretry = 10
-
-	if bool(os.getenv("TEST_GENERATE")): sys.exit(0)
-
-	include_passed = False
-	if '--include-passed' in sys.argv: include_passed = True
-
-	dry = False
-	debug( 'running test_pretty_print' )
-	if '--dryrun' in sys.argv: dry=True
-	suffix = ezsearch('--suffix=(.*?) ',string.join(sys.argv)+' ')
-	builddir = ezsearch('--builddir=(.*?) ',string.join(sys.argv)+' ')
-	if builddir=='': builddir=os.getcwd()
-	debug( 'build dir set to ' +  builddir )
-
-	sysinfo, sysid = read_sysinfo(os.path.join(builddir,'sysinfo.txt'))
-	makefiles = load_makefiles(builddir)
-	logpath, logfilename = findlogfile(builddir)
-	testlog = tryread(logfilename)
-	startdate, tests, enddate = parselog(testlog)
-	if debug_test_pp:
-		print 'found sysinfo.txt,',
-		print 'found', len(makefiles),'makefiles,',
-		print 'found', len(tests),'test results'
-
-	imgs, txtpages = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles)
-
-	wikidir = os.path.join(logpath,sysid+'_report')
-	debug( 'erasing files in ' + wikidir )
-	try: map(lambda x:os.remove(os.path.join(wikidir,x)), os.listdir(wikidir))
-	except: pass
-	debug( 'output dir:\n' + wikidir.replace(os.getcwd(),'') )
-	debug( 'writing ' + str(len(imgs)) + ' images' )
-	debug( 'writing ' + str(len(txtpages)-1) + ' text pages' )
-	debug( 'writing index.html ' )
-	if '--wiki' in string.join(sys.argv):
-		print "wiki output is deprecated"
-		for pgname in sorted(imgs): trysave( os.path.join(wikidir,pgname), imgs[pgname])
-		for pgname in sorted(txtpages): trysave( os.path.join(wikidir,pgname), txtpages[pgname])
-
-	htmldata = tohtml(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles)
-	html_basename = sysid+'_report.html'
-	html_filename = os.path.join(builddir,'Testing','Temporary',html_basename)
-	debug('saving ' +html_filename + ' ' + str(len(htmldata)) + ' bytes')
-	trysave( html_filename, htmldata )
-	print "report saved:", html_filename.replace(os.getcwd()+os.path.sep,'')
-
-	if '--wiki-upload' in sys.argv:
-		print "wiki upload is deprecated."
-		upload(wikisite,wiki_api_path,wiki_rootpath,sysid,'openscadbot',
-			'tobdacsnepo',wikidir,dryrun=dry)
-		print 'upload attempt complete'
-
-	debug( 'test_pretty_print complete' )
+    global builddir, debug_test_pp
+    global maxretry, dry, include_passed
+    project_name = 'OpenSCAD'
+    
+    if bool(os.getenv("TEST_GENERATE")):
+        sys.exit(0)
+    
+    # --- Command Line Parsing ---
+    
+    if '--debug' in ' '.join(sys.argv):
+        debug_test_pp = True
+    maxretry = 10
+
+    include_passed = False
+    if '--include-passed' in sys.argv:
+        include_passed = True
+
+    dry = False
+    debug('running test_pretty_print')
+    if '--dryrun' in sys.argv:
+        dry = True
+
+    suffix = ezsearch('--suffix=(.*?) ', ' '.join(sys.argv) + ' ')
+    builddir = ezsearch('--builddir=(.*?) ', ' '.join(sys.argv) + ' ')
+    if not builddir:
+        builddir = os.getcwd()
+    debug('build dir set to ' +  builddir)
+    
+    # --- End Command Line Parsing ---
+
+    sysinfo, sysid = read_sysinfo(os.path.join(builddir, 'sysinfo.txt'))
+    makefiles = load_makefiles(builddir)
+    logfilename = findlogfile(builddir)
+    testlog = tryread(logfilename)
+    startdate, tests, enddate = parselog(testlog)
+    if debug_test_pp:
+        print 'found sysinfo.txt,',
+        print 'found', len(makefiles),'makefiles,',
+        print 'found', len(tests),'test results'
+
+    html = to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles)
+    html_basename = sysid + '_report.html'
+    html_filename = os.path.join(builddir, 'Testing', 'Temporary', html_basename)
+    debug('saving ' + html_filename + ' ' + str(len(htmldata)) + ' bytes')
+    trysave(html_filename, html)
+
+    page_url = create_page()
+    if upload_html(page_url, title='OpenSCAD test results', html=html):
+        share_url = page_url.partition('?')[0]
+        print 'html report uploaded at', share_url
+    else:
+        print 'could not upload html report'
+
+    debug('test_pretty_print complete')
 
 if __name__=='__main__':
-	main()
+    main()
-- 
cgit v0.10.1
From 8d3365a79ab7afd287545da3f8b2ba12f0c43585 Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sat, 7 Dec 2013 14:50:49 -0800
Subject: Update design philosopy comments
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index c26b919..44df156 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -17,11 +17,9 @@
 # Design philosophy
 #
 # 1. parse the data (images, logs) into easy-to-use data structures
-# 2. wikifiy the data 
-# 3. save the wikified data to disk
-# 4. generate html, including base64 encoding of images
-# 5. save html file
-# 6. upload html to public site and share with others
+# 2. generate html, including base64 encoding of images
+# 3. save html file
+# 4. upload html to public site and share with others
 
 # todo
 #
-- 
cgit v0.10.1
From 8a21092dc01dfe54550b8fceada35999bed3056b Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sat, 7 Dec 2013 17:59:07 -0800
Subject: Adding imports
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index 44df156..c2deab8 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -33,6 +33,13 @@ import hashlib
 import subprocess
 import time
 import platform
+try:
+    from urllib.request import urlopen
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib2 import urlopen
+    from urllib import urlencode
+    
 
 def tryread(filename):
     data = None
-- 
cgit v0.10.1
From b131464f954b2d10f6b065b45038652af2379742 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 8 Dec 2013 13:50:03 -0500
Subject: #559 CMAKE_OSX_DEPLOYMENT_TARGET needs to be cached
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b84775b..f92eddf 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -14,14 +14,14 @@ include(CMakeParseArguments.cmake)
 
 # Detect Lion and force gcc
 IF (APPLE)
-   # Somehow, since we build dependencies for 10.7, we need to also build executables
-   # for 10.7. This used to not be necessary, but since 10.9 it apparently is..
-   SET(CMAKE_OSX_DEPLOYMENT_TARGET 10.7)
    EXECUTE_PROCESS(COMMAND sw_vers -productVersion OUTPUT_VARIABLE MACOSX_VERSION)
    IF (NOT ${MACOSX_VERSION} VERSION_LESS "10.9.0")
      message("Detected Maverick (10.9) or later")
      set(CMAKE_C_COMPILER "clang")
      set(CMAKE_CXX_COMPILER "clang++")
+     # Somehow, since we build dependencies for 10.7, we need to also build executables
+     # for 10.7. This used to not be necessary, but since 10.9 it apparently is..
+     SET(CMAKE_OSX_DEPLOYMENT_TARGET 10.7 CACHE STRING "Deployment target")
    ELSEIF (NOT ${MACOSX_VERSION} VERSION_LESS "10.8.0")
      message("Detected Mountain Lion (10.8)")
      set(CMAKE_C_COMPILER "clang")
-- 
cgit v0.10.1
From cb2b094b269646c79f48f525306b74cd7bc0f633 Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 10:51:25 -0800
Subject: Fixed small error in Templates.get
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index c2deab8..349a730 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -304,10 +304,10 @@ class Templates(object):
 
     def add(self, template, var, *args, **kwargs):
         self.filled[var] = self.filled.get(var, '') + self.fill(template, *args, **kwargs)
-        return self.filled[var]
+        return self.get(var)
 
     def get(self, var):
-        return self.filled[var]
+        return self.filled.get(var, '')
 
 
 def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles):
-- 
cgit v0.10.1
From 8971c67fa209fe29ead54034cd0f9be39c0909ff Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 11:18:58 -0800
Subject: Fixed variable name error
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index 349a730..359165e 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -441,7 +441,7 @@ def main():
     html = to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles)
     html_basename = sysid + '_report.html'
     html_filename = os.path.join(builddir, 'Testing', 'Temporary', html_basename)
-    debug('saving ' + html_filename + ' ' + str(len(htmldata)) + ' bytes')
+    debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes')
     trysave(html_filename, html)
 
     page_url = create_page()
-- 
cgit v0.10.1
From a22ebd608d941b5ae1767a2903f4bacc925840ed Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 11:28:19 -0800
Subject: Moved include_passed
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index 359165e..c3035c5 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -395,6 +395,7 @@ def debug(x):
         print 'test_pretty_print: ' + x
 
 debug_test_pp = False
+include_passed = False
 builddir = os.getcwd()
 
 def main():
@@ -411,7 +412,6 @@ def main():
         debug_test_pp = True
     maxretry = 10
 
-    include_passed = False
     if '--include-passed' in sys.argv:
         include_passed = True
 
-- 
cgit v0.10.1
From e27f971d9c3dc26a9a023a2aea4c265dac53437b Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 12:15:04 -0800
Subject: Changed upload code to use gists
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index c3035c5..a88a22e 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -17,9 +17,11 @@
 # Design philosophy
 #
 # 1. parse the data (images, logs) into easy-to-use data structures
-# 2. generate html, including base64 encoding of images
-# 3. save html file
-# 4. upload html to public site and share with others
+# 2. wikifiy the data 
+# 3. save the wikified data to disk
+# 4. generate html, including base64 encoding of images
+# 5. save html file
+# 6. upload html to public site and share with others
 
 # todo
 #
@@ -34,12 +36,12 @@ import subprocess
 import time
 import platform
 try:
-    from urllib.request import urlopen
-    from urllib.parse import urlencode
-except ImportError:
-    from urllib2 import urlopen
-    from urllib import urlencode
-    
+    from urllib.request import urlopen, Request
+except:
+    from urllib2 import urlopen, Request
+import json
+import base64
+
 
 def tryread(filename):
     data = None
@@ -304,7 +306,7 @@ class Templates(object):
 
     def add(self, template, var, *args, **kwargs):
         self.filled[var] = self.filled.get(var, '') + self.fill(template, *args, **kwargs)
-        return self.get(var)
+        return self.filled[var]
 
     def get(self, var):
         return self.filled.get(var, '')
@@ -361,32 +363,46 @@ def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles):
 
 # --- Web Upload ---
 
-def postify(data):
-    return urlencode(data).encode()
-
-def create_page():
-    data = {
-        'action': 'create',
-        'type': 'html'
-    }
+API_URL = 'https://api.github.com/%s'
+# Username is personal access token, from https://github.com/settings/applications
+# This way, no password is needed
+USERNAME = 'b2af28787fb1efd9a5b3a3b4f1be8a3ac9b5b335'
+PASSWORD = ''
+
+def make_auth(username, password):
+    auth = '%s:%s' % (USERNAME, PASSWORD)
+    return base64.b64encode(auth.encode())
+
+def post_gist(name, content):
+    gist = '''{
+      "description": "",
+      "public": true,
+      "files": {
+        "%s": {
+          "content": "%s"
+        }
+      }
+    }'''
+    gist = gist % (name, content)
+    
+    req = Request(API_URL % 'gists')
+    req.add_header('Authorization', b'Basic ' + make_auth(USERNAME, PASSWORD))
     try:
-        response = urlopen('http://www.dinkypage.com', data=postify(data))
+        result = urlopen(req, data=gist)
     except:
+        print 'Could not upload results'
         return None
-    return response.geturl()
-
-def upload_html(page_url, title, html):
-    data = {
-        'mode': 'editor',
-        'title': title,
-        'html': html,
-        'ajax': '1'
-    }
-    try:
-        response = urlopen(page_url, data=postify(data))
-    except:
-        return False
-    return 'success' in response.read().decode()
+    return json.loads(result.read())
+
+
+def get_raw_urls(result):
+    files = result.get('files', {})
+    for file in files:
+        yield files[file].get('raw_url').replace('gist.github.com', 'rawgithub.com')
+
+result = post_gist('aaabbb.html', '''I\'m asdf
''')
+for url in get_raw_urls(result):
+    print(url)
 
 # --- End Web Upload ---
 
@@ -438,18 +454,20 @@ def main():
         print 'found', len(makefiles),'makefiles,',
         print 'found', len(tests),'test results'
 
+
     html = to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles)
     html_basename = sysid + '_report.html'
     html_filename = os.path.join(builddir, 'Testing', 'Temporary', html_basename)
     debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes')
     trysave(html_filename, html)
 
-    page_url = create_page()
-    if upload_html(page_url, title='OpenSCAD test results', html=html):
-        share_url = page_url.partition('?')[0]
-        print 'html report uploaded at', share_url
-    else:
+    result = post_gist(name=html_basename, content=html)
+    if result is None:
         print 'could not upload html report'
+        return
+    
+    for url in get_raw_urls(result):
+        print 'html report uploaded at', url
 
     debug('test_pretty_print complete')
 
-- 
cgit v0.10.1
From 88cc7edafd47ec167609c05eb22c3f8eb642d225 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 8 Dec 2013 15:16:58 -0500
Subject: #559 Fix Qt font rendering on OS X 10.9
diff --git a/patches/qt4/patch-qeventdispatcher.diff b/patches/qt4/patch-qeventdispatcher.diff
new file mode 100644
index 0000000..89ed478
--- /dev/null
+++ b/patches/qt4/patch-qeventdispatcher.diff
@@ -0,0 +1,86 @@
+--- src/gui/kernel/qeventdispatcher_mac_p.h	2013-06-07 01:16:59.000000000 -0400
++++ src/gui/kernel/qeventdispatcher_mac_p_new-8184b49c12d887928921ed5b695c8c6f04a07514.h	2013-12-08 14:31:01.000000000 -0500
+@@ -173,6 +173,7 @@
+ #ifdef QT_MAC_USE_COCOA
+     // The following variables help organizing modal sessions:
+     static QStack cocoaModalSessionStack;
++    static QStack cocoaModalSessionStackPendingEnd;
+     static bool currentExecIsNSAppRun;
+     static bool nsAppRunCalledByQt;
+     static bool cleanupModalSessionsNeeded;
+@@ -180,6 +181,7 @@
+     static NSModalSession currentModalSession();
+     static void updateChildrenWorksWhenModal();
+     static void temporarilyStopAllModalSessions();
++    static void stopAllPendingEndModalSessions();
+     static void beginModalSession(QWidget *widget);
+     static void endModalSession(QWidget *widget);
+     static void cancelWaitForMoreEvents();
+--- src/gui/kernel/qeventdispatcher_mac.mm	2013-06-07 01:16:59.000000000 -0400
++++ src/gui/kernel/qeventdispatcher_mac_new-833e02de99494686f8dd7a567f6e19e847508f11.mm	2013-12-08 14:30:59.000000000 -0500
+@@ -603,6 +603,9 @@
+                 while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt)
+                     qt_mac_waitForMoreModalSessionEvents();
+ 
++                // stop all pending end modal sessions
++                d->stopAllPendingEndModalSessions();
++
+                 if (!d->interrupt && session == d->currentModalSessionCached) {
+                     // Someone called [NSApp stopModal:] from outside the event
+                     // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
+@@ -678,6 +681,9 @@
+             if (!d->interrupt)
+                 QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
+ 
++            // stop all pending end modal sessions
++            d->stopAllPendingEndModalSessions();
++
+             // Since the window that holds modality might have changed while processing
+             // events, we we need to interrupt when we return back the previous process
+             // event recursion to ensure that we spin the correct modal session.
+@@ -781,6 +787,7 @@
+ 
+ #ifdef QT_MAC_USE_COCOA
+ QStack QEventDispatcherMacPrivate::cocoaModalSessionStack;
++QStack QEventDispatcherMacPrivate::cocoaModalSessionStackPendingEnd;
+ bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false;
+ bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false;
+ bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false;
+@@ -828,6 +835,20 @@
+     currentModalSessionCached = 0;
+ }
+ 
++void QEventDispatcherMacPrivate::stopAllPendingEndModalSessions()
++{
++    // stop all modal sessions pending end
++    int stackSize = cocoaModalSessionStackPendingEnd.size();
++    for (int i=stackSize-1; i>=0; --i) {
++        QCocoaModalSessionInfo &info = cocoaModalSessionStackPendingEnd[i];
++        cocoaModalSessionStackPendingEnd.remove(i);
++        if (info.session) {
++            [NSApp endModalSession:info.session];
++            [(NSWindow *)info.nswindow release];
++        }
++    }
++}
++
+ NSModalSession QEventDispatcherMacPrivate::currentModalSession()
+ {
+     // If we have one or more modal windows, this function will create
+@@ -925,10 +946,12 @@
+         }
+         cocoaModalSessionStack.remove(i);
+         currentModalSessionCached = 0;
+-        if (info.session) {
+-            [NSApp endModalSession:info.session];
+-            [(NSWindow *)info.nswindow release];
+-        }
++
++        // Cannot stop the sessions here since we might still be inside a
++        // [NSApp runModalSession:] call. Add the session to the pending end stack and
++        // process the stack after the call to [NSApp runModalSession:] returns.
++        if (info.session)
++            cocoaModalSessionStackPendingEnd.push(info);
+     }
+ 
+     updateChildrenWorksWhenModal();
diff --git a/patches/qt4/patch-qfontdatabase.diff b/patches/qt4/patch-qfontdatabase.diff
new file mode 100644
index 0000000..c078890
--- /dev/null
+++ b/patches/qt4/patch-qfontdatabase.diff
@@ -0,0 +1,29 @@
+--- src/gui/text/qfontdatabase.cpp	2013-06-07 01:16:59.000000000 -0400
++++ src/gui/text/qfontdatabase_new-bb2beddc3ae55c4676d190d0ac99aa32d322a6a5.cpp	2013-12-08 14:51:10.000000000 -0500
+@@ -441,6 +441,7 @@
+ #endif
+ #if !defined(QWS) && defined(Q_OS_MAC)
+     bool fixedPitchComputed : 1;
++    QString postscriptName;
+ #endif
+ #ifdef Q_WS_X11
+     bool symbol_checked : 1;
+--- src/gui/text/qfontdatabase_mac.cpp	2013-06-07 01:16:59.000000000 -0400
++++ src/gui/text/qfontdatabase_mac_new-41f29865db84152efb41c048470f713353a0a84c.cpp	2013-12-08 14:51:05.000000000 -0500
+@@ -147,6 +147,7 @@
+         QCFString family_name = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontFamilyNameAttribute, NULL);
+         QCFString style_name = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontStyleNameAttribute, NULL);
+         QtFontFamily *family = db->family(family_name, true);
++        family->postscriptName = QCFString((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontNameAttribute));
+ 
+         if (QCFType languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
+             CFIndex length = CFArrayGetCount(languages);
+@@ -327,7 +328,7 @@
+             if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) {
+                 QByteArray family_name = db->families[k]->name.toUtf8();
+ #if defined(QT_MAC_USE_COCOA)
+-                QCFType ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL);
++                QCFType ctFont = CTFontCreateWithName(QCFString(db->families[k]->postscriptName), 12, NULL);
+                 if (ctFont) {
+                     fontName = CTFontCopyFullName(ctFont);
+                     goto found;
-- 
cgit v0.10.1
From eb046015d2fd4fa3e4e3c8844cd6dc8f4d3eca99 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 8 Dec 2013 15:17:17 -0500
Subject: #559 Fix Qt font rendering on OS X 10.9
diff --git a/src/openscad.cc b/src/openscad.cc
index ece6818..ab84235 100644
--- a/src/openscad.cc
+++ b/src/openscad.cc
@@ -474,6 +474,13 @@ bool QtUseGUI()
 
 int gui(vector &inputFiles, const fs::path &original_path, int argc, char ** argv)
 {
+#ifdef Q_OS_MACX
+    if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) {
+			// fix Mac OS X 10.9 (mavericks) font issue
+			// https://bugreports.qt-project.org/browse/QTBUG-32789
+			QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
+    }
+#endif
 	QApplication app(argc, argv, true); //useGUI);
 #ifdef Q_WS_MAC
 	app.installEventFilter(new EventFilter(&app));
-- 
cgit v0.10.1
From d6bffc4691cf1467cf93d527724f7278b418273d Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 12:17:19 -0800
Subject: Taking out test accidentially left in, my user token
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index a88a22e..b601c84 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -366,7 +366,7 @@ def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles):
 API_URL = 'https://api.github.com/%s'
 # Username is personal access token, from https://github.com/settings/applications
 # This way, no password is needed
-USERNAME = 'b2af28787fb1efd9a5b3a3b4f1be8a3ac9b5b335'
+USERNAME = '' # add OpenScad user token
 PASSWORD = ''
 
 def make_auth(username, password):
@@ -400,9 +400,6 @@ def get_raw_urls(result):
     for file in files:
         yield files[file].get('raw_url').replace('gist.github.com', 'rawgithub.com')
 
-result = post_gist('aaabbb.html', '''I\'m asdf
''')
-for url in get_raw_urls(result):
-    print(url)
 
 # --- End Web Upload ---
 
-- 
cgit v0.10.1
From 7075d8d9c4dde62798022bdfe02c5d57e997ba43 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 5 Dec 2013 15:56:50 +1100
Subject: Fix for bad boost libraries
Get this error because of a search for a non-existent library on linux64
-----------
[ 69%] Built target tests-cgal
Scanning dependencies of target cgalcachetest
[ 70%] Building CXX object
CMakeFiles/cgalcachetest.dir/cgalcachetest.cc.o
make[2]: *** No rule to make target `/usr/lib/libboost_thread.so',
needed by `cgalcachetest'.  Stop.
make[1]: *** [CMakeFiles/cgalcachetest.dir/all] Error 2
make: *** [all] Error 2
[2]+  Done                    gedit openscad.pro  (wd:
~/git/openscad_unicode)
----------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f92eddf..d7ad18a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -384,11 +384,45 @@ if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6)
 endif()
 inclusion(CGAL_DIR CGAL_INCLUDE_DIRS)
 
+#Get rid of bad libraries suggested for BOOST dependencies (they don't exist on some machines and cause build failures).
+#/usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_system.so" FIND_POSITION  )
+if(NOT "-1" STREQUAL ${FIND_POSITION} )
+if(NOT EXISTS "/usr/lib/libboost_system.so")
+  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_system.so -- stripping" )
+  string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+endif()
+endif() 
+string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_thread.so" FIND_POSITION  )
+if(NOT "-1" STREQUAL ${FIND_POSITION} )
+if(NOT EXISTS "/usr/lib/libboost_thread.so")
+  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_thread.so -- stripping" )
+  string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+endif()
+endif() 
 if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" )
 	string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 	string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 endif()
 
+if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
+  # Force pkg-config to look _only_ in the local library folder
+  # in case OPENSCAD_LIBRARIES is set.
+  set(ENV{PKG_CONFIG_PATH} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig")
+  set(ENV{PKG_CONFIG_LIBDIR} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig")
+endif()
+
+# Find libraries (system installed or dependency built) using pkg-config
+find_package(PkgConfig REQUIRED)
+
+#GLib-2
+pkg_search_module(GLIB2 REQUIRED glib-2.0>=2.2.0)
+#Can't use the CXXFlags directly as they are ;-separated
+string(REPLACE ";" " " GLIB2_CFLAGS "${GLIB2_CFLAGS}")
+message(STATUS "glib-2.0 found: ${GLIB2_VERSION}")
+
+add_definitions(${GLIB2_CFLAGS})
+
 # Imagemagick
 
 if (SKIP_IMAGEMAGICK)
-- 
cgit v0.10.1
From 3abf64249fd667f0b7f558ecfbbf35cfe9916a5d Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 5 Dec 2013 17:56:54 +1100
Subject: Unicode support for strings
Add suport for using unicode strings in .scad files. Support iterating
across them/accessing them via [] and searching.
--------
Add GLIB (to build for test and normal build -- both with installed and
built locally development files).
Add support for unicode chars to length and search builtin functions and
[] for strings.
Added unicode testing functions.
Ad GLIB to library info page.
diff --git a/.gitignore b/.gitignore
index 50dace1..59bac49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 /*.scad
 *.dmg
+*~
 *.tar*
 Makefile
 objects
diff --git a/README.md b/README.md
index 27f12ce..1e97e0f 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ Follow the instructions for the platform you're compiling on below.
 * [OpenCSG (1.3.2)](http://www.opencsg.org/)
 * [GLEW (1.5.4 ->)](http://glew.sourceforge.net/)
 * [Eigen (3.0 - 3.2)](http://eigen.tuxfamily.org/)
+* [glib2 (2.2.0)](https://developer.gnome.org/glib/)
 * [GCC C++ Compiler (4.2 ->)](http://gcc.gnu.org/)
 * [Bison (2.4)](http://www.gnu.org/software/bison/)
 * [Flex (2.5.35)](http://flex.sourceforge.net/)
diff --git a/common.pri b/common.pri
index 7153ded..696c8b1 100644
--- a/common.pri
+++ b/common.pri
@@ -11,3 +11,4 @@ include(opencsg.pri)
 include(glew.pri)
 include(eigen.pri)
 include(boost.pri)
+include(glib-2.0.pri)
\ No newline at end of file
diff --git a/glib-2.0.pri b/glib-2.0.pri
new file mode 100644
index 0000000..0fbc4e2
--- /dev/null
+++ b/glib-2.0.pri
@@ -0,0 +1,38 @@
+# Detect glib-2.0, then use this priority list to determine
+# which library to use:
+#
+# Priority
+# 1. GLIB2_INCLUDEPATH / GLIB2_LIBPATH (qmake parameter, not checked it given on commandline)
+# 2. OPENSCAD_LIBRARIES (environment variable)
+# 3. system's standard include paths from pkg-config
+
+glib-2.0 {
+
+# read environment variables
+OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
+GLIB2_DIR = $$(GLIB2DIR)
+
+!isEmpty(OPENSCAD_LIBRARIES_DIR) {
+  isEmpty(GLIB2_INCLUDEPATH) {
+    GLIB2_INCLUDEPATH_1 = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
+    GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include
+    GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
+  }
+}
+
+isEmpty(GLIB2_INCLUDEPATH) {
+  GLIB2_CFLAGS = $$system("pkg-config --cflags glib-2.0")
+} else {
+  GLIB2_CFLAGS = -I$$GLIB2_INCLUDEPATH_1
+  GLIB2_CFLAGS += -I$$GLIB2_INCLUDEPATH_2
+}
+
+isEmpty(GLIB2_LIBPATH) {
+  GLIB2_LIBS = $$system("pkg-config --libs glib-2.0")
+} else {
+  GLIB2_LIBS = -L$$GLIB2_LIBPATH -lglib-2.0
+}
+
+QMAKE_CXXFLAGS += $$GLIB2_CFLAGS
+LIBS += $$GLIB2_LIBS
+}
diff --git a/openscad.pro b/openscad.pro
index b38419e..ec5af20 100644
--- a/openscad.pro
+++ b/openscad.pro
@@ -8,7 +8,7 @@
 #   OPENCSGDIR
 #   OPENSCAD_LIBRARIES
 #
-# Please see the 'Buildling' sections of the OpenSCAD user manual 
+# Please see the 'Building' sections of the OpenSCAD user manual 
 # for updated tips & workarounds.
 #
 # http://en.wikibooks.org/wiki/OpenSCAD_User_Manual
@@ -156,6 +156,7 @@ CONFIG += cgal
 CONFIG += opencsg
 CONFIG += boost
 CONFIG += eigen
+CONFIG += glib-2.0
 
 #Uncomment the following line to enable QCodeEdit
 #CONFIG += qcodeedit
diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh
index b63c677..e587198 100755
--- a/scripts/check-dependencies.sh
+++ b/scripts/check-dependencies.sh
@@ -66,6 +66,21 @@ cgal_sysver()
   cgal_sysver_result=`grep "define  *CGAL_VERSION  *[0-9.]*" $cgalpath | awk '{print $3}'`
 }
 
+glib2_sysver()
+{
+  #Get architecture triplet - e.g. x86_64-linux-gnu
+  glib2archtriplet=`gcc -dumpmachine 2>/dev/null`
+  if [ -z "$VAR" ]; then
+    glib2archtriplet=`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`
+  fi
+  glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h
+  if [ ! -e $glib2path ]; then return; fi
+  glib2major=`grep "define  *GLIB_MAJOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2minor=`grep "define  *GLIB_MINOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2micro=`grep "define  *GLIB_MICRO_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
+  glib2_sysver_result="${glib2major}.${glib2minor}.${glib2micro}"
+}
+
 boost_sysver()
 {
   boostpath=$1/include/boost/version.hpp
@@ -530,7 +545,7 @@ checkargs()
 
 main()
 {
-  deps="qt4 cgal gmp mpfr boost opencsg glew eigen gcc bison flex make"
+  deps="qt4 cgal gmp mpfr boost opencsg glew eigen glib2 gcc bison flex make"
   #deps="$deps curl git" # not technically necessary for build
   #deps="$deps python cmake imagemagick" # only needed for tests
   #deps="cgal"
diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh
index e652c47..8d912c3 100755
--- a/scripts/uni-build-dependencies.sh
+++ b/scripts/uni-build-dependencies.sh
@@ -603,5 +603,6 @@ build_boost 1.53.0
 build_cgal 4.0.2
 build_glew 1.9.0
 build_opencsg 1.3.2
+build_glib2 2.38.2
 
 echo "OpenSCAD dependencies built and installed to " $BASEDIR
diff --git a/scripts/uni-get-dependencies.sh b/scripts/uni-get-dependencies.sh
index a0306ef..d2408c0 100755
--- a/scripts/uni-get-dependencies.sh
+++ b/scripts/uni-get-dependencies.sh
@@ -8,7 +8,7 @@ get_fedora_deps()
 {
  sudo yum install qt-devel bison flex eigen3-devel python-paramiko \
   boost-devel mpfr-devel gmp-devel glew-devel CGAL-devel gcc gcc-c++ pkgconfig \
-  opencsg-devel git libXmu-devel curl imagemagick ImageMagick make \
+  opencsg-devel git libXmu-devel curl imagemagick ImageMagick glib2-devel make \
   xorg-x11-server-Xvfb
 }
 
@@ -21,7 +21,7 @@ get_altlinux_deps()
 {
  for i in boost-devel boost-filesystem-devel gcc4.5 gcc4.5-c++ boost-program_options-devel \
   boost-thread-devel boost-system-devel boost-regex-devel eigen3 libmpfr libgmp libgmp_cxx-devel qt4-devel libcgal-devel git-core \
-  libglew-devel flex bison curl imagemagick; do sudo apt-get install $i; done
+  libglew-devel flex bison curl imagemagick glib2-devel; do sudo apt-get install $i; done
 }
 
 get_freebsd_deps()
@@ -29,20 +29,21 @@ get_freebsd_deps()
  pkg_add -r bison boost-libs cmake git bash eigen3 flex gmake gmp mpfr \
   xorg libGLU libXmu libXi xorg-vfbserver glew \
   qt4-corelib qt4-gui qt4-moc qt4-opengl qt4-qmake qt4-rcc qt4-uic \
-  opencsg cgal curl imagemagick
+  opencsg cgal curl imagemagick glib2-devel
 }
 
 get_netbsd_deps()
 {
  sudo pkgin install bison boost cmake git bash eigen flex gmake gmp mpfr \
   qt4 glew cgal opencsg modular-xorg python27 py27-paramiko curl \
-  imagemagick ImageMagick
+  imagemagick ImageMagick glib2-devel
 }
 
 get_opensuse_deps()
 {
  sudo zypper install libeigen3-devel mpfr-devel gmp-devel boost-devel \
-  libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl
+  libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl \
+  glib2-devel
 }
 
 get_mageia_deps()
@@ -50,7 +51,7 @@ get_mageia_deps()
  sudo urpmi ctags
  sudo urpmi task-c-devel task-c++-devel libqt4-devel libgmp-devel \
   libmpfr-devel libboost-devel eigen3-devel libglew-devel bison flex \
-  cmake imagemagick python curl git x11-server-xvfb
+  cmake imagemagick glib2-devel python curl git x11-server-xvfb
 }
 
 get_debian_deps()
@@ -59,7 +60,7 @@ get_debian_deps()
   libxmu-dev cmake bison flex git-core libboost-all-dev \
   libXi-dev libmpfr-dev libboost-dev libglew-dev \
   libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev \
-  python-paramiko curl imagemagick; do
+  python-paramiko curl imagemagick libglib2.0-dev; do
    sudo apt-get -y install $pkg;
  done
 }
diff --git a/src/AboutDialog.html b/src/AboutDialog.html
index 99e7c3b..65a54d7 100644
--- a/src/AboutDialog.html
+++ b/src/AboutDialog.html
@@ -64,6 +64,7 @@ Please visit this link for a copy of the license: C++, GCC, clang
 python
 Nullsoft installer
+GLib
 
 
 
diff --git a/src/PlatformUtils.cc b/src/PlatformUtils.cc
index b02b822..8b39f6d 100644
--- a/src/PlatformUtils.cc
+++ b/src/PlatformUtils.cc
@@ -1,6 +1,8 @@
 #include "PlatformUtils.h"
 #include "boosty.h"
 
+#include 
+
 bool PlatformUtils::createLibraryPath()
 {
 	std::string path = PlatformUtils::libraryPath();
@@ -114,6 +116,7 @@ std::string PlatformUtils::info()
 	  << "\nOpenCSG version: " << OPENCSG_VERSION_STRING
 	  << "\nQt version: " << qtVersion
 	  << "\nMingW build: " << mingwstatus
+	  << "\nGLib version: "       << GLIB_MAJOR_VERSION << "." << GLIB_MINOR_VERSION << "." << GLIB_MICRO_VERSION
 	  << "\nOPENSCADPATH: " << getenv("OPENSCADPATH") << "\n"
 	;
 	return s.str();
diff --git a/src/func.cc b/src/func.cc
index 865a2b4..4587f72 100644
--- a/src/func.cc
+++ b/src/func.cc
@@ -45,6 +45,8 @@
 
 #include 
 #include 
+/*Unicode support for string lengths and array accesses*/
+#include 
 
 #ifdef __WIN32__
 #include 
@@ -306,7 +308,11 @@ Value builtin_length(const Context *, const EvalContext *evalctx)
 {
 	if (evalctx->numArgs() == 1) {
 		if (evalctx->getArgValue(0).type() == Value::VECTOR) return Value(int(evalctx->getArgValue(0).toVector().size()));
-		if (evalctx->getArgValue(0).type() == Value::STRING) return Value(int(evalctx->getArgValue(0).toString().size()));
+		if (evalctx->getArgValue(0).type() == Value::STRING) {
+			//Unicode glyph count for the length -- rather than the string (num. of bytes) length.
+			std::string text = evalctx->getArgValue(0).toString();
+			return Value(int( g_utf8_strlen( text.c_str(), text.size() ) ));
+		}
 	}
 	return Value();
 }
@@ -380,10 +386,17 @@ Value builtin_lookup(const Context *, const EvalContext *evalctx)
   num_returns_per_match : int;
   index_col_num : int;
 
+ The search string and searched strings can be unicode strings.
  Examples:
   Index values return as list:
     search("a","abcdabcd");
-        - returns [0,4]
+        - returns [0]
+    search("Π","Π");  //A unicode string
+        - returns [0]
+    search("π‘aΠ","aπ‘Ππ‘aπ‘Ππ‘a",0);
+        - returns [[1,3,5,7],[0,4,8],[2,6]]
+    search("a","abcdabcd",0); //Search up to all matches
+        - returns [[0,4]]
     search("a","abcdabcd",1);
         - returns [0]
     search("e","abcdabcd",1);
@@ -433,16 +446,25 @@ Value builtin_search(const Context *, const EvalContext *evalctx)
 		}
 	} else if (findThis.type() == Value::STRING) {
 		unsigned int searchTableSize;
-		if (searchTable.type() == Value::STRING) searchTableSize = searchTable.toString().size();
-		else searchTableSize = searchTable.toVector().size();
-		for (size_t i = 0; i < findThis.toString().size(); i++) {
+		//Unicode glyph count for the length
+		unsigned int findThisSize =  g_utf8_strlen( findThis.toString().c_str(), findThis.toString().size() );
+		if (searchTable.type() == Value::STRING) {
+			searchTableSize = g_utf8_strlen( searchTable.toString().c_str(), searchTable.toString().size() );
+		} else {
+		    searchTableSize = searchTable.toVector().size();
+		}
+		for (size_t i = 0; i < findThisSize; i++) {
 		  unsigned int matchCount = 0;
 			Value::VectorType resultvec;
 		  for (size_t j = 0; j < searchTableSize; j++) {
-		    if ((searchTable.type() == Value::VECTOR && 
-						 findThis.toString()[i] == searchTable.toVector()[j].toVector()[index_col_num].toString()[0]) ||
-						(searchTable.type() == Value::STRING && 
-						 findThis.toString()[i] == searchTable.toString()[j])) {
+		    gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i);
+		    gchar* ptr_st = NULL;
+		    if(searchTable.type() == Value::VECTOR) {
+		        ptr_st = g_utf8_offset_to_pointer(searchTable.toVector()[j].toVector()[index_col_num].toString().c_str(), 0);
+		    } else if(searchTable.type() == Value::STRING){
+		    	ptr_st = g_utf8_offset_to_pointer(searchTable.toString().c_str(), j);
+		    }
+		    if( (ptr_ft) && (ptr_st) && (g_utf8_get_char(ptr_ft) == g_utf8_get_char(ptr_st)) ) {
 		      Value resultValue((double(j)));
 		      matchCount++;
 		      if (num_returns_per_match == 1) {
@@ -454,7 +476,14 @@ Value builtin_search(const Context *, const EvalContext *evalctx)
 		      if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break;
 		    }
 		  }
-		  if (matchCount == 0) PRINTB("  WARNING: search term not found: \"%s\"", findThis.toString()[i]);
+		  if (matchCount == 0) {
+			  gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i);
+			  gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
+			  if(ptr_ft) {
+			      g_utf8_strncpy( utf8_of_cp, ptr_ft, 1 );
+		      }
+			  PRINTB("  WARNING: search term not found: \"%s\"", utf8_of_cp );
+		  }
 		  if (num_returns_per_match == 0 || num_returns_per_match > 1) {
 				returnvec.push_back(Value(resultvec));
 			}
diff --git a/src/value.cc b/src/value.cc
index 5afb650..c8a88c6 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -36,6 +36,8 @@
 #include 
 #include "boost-utils.h"
 #include "boosty.h"
+/*Unicode support for string lengths and array accesses*/
+#include 
 
 std::ostream &operator<<(std::ostream &stream, const Filename &filename)
 {
@@ -579,14 +581,28 @@ Value Value::operator-() const
   }
 */
 
+/*
+ * bracket operation [] detecting multi-byte unicode.
+ * If the string is multi-byte unicode then the index will offset to the character (2 or 4 byte) and not to the byte.
+ * A 'normal' string with byte chars are a subset of unicode and still work.
+ */
 class bracket_visitor : public boost::static_visitor
 {
 public:
   Value operator()(const std::string &str, const double &idx) const {
     int i = int(idx);
     Value v;
+    //Check that the index is positive and less than the size in bytes
     if ((i >= 0) && (i < (int)str.size())) {
-      v = Value(str[int(idx)]);
+	  //Ensure character (not byte) index is inside the character/glyph array
+	  if( (unsigned) i < g_utf8_strlen( str.c_str(), str.size() ) )	{
+		  gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
+		  gchar* ptr = g_utf8_offset_to_pointer(str.c_str(), i);
+		  if(ptr) {
+		    g_utf8_strncpy(utf8_of_cp, ptr, 1);
+		  }
+		  v = std::string(utf8_of_cp);
+	  }
       //      std::cout << "bracket_visitor: " <<  v << "\n";
     }
     return v;
diff --git a/testdata/scad/misc/search-tests-unicode.scad b/testdata/scad/misc/search-tests-unicode.scad
new file mode 100644
index 0000000..d863eff
--- /dev/null
+++ b/testdata/scad/misc/search-tests-unicode.scad
@@ -0,0 +1,116 @@
+//Test search with unicode strings
+
+//Helper function that pretty prints our search test
+//Expected result is checked against execution of a search() invocation and OK/FAIL is indicated
+module test_search_and_echo( exp_res, search_to_find, search_to_search, search_up_to_num_matches = undef)
+{
+   if(undef != search_up_to_num_matches)
+   {
+      assign( test_res = search(search_to_find, search_to_search, search_up_to_num_matches) )
+      echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ", ", search_up_to_num_matches, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL"  ));
+   }
+   else
+   {
+      assign( test_res = search(search_to_find, search_to_search) )
+      echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL"  ));
+   }
+}
+
+
+//"Normal" text for comparison
+echo ("----- Lookup of 1 byte into 1 byte");
+//Hits - up_to_count 1
+test_search_and_echo( [0],   "a","aaaa" );
+test_search_and_echo( [0],   "a","aaaa",1 );
+test_search_and_echo( [0,0], "aa","aaaa" );
+test_search_and_echo( [0,0], "aa","aaaa",1 );
+
+
+//Hits - up to count 1+ (incl 0 == all)
+test_search_and_echo( [[0,1,2,3]] , 	"a","aaaa",0 );
+test_search_and_echo( [[0,1]], 			"a","aaaa",2 );
+test_search_and_echo( [[0,1,2]], 		"a","aaaa",3 );
+test_search_and_echo( [[0,1,2,3]] , 	"a","aaaa",4 );
+test_search_and_echo( [[0,1,2,3],[0,1,2,3]] , "aa","aaaa",0 );
+//Misses
+test_search_and_echo( [],		"b","aaaa" );
+test_search_and_echo( [],		"b","aaaa",1 );
+test_search_and_echo( [[]],		"b","aaaa",0 );
+test_search_and_echo( [[]],		"b","aaaa",2 );
+
+test_search_and_echo( [],			"bb","aaaa" );
+test_search_and_echo( [],			"bb","aaaa",1 );
+test_search_and_echo( [[],[]],		"bb","aaaa",0 );
+test_search_and_echo( [[],[]],		"bb","aaaa",2 );
+//Miss - empties
+test_search_and_echo( [], "","aaaa" );
+test_search_and_echo( [], "","" );
+test_search_and_echo( [], "a","" );
+
+
+//Unicode tests
+echo ("----- Lookup of multi-byte into 1 byte");
+test_search_and_echo( [],		"Π","aaaa" );
+test_search_and_echo( [],		"π‘","aaaa" );
+test_search_and_echo( [[]],		"Π","aaaa",0 );
+test_search_and_echo( [[]],		"π‘","aaaa",0 );
+
+test_search_and_echo( [],		"ΠΠ","aaaa" );
+test_search_and_echo( [],		"π‘π‘","aaaa" );
+test_search_and_echo( [[],[]],		"ΠΠ","aaaa",0 );
+test_search_and_echo( [[],[]],		"π‘π‘","aaaa",0 );
+
+echo ("----- Lookup of 1-byte into multi-byte");
+test_search_and_echo( [] , "a","ΠΠΠΠ" );
+test_search_and_echo( [] , "a","π‘π‘π‘π‘" );
+test_search_and_echo( [] , "a","ΠΠΠΠ",1 );
+
+test_search_and_echo( [[]] , "a","π‘π‘π‘π‘",0 );
+test_search_and_echo( [[]] , "a","π‘π‘π‘π‘",2 );
+
+echo ("----- Lookup of 1-byte into mixed multi-byte");
+test_search_and_echo( [0], "a","aΠaΠaΠaΠa" );
+test_search_and_echo( [0], "a","aπ‘aπ‘aπ‘aπ‘a" );
+test_search_and_echo( [0], "a","aπ‘Ππ‘aπ‘Ππ‘a" );
+
+test_search_and_echo( [[0,2,4,6,8]], "a","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[0,2,4,6,8]], "a","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[0,4,8]]    , "a","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of 2-byte into 2-byte");
+test_search_and_echo( [0]       , "Π","ΠΠΠΠ" );
+test_search_and_echo( [[0,1,2,3]] , "Π","ΠΠΠΠ",0 );
+
+echo ("----- Lookup of 2-byte into 4-byte");
+test_search_and_echo( [] , "Π","π‘π‘π‘π‘" );
+
+echo ("----- Lookup of 4-byte into 4-byte");
+test_search_and_echo( [0] , 		  "π‘","π‘π‘π‘π‘" );
+test_search_and_echo( [[0,1,2,3]], "π‘","π‘π‘π‘π‘",0 );
+
+echo ("----- Lookup of 4-byte into 2-byte");
+test_search_and_echo( [] , "π‘","ΠΠΠΠ" );
+
+echo ("----- Lookup of 2-byte into mixed multi-byte");
+test_search_and_echo( [1] , 	"Π","aΠaΠaΠaΠa",1 );
+test_search_and_echo( [] , 	"Π","aπ‘aπ‘aπ‘aπ‘a", 1 );
+test_search_and_echo( [2] , 	"Π","aπ‘Ππ‘aπ‘Ππ‘a", 1 );
+
+test_search_and_echo( [[1,3,5,7]] , 	"Π","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[]] , 				"Π","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[2,6]] , 			"Π","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of 4-byte into mixed multi-byte");
+test_search_and_echo( [] , 			"π‘","aΠaΠaΠaΠa",1 );
+test_search_and_echo( [1] , "π‘","aπ‘aπ‘aπ‘aπ‘a", 1 );
+
+test_search_and_echo( [[]] , 			"π‘","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[1,3,5,7]] , "π‘","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[1,3,5,7]] , "π‘","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
+echo ("----- Lookup of mixed multi-byte into mixed multi-byte");
+test_search_and_echo( [[0,2,4,6,8],[1,3,5,7],[]], "aΠπ‘","aΠaΠaΠaΠa",0 );
+test_search_and_echo( [[0,2,4,6,8],[],[1,3,5,7]], "aΠπ‘","aπ‘aπ‘aπ‘aπ‘a", 0 );
+test_search_and_echo( [[0,4,8],[2,6],[1,3,5,7]]    , "aΠπ‘","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+test_search_and_echo( [[1,3,5,7],[0,4,8],[2,6]]    , "π‘aΠ","aπ‘Ππ‘aπ‘Ππ‘a", 0 );
+
diff --git a/testdata/scad/misc/string-unicode.scad b/testdata/scad/misc/string-unicode.scad
new file mode 100644
index 0000000..d8e3e5c
--- /dev/null
+++ b/testdata/scad/misc/string-unicode.scad
@@ -0,0 +1,36 @@
+//Test how well arrays of unicode string are accessed.
+
+texts_array = [
+"DEADBEEF",
+"ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ",
+"ΩΨ³ΩΩ Ψ§ΩΨ²ΩΨ¬Ψ¨ΩΩ Ψ§ΩΩΨ·",
+"ζΆζ°ηε§θ²",
+"Àâü ΓΓΓ Γ",
+"πππππ
πππππππππππ",
+"β β β β β 
β β β β β β β β β β ",
+"π‘π±ππ",
+];
+
+text_2bytes = "ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ";
+text_4bytes = "π‘π±ππ";
+
+
+//Test all the normal accesses
+for (text_array_idx = [0:(len(texts_array)-1)])
+{
+	echo( "[", text_array_idx, "] = ", texts_array[text_array_idx], " of len=", len(texts_array[text_array_idx]), ":"  );
+    for (text_idx = [0:(len(texts_array[text_array_idx])-1)])
+    {
+	    echo( "    [", text_idx, ,"]=", texts_array[text_array_idx][text_idx]  );
+    }
+}
+
+//Test one past the last element of (x-byte unicode). This will be one past the length but inside the char length of the string
+echo( "Past end of unicode only 2-byte ", text_2bytes[len(text_2bytes)]  );
+echo( "Past end of unicode only 4-byte ", text_4bytes[len(text_4bytes)]  );
+
+//Test past the last element of (x-byte unicode). Outside both lengths.
+echo( "Past end of both 2-byte ", text_2bytes[ len(text_2bytes) * 2 ]   );
+echo( "Past end of both 4-byte ", text_4bytes[ len(text_4bytes) * 4 ]   );
+
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d7ad18a..3c19b77 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -594,8 +594,8 @@ set(OFFSCREEN_SOURCES
   ../src/OpenCSGRenderer.cc)
 
 add_library(tests-core STATIC ${CORE_SOURCES})
-target_link_libraries(tests-core ${OPENGL_LIBRARIES})
-set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${Boost_LIBRARIES})
+target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} )
+set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${Boost_LIBRARIES} )
 
 add_library(tests-common STATIC ${COMMON_SOURCES})
 target_link_libraries(tests-common tests-core)
@@ -815,8 +815,10 @@ list(APPEND ECHO_FILES ${FUNCTION_FILES}
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/dim-all.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-test.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-indexing.scad
+            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-unicode.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/vector-values.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests.scad
+            ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests-unicode.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/recursion-tests.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests.scad
             ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests2.scad
diff --git a/tests/regression/echotest/search-tests-unicode-expected.echo b/tests/regression/echotest/search-tests-unicode-expected.echo
new file mode 100644
index 0000000..801bc8c
--- /dev/null
+++ b/tests/regression/echotest/search-tests-unicode-expected.echo
@@ -0,0 +1,109 @@
+ECHO: "----- Lookup of 1 byte into 1 byte"
+ECHO: "Expect [0] for search(a, aaaa)=[0]. OK"
+ECHO: "Expect [0] for search(a, aaaa, 1)=[0]. OK"
+ECHO: "Expect [0, 0] for search(aa, aaaa)=[0, 0]. OK"
+ECHO: "Expect [0, 0] for search(aa, aaaa, 1)=[0, 0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "Expect [[0, 1]] for search(a, aaaa, 2)=[[0, 1]]. OK"
+ECHO: "Expect [[0, 1, 2]] for search(a, aaaa, 3)=[[0, 1, 2]]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 4)=[[0, 1, 2, 3]]. OK"
+ECHO: "Expect [[0, 1, 2, 3], [0, 1, 2, 3]] for search(aa, aaaa, 0)=[[0, 1, 2, 3], [0, 1, 2, 3]]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(b, aaaa)=[]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(b, aaaa, 1)=[]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[]] for search(b, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[]] for search(b, aaaa, 2)=[[]]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(bb, aaaa)=[]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [] for search(bb, aaaa, 1)=[]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[], []] for search(bb, aaaa, 0)=[[], []]. OK"
+  WARNING: search term not found: "b"
+  WARNING: search term not found: "b"
+ECHO: "Expect [[], []] for search(bb, aaaa, 2)=[[], []]. OK"
+ECHO: "Expect [] for search(, aaaa)=[]. OK"
+ECHO: "Expect [] for search(, )=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, )=[]. OK"
+ECHO: "----- Lookup of multi-byte into 1 byte"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, aaaa)=[]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, aaaa)=[]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[]] for search(Π, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[]] for search(π‘, aaaa, 0)=[[]]. OK"
+  WARNING: search term not found: "Π"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(ΠΠ, aaaa)=[]. OK"
+  WARNING: search term not found: "π‘"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘π‘, aaaa)=[]. OK"
+  WARNING: search term not found: "Π"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[], []] for search(ΠΠ, aaaa, 0)=[[], []]. OK"
+  WARNING: search term not found: "π‘"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[], []] for search(π‘π‘, aaaa, 0)=[[], []]. OK"
+ECHO: "----- Lookup of 1-byte into multi-byte"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, ΠΠΠΠ)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, π‘π‘π‘π‘)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [] for search(a, ΠΠΠΠ, 1)=[]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [[]] for search(a, π‘π‘π‘π‘, 0)=[[]]. OK"
+  WARNING: search term not found: "a"
+ECHO: "Expect [[]] for search(a, π‘π‘π‘π‘, 2)=[[]]. OK"
+ECHO: "----- Lookup of 1-byte into mixed multi-byte"
+ECHO: "Expect [0] for search(a, aΠaΠaΠaΠa)=[0]. OK"
+ECHO: "Expect [0] for search(a, aπ‘aπ‘aπ‘aπ‘a)=[0]. OK"
+ECHO: "Expect [0] for search(a, aπ‘Ππ‘aπ‘Ππ‘a)=[0]. OK"
+ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aΠaΠaΠaΠa, 0)=[[0, 2, 4, 6, 8]]. OK"
+ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aπ‘aπ‘aπ‘aπ‘a, 0)=[[0, 2, 4, 6, 8]]. OK"
+ECHO: "Expect [[0, 4, 8]] for search(a, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[0, 4, 8]]. OK"
+ECHO: "----- Lookup of 2-byte into 2-byte"
+ECHO: "Expect [0] for search(Π, ΠΠΠΠ)=[0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(Π, ΠΠΠΠ, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "----- Lookup of 2-byte into 4-byte"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, π‘π‘π‘π‘)=[]. OK"
+ECHO: "----- Lookup of 4-byte into 4-byte"
+ECHO: "Expect [0] for search(π‘, π‘π‘π‘π‘)=[0]. OK"
+ECHO: "Expect [[0, 1, 2, 3]] for search(π‘, π‘π‘π‘π‘, 0)=[[0, 1, 2, 3]]. OK"
+ECHO: "----- Lookup of 4-byte into 2-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, ΠΠΠΠ)=[]. OK"
+ECHO: "----- Lookup of 2-byte into mixed multi-byte"
+ECHO: "Expect [1] for search(Π, aΠaΠaΠaΠa, 1)=[1]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [] for search(Π, aπ‘aπ‘aπ‘aπ‘a, 1)=[]. OK"
+ECHO: "Expect [2] for search(Π, aπ‘Ππ‘aπ‘Ππ‘a, 1)=[2]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(Π, aΠaΠaΠaΠa, 0)=[[1, 3, 5, 7]]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[]] for search(Π, aπ‘aπ‘aπ‘aπ‘a, 0)=[[]]. OK"
+ECHO: "Expect [[2, 6]] for search(Π, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[2, 6]]. OK"
+ECHO: "----- Lookup of 4-byte into mixed multi-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [] for search(π‘, aΠaΠaΠaΠa, 1)=[]. OK"
+ECHO: "Expect [1] for search(π‘, aπ‘aπ‘aπ‘aπ‘a, 1)=[1]. OK"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[]] for search(π‘, aΠaΠaΠaΠa, 0)=[[]]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(π‘, aπ‘aπ‘aπ‘aπ‘a, 0)=[[1, 3, 5, 7]]. OK"
+ECHO: "Expect [[1, 3, 5, 7]] for search(π‘, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[1, 3, 5, 7]]. OK"
+ECHO: "----- Lookup of mixed multi-byte into mixed multi-byte"
+  WARNING: search term not found: "π‘"
+ECHO: "Expect [[0, 2, 4, 6, 8], [1, 3, 5, 7], []] for search(aΠπ‘, aΠaΠaΠaΠa, 0)=[[0, 2, 4, 6, 8], [1, 3, 5, 7], []]. OK"
+  WARNING: search term not found: "Π"
+ECHO: "Expect [[0, 2, 4, 6, 8], [], [1, 3, 5, 7]] for search(aΠπ‘, aπ‘aπ‘aπ‘aπ‘a, 0)=[[0, 2, 4, 6, 8], [], [1, 3, 5, 7]]. OK"
+ECHO: "Expect [[0, 4, 8], [2, 6], [1, 3, 5, 7]] for search(aΠπ‘, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[0, 4, 8], [2, 6], [1, 3, 5, 7]]. OK"
+ECHO: "Expect [[1, 3, 5, 7], [0, 4, 8], [2, 6]] for search(π‘aΠ, aπ‘Ππ‘aπ‘Ππ‘a, 0)=[[1, 3, 5, 7], [0, 4, 8], [2, 6]]. OK"
diff --git a/tests/regression/echotest/string-unicode-expected.echo b/tests/regression/echotest/string-unicode-expected.echo
new file mode 100644
index 0000000..b4b848f
--- /dev/null
+++ b/tests/regression/echotest/string-unicode-expected.echo
@@ -0,0 +1,104 @@
+ECHO: "[", 0, "] = ", "DEADBEEF", " of len=", 8, ":"
+ECHO: "    [", 0, "]=", "D"
+ECHO: "    [", 1, "]=", "E"
+ECHO: "    [", 2, "]=", "A"
+ECHO: "    [", 3, "]=", "D"
+ECHO: "    [", 4, "]=", "B"
+ECHO: "    [", 5, "]=", "E"
+ECHO: "    [", 6, "]=", "E"
+ECHO: "    [", 7, "]=", "F"
+ECHO: "[", 1, "] = ", "ΠΠ΅Π½ΠΈΠ²ΡΠΉ ΡΡΠΆΠΈΠΉ ΠΊΠΎΡ", " of len=", 17, ":"
+ECHO: "    [", 0, "]=", "Π"
+ECHO: "    [", 1, "]=", "Π΅"
+ECHO: "    [", 2, "]=", "Π½"
+ECHO: "    [", 3, "]=", "ΠΈ"
+ECHO: "    [", 4, "]=", "Π²"
+ECHO: "    [", 5, "]=", "Ρ"
+ECHO: "    [", 6, "]=", "ΠΉ"
+ECHO: "    [", 7, "]=", " "
+ECHO: "    [", 8, "]=", "Ρ"
+ECHO: "    [", 9, "]=", "Ρ"
+ECHO: "    [", 10, "]=", "ΠΆ"
+ECHO: "    [", 11, "]=", "ΠΈ"
+ECHO: "    [", 12, "]=", "ΠΉ"
+ECHO: "    [", 13, "]=", " "
+ECHO: "    [", 14, "]=", "ΠΊ"
+ECHO: "    [", 15, "]=", "ΠΎ"
+ECHO: "    [", 16, "]=", "Ρ"
+ECHO: "[", 2, "] = ", "ΩΨ³ΩΩ Ψ§ΩΨ²ΩΨ¬Ψ¨ΩΩ Ψ§ΩΩΨ·", " of len=", 18, ":"
+ECHO: "    [", 0, "]=", "Ω"
+ECHO: "    [", 1, "]=", "Ψ³"
+ECHO: "    [", 2, "]=", "Ω"
+ECHO: "    [", 3, "]=", "Ω"
+ECHO: "    [", 4, "]=", " "
+ECHO: "    [", 5, "]=", "Ψ§"
+ECHO: "    [", 6, "]=", "Ω"
+ECHO: "    [", 7, "]=", "Ψ²"
+ECHO: "    [", 8, "]=", "Ω"
+ECHO: "    [", 9, "]=", "Ψ¬"
+ECHO: "    [", 10, "]=", "Ψ¨"
+ECHO: "    [", 11, "]=", "Ω"
+ECHO: "    [", 12, "]=", "Ω"
+ECHO: "    [", 13, "]=", " "
+ECHO: "    [", 14, "]=", "Ψ§"
+ECHO: "    [", 15, "]=", "Ω"
+ECHO: "    [", 16, "]=", "Ω"
+ECHO: "    [", 17, "]=", "Ψ·"
+ECHO: "[", 3, "] = ", "ζΆζ°ηε§θ²", " of len=", 5, ":"
+ECHO: "    [", 0, "]=", "ζΆ"
+ECHO: "    [", 1, "]=", "ζ°"
+ECHO: "    [", 2, "]=", "η"
+ECHO: "    [", 3, "]=", "ε§"
+ECHO: "    [", 4, "]=", "θ²"
+ECHO: "[", 4, "] = ", "Àâü ΓΓΓ Γ", " of len=", 9, ":"
+ECHO: "    [", 0, "]=", "Γ€"
+ECHO: "    [", 1, "]=", "ΓΆ"
+ECHO: "    [", 2, "]=", "ΓΌ"
+ECHO: "    [", 3, "]=", " "
+ECHO: "    [", 4, "]=", "Γ"
+ECHO: "    [", 5, "]=", "Γ"
+ECHO: "    [", 6, "]=", "Γ"
+ECHO: "    [", 7, "]=", " "
+ECHO: "    [", 8, "]=", "Γ"
+ECHO: "[", 5, "] = ", "πππππ
πππππππππππ", " of len=", 16, ":"
+ECHO: "    [", 0, "]=", "π"
+ECHO: "    [", 1, "]=", "π"
+ECHO: "    [", 2, "]=", "π"
+ECHO: "    [", 3, "]=", "π"
+ECHO: "    [", 4, "]=", "π
"
+ECHO: "    [", 5, "]=", "π"
+ECHO: "    [", 6, "]=", "π"
+ECHO: "    [", 7, "]=", "π"
+ECHO: "    [", 8, "]=", "π"
+ECHO: "    [", 9, "]=", "π"
+ECHO: "    [", 10, "]=", "π"
+ECHO: "    [", 11, "]=", "π"
+ECHO: "    [", 12, "]=", "π"
+ECHO: "    [", 13, "]=", "π"
+ECHO: "    [", 14, "]=", "π"
+ECHO: "    [", 15, "]=", "π"
+ECHO: "[", 6, "] = ", "β β β β β 
β β β β β β β β β β ", " of len=", 15, ":"
+ECHO: "    [", 0, "]=", "β "
+ECHO: "    [", 1, "]=", "β "
+ECHO: "    [", 2, "]=", "β "
+ECHO: "    [", 3, "]=", "β "
+ECHO: "    [", 4, "]=", "β 
"
+ECHO: "    [", 5, "]=", "β "
+ECHO: "    [", 6, "]=", "β "
+ECHO: "    [", 7, "]=", "β "
+ECHO: "    [", 8, "]=", "β "
+ECHO: "    [", 9, "]=", "β "
+ECHO: "    [", 10, "]=", "β "
+ECHO: "    [", 11, "]=", "β "
+ECHO: "    [", 12, "]=", "β "
+ECHO: "    [", 13, "]=", "β "
+ECHO: "    [", 14, "]=", "β "
+ECHO: "[", 7, "] = ", "π‘π±ππ", " of len=", 4, ":"
+ECHO: "    [", 0, "]=", "π‘"
+ECHO: "    [", 1, "]=", "π±"
+ECHO: "    [", 2, "]=", "π"
+ECHO: "    [", 3, "]=", "π"
+ECHO: "Past end of unicode only 2-byte ", undef
+ECHO: "Past end of unicode only 4-byte ", undef
+ECHO: "Past end of both 2-byte ", undef
+ECHO: "Past end of both 4-byte ", undef
-- 
cgit v0.10.1
From c0849eb1d3c98db505eec0396c56276a9a35120f Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 09:56:22 +1100
Subject: Update comments/messages for CGAL source of bad boost libraries
Add comments and change to a status instead of a warning (as we recover
nicely and it is a known issue in CGAL @
https://bugs.launchpad.net/ubuntu/+source/cgal/+bug/1242111)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3c19b77..2533103 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -383,23 +383,24 @@ if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6)
   message(FATAL_ERROR "CGAL >= 3.6 required")
 endif()
 inclusion(CGAL_DIR CGAL_INCLUDE_DIRS)
-
-#Get rid of bad libraries suggested for BOOST dependencies (they don't exist on some machines and cause build failures).
-#/usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+#Remove bad BOOST libraries from CGAL 3rd party dependencies when they don't exist (such as on 64-bit Ubuntu 13.10).
+#Libs of concern are /usr/lib/libboost_thread.so;/usr/lib/libboost_system.so;
+#Confirmed bug in CGAL @ https://bugs.launchpad.net/ubuntu/+source/cgal/+bug/1242111
 string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_system.so" FIND_POSITION  )
 if(NOT "-1" STREQUAL ${FIND_POSITION} )
-if(NOT EXISTS "/usr/lib/libboost_system.so")
-  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_system.so -- stripping" )
-  string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
-endif()
+  if(NOT EXISTS "/usr/lib/libboost_system.so")
+    MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_system.so" )
+    string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+  endif()
 endif() 
 string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_thread.so" FIND_POSITION  )
 if(NOT "-1" STREQUAL ${FIND_POSITION} )
-if(NOT EXISTS "/usr/lib/libboost_thread.so")
-  MESSAGE( WARNING "CGAL_3RD_PARTY_LIBRARIES:Found erroneous /usr/lib/libboost_thread.so -- stripping" )
-  string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
-endif()
+  if(NOT EXISTS "/usr/lib/libboost_thread.so")
+    MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_thread.so" )
+    string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES})
+  endif()
 endif() 
+
 if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" )
 	string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
 	string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
-- 
cgit v0.10.1
From d2575016b989b9cee5b44c29f10a76d84a7bf182 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 17:46:52 +1100
Subject: Add in missed glib build dependencies for OS X and unix
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index d4ca1f7..5e26fee 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -285,6 +285,30 @@ build_glew()
   make GLEW_DEST=$DEPLOYDIR CC=$CC CFLAGS.EXTRA="-no-cpp-precomp -dynamic -fno-common -mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" LDFLAGS.EXTRA="-mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" STRIP= install
 }
 
+build_glib2()
+{
+  version="$1"
+  maj_min_version="${version%.*}" #Drop micro
+
+  if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then
+    echo "glib2 already installed. not building"
+    return
+  fi
+
+   echo "Building glib2 $version..."
+  cd "$BASEDIR"/src
+  rm -rf "glib-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+    curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz"
+  fi
+  tar xJf "glib-$version.tar.xz"
+  cd "glib-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j$NUMCPU
+  make install
+}
+
 build_opencsg()
 {
   version=$1
@@ -449,6 +473,7 @@ build_boost 1.54.0
 # NB! For CGAL, also update the actual download URL in the function
 build_cgal 4.3
 build_glew 1.10.0
+build_glib2 2.38.1
 build_opencsg 1.3.2
 if $OPTION_DEPLOY; then
 #  build_sparkle andymatuschak 0ed83cf9f2eeb425d4fdd141c01a29d843970c20
diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh
index 8d912c3..ba328b7 100755
--- a/scripts/uni-build-dependencies.sh
+++ b/scripts/uni-build-dependencies.sh
@@ -409,6 +409,31 @@ build_glew()
   GLEW_DEST=$DEPLOYDIR $MAKER install
 }
 
+build_glib2()
+{
+  version="$1"
+  maj_min_version="${version%.*}" #Drop micro
+
+  if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then
+echo "glib2 already installed. not building"
+    return
+fi
+
+echo "Building glib2 $version..."
+  cd "$BASEDIR"/src
+  rm -rf "glib-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz"
+  fi
+tar xJf "glib-$version.tar.xz"
+  cd "glib-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j$NUMCPU
+  make install
+   
+}
+
 build_opencsg()
 {
   if [ -e $DEPLOYDIR/lib/libopencsg.so ]; then
-- 
cgit v0.10.1
From d46ce3fb8150fe01c0c07fac11ea2e9a8ed97038 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Fri, 6 Dec 2013 18:33:42 +1100
Subject: Add specific tests for unicode len()
diff --git a/testdata/scad/misc/string-unicode.scad b/testdata/scad/misc/string-unicode.scad
index d8e3e5c..1386d63 100644
--- a/testdata/scad/misc/string-unicode.scad
+++ b/testdata/scad/misc/string-unicode.scad
@@ -1,3 +1,12 @@
+//Test length reporting
+text_1bytes_len = "1234";
+text_2bytes_len = "ΠΠΠΠ";
+text_4bytes_len = "π‘π±ππ";
+
+echo( "text_1bytes_len = ", text_1bytes_len, " len = ", len(text_1bytes_len)  );
+echo( "text_2bytes_len = ", text_2bytes_len, " len = ", len(text_2bytes_len)  );
+echo( "text_4bytes_len = ", text_4bytes_len, " len = ", len(text_4bytes_len)  );
+
 //Test how well arrays of unicode string are accessed.
 
 texts_array = [
@@ -33,4 +42,3 @@ echo( "Past end of unicode only 4-byte ", text_4bytes[len(text_4bytes)]  );
 echo( "Past end of both 2-byte ", text_2bytes[ len(text_2bytes) * 2 ]   );
 echo( "Past end of both 4-byte ", text_4bytes[ len(text_4bytes) * 4 ]   );
 
-
diff --git a/tests/regression/echotest/string-unicode-expected.echo b/tests/regression/echotest/string-unicode-expected.echo
index b4b848f..a1cd3be 100644
--- a/tests/regression/echotest/string-unicode-expected.echo
+++ b/tests/regression/echotest/string-unicode-expected.echo
@@ -1,3 +1,6 @@
+ECHO: "text_1bytes_len = ", "1234", " len = ", 4
+ECHO: "text_2bytes_len = ", "ΠΠΠΠ", " len = ", 4
+ECHO: "text_4bytes_len = ", "π‘π±ππ", " len = ", 4
 ECHO: "[", 0, "] = ", "DEADBEEF", " of len=", 8, ":"
 ECHO: "    [", 0, "]=", "D"
 ECHO: "    [", 1, "]=", "E"
-- 
cgit v0.10.1
From b7c818bf00b5c9e23ee97ce6669eb66a9b404562 Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 16:15:50 -0800
Subject: Revert to uploading to dinkypage, gists won't work
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index b601c84..675867e 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -36,12 +36,11 @@ import subprocess
 import time
 import platform
 try:
-    from urllib.request import urlopen, Request
+    from urllib.request import urlopen
+    from urllib.parse import urlencode
 except:
-    from urllib2 import urlopen, Request
-import json
-import base64
-
+    from urllib2 import urlopen
+    from urllib import urlencode
 
 def tryread(filename):
     data = None
@@ -363,43 +362,32 @@ def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles):
 
 # --- Web Upload ---
 
-API_URL = 'https://api.github.com/%s'
-# Username is personal access token, from https://github.com/settings/applications
-# This way, no password is needed
-USERNAME = '' # add OpenScad user token
-PASSWORD = ''
-
-def make_auth(username, password):
-    auth = '%s:%s' % (USERNAME, PASSWORD)
-    return base64.b64encode(auth.encode())
-
-def post_gist(name, content):
-    gist = '''{
-      "description": "",
-      "public": true,
-      "files": {
-        "%s": {
-          "content": "%s"
-        }
-      }
-    }'''
-    gist = gist % (name, content)
-    
-    req = Request(API_URL % 'gists')
-    req.add_header('Authorization', b'Basic ' + make_auth(USERNAME, PASSWORD))
+def postify(data):
+    return urlencode(data).encode()
+
+def create_page():
+    data = {
+        'action': 'create',
+        'type': 'html'
+    }
     try:
-        result = urlopen(req, data=gist)
+        response = urlopen('http://www.dinkypage.com', data=postify(data))
     except:
-        print 'Could not upload results'
         return None
-    return json.loads(result.read())
-
-
-def get_raw_urls(result):
-    files = result.get('files', {})
-    for file in files:
-        yield files[file].get('raw_url').replace('gist.github.com', 'rawgithub.com')
-
+    return response.geturl()
+
+def upload_html(page_url, title, html):
+    data = {
+        'mode': 'editor',
+        'title': title,
+        'html': html,
+        'ajax': '1'
+    }
+    try:
+        response = urlopen(page_url, data=postify(data))
+    except:
+        return False
+    return 'success' in response.read().decode()
 
 # --- End Web Upload ---
 
@@ -458,13 +446,12 @@ def main():
     debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes')
     trysave(html_filename, html)
 
-    result = post_gist(name=html_basename, content=html)
-    if result is None:
+    page_url = create_page()
+    if upload_html(page_url, title='OpenSCAD test results', html=html):
+        share_url = page_url.partition('?')[0]
+        print 'html report uploaded at', share_url
+    else:
         print 'could not upload html report'
-        return
-    
-    for url in get_raw_urls(result):
-        print 'html report uploaded at', url
 
     debug('test_pretty_print complete')
 
-- 
cgit v0.10.1
From 58bf7386a11d6f6fb247cf95b89bbbb2682832ca Mon Sep 17 00:00:00 2001
From: a-e-m 
Date: Sun, 8 Dec 2013 16:32:36 -0800
Subject: Added --upload command line option
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index 675867e..c0d35bb 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -426,6 +426,11 @@ def main():
     if not builddir:
         builddir = os.getcwd()
     debug('build dir set to ' +  builddir)
+
+    upload = False
+    if '--upload' in sys.argv:
+        upload = True
+        debug('will upload test report')
     
     # --- End Command Line Parsing ---
 
@@ -446,12 +451,13 @@ def main():
     debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes')
     trysave(html_filename, html)
 
-    page_url = create_page()
-    if upload_html(page_url, title='OpenSCAD test results', html=html):
-        share_url = page_url.partition('?')[0]
-        print 'html report uploaded at', share_url
-    else:
-        print 'could not upload html report'
+    if upload:
+        page_url = create_page()
+        if upload_html(page_url, title='OpenSCAD test results', html=html):
+            share_url = page_url.partition('?')[0]
+            print 'html report uploaded at', share_url
+        else:
+            print 'could not upload html report'
 
     debug('test_pretty_print complete')
 
-- 
cgit v0.10.1
From a407e4bf29024dbed6f3e89376b8e7d134b98776 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 8 Dec 2013 23:13:28 -0500
Subject: Enable upload of test results (#525)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f92eddf..779ef08 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -904,6 +904,12 @@ string(REPLACE __header__ "Generated by cmake from ${CMAKE_CURRENT_SOURCE_DIR}/C
 string(REPLACE __cmake_system_name__ ${CMAKE_SYSTEM_NAME} TMP ${TMP})
 string(REPLACE __openscad_binpath__ ${OPENSCAD_BINPATH} TMP ${TMP})
 
+set(OPENSCAD_UPLOAD_TESTS $ENV{OPENSCAD_UPLOAD_TESTS})
+if (OPENSCAD_UPLOAD_TESTS)
+  set(UPLOADARG "--upload")
+endif()
+string(REPLACE __openscad_upload_tests__ ${UPLOADARG} TMP ${TMP})
+
 if (MINGW_CROSS_ENV_DIR)
   string(REPLACE __wine__ wine TMP ${TMP})
 else()
diff --git a/tests/CTestCustom.template b/tests/CTestCustom.template
index 3f82d73..a01f2b5 100644
--- a/tests/CTestCustom.template
+++ b/tests/CTestCustom.template
@@ -63,7 +63,12 @@ endif()
 
 message("running '__openscad_binpath__ --info' to generate sysinfo.txt")
 execute_process(COMMAND __wine__ __openscad_binpath__ --info OUTPUT_FILE sysinfo.txt)
-set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__cmake_current_binary_dir__/test_pretty_print")
+
+if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_LESS 2.8.12)
+  set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__cmake_current_binary_dir__/test_pretty_print")
+else()
+  set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__python__ __cmake_current_source_dir__/test_pretty_print.py --builddir=__cmake_current_binary_dir__ __openscad_upload_tests__")
+endif()
 
 if ( ${debug_openscad_template} )
   foreach(post_test ${CTEST_CUSTOM_POST_TEST} )
-- 
cgit v0.10.1
From 8f103043a280290802559ce8770c5984f7f0730a Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 8 Dec 2013 23:16:49 -0500
Subject: Upload test results from travis
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
index 9d4258a..362c2df 100755
--- a/scripts/travis-ci.sh
+++ b/scripts/travis-ci.sh
@@ -1,12 +1,7 @@
 #!/bin/bash
 
-qmake && make -j4
-if [[ $? != 0 ]]; then
-  echo "Error building OpenSCAD executable"
-  exit 1
-fi
 cd tests
-cmake . 
+cmake -DOPENSCAD_UPLOAD_TESTS=yes . 
 if [[ $? != 0 ]]; then
   echo "Error configuring test suite"
   exit 1
-- 
cgit v0.10.1
From a22394fc392451919f5b378ffe85dbeac7fd3a22 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 9 Dec 2013 00:13:14 -0500
Subject: Set upload env. variable in travis env since the cmake version on
 Travis is too old
diff --git a/.travis.yml b/.travis.yml
index 9442ca4..5b9f215 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,8 @@ before_install:
  - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick
  - sudo apt-get install -qq libopencsg-dev
 
+env: OPENSCAD_UPLOAD_TESTS=yes 
+
 branches:
   only:
     - travis
diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh
index 362c2df..9f44b0c 100755
--- a/scripts/travis-ci.sh
+++ b/scripts/travis-ci.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 cd tests
-cmake -DOPENSCAD_UPLOAD_TESTS=yes . 
+cmake . 
 if [[ $? != 0 ]]; then
   echo "Error configuring test suite"
   exit 1
-- 
cgit v0.10.1
From 6fd378e9af2dab353764afc4bfad834bd14349b7 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 9 Dec 2013 00:24:55 -0500
Subject: Set upload env. variable in travis env since the cmake version on
 Travis is too old
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index c0d35bb..0f86cb6 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -431,6 +431,11 @@ def main():
     if '--upload' in sys.argv:
         upload = True
         debug('will upload test report')
+
+    # Workaround for old cmake's not being able to pass parameters
+    # to CTEST_CUSTOM_POST_TEST
+    if bool(os.getenv("OPENSCAD_UPLOAD_TESTS")):
+        upload = True
     
     # --- End Command Line Parsing ---
 
-- 
cgit v0.10.1
From e96305ddf5a728eb323a86e4e900752ae2a6ccfe Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Mon, 9 Dec 2013 00:46:38 -0500
Subject: minor tuning
diff --git a/.travis.yml b/.travis.yml
index 5b9f215..c499a8d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
 language: cpp
-compiler:
-  - gcc
+cache: apt
+compiler: gcc
+
 before_install:
  - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad
  - sudo apt-get update -qq
@@ -11,6 +12,6 @@ env: OPENSCAD_UPLOAD_TESTS=yes
 
 branches:
   only:
-    - travis
+    - master
 
 script: ./scripts/travis-ci.sh
-- 
cgit v0.10.1
From 7cb3ea77ff090e136eb4a9536a394b3766908d17 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Tue, 10 Dec 2013 23:08:04 -0500
Subject: Build glib2 and gettext on Mac, Find macro for glib2
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index 5e26fee..b61c656 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -285,27 +285,40 @@ build_glew()
   make GLEW_DEST=$DEPLOYDIR CC=$CC CFLAGS.EXTRA="-no-cpp-precomp -dynamic -fno-common -mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" LDFLAGS.EXTRA="-mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" STRIP= install
 }
 
-build_glib2()
+build_gettext()
 {
-  version="$1"
-  maj_min_version="${version%.*}" #Drop micro
+  version=$1
+  echo "Building gettext $version..."
 
-  if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then
-    echo "glib2 already installed. not building"
-    return
+  cd "$BASEDIR"/src
+  rm -rf "gettext-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+    curl --insecure -LO "http://ftpmirror.gnu.org/gettext/gettext-$version.tar.gz"
   fi
+  tar xzf "gettext-$version.tar.gz"
+  cd "gettext-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j4
+  make install
+}
+
+build_glib2()
+{
+  version=$1
+  echo "Building glib2 $version..."
 
-   echo "Building glib2 $version..."
   cd "$BASEDIR"/src
   rm -rf "glib-$version"
+  maj_min_version="${version%.*}" #Drop micro
   if [ ! -f "glib-$version.tar.xz" ]; then
     curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz"
   fi
   tar xJf "glib-$version.tar.xz"
   cd "glib-$version"
 
-  ./configure --prefix="$DEPLOYDIR"
-  make -j$NUMCPU
+  ./configure --disable-gtk-doc --disable-man --prefix="$DEPLOYDIR" CFLAGS="-I$DEPLOYDIR/include" LDFLAGS="-L$DEPLOYDIR/lib"
+  make -j4
   make install
 }
 
@@ -473,7 +486,8 @@ build_boost 1.54.0
 # NB! For CGAL, also update the actual download URL in the function
 build_cgal 4.3
 build_glew 1.10.0
-build_glib2 2.38.1
+build_gettext 0.18.3.1
+build_glib2 2.38.2
 build_opencsg 1.3.2
 if $OPTION_DEPLOY; then
 #  build_sparkle andymatuschak 0ed83cf9f2eeb425d4fdd141c01a29d843970c20
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2533103..e590070 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -366,10 +366,10 @@ if (NOT $ENV{CGALDIR} STREQUAL "")
 elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
   if (EXISTS "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL")
     set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL")
-    set(CMAKE_MODULE_PATH "${CGAL_DIR}")
+    set(CMAKE_MODULE_PATH "${CGAL_DIR}" ${CMAKE_MODULE_PATH})
   elseif (EXISTS "$ENV{OPENSCAD_LIBRARIES}/include/CGAL")
     set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}")
-    set(CMAKE_MODULE_PATH "${CGAL_DIR}")
+    set(CMAKE_MODULE_PATH "${CGAL_DIR}" ${CMAKE_MODULE_PATH})
   endif()
 endif()
 message(STATUS "CGAL_DIR: " ${CGAL_DIR})
@@ -413,16 +413,9 @@ if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
   set(ENV{PKG_CONFIG_LIBDIR} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig")
 endif()
 
-# Find libraries (system installed or dependency built) using pkg-config
-find_package(PkgConfig REQUIRED)
-
-#GLib-2
-pkg_search_module(GLIB2 REQUIRED glib-2.0>=2.2.0)
-#Can't use the CXXFlags directly as they are ;-separated
-string(REPLACE ";" " " GLIB2_CFLAGS "${GLIB2_CFLAGS}")
-message(STATUS "glib-2.0 found: ${GLIB2_VERSION}")
-
-add_definitions(${GLIB2_CFLAGS})
+find_package(GLIB2 2.2.0 REQUIRED)
+add_definitions(${GLIB2_DEFINITIONS})
+inclusion(GLIB2_DIR GLIB2_INCLUDE_DIRS)
 
 # Imagemagick
 
@@ -595,7 +588,7 @@ set(OFFSCREEN_SOURCES
   ../src/OpenCSGRenderer.cc)
 
 add_library(tests-core STATIC ${CORE_SOURCES})
-target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} )
+target_link_libraries(tests-core)
 set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${Boost_LIBRARIES} )
 
 add_library(tests-common STATIC ${COMMON_SOURCES})
@@ -616,7 +609,7 @@ set(TESTS-NOCGAL-LIBRARIES ${TESTS-CORE-LIBRARIES})
 # modulecachetest
 #
 add_executable(modulecachetest modulecachetest.cc)
-target_link_libraries(modulecachetest tests-nocgal ${TESTS-NOCGAL-LIBRARIES} ${Boost_LIBRARIES})
+target_link_libraries(modulecachetest tests-nocgal ${TESTS-NOCGAL-LIBRARIES})
 
 #
 # csgtexttest
@@ -636,7 +629,7 @@ target_link_libraries(cgalcachetest tests-cgal ${TESTS-CGAL-LIBRARIES} ${GLEW_LI
 #
 add_executable(openscad_nogui ../src/openscad.cc)
 set_target_properties(openscad_nogui PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -DEIGEN_DONT_ALIGN -DENABLE_CGAL -DENABLE_OPENCSG ${CGAL_CXX_FLAGS_INIT}")
-target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${Boost_LIBRARIES} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} )
+target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} )
 
 #
 # GUI binary tests
diff --git a/tests/FindGLIB2.cmake b/tests/FindGLIB2.cmake
new file mode 100644
index 0000000..b27b9a9
--- /dev/null
+++ b/tests/FindGLIB2.cmake
@@ -0,0 +1,27 @@
+find_package(PkgConfig REQUIRED)
+
+pkg_search_module(GLIB2 REQUIRED glib-2.0)
+#message("GLIB2_LIBRARIES ${GLIB2_LIBRARIES}")
+#message("GLIB2_LIBRARY_DIRS ${GLIB2_LIBRARY_DIRS}")
+#message("GLIB2_LDFLAGS ${GLIB2_LDFLAGS}")
+#message("GLIB2_LDFLAGS_OTHER ${GLIB2_LDFLAGS_OTHER}")
+message("GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS}")
+#message("GLIB2_CFLAGS ${GLIB2_CFLAGS}")
+#message("GLIB2_CFLAGS_OTHER ${GLIB2_CFLAGS_OTHER}")
+#message("GLIB2_LIBDIR ${GLIB2_LIBDIR}")
+
+set(GLIB2_DEFINITIONS ${GLIB2_CFLAGS_OTHER})
+#message("GLIB2_DEFINITIONS ${GLIB2_DEFINITIONS}")
+
+set(GLIB2_LIBRARY_NAMES ${GLIB2_LIBRARIES})
+set(GLIB2_LIBRARIES "")
+foreach(GLIB2_LIB ${GLIB2_LIBRARY_NAMES})
+#  message("lib: ${GLIB2_LIB}")
+  set(TMP TMP-NOTFOUND)
+  find_library(TMP NAMES ${GLIB2_LIB}
+               PATHS ${GLIB2_LIBRARY_DIRS}
+               NO_DEFAULT_PATH)
+#  message("TMP: ${TMP}")
+  list(APPEND GLIB2_LIBRARIES "${TMP}")
+endforeach()
+message("GLIB2_LIBRARIES: ${GLIB2_LIBRARIES}")
-- 
cgit v0.10.1
From 379e7a05472a50cb417fd0582d2ca4466b2365fe Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Wed, 11 Dec 2013 00:16:27 -0500
Subject: Updated gmp patch. Fixes #558
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index d4ca1f7..eadea9f 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -83,6 +83,51 @@ build_gmp()
   fi
   tar xjf gmp-$version.tar.bz2
   cd gmp-$version
+  patch -p0 gmp-h.in << EOF
+--- gmp-5.1.3/gmp-h.in.old	2013-12-02 20:16:26.000000000 -0800
++++ gmp-5.1.3/gmp-h.in	2013-12-02 20:21:22.000000000 -0800
+@@ -27,13 +27,38 @@
+ #endif
+ 
+ 
+-/* Instantiated by configure. */
+ #if ! defined (__GMP_WITHIN_CONFIGURE)
++/* For benefit of fat builds on MacOSX, generate a .h file that can
++ * be used with a universal fat library
++ */
++#if defined(__x86_64__)
++#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0
++#define GMP_LIMB_BITS                      64
++#define GMP_NAIL_BITS                      0
++#elif defined(__i386__)
++#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0
++#define GMP_LIMB_BITS                      32
++#define GMP_NAIL_BITS                      0
++#elif defined(__powerpc64__)
++#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1
++#define GMP_LIMB_BITS                      64
++#define GMP_NAIL_BITS                      0
++#elif defined(__ppc__)
++#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1
++#define GMP_LIMB_BITS                      32
++#define GMP_NAIL_BITS                      0
++#else
++/* For other architectures, fall back on values computed by configure */
+ #define __GMP_HAVE_HOST_CPU_FAMILY_power   @HAVE_HOST_CPU_FAMILY_power@
+ #define __GMP_HAVE_HOST_CPU_FAMILY_powerpc @HAVE_HOST_CPU_FAMILY_powerpc@
+ #define GMP_LIMB_BITS                      @GMP_LIMB_BITS@
+ #define GMP_NAIL_BITS                      @GMP_NAIL_BITS@
+ #endif
++#endif
+ #define GMP_NUMB_BITS     (GMP_LIMB_BITS - GMP_NAIL_BITS)
+ #define GMP_NUMB_MASK     ((~ __GMP_CAST (mp_limb_t, 0)) >> GMP_NAIL_BITS)
+ #define GMP_NUMB_MAX      GMP_NUMB_MASK
+EOF
+
   if $OPTION_32BIT; then
     mkdir build-i386
     cd build-i386
@@ -119,42 +164,6 @@ build_gmp()
   mkdir -p include
   cp x86_64/include/gmp.h include/
   cp x86_64/include/gmpxx.h include/
-
-  patch -p0 include/gmp.h << EOF
---- gmp.h.orig	2011-11-08 01:03:41.000000000 +0100
-+++ gmp.h	2011-11-08 01:06:21.000000000 +0100
-@@ -26,12 +26,28 @@
- #endif
- 
- 
--/* Instantiated by configure. */
--#if ! defined (__GMP_WITHIN_CONFIGURE)
-+#if defined(__i386__)
-+#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
-+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0
-+#define GMP_LIMB_BITS                      32
-+#define GMP_NAIL_BITS                      0
-+#elif defined(__x86_64__)
- #define __GMP_HAVE_HOST_CPU_FAMILY_power   0
- #define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0
- #define GMP_LIMB_BITS                      64
- #define GMP_NAIL_BITS                      0
-+#elif defined(__ppc__)
-+#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
-+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1
-+#define GMP_LIMB_BITS                      32
-+#define GMP_NAIL_BITS                      0
-+#elif defined(__powerpc64__)
-+#define __GMP_HAVE_HOST_CPU_FAMILY_power   0
-+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1
-+#define GMP_LIMB_BITS                      64
-+#define GMP_NAIL_BITS                      0
-+#else
-+#error Unsupported architecture
- #endif
- #define GMP_NUMB_BITS     (GMP_LIMB_BITS - GMP_NAIL_BITS)
- #define GMP_NUMB_MASK     ((~ __GMP_CAST (mp_limb_t, 0)) >> GMP_NAIL_BITS)
-EOF
 }
 
 # As with gmplib, mpfr is built separately in 32-bit and 64-bit mode and then merged
-- 
cgit v0.10.1
From 745664f1266b3a46898c671ba17db21df19adebf Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Wed, 11 Dec 2013 17:06:27 +1100
Subject: Ctests build for Glib2 - Add GLIB2_LIBDIR to find_library paths
diff --git a/tests/FindGLIB2.cmake b/tests/FindGLIB2.cmake
index b27b9a9..9164c39 100644
--- a/tests/FindGLIB2.cmake
+++ b/tests/FindGLIB2.cmake
@@ -2,13 +2,13 @@ find_package(PkgConfig REQUIRED)
 
 pkg_search_module(GLIB2 REQUIRED glib-2.0)
 #message("GLIB2_LIBRARIES ${GLIB2_LIBRARIES}")
-#message("GLIB2_LIBRARY_DIRS ${GLIB2_LIBRARY_DIRS}")
+message("GLIB2_LIBRARY_DIRS ${GLIB2_LIBRARY_DIRS}")
 #message("GLIB2_LDFLAGS ${GLIB2_LDFLAGS}")
 #message("GLIB2_LDFLAGS_OTHER ${GLIB2_LDFLAGS_OTHER}")
 message("GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS}")
 #message("GLIB2_CFLAGS ${GLIB2_CFLAGS}")
 #message("GLIB2_CFLAGS_OTHER ${GLIB2_CFLAGS_OTHER}")
-#message("GLIB2_LIBDIR ${GLIB2_LIBDIR}")
+message("GLIB2_LIBDIR ${GLIB2_LIBDIR}")
 
 set(GLIB2_DEFINITIONS ${GLIB2_CFLAGS_OTHER})
 #message("GLIB2_DEFINITIONS ${GLIB2_DEFINITIONS}")
@@ -20,6 +20,7 @@ foreach(GLIB2_LIB ${GLIB2_LIBRARY_NAMES})
   set(TMP TMP-NOTFOUND)
   find_library(TMP NAMES ${GLIB2_LIB}
                PATHS ${GLIB2_LIBRARY_DIRS}
+               PATHS ${GLIB2_LIBDIR}
                NO_DEFAULT_PATH)
 #  message("TMP: ${TMP}")
   list(APPEND GLIB2_LIBRARIES "${TMP}")
-- 
cgit v0.10.1
From 225afc966be6a99453b2ff2f77c621b7db01b12e Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Wed, 11 Dec 2013 17:53:43 +1100
Subject: Add gettext lib for linux also
diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh
index ba328b7..5b9e129 100755
--- a/scripts/uni-build-dependencies.sh
+++ b/scripts/uni-build-dependencies.sh
@@ -409,6 +409,24 @@ build_glew()
   GLEW_DEST=$DEPLOYDIR $MAKER install
 }
 
+build_gettext()
+{
+  version=$1
+  echo "Building gettext $version..."
+
+  cd "$BASEDIR"/src
+  rm -rf "gettext-$version"
+  if [ ! -f "glib-$version.tar.xz" ]; then
+    curl --insecure -LO "http://ftpmirror.gnu.org/gettext/gettext-$version.tar.gz"
+  fi
+  tar xzf "gettext-$version.tar.gz"
+  cd "gettext-$version"
+
+  ./configure --prefix="$DEPLOYDIR"
+  make -j4
+  make install
+}
+
 build_glib2()
 {
   version="$1"
@@ -428,10 +446,9 @@ curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_versio
 tar xJf "glib-$version.tar.xz"
   cd "glib-$version"
 
-  ./configure --prefix="$DEPLOYDIR"
+  ./configure --disable-gtk-doc --disable-man --prefix="$DEPLOYDIR" CFLAGS="-I$DEPLOYDIR/include" LDFLAGS="-L$DEPLOYDIR/lib"
   make -j$NUMCPU
   make install
-   
 }
 
 build_opencsg()
@@ -628,6 +645,7 @@ build_boost 1.53.0
 build_cgal 4.0.2
 build_glew 1.9.0
 build_opencsg 1.3.2
+build_gettext 0.18.3.1
 build_glib2 2.38.2
 
 echo "OpenSCAD dependencies built and installed to " $BASEDIR
-- 
cgit v0.10.1
From 4552d2d26352330ea0786dc65bc8f49328f90e02 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Wed, 11 Dec 2013 17:59:38 +1100
Subject: Add back in GL and GLIB2 links for tests-core
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e590070..519f1e1 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -588,7 +588,7 @@ set(OFFSCREEN_SOURCES
   ../src/OpenCSGRenderer.cc)
 
 add_library(tests-core STATIC ${CORE_SOURCES})
-target_link_libraries(tests-core)
+target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} )
 set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${Boost_LIBRARIES} )
 
 add_library(tests-common STATIC ${COMMON_SOURCES})
-- 
cgit v0.10.1
From d14f0be01c06a872a7fb0cef6e0fa67ad7bc4a4f Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Wed, 11 Dec 2013 02:01:50 -0500
Subject: minor fix: Only use UPLOADARG if it exists
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 779ef08..370c8be 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -908,7 +908,9 @@ set(OPENSCAD_UPLOAD_TESTS $ENV{OPENSCAD_UPLOAD_TESTS})
 if (OPENSCAD_UPLOAD_TESTS)
   set(UPLOADARG "--upload")
 endif()
-string(REPLACE __openscad_upload_tests__ ${UPLOADARG} TMP ${TMP})
+if (UPLOADARG)
+  string(REPLACE __openscad_upload_tests__ ${UPLOADARG} TMP ${TMP})
+endif()
 
 if (MINGW_CROSS_ENV_DIR)
   string(REPLACE __wine__ wine TMP ${TMP})
-- 
cgit v0.10.1
From 966f7eb6242cca891b52a0fe4f52775a086c9cd9 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Thu, 12 Dec 2013 01:15:34 -0500
Subject: #514 Change gcc 4.8.2 version check error to a warning
diff --git a/src/version_check.h b/src/version_check.h
index be52e61..2688f2a 100644
--- a/src/version_check.h
+++ b/src/version_check.h
@@ -113,7 +113,7 @@ a time, to avoid confusion.
                    + __GNUC_MINOR__ * 100 \
                    + __GNUC_PATCHLEVEL__)
 #if GCC_VERSION == 40802
-#error "OpenSCAD isnt compatible with gcc 4.8.2. Please try a different version"
+#warning "gcc 4.8.2 contains a bug causing a crash in CGAL."
 #endif
 
 #endif // OPENSCAD_SKIP_VERSION_CHECK
-- 
cgit v0.10.1
From 68c706da8c9ed5d7bc32d41d7cce86531eb2a673 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 12 Dec 2013 17:48:55 +1100
Subject: Fix check_dependencies for Ubuntu 12.10 and ...
... and other platforms that report a different arch triplet than where
they store their libraries)
check-dependencies.sh doesn't find glib2 on Ubuntu 12.10 since glib is
installed in /usr/lib/i386-linux-gnu/glib-2.0/include/glibconfig.h while
gcc -dumpmachine reports the arch as i686-linux-gnu
diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh
index e587198..c431255 100755
--- a/scripts/check-dependencies.sh
+++ b/scripts/check-dependencies.sh
@@ -74,7 +74,19 @@ glib2_sysver()
     glib2archtriplet=`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`
   fi
   glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h
-  if [ ! -e $glib2path ]; then return; fi
+  if [ ! -e $glib2path ]; then
+    #No glib found
+    #glib can be installed in /usr/lib/i386-linux-gnu/glib-2.0/ on arch i686-linux-gnu (sometimes?)
+    if [ $glib2archtriplet = "i686-linux-gnu" ]; then
+        glib2archtriplet=i386-linux-gnu
+        glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h
+        if [ ! -e $glib2path ]; then
+            return;
+        fi
+    else
+        return;
+    fi
+  fi
   glib2major=`grep "define  *GLIB_MAJOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
   glib2minor=`grep "define  *GLIB_MINOR_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
   glib2micro=`grep "define  *GLIB_MICRO_VERSION  *[0-9.]*" $glib2path | awk '{print $3}'`
-- 
cgit v0.10.1
From b8859a6716f2e42395ca96ca5c6af10e0d02cec8 Mon Sep 17 00:00:00 2001
From: Brody Kenrick 
Date: Thu, 12 Dec 2013 17:54:40 +1100
Subject: Correct coding style for last commit (check_dependencies).
diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh
index c431255..72c4c74 100755
--- a/scripts/check-dependencies.sh
+++ b/scripts/check-dependencies.sh
@@ -80,9 +80,7 @@ glib2_sysver()
     if [ $glib2archtriplet = "i686-linux-gnu" ]; then
         glib2archtriplet=i386-linux-gnu
         glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h
-        if [ ! -e $glib2path ]; then
-            return;
-        fi
+        if [ ! -e $glib2path ]; then return; fi
     else
         return;
     fi
-- 
cgit v0.10.1
From 585412bed623dc6adc8df5f27131997e6d2541a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= 
Date: Fri, 13 Dec 2013 15:51:58 +0100
Subject: Change invalid Programming category to Development
Programming is not a valid category of .desktop file, it is not listed in either [Main Categories](http://standards.freedesktop.org/menu-spec/latest/apa.html) or [Additional Categories](http://standards.freedesktop.org/menu-spec/latest/apas02.html).
desktop-file-validate returns error:
    openscad.desktop: error: value "Graphics;3DGraphics;Engineering;Programming;" for key "Categories" in group "Desktop Entry" contains an unregistered value "Programming"; values extending the format should start with "X-"
This changes it to Development, witch is a valid category listed in Additional Categories. Now desktop-file-validate only gives us a hint:
    openscad.desktop: hint: value "Graphics;3DGraphics;Engineering;Development;" for key "Categories" in group "Desktop Entry" contains more than one main category; application might appear more than once in the application menu.
That is fine, as we want it both in Graphics and Development.
This is a valid response to #533
diff --git a/icons/openscad.desktop b/icons/openscad.desktop
index f0282ac..46150a2 100644
--- a/icons/openscad.desktop
+++ b/icons/openscad.desktop
@@ -4,4 +4,4 @@ Version=1.0
 Name=OpenSCAD
 Icon=openscad
 Exec=openscad %f
-Categories=Graphics;3DGraphics;Engineering;Programming;
+Categories=Graphics;3DGraphics;Engineering;Development;
-- 
cgit v0.10.1
From a3aa61bab2c88fce649a9515dec0ddb7d15be085 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sat, 14 Dec 2013 12:49:23 -0500
Subject: bugfix: glib include fix
diff --git a/glib-2.0.pri b/glib-2.0.pri
index 0fbc4e2..2f293bf 100644
--- a/glib-2.0.pri
+++ b/glib-2.0.pri
@@ -7,14 +7,13 @@
 # 3. system's standard include paths from pkg-config
 
 glib-2.0 {
-
 # read environment variables
 OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
 GLIB2_DIR = $$(GLIB2DIR)
 
 !isEmpty(OPENSCAD_LIBRARIES_DIR) {
   isEmpty(GLIB2_INCLUDEPATH) {
-    GLIB2_INCLUDEPATH_1 = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
+    GLIB2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
     GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include
     GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
   }
@@ -23,7 +22,7 @@ GLIB2_DIR = $$(GLIB2DIR)
 isEmpty(GLIB2_INCLUDEPATH) {
   GLIB2_CFLAGS = $$system("pkg-config --cflags glib-2.0")
 } else {
-  GLIB2_CFLAGS = -I$$GLIB2_INCLUDEPATH_1
+  GLIB2_CFLAGS = -I$$GLIB2_INCLUDEPATH
   GLIB2_CFLAGS += -I$$GLIB2_INCLUDEPATH_2
 }
 
@@ -35,4 +34,6 @@ isEmpty(GLIB2_LIBPATH) {
 
 QMAKE_CXXFLAGS += $$GLIB2_CFLAGS
 LIBS += $$GLIB2_LIBS
+
+message("glib: $$GLIB2_CFLAGS")
 }
-- 
cgit v0.10.1
From cca80a15590c24de9bfd4654b19a64bf1426cf54 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sat, 14 Dec 2013 17:44:05 -0500
Subject: bugfix: #562 didn't take into account that it's allowed with all X
 coordinates being negative
diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc
index 599fd7f..a2d896d 100644
--- a/src/PolySetCGALEvaluator.cc
+++ b/src/PolySetCGALEvaluator.cc
@@ -455,19 +455,21 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD
 
 	for (size_t i = 0; i < dxf.paths.size(); i++)
 	{
+		double min_x = 0;
 		double max_x = 0;
 		for (size_t j = 0; j < dxf.paths[i].indices.size(); j++) {
 			double point_x = dxf.points[dxf.paths[i].indices[j]][0];
-			if (point_x < 0) {
-				PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates");
-				PRINTB("[Point %d on path %d has X coordinate %f]", j % i % point_x);
+			min_x = fmin(min_x, point_x);
+			max_x = fmax(max_x, point_x);
+
+			if ((max_x - min_x) > max_x && (max_x - min_x) > fabs(min_x)) {
+				PRINTB("ERROR: all points for rotate_extrude() must have the same X coordinate sign (range is %.2f -> %.2f)", min_x % max_x);
 				delete ps;
 				return NULL;
 			}
-			max_x = fmax(max_x, point_x);
 		}
 
-		int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa);
+		int fragments = get_fragments_from_r(max_x-min_x, node.fn, node.fs, node.fa);
 
 		double ***points;
 		points = new double**[fragments];
diff --git a/testdata/scad/features/rotate_extrude-tests.scad b/testdata/scad/features/rotate_extrude-tests.scad
index 010b7d2..ec8d1cc 100644
--- a/testdata/scad/features/rotate_extrude-tests.scad
+++ b/testdata/scad/features/rotate_extrude-tests.scad
@@ -32,3 +32,6 @@ translate([50,50,0]) {
 // Minimal $fn
 translate([0,-60,0]) rotate_extrude($fn=1) translate([20,0,0]) circle(r=10,$fn=1);
 
+// Object in negative X
+translate([0,60,0]) rotate_extrude() translate([-20,0]) square(10);
+
diff --git a/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png b/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png
index 1488c85..d198344 100644
Binary files a/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png and b/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png differ
diff --git a/tests/regression/dumptest/rotate_extrude-tests-expected.csg b/tests/regression/dumptest/rotate_extrude-tests-expected.csg
index a86dd8f..d010bfd 100644
--- a/tests/regression/dumptest/rotate_extrude-tests-expected.csg
+++ b/tests/regression/dumptest/rotate_extrude-tests-expected.csg
@@ -50,4 +50,11 @@ group() {
 			}
 		}
 	}
+	multmatrix([[1, 0, 0, 0], [0, 1, 0, 60], [0, 0, 1, 0], [0, 0, 0, 1]]) {
+		rotate_extrude(convexity = 1, $fn = 0, $fa = 12, $fs = 2) {
+			multmatrix([[1, 0, 0, -20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
+				square(size = [10, 10], center = false);
+			}
+		}
+	}
 }
diff --git a/tests/regression/opencsgtest/rotate_extrude-tests-expected.png b/tests/regression/opencsgtest/rotate_extrude-tests-expected.png
index 861f6ab..0be247e 100644
Binary files a/tests/regression/opencsgtest/rotate_extrude-tests-expected.png and b/tests/regression/opencsgtest/rotate_extrude-tests-expected.png differ
diff --git a/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png b/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png
index 8956be2..1da4873 100644
Binary files a/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png and b/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png differ
-- 
cgit v0.10.1
From 0eaa5333dbab54f81b6a53f24b06d7c8f46f10c9 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sat, 14 Dec 2013 17:57:28 -0500
Subject: Put back printing path to test report
diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py
index 0f86cb6..4c3993c 100755
--- a/tests/test_pretty_print.py
+++ b/tests/test_pretty_print.py
@@ -455,6 +455,7 @@ def main():
     html_filename = os.path.join(builddir, 'Testing', 'Temporary', html_basename)
     debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes')
     trysave(html_filename, html)
+    print "report saved:\n", html_filename.replace(os.getcwd()+os.path.sep,'')
 
     if upload:
         page_url = create_page()
-- 
cgit v0.10.1
From c7cea0082e427f3c53985845f05e8737873c8a25 Mon Sep 17 00:00:00 2001
From: Marius Kintel 
Date: Sun, 15 Dec 2013 15:02:32 -0500
Subject: Added testcase for concave polygons in polyhedrons
diff --git a/testdata/scad/features/polyhedron-tests.scad b/testdata/scad/features/polyhedron-tests.scad
index 690d962..1f11d7e 100644
--- a/testdata/scad/features/polyhedron-tests.scad
+++ b/testdata/scad/features/polyhedron-tests.scad
@@ -11,10 +11,35 @@ module polyhedrons() {
  translate([4,0,0])
   polyhedron(points = [[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]],
             triangles = [[0,2,4],[0,5,2],[0,4,3],[0,3,5],[1,4,2],[1,2,5],[1,3,4], [1,5,3]]);
+
+// Containing concave polygons
+translate([6,0,0])
+polyhedron(points=[
+        [-0.8,-0.8,-0.8],
+        [0,0,-0.8],
+        [0.8,-0.8,-0.8],
+        [0.8,0.8,-0.8],
+        [-0.8,0.8,-0.8],
+        [-0.8,-0.8,0.8],
+        [0,0,0.8],
+        [0.8,-0.8,0.8],
+        [0.8,0.8,0.8],
+        [-0.8,0.8,0.8],
+    ],
+    triangles=[
+        [0,1,2,3,4],
+        [5,6,1,0],
+        [6,7,2,1],
+        [7,8,3,2],
+        [8,9,4,3],
+        [9,5,0,4],
+        [9,8,7,6,5],
+    ], convexity=2);
 }
 
 polyhedrons();
 translate([0,2,0]) difference() {
   polyhedrons();
-  translate([2,0,2]) cube([8,3,3], center=true);
+  translate([3,0,2]) cube([8,3,3], center=true);
 }
+
diff --git a/tests/regression/cgalpngtest/polyhedron-tests-expected.png b/tests/regression/cgalpngtest/polyhedron-tests-expected.png
index c80990f..322160d 100644
Binary files a/tests/regression/cgalpngtest/polyhedron-tests-expected.png and b/tests/regression/cgalpngtest/polyhedron-tests-expected.png differ
diff --git a/tests/regression/dumptest/polyhedron-tests-expected.csg b/tests/regression/dumptest/polyhedron-tests-expected.csg
index f59baa2..4bf980d 100644
--- a/tests/regression/dumptest/polyhedron-tests-expected.csg
+++ b/tests/regression/dumptest/polyhedron-tests-expected.csg
@@ -7,6 +7,9 @@ group() {
 		multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
 			polyhedron(points = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]], triangles = [[0, 2, 4], [0, 5, 2], [0, 4, 3], [0, 3, 5], [1, 4, 2], [1, 2, 5], [1, 3, 4], [1, 5, 3]], convexity = 1);
 		}
+		multmatrix([[1, 0, 0, 6], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
+			polyhedron(points = [[-0.8, -0.8, -0.8], [0, 0, -0.8], [0.8, -0.8, -0.8], [0.8, 0.8, -0.8], [-0.8, 0.8, -0.8], [-0.8, -0.8, 0.8], [0, 0, 0.8], [0.8, -0.8, 0.8], [0.8, 0.8, 0.8], [-0.8, 0.8, 0.8]], triangles = [[0, 1, 2, 3, 4], [5, 6, 1, 0], [6, 7, 2, 1], [7, 8, 3, 2], [8, 9, 4, 3], [9, 5, 0, 4], [9, 8, 7, 6, 5]], convexity = 2);
+		}
 	}
 	multmatrix([[1, 0, 0, 0], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) {
 		difference() {
@@ -18,8 +21,11 @@ group() {
 				multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
 					polyhedron(points = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]], triangles = [[0, 2, 4], [0, 5, 2], [0, 4, 3], [0, 3, 5], [1, 4, 2], [1, 2, 5], [1, 3, 4], [1, 5, 3]], convexity = 1);
 				}
+				multmatrix([[1, 0, 0, 6], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
+					polyhedron(points = [[-0.8, -0.8, -0.8], [0, 0, -0.8], [0.8, -0.8, -0.8], [0.8, 0.8, -0.8], [-0.8, 0.8, -0.8], [-0.8, -0.8, 0.8], [0, 0, 0.8], [0.8, -0.8, 0.8], [0.8, 0.8, 0.8], [-0.8, 0.8, 0.8]], triangles = [[0, 1, 2, 3, 4], [5, 6, 1, 0], [6, 7, 2, 1], [7, 8, 3, 2], [8, 9, 4, 3], [9, 5, 0, 4], [9, 8, 7, 6, 5]], convexity = 2);
+				}
 			}
-			multmatrix([[1, 0, 0, 2], [0, 1, 0, 0], [0, 0, 1, 2], [0, 0, 0, 1]]) {
+			multmatrix([[1, 0, 0, 3], [0, 1, 0, 0], [0, 0, 1, 2], [0, 0, 0, 1]]) {
 				cube(size = [8, 3, 3], center = true);
 			}
 		}
diff --git a/tests/regression/opencsgtest/polyhedron-tests-expected.png b/tests/regression/opencsgtest/polyhedron-tests-expected.png
index 0740f1a..f6665cf 100644
Binary files a/tests/regression/opencsgtest/polyhedron-tests-expected.png and b/tests/regression/opencsgtest/polyhedron-tests-expected.png differ
diff --git a/tests/regression/throwntogethertest/polyhedron-tests-expected.png b/tests/regression/throwntogethertest/polyhedron-tests-expected.png
index 95ffeaa..c293d88 100644
Binary files a/tests/regression/throwntogethertest/polyhedron-tests-expected.png and b/tests/regression/throwntogethertest/polyhedron-tests-expected.png differ
-- 
cgit v0.10.1