diff --git a/src/sunstone/public/SConstruct b/src/sunstone/public/SConstruct
index 14f43b838b..4105167849 100644
--- a/src/sunstone/public/SConstruct
+++ b/src/sunstone/public/SConstruct
@@ -22,16 +22,11 @@ Import('env')
 
 
 if env['sunstone']=='yes':
-    os.system("mv dist/main.js ../")
     print "Generating Sunstone minified files\n"
-    exit_code=os.system("grunt --gruntfile ./Gruntfile.js sass")
-    if exit_code != 0:
-        print "Error generating Sunstone css files\n"
-        exit(-1)
 
-    exit_code=os.system("grunt --gruntfile ./Gruntfile.js requirejs")
+    exit_code=os.system("./build.sh")
+
     if exit_code != 0:
         print "Error generating minifying Sunstone files\n"
         exit(-1)
-    os.system("mv dist/main.js dist/main-dist.js && mv dist/main.js.map dist/main-dist.js.map")
-    os.system("mv -f ../main.js dist/main.js")
+
diff --git a/src/sunstone/public/build.sh b/src/sunstone/public/build.sh
new file mode 100755
index 0000000000..854ae48eeb
--- /dev/null
+++ b/src/sunstone/public/build.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+#-------------------------------------------------------------------------------
+usage() {
+ echo
+ echo "Usage: build.sh [-d] [-c] [-h]"
+ echo
+ echo "-d: install build dependencies (bower, grunt)"
+ echo "-c: clean build"
+ echo "-h: prints this help"
+}
+
+clean() {
+    rm -rf dist node_modules bower_components
+}
+
+dependencies() {
+    npm install bower
+    npm install grunt
+    npm install grunt-cli
+
+    export PATH=$PATH:$PWD/node_modules/.bin
+}
+
+install_patch() {
+
+    npm install
+
+    bower install --force --allow-root --config.interactive=false
+
+    grunt --gruntfile ./Gruntfile.js sass
+
+    grunt --gruntfile ./Gruntfile.js requirejs
+
+    mv dist/main.js dist/main-dist.js
+
+    mv dist/main.js.map dist/main-dist.js.map
+
+    PATCH_DIR="./patches/"
+
+    for i in `ls ${PATCH_DIR}` ; do
+        if [ -f "${PATCH_DIR}/$i" ]; then
+            patch -p1 <"${PATCH_DIR}/$i"
+        fi
+    done
+}
+#-------------------------------------------------------------------------------
+
+PARAMETERS="dch"
+
+if [ $(getopt --version | tr -d " ") = "--" ]; then
+    TEMP_OPT=`getopt $PARAMETERS "$@"`
+else
+    TEMP_OPT=`getopt -o $PARAMETERS -n 'build.sh' -- "$@"`
+fi
+
+DEPENDENCIES="no"
+CLEAN="no"
+
+eval set -- "$TEMP_OPT"
+
+while true ; do
+    case "$1" in
+        -d) DEPENDENCIES="yes"   ; shift ;;
+        -c) CLEAN="yes"   ; shift ;;
+        -h) usage; exit 0;;
+        --) shift ; break ;;
+        *)  usage; exit 1 ;;
+    esac
+done
+
+if [ "$CLEAN" = "yes" ]; then
+    clean
+    exit 0
+fi
+
+if [ "$DEPENDENCIES" = "yes" ]; then
+    dependencies
+    exit 0
+fi
+
+install_patch
diff --git a/src/sunstone/public/patches/bug4304-novnc.patch b/src/sunstone/public/patches/bug4304-novnc.patch
new file mode 100644
index 0000000000..6f305287f3
--- /dev/null
+++ b/src/sunstone/public/patches/bug4304-novnc.patch
@@ -0,0 +1,16 @@
+diff -ru public.old/bower_components/no-vnc/include/keyboard.js public.new/bower_components/no-vnc/include/keyboard.js
+--- public.old/bower_components/no-vnc/include/keyboard.js	2014-11-29 10:46:27.000000000 +0100
++++ public.new/bower_components/no-vnc/include/keyboard.js	2017-05-25 17:02:59.184008613 +0200
+@@ -407,12 +407,6 @@
+             case 'keydown':
+                 // is the next element a keypress? Then we should merge the two
+                 if (queue.length !== 0 && queue[0].type === 'keypress') {
+-                    // Firefox sends keypress even when no char is generated.
+-                    // so, if keypress keysym is the same as we'd have guessed from keydown,
+-                    // the modifier didn't have any effect, and should not be escaped
+-                    if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
+-                        cur.escape = queue[0].escape;
+-                    }
+                     cur.keysym = queue[0].keysym;
+                     queue = queue.splice(1);
+                 }